Skip to content

Commit f87f384

Browse files
committed
SERVER-6860 Assertion: 13548:BufBuilder grow() > 64MB when trying to profile a huge document
Changed objsize() to size()
1 parent e6b9080 commit f87f384

File tree

4 files changed

+192
-22
lines changed

4 files changed

+192
-22
lines changed

src/mongo/db/client.cpp

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -693,17 +693,54 @@ namespace mongo {
693693

694694
#define OPDEBUG_APPEND_NUMBER(x) if( x != -1 ) b.appendNumber( #x , (x) )
695695
#define OPDEBUG_APPEND_BOOL(x) if( x ) b.appendBool( #x , (x) )
696-
void OpDebug::append( const CurOp& curop, BSONObjBuilder& b ) const {
696+
bool OpDebug::append(const CurOp& curop, BSONObjBuilder& b, size_t maxSize) const {
697697
b.append( "op" , iscommand ? "command" : opToString( op ) );
698698
b.append( "ns" , ns.toString() );
699-
if ( ! query.isEmpty() )
700-
b.append( iscommand ? "command" : "query" , query );
701-
else if ( ! iscommand && curop.haveQuery() )
702-
curop.appendQuery( b , "query" );
703-
704-
if ( ! updateobj.isEmpty() )
705-
b.append( "updateobj" , updateobj );
706699

700+
int queryUpdateObjSize = 0;
701+
if (!query.isEmpty()) {
702+
queryUpdateObjSize += query.objsize();
703+
}
704+
else if (!iscommand && curop.haveQuery()) {
705+
queryUpdateObjSize += curop.query()["query"].size();
706+
}
707+
708+
if (!updateobj.isEmpty()) {
709+
queryUpdateObjSize += updateobj.objsize();
710+
}
711+
712+
if (static_cast<size_t>(queryUpdateObjSize) > maxSize) {
713+
if (!query.isEmpty()) {
714+
// Use 60 since BSONObj::toString can truncate strings into 150 chars
715+
// and we want to have enough room for both query and updateobj when
716+
// the entire document is going to be serialized into a string
717+
const string abbreviated(query.toString(false, false), 0, 60);
718+
b.append(iscommand ? "command" : "query", abbreviated + "...");
719+
}
720+
else if (!iscommand && curop.haveQuery()) {
721+
const string abbreviated(curop.query()["query"].toString(false, false), 0, 60);
722+
b.append("query", abbreviated + "...");
723+
}
724+
725+
if (!updateobj.isEmpty()) {
726+
const string abbreviated(updateobj.toString(false, false), 0, 60);
727+
b.append("updateobj", abbreviated + "...");
728+
}
729+
730+
return false;
731+
}
732+
733+
if (!query.isEmpty()) {
734+
b.append(iscommand ? "command" : "query", query);
735+
}
736+
else if (!iscommand && curop.haveQuery()) {
737+
curop.appendQuery(b, "query");
738+
}
739+
740+
if (!updateobj.isEmpty()) {
741+
b.append("updateobj", updateobj);
742+
}
743+
707744
const bool moved = (nmoved >= 1);
708745

709746
OPDEBUG_APPEND_NUMBER( cursorid );
@@ -724,14 +761,15 @@ namespace mongo {
724761

725762
b.appendNumber( "numYield" , curop.numYields() );
726763
b.append( "lockStats" , curop.lockStat().report() );
727-
728-
if ( ! exceptionInfo.empty() )
764+
765+
if ( ! exceptionInfo.empty() )
729766
exceptionInfo.append( b , "exception" , "exceptionCode" );
730-
767+
731768
OPDEBUG_APPEND_NUMBER( nreturned );
732769
OPDEBUG_APPEND_NUMBER( responseLength );
733770
b.append( "millis" , executionTime );
734-
771+
772+
return true;
735773
}
736774

737775
}

src/mongo/db/curop.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,21 @@ namespace mongo {
3939
void reset();
4040

4141
string report( const CurOp& curop ) const;
42-
void append( const CurOp& curop, BSONObjBuilder& b ) const;
42+
43+
/**
44+
* Appends stored data and information from curop to the builder.
45+
*
46+
* @param curop information about the current operation which will be
47+
* use to append data to the builder.
48+
* @param builder the BSON builder to use for appending data. Data can
49+
* still be appended even if this method returns false.
50+
* @param maxSize the maximum allowed combined size for the query object
51+
* and update object
52+
*
53+
* @return false if the sum of the sizes for the query object and update
54+
* object exceeded maxSize
55+
*/
56+
bool append(const CurOp& curop, BSONObjBuilder& builder, size_t maxSize) const;
4357

4458
// -------------------
4559

@@ -152,7 +166,7 @@ namespace mongo {
152166
~CurOp();
153167

154168
bool haveQuery() const { return _query.have(); }
155-
BSONObj query() { return _query.get(); }
169+
BSONObj query() const { return _query.get(); }
156170
void appendQuery( BSONObjBuilder& b , const StringData& name ) const { _query.append( b , name ); }
157171

158172
void ensureStarted();

src/mongo/db/introspect.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
#include "pdfile.h"
2626
#include "curop.h"
2727

28+
namespace {
29+
const size_t MAX_PROFILE_DOC_SIZE_BYTES = 100*1024;
30+
}
31+
2832
namespace mongo {
2933

3034
BufBuilder profileBufBuilder; // reused, instead of allocated every time - avoids a malloc/free cycle
@@ -39,20 +43,23 @@ namespace mongo {
3943
// build object
4044
profileBufBuilder.reset();
4145
BSONObjBuilder b(profileBufBuilder);
42-
b.appendDate("ts", jsTime());
43-
currentOp.debug().append( currentOp , b );
4446

45-
b.append("client", c.clientAddress() );
47+
const bool isQueryObjTooBig = !currentOp.debug().append(currentOp, b,
48+
MAX_PROFILE_DOC_SIZE_BYTES);
49+
50+
b.appendDate("ts", jsTime());
51+
b.append("client", c.clientAddress());
4652

47-
if ( c.getAuthenticationInfo() )
48-
b.append( "user" , c.getAuthenticationInfo()->getUser( nsToDatabase( ns ) ) );
53+
if (c.getAuthenticationInfo()) {
54+
b.append("user", c.getAuthenticationInfo()->getUser(nsToDatabase(ns)));
55+
}
4956

5057
BSONObj p = b.done();
5158

52-
if (p.objsize() > 100*1024){
59+
if (static_cast<size_t>(p.objsize()) > MAX_PROFILE_DOC_SIZE_BYTES || isQueryObjTooBig) {
5360
string small = p.toString(/*isArray*/false, /*full*/false);
5461

55-
warning() << "can't add full line to system.profile: " << small;
62+
warning() << "can't add full line to system.profile: " << small << endl;
5663

5764
// rebuild with limited info
5865
BSONObjBuilder b(profileBufBuilder);
@@ -62,7 +69,9 @@ namespace mongo {
6269
b.append( "user" , c.getAuthenticationInfo()->getUser( nsToDatabase( ns ) ) );
6370

6471
b.append("err", "profile line too large (max is 100KB)");
65-
if (small.size() < 100*1024){ // should be much smaller but if not don't break anything
72+
73+
// should be much smaller but if not don't break anything
74+
if (small.size() < MAX_PROFILE_DOC_SIZE_BYTES){
6675
b.append("abbreviated", small);
6776
}
6877

src/mongo/dbtests/profile_test.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Copyright (C) 2012 10gen Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License, version 3,
6+
* as published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU Affero General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU Affero General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
/**
18+
* This file contains tests for the profile command
19+
*/
20+
21+
#include "mongo/db/instance.h"
22+
#include "mongo/unittest/unittest.h"
23+
24+
using mongo::BSONObj;
25+
using mongo::DBDirectClient;
26+
27+
using std::string;
28+
29+
namespace mongo_test {
30+
class Profiler: public mongo::unittest::Test {
31+
public:
32+
static const string PROFILER_TEST_DB;
33+
static const string PROFILER_TEST_NS;
34+
static const string PROFILE_NS;
35+
36+
protected:
37+
void setUp() {
38+
BSONObj ret;
39+
db.runCommand(PROFILER_TEST_DB, BSON("dropDatabase" << 1), ret);
40+
ASSERT(ret["ok"].trueValue());
41+
}
42+
43+
DBDirectClient db;
44+
};
45+
46+
const string Profiler::PROFILER_TEST_DB = "profilerTestDB";
47+
const string Profiler::PROFILER_TEST_NS = Profiler::PROFILER_TEST_DB + ".test";
48+
const string Profiler::PROFILE_NS = Profiler::PROFILER_TEST_DB + ".system.profile";
49+
50+
TEST_F(Profiler, BigDoc) {
51+
// Test that update with large document with a long string can be
52+
// be profiled in a shortened version
53+
const string bigStr(16 * (1 << 20) - 200, 'a');
54+
55+
{
56+
BSONObj replyObj;
57+
db.runCommand(PROFILER_TEST_DB, BSON("profile" << 2), replyObj);
58+
ASSERT(replyObj["ok"].trueValue());
59+
}
60+
61+
const BSONObj doc(BSON("field" << bigStr));
62+
db.update(PROFILER_TEST_NS, doc, doc);
63+
64+
std::auto_ptr<mongo::DBClientCursor> cursor = db.query(PROFILE_NS, BSONObj());
65+
ASSERT(cursor->more());
66+
67+
BSONObj profileDoc(cursor->next());
68+
ASSERT(profileDoc.hasField("abbreviated"));
69+
const string abbreviatedField(profileDoc["abbreviated"].str());
70+
71+
// Make sure that the abbreviated field contains the query and the updateobj info
72+
ASSERT(abbreviatedField.find("query:") != string::npos);
73+
ASSERT(abbreviatedField.find("updateobj:") != string::npos);
74+
}
75+
76+
TEST_F(Profiler, BigDocWithManyFields) {
77+
// Test that update with large document with lots of fields can be
78+
// be profiled in a shortened version
79+
mongo::BSONObjBuilder builder;
80+
81+
for (int x = 0; x < (1 << 20); x++) {
82+
const string fieldName(mongo::str::stream() << "x" << x);
83+
builder.append(fieldName, x);
84+
}
85+
86+
DBDirectClient db;
87+
88+
{
89+
BSONObj replyObj;
90+
db.runCommand(PROFILER_TEST_DB, BSON("profile" << 2), replyObj);
91+
ASSERT(replyObj["ok"].trueValue());
92+
}
93+
94+
const BSONObj doc(builder.done());
95+
db.update(PROFILER_TEST_NS, doc, doc);
96+
97+
std::auto_ptr<mongo::DBClientCursor> cursor = db.query(PROFILE_NS, BSONObj());
98+
ASSERT(cursor->more());
99+
100+
BSONObj profileDoc(cursor->next());
101+
ASSERT(profileDoc.hasField("abbreviated"));
102+
const string abbreviatedField(profileDoc["abbreviated"].str());
103+
104+
// Make sure that the abbreviated field contains the query and the updateobj info
105+
ASSERT(abbreviatedField.find("query:") != string::npos);
106+
ASSERT(abbreviatedField.find("updateobj:") != string::npos);
107+
}
108+
}
109+

0 commit comments

Comments
 (0)