偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Node.js SetTimeout 引起的內(nèi)存泄露問題

開發(fā) 前端
clearTimeout 中支持傳入 id 刪除定時器,而之前只支持傳入定時器對象。一切看起來沒問題,但是實現(xiàn)這個特性的時候,忘了一種場景,那就是如果用戶沒有執(zhí)行 clearTimeout,而是定時器正常觸發(fā),因為在定時器正常觸發(fā)的邏輯中沒有刪除映射關系,從而導致了內(nèi)存泄露。

這是之前寫的一篇文章,分享一下避免大家踩坑。

定時器回調通常會通過閉包持有外部的對象,比如下面的例子。

function demo() {
 const dummy = {}
 setTimeout(() => {
      dummy;
   }, 10000)
}

demo();

demo 執(zhí)行完后,demo 函數(shù)里的 dummy 對象是不會釋放的,因為它還被 setTimeout 引用著,如果執(zhí)行很多次 demo 的話,就會導致大量的內(nèi)存無法被釋放,直到執(zhí)行完 setTimeout,這通常不是什么問題,除非 dummy 對象非常大。

但是如果是 setInterval 的話,情況就不一樣了。

function demo() {
 const dummy = {}
 setInterval(() => {
      dummy;
   }, 10000)
}

demo();

上面的代碼會導致 dummy 永遠不會被釋放,當然這個例子很直接,大家并不會寫出這樣的代碼,但是有時候代碼復雜的時候,就不好說了,比如之前幫助業(yè)務排查問題的時候經(jīng)常發(fā)現(xiàn) setInterval 導致的內(nèi)存泄露問題,使用場景基本如下。

class Demo { 
    timer = null
    start() {
        this.timer = setInterval(() => {
            this;
        }, 10000)
    }
    stop() {
    // 通常會漏了這一句
    // clearInterval(this.timer);
    }
}

const demo = new Demo();
demo.start();
demo.stop();

所以使用 setInterval 的時候需要特別注意。

setInterval 導致內(nèi)存泄露很好理解,但是 setTimeout 導致的內(nèi)存泄露并不常見,因為 setTimeout 執(zhí)行完后,相應的內(nèi)存都會被釋放了。下面分享一個因為 Node.js Core 導致的 setTimeout 內(nèi)存泄露問題,相關 issue 可以參考這里。復現(xiàn)代碼如下。

for (i = 0; i < 500000; i++) {
   +setTimeout(() => {}, 0);
}

上面的代碼會導致 setTimeout 創(chuàng)建的 timer 對象無法釋放,乍一看,我們可以會被嚇到,這不就是我們平時的用法嗎?但是不用擔心,下面的例子并不會出現(xiàn)這個問題。

for (i = 0; i < 500000; i++) {
 setTimeout(() => {}, 0);
}

仔細一看,有問題的例子中 setTimeout 還有個 + 號,那么這個是做什么的呢?

這個還要說起 setTimeout 在瀏覽器的實現(xiàn),在瀏覽器中,setTimeout 返回的是一個 id,但是 Node.js 中返回的是一個對象,為了和瀏覽器兼容,Node.js 支持把返回的對象轉成 id,這個 id 是定時器對應的 async_hooks id,那么這個是怎么實現(xiàn)的呢?下面看一個例子。

const dummy = {
  [Symbol.toPrimitive]() {
   return 1
  }
};
console.log(+dummy)

上面例子會輸出 1,可以看到通過 Symbol.toPrimitive 可以定義對象轉成原生類型時的行為。下面是另一個例子。

const dummy = {
  [Symbol.toPrimitive]() {
    return "hello ";
  }
};
console.log(dummy + "world")

Node.js 正是利用這個能力實現(xiàn)了和瀏覽器的兼容,源碼如下。

Timeout.prototype[SymbolToPrimitive] = function() {
  const id = this[async_id_symbol];
  if (!this[kHasPrimitive]) {
    this[kHasPrimitive] = true;
    knownTimersById[id] = this;
  }
  return id;
};

所以一開始那個例子中 +setTimeout 最終會執(zhí)行上面的代碼,從而拿到一個 id。但是事情沒有那么簡單,從上面的代碼中可以看到,除了返回一個 id 外,還有另外一個邏輯,那就是把定時器對象保存到了一個 map 中,其中 key 正是給用戶返回的 id,那么這個有什么用呢?看一下 clearTimeout 代碼。

function clearTimeout(timer) {
  if (typeof timer === 'number' || typeof timer === 'string') {
    const timerInstance = knownTimersById[timer];
    if (timerInstance !== undefined) {
      timerInstance._onTimeout = null;
      /*
         if (timerInstance[kHasPrimitive])
            delete knownTimersById[timerInstance[async_id_symbol]];
      */
      unenroll(timerInstance);
    }
  }
}

可以看到 clearTimeout 中支持傳入 id 刪除定時器,而之前只支持傳入定時器對象。一切看起來沒問題,但是實現(xiàn)這個特性的時候,忘了一種場景,那就是如果用戶沒有執(zhí)行 clearTimeout,而是定時器正常觸發(fā),因為在定時器正常觸發(fā)的邏輯中沒有刪除映射關系,從而導致了內(nèi)存泄露。具體修復方案就是刪除這個映射關系就行,具體可以參考這個 PR

1. issue: https://github.com/nodejs/node/issues/53335.

2: pr: https://github.com/nodejs/node/pull/53337

責任編輯:姜華 來源: 編程雜技
相關推薦

2023-06-30 23:25:46

HTTP模塊內(nèi)存

2013-08-07 10:07:07

Handler內(nèi)存泄露

2017-03-19 16:40:28

漏洞Node.js內(nèi)存泄漏

2017-03-20 13:43:51

Node.js內(nèi)存泄漏

2022-01-02 06:55:08

Node.js ObjectWrapAddon

2020-01-03 16:04:10

Node.js內(nèi)存泄漏

2015-01-14 13:50:58

AndroidHandler內(nèi)存泄露

2022-06-23 06:34:56

Node.js子線程

2013-11-01 09:34:56

Node.js技術

2015-03-10 10:59:18

Node.js開發(fā)指南基礎介紹

2012-04-11 13:46:33

ibmdw

2021-12-25 22:29:57

Node.js 微任務處理事件循環(huán)

2012-02-03 09:25:39

Node.js

2020-05-29 15:33:28

Node.js框架JavaScript

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node

2011-09-09 14:23:13

Node.js

2011-11-10 08:55:00

Node.js

2012-10-24 14:56:30

IBMdw
點贊
收藏

51CTO技術棧公眾號