...
Issue Status as of Aug 21, 2014 ISSUE SUMMARY The server will leak memory when receiving a query with a $where expression inside an $elemMatch expression. Note that these queries are not valid and an error message will be correctly returned to the user. For example: > db.foo.find({a:{$elemMatch:{$where:"1"}}}) error: { "$err" : "Can't canonicalize query: BadValue $elemMatch cannot contain $where expression", "code" : 17287 } > USER IMPACT Users may see increased memory consumption by mongod processes. In extreme circumstances with many such queries, the memory leak may lead mongod to consume too much memory and either crash or be killed by the operating system. WORKAROUNDS Fix the invalid query to remove either the $elemMatch operator or the $where operator. AFFECTED VERSIONS MongoDB production releases from 2.6.1 to 2.6.4 (inclusive) are affected by this issue. FIX VERSION The fix is included in the 2.6.5 production release. RESOLUTION DETAILS Fix memory leak in MatchExpressionParser::_parseElemMatch(). Original description The above JS test will cause the server to leak memory (and eventually crash). The leak is reproducible in versions 2.6.1+ but not 2.6.0. SERVER-13503 seems like the obvious culprit. Valgrind output: valgrind -q --leak-check=full --show-leak-kinds=definite --soname-synonyms=somalloc=NONE --suppressions=valgrind.suppressions --num-callers=35 ./mongod ==29826== 3,250 (800 direct, 2,450 indirect) bytes in 10 blocks are definitely lost in loss record 4,322 of 4,395 ==29826== at 0x4C2B2C0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==29826== by 0xA84311: mongo::WhereCallbackReal::parseWhere(mongo::BSONElement const&) const (expression_where.cpp:189) ==29826== by 0xA76739: mongo::MatchExpressionParser::_parse(mongo::BSONObj const&, int) (expression_parser.cpp:336) ==29826== by 0xA76ED7: mongo::MatchExpressionParser::_parseElemMatch(char const*, mongo::BSONElement const&, int) (expression_parser.cpp:699) ==29826== by 0xA78908: mongo::MatchExpressionParser::_parseSubField(mongo::BSONObj const&, mongo::AndMatchExpression const*, char const*, mongo::BSONElement const&, int) (expression_parser.cpp:262) ==29826== by 0xA75659: mongo::MatchExpressionParser::_parseSub(char const*, mongo::BSONObj const&, mongo::AndMatchExpression*, int) (expression_parser.cpp:456) ==29826== by 0xA76114: mongo::MatchExpressionParser::_parse(mongo::BSONObj const&, int) (expression_parser.cpp:376) ==29826== by 0xB3AE58: mongo::CanonicalQuery::canonicalize(mongo::QueryMessage const&, mongo::CanonicalQuery**, mongo::MatchExpressionParser::WhereCallback const&) (expression_parser.h:71) ==29826== by 0xB6564B: mongo::newRunQuery(mongo::OperationContext*, mongo::Message&, mongo::QueryMessage&, mongo::CurOp&, mongo::Message&) (new_find.cpp:499) ==29826== by 0xA4877D: mongo::assembleResponse(mongo::OperationContext*, mongo::Message&, mongo::DbResponse&, mongo::HostAndPort const&) (instance.cpp:263) ==29826== by 0x7D5C6B: mongo::MyMessageHandler::process(mongo::Message&, mongo::AbstractMessagingPort*, mongo::LastError*) (db.cpp:198) ==29826== by 0xE682E0: mongo::PortMessageServer::handleIncomingMsg(void*) (message_server_port.cpp:227) ==29826== by 0x4E3F181: start_thread (pthread_create.c:312) ==29826== by 0x5D7C38C: clone (clone.S:111)
xgen-internal-githook commented on Wed, 20 Aug 2014 18:17:01 +0000: Author: {u'username': u'jrassi', u'name': u'Jason Rassi', u'email': u'rassi@10gen.com'} Message: SERVER-14892 Fix memory leak in MatchExpressionParser::_parseElemMatch Branch: master https://github.com/mongodb/mongo/commit/71277e8a5d072fbfa956a74f3c0295f69ead120f xgen-internal-githook commented on Wed, 20 Aug 2014 18:16:40 +0000: Author: {u'username': u'jrassi', u'name': u'Jason Rassi', u'email': u'rassi@10gen.com'} Message: SERVER-14892 Fix memory leak in MatchExpressionParser::_parseElemMatch (cherry picked from commit cc80c4d9a0556926a3f3fa9556c95ffd56aa4814) Branch: v2.6 https://github.com/mongodb/mongo/commit/2b23514f91c1ab0cc6b8f7e928138fbf513ce34e
var t = db.query_leak; t.drop(); t.insert({}); for (var i = 0; i < 1000000; i++) { try { t.find({a: {$elemMatch: {$where: "1"}}}).itcount(); } catch(e) { // ignore } }