I am running into a case where nodejs seems to be leaking memory when mongoose objects are attempted be created, but fail because of validation errors. It seems strange to me, and I am seeking help from the community in getting to the bottom of it.
In the sample code where I've tried to recreate my problem, I have a very simple Mongoose model which validates one of its fields to be non-negative number. Repeated attempts at creation of invalid objects (with XVALUE as -1 in the code below), I see memory growing forever. But valid objects (with XVALUE as +1, say) works as expected, without any leaks. I am tracking memory using process.memoryUsage().heapUsed variable.
How do I get rid of the memory leak when attempts are made to create invalid objects? If I am using mongoose incorrectly, any suggestions on improving the usage would be much appreciated.
Versions: mongoose - 3.8.x and nodejs - 0.10.x.
The following is the sample code I have used to recreate my problem:
(function() {
var MAX_ITER_COUNT, MyModel, MyStuff, Schema, XVALUE, addEntry, conn, iterCount, mongoose;
mongoose = require("mongoose");
conn = mongoose.createConnection('mongodb://@127.0.0.1:27017/memtest');
conn.on("error", function(err) {
return console.error(err);
});
Schema = mongoose.Schema;
MyStuff = new Schema({
x: {
type: Number
}
});
XVALUE = -1;
MyStuff.path("x").validate((function(val) {
if (val < 0) {
return false;
}
return true;
}));
MyModel = conn.model("MyStuff", MyStuff, "stuff");
iterCount = 0;
MAX_ITER_COUNT = 100 * 1000;
addEntry = function() {
var currentMem, x;
currentMem = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
console.log("" + iterCount + " - memory is: " + currentMem + " MB");
x = new MyModel({
x: XVALUE
});
return x.save(function(err) {
if (err) {
console.log("" + iterCount + " - failed");
} else {
console.log("" + iterCount + " - Save successful");
}
if (iterCount < MAX_ITER_COUNT) {
addEntry();
}
return iterCount++;
});
};
conn.on("open", addEntry);
}).call(this);
What's happening is that with an invalid document, the
x.saveasync operation completes immediately, calling its callback as soon as its parentaddEntryfunction call completes. This leaves no chance for garbage collection to run and the memory usage keeps growing.To fix this, put the recursive
addEntrycall within a call tosetImmediateto give GC a chance to run between iterations: