Mongoose数组更新引用自身导致内存溢出

运行脚本时报出 Maximum call stack size exceeded 这样的错,直觉上感觉写出了死循环导致堆栈超出,可是看了N久代码,递归调用的函数也没有找到错误,采用局部测试法耐心测了测,找到了出错的代码行,看着貌似没问题。

var PoiModel = require(../model/poi).Poi;
PoiModel.findOne({id: 1}, function(err, poi){
 var row = poi; // poi.albums [{title: 'test1', image: 'test1.jpg', summary: '', priority: 0}]
 // ...
 var newPoi = {};
 newPoi.id = row.id;
 newPoi.albums = [];
 newPoi.albums = row.albums; // the error is this line
 newPoi.albums.push({title: 'test2', image: 'test2.jpg', summary: '', priority: 0});
 PoiModel.findOneAndUpdate({id: newPoi.id} , newPoi, {upsert: true}, function(err, result){
 res.send('ok');
 });
});

运行就会报出错误:

.../node_modules/mongoose/node_modules/mquery/lib/utils.js:18
var clone = exports.clone = function clone (obj, options) {
^
RangeError: Maximum call stack size exceeded

错误呢就出现在newPoi.albums = row.albums; 这一行注释后程序就正常了,开始是什么也没看出来问题。贴出错误查了查,发现造成死循环的原因中有一种函数自身调用自己。

(function a() {
  a();
})();

可是这里感觉应该也是这种原因,因为这个albums属性也不是很大。由于前两天已经遇到过数组引用赋值的事情,很快就觉得是
newPoi.albums = row.albums; 发生了引用,经过对错误代码的分析定位。终于找到了从mongoose中查询到的row.albums这个数组并不仅仅是我们看到的值,它还包含很多私有属性及方法。

var album = row.albums[0];
for(key in album) {
  console.log('key', key);
}

结果打印出来:

key __parentArray
key __parent
key $__
key isNew
key errors
key _maxListeners
key _doc
key _pres
key _posts
key save
key _events
key id
key priority
key summary
key title
key image
key _id
key schema
...

其实追踪代码出现死循环的原因是mongoose查询时对poi进行了复制,细看了/mongoose/node_modules/mquery/lib/utils.js中clone复制代码中对poi的属性进行类似上述for(key in obj){}的遍历,直至复制到基本属性。而“__parentArray” 存储了完整的row.albums数组。这个造成不断的递归调用。所以屏蔽newPoi.albums = row.albums; 不会报错的原因也找到了。由于是临时用的函数,就手工写循环遍历了row.albums 将其元素及基本属性Push到newRow.albums; 这样就把__parentArray这种有mongoose引人的私有属性给清除掉了。错误也得以解决。

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>