Skip to content

Commit

Permalink
Fixes findAndModify memory leak on errors
Browse files Browse the repository at this point in the history
  • Loading branch information
christkv committed Jun 5, 2013
1 parent e1af41a commit 3db873f
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 36 deletions.
4 changes: 4 additions & 0 deletions HISTORY
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
1.3.9 2013-06-05
----------------
- Fixed memory leak when findAndModify errors out on w>1 and chained callbacks not properly cleaned up.

1.3.8 2013-05-31
----------------
- Fixed issue with socket death on windows where it emits error event instead of close event (Issue #987)
Expand Down
12 changes: 10 additions & 2 deletions lib/mongodb/connection/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,16 @@ Base.prototype._callHandler = function(id, document, err) {
if(this._callBackStore.listeners(id).length >= 1) {
// Get info object
var info = this._callBackStore._notReplied[id];
// Delete the current object
delete this._callBackStore._notReplied[id];
// // Remove any chained callbacks
// if(Array.isArray(info.chained)) {
// for(var i = 0; i < info.chained.length; i++) {
// delete this._callBackStore._notReplied[info.chained[i]];
// }
// } else {
// Delete the current object
delete this._callBackStore._notReplied[id];
// }

// Emit to the callback of the object
this._callBackStore.emit(id, err, document, info.connection);
}
Expand Down
9 changes: 8 additions & 1 deletion lib/mongodb/connection/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,13 @@ Server.prototype.connect = function(dbInstance, options, callback) {
// Check for an error, if we have one let's trigger the callback and clean up
// The chained callbacks
if(firstResult[0].err != null || firstResult[0].errmsg != null) {
// Cleanup the remaining callback
if(Array.isArray(callbackInfo.info.chained)) {
for(var i = 0; i < callbackInfo.info.chained.length; i++) {
if(callbackInfo.info.chained[i] != mongoReply.responseTo)
server._removeHandler(callbackInfo.info.chained[i]);
}
}
// Trigger the callback for the error
server._callHandler(mongoReply.responseTo, mongoReply, null);
} else {
Expand All @@ -495,7 +502,7 @@ Server.prototype.connect = function(dbInstance, options, callback) {
for(var i = 0; i < chainedIds.length; i++) server._removeHandler(chainedIds[i]);
// Call the handler
server._callHandler(mongoReply.responseTo, callbackInfo.info.results.shift(), null);
} else{
} else{
// Add the results to all the results
for(var i = 0; i < chainedIds.length; i++) {
var handler = server._findHandler(chainedIds[i]);
Expand Down
7 changes: 1 addition & 6 deletions test/runners/replicaset_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,12 @@ module.exports = function(configurations) {
, '/test/tests/repl_set/read_preferences_tests.js'
, '/test/tests/repl_set/read_preferences_spec_tests.js'
, '/test/tests/repl_set/failover_query_tests.js'
, '/test/tests/repl_set/replicaset_examples_tests.js'
]
);

// After each test is done
repl_set_parallel_tests_runner.on('test_done', function(test_statistics) {
console.log("==================================== TEST_DONE")
console.log("==================================== TEST_DONE")
console.log("==================================== TEST_DONE")
// Unpack statistics
var time_spent = test_statistics.end_time.getTime() - test_statistics.start_time.getTime();
var test = test_statistics.name;
Expand Down Expand Up @@ -78,9 +76,6 @@ module.exports = function(configurations) {

// After test suite is finished
repl_set_parallel_tests_runner.on('end', function() {
console.log("==================================== TEST_END")
console.log("==================================== TEST_END")
console.log("==================================== TEST_END")
for(var name in buckets) {
var tests = buckets[name];
var total_time = 0;
Expand Down
25 changes: 24 additions & 1 deletion test/tests/functional/find_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,28 @@ exports.shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedW0 = function(confi
});
}

/**
* Test findAndModify a document with no write concern set
* @ignore
*/
exports.shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedW2 = function(configuration, test) {
var client = configuration.db();
client.createCollection('shouldCorrectlyFindAndModifyWithNoGetLastErrorChained', function(err, collection) {
// Let's modify the document in place
collection.findAndModify({'a':1}, [['a', 1]], {'$set':{'b':3}}, {'new':true, 'fields': {a:1}, w:2}, function(err, updated_doc) {
// Check if we have a chained command or not
var ids = client.serverConfig._callBackStore.notRepliedToIds();
test.equal(0, ids.length);
test.done();
});

// Check if we have a chained command or not
var ids = client.serverConfig._callBackStore.notRepliedToIds();
test.equal(2, ids.length);
test.ok(client.serverConfig._callBackStore.callbackInfo(ids[0].chained) == undefined);
});
}

exports.shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedSafe = function(configuration, test) {
var client = configuration.db();
client.createCollection('shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedSafe', function(err, collection) {
Expand All @@ -694,7 +716,8 @@ exports.shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedSafe = function(con

// Check if we have a chained command or not
var ids = client.serverConfig._callBackStore.notRepliedToIds();
test.equal(1, ids.length);
// test.equal(1, ids.length);
console.dir(client.serverConfig._callBackStore._notReplied)
test.ok(client.serverConfig._callBackStore.callbackInfo(ids[0].chained) == undefined);
});
}
Expand Down
55 changes: 29 additions & 26 deletions test/tests/manual_tests/find_modify_break_test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
var mongodb = require("../../lib/mongodb"),
request = true;

var db = new mongodb.Db('test_db', new mongodb.Server("127.0.0.1", 27017, {
auto_reconnect: true
}), {});

// listen on error
db.on("error", function(err) {
console.log('open request ', request);
console.error('db on error');
console.dir(err);
});
var mongodb = require("../../../lib/mongodb")
, MongoClient = mongodb.MongoClient
, request = true;

// var db = new mongodb.Db('test_db', new mongodb.Server("127.0.0.1", 27017, {
// auto_reconnect: true
// }), {});

// // listen on error
// db.on("error", function(err) {
// console.log('open request ', request);
// console.error('db on error');
// console.dir(err);
// });

// open connection
db.open(function(err, client) {
// db.open(function(err, client) {
MongoClient.connect('mongodb://127.0.0.1:31000/test_db', function(err, db) {
if (err) {
console.error(err);
}

var collection = new mongodb.Collection(client, 'test_collection');
var collection = db.collection('test_collection');

// define find and modify
var findAndModifyLoop = function() {
Expand All @@ -27,7 +29,7 @@ db.open(function(err, client) {

console.log('findAndModify request (should not be last)');

collection.findAndModify({hello: 'world'}, [['_id', 'asc']], {$set: {hi: 'there'}},{w:1, upsert:true}, function(err, object) {
collection.findAndModify({hello: 'world'}, [['_id', 'asc']], {$set: {hi: 'there'}},{w:5, wtimeout:1000, upsert:true}, function(err, object) {
if (err) {
console.warn('findAndModify error response ', err.message); // returns error if no matching object found
} else {
Expand All @@ -38,6 +40,7 @@ db.open(function(err, client) {
request = false;

process.nextTick(function() {
console.dir("number of callbacks :: " + Object.keys(db.serverConfig._callBackStore._notReplied).length);
// on result does it again
findAndModifyLoop();
})
Expand All @@ -48,14 +51,14 @@ db.open(function(err, client) {
findAndModifyLoop();
});

db.on("error", function(err) {
console.log('open request ', request);
console.error('db on error');
console.dir(err);
});
// db.on("error", function(err) {
// console.log('open request ', request);
// console.error('db on error');
// console.dir(err);
// });

db.on("close", function(err) {
console.log('open request ', request);
console.error('db on close');
console.dir(err);
});
// db.on("close", function(err) {
// console.log('open request ', request);
// console.error('db on close');
// console.dir(err);
// });
25 changes: 25 additions & 0 deletions test/tests/repl_set/replicaset_examples_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,28 @@ exports['Connection to replicaset with secondary only read preference no seconda
// DOC_END
}

/**
* Test findAndModify a document with no write concern set
* @ignore
*/
exports.shouldCorrectlyFindAndModifyWithNoGetLastErrorChainedW2 = function(configuration, test) {
var client = configuration.db();
client.createCollection('shouldCorrectlyFindAndModifyWithNoGetLastErrorChained', function(err, collection) {
// Let's modify the document in place
collection.findAndModify({'a':1}, [['a', 1]], {'$set':{'b':3}}, {'new':true, 'fields': {a:1}, w:5, wtimeout:1000}, function(err, updated_doc) {
console.dir(err)
console.dir(updated_doc)
// Check if we have a chained command or not
var ids = client.serverConfig._callBackStore.notRepliedToIds();
test.equal(0, ids.length);
test.done();
});

// Check if we have a chained command or not
var ids = client.serverConfig._callBackStore.notRepliedToIds();
test.equal(2, ids.length);
test.ok(client.serverConfig._callBackStore.callbackInfo(ids[0].chained) == undefined);
});
}


0 comments on commit 3db873f

Please sign in to comment.