Co Memory Leak

Case

Case - Code

    var co = require('co')
    
    function* sleep() {
        return new Promise(function(resolve) {
            setTimeout(resolve, 1)
        })
    }
    
    co(function* () {
        for(var i = 0; true; ++i) {
            yield sleep()
            if (i % 10000 === 0) {
                global.gc()
                console.log(process.memoryUsage())
            }
        }
    }).then(function () {
        console.log('finished')
    }, function (err) {
        console.log('caught error: ', err.stack)
    })

Case - Memory

    { rss: 17420288, heapTotal: 9620736, heapUsed: 3590768 }
    { rss: 44822528, heapTotal: 49288192, heapUsed: 12972200 }
    { rss: 70955008, heapTotal: 58575616, heapUsed: 21688912 }
    { rss: 80048128, heapTotal: 66831104, heapUsed: 30531560 }
    { rss: 89157632, heapTotal: 76118528, heapUsed: 39490184 }
    { rss: 98275328, heapTotal: 85405952, heapUsed: 48445040 }
    { rss: 107368448, heapTotal: 93661440, heapUsed: 57410024 }
    { rss: 116477952, heapTotal: 102948864, heapUsed: 66365712 }
    { rss: 125591552, heapTotal: 112236288, heapUsed: 75330040 }
    { rss: 134684672, heapTotal: 120491776, heapUsed: 84285144 }
    { rss: 143798272, heapTotal: 129779200, heapUsed: 93250072 }
    { rss: 152907776, heapTotal: 139066624, heapUsed: 102205152 }
    { rss: 162000896, heapTotal: 147322112, heapUsed: 111170352 }
    { rss: 171114496, heapTotal: 156609536, heapUsed: 120125032 }

Case - Simplify

    var co = require('co')

    co(function* () {
        for(var i = 0; true; ++i) {
            yield Promise.resolve()
            if (i % 100000 === 0) {
                global.gc()
                console.log(process.memoryUsage())
            }
        }
    }).then(function() {
        console.log('finished')
    }, function(err) {
        console.log('caught error: ', err.stack)
    });

Co

function co(gen) {
  var ctx = this;
  if (typeof gen === 'function') gen = gen.call(this);
  return Promise.resolve(onFulfilled());

  function onFulfilled(res) {
    var ret;
    try {
      ret = gen.next(res);
    } catch (e) {
      return Promise.reject(e);
    }
    return next(ret);
  }

  function onRejected(err) {
    var ret;
    try {
      ret = gen.throw(err);
    } catch (e) {
      return Promise.reject(e);
    }
    return next(ret);
  }

  function next(ret) {
    if (ret.done) return Promise.resolve(ret.value);
    var value = toPromise.call(ctx, ret.value);
    if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
    return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
      + 'but the following object was passed: "' + String(ret.value) + '"'));
  }
}

Promise Chain

     Promise.resolve(
        Promise.resolve(
            Promise.resolve(
                Promise.resolve(
                    ...
                )
            )
        )
    )

======================================

    next()
    function next() {
      Promise.resolve(next())
    }

Promise Chain - Code

    var i = 0
    next()
    function next() {
      new Promise(function (resolve) {
        i++
        if (i % 100000 === 0) {
          global.gc();
          console.log(process.memoryUsage());
        }
        setImmediate(resolve)
      }).then(next)
    }

Promise Chain - Memory

    { rss: 20205568, heapTotal: 9620736, heapUsed: 3663792 }
    { rss: 20725760, heapTotal: 9620736, heapUsed: 3890600 }
    { rss: 20738048, heapTotal: 9620736, heapUsed: 3917888 }
    { rss: 20561920, heapTotal: 9620736, heapUsed: 3740568 }
    { rss: 20574208, heapTotal: 9620736, heapUsed: 3566856 }
    { rss: 20574208, heapTotal: 9620736, heapUsed: 3556256 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3557760 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3555984 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3557704 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3556464 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3557520 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3556840 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3559136 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3556816 }
    { rss: 24776704, heapTotal: 13815040, heapUsed: 3559040 }

Promise Chain - Fact

不是嵌套而是依赖关系

return promise 导致的 promise chain

Promise Chain - Code

    var i = 0
    next()
    function next() {
      return new Promise(function (resolve) {
        i++
        if (i % 100000 === 0) {
          global.gc();
          console.log(process.memoryUsage());
        }
        setImmediate(resolve)
      }).then(next)
    }

Promise Chain - Memory

{ rss: 142872576, heapTotal: 128759296, heapUsed: 93180624 }
{ rss: 234328064, heapTotal: 218537728, heapUsed: 182848952 }
{ rss: 325378048, heapTotal: 308316160, heapUsed: 272469896 }
{ rss: 416411648, heapTotal: 397062656, heapUsed: 362067504 }
{ rss: 507457536, heapTotal: 486841088, heapUsed: 451553360 }
{ rss: 598507520, heapTotal: 576619520, heapUsed: 541141152 }
{ rss: 689557504, heapTotal: 666397952, heapUsed: 630743584 }
{ rss: 780607488, heapTotal: 756176384, heapUsed: 720341448 }
{ rss: 871657472, heapTotal: 845954816, heapUsed: 809943840 }
{ rss: 962691072, heapTotal: 934701312, heapUsed: 899541192 }
{ rss: 1053745152, heapTotal: 1024479744, heapUsed: 989143680 }

Avoid Promise Chain

    next()
    function next() {
      return Promise.resolve(next())
    }

======================================

    next()
    function next() {
        Promise.resolve().then(next)
    }

Fix

function co(gen) {
  var ctx = this;
  if (typeof gen === 'function') gen = gen.call(this);
  return new Promise(function(resolve, reject) {
    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
    
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

THX

Co Memory Leak

By jiawei chen

Co Memory Leak

This is a share about "co memory leak" case

  • 434