Skip to content

Commit 9f904dc

Browse files
committed
JAVA-1156: Properly ensure that none of the server-defined limits on the write commands are exceeded.
There is one limit for the number of items that are allowed in each command (maxWriteBatchSize from ismaster). There is a second limit for the number of bytes in the encoded message (maxBsonObjectSize from ismaster), with an exception for a write command containing just a single item, which is allowed to exceed that limit.
1 parent 938c7ff commit 9f904dc

File tree

7 files changed

+58
-17
lines changed

7 files changed

+58
-17
lines changed

src/main/com/mongodb/BaseWriteCommandMessage.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,19 @@ private void writeCommandHeader(final OutputBuffer buffer) {
8787
protected abstract BaseWriteCommandMessage writeTheWrites(final OutputBuffer buffer, final int commandStartPosition,
8888
final BSONBinaryWriter writer);
8989

90-
protected boolean maximumCommandDocumentSizeExceeded(final OutputBuffer buffer, final int commandStartPosition) {
91-
// Subtract 2 to account for the trailing 0x0 at the end of the enclosing array and command document
92-
return buffer.getPosition() - commandStartPosition > getSettings().getMaxDocumentSize() + HEADROOM - 2;
90+
protected boolean exceedsLimits(final int batchLength, final int batchItemCount) {
91+
return (exceedsBatchLengthLimit(batchLength, batchItemCount) || exceedsBatchItemCountLimit(batchItemCount));
92+
}
93+
94+
// make a special exception for a command with only a single item added to it. It's allowed to exceed maximum document size so that
95+
// it's possible to, say, send a replacement document that is itself 16MB, which would push the size of the containing command
96+
// document to be greater than the maximum document size.
97+
private boolean exceedsBatchLengthLimit(final int batchLength, final int batchItemCount) {
98+
return batchLength > getSettings().getMaxDocumentSize() && batchItemCount > 1;
99+
}
100+
101+
private boolean exceedsBatchItemCountLimit(final int batchItemCount) {
102+
return batchItemCount > getSettings().getMaxWriteBatchSize();
93103
}
94104

95105
public abstract int getItemCount();

src/main/com/mongodb/DBCollectionImpl.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ private BulkWriteResult insertWithCommandProtocol(final List<DBObject> list, fin
372372

373373
BaseWriteCommandMessage message = new InsertCommandMessage(getNamespace(), writeConcern, list,
374374
DefaultDBEncoder.FACTORY.create(), encoder,
375-
getMessageSettings(port.getAddress()));
375+
getMessageSettings(port));
376376
return writeWithCommandProtocol(port, INSERT, message, writeConcern);
377377
}
378378

@@ -392,7 +392,7 @@ private BulkWriteResult removeWithCommandProtocol(final List<RemoveRequest> remo
392392
final DBEncoder encoder, final DBPort port) {
393393
BaseWriteCommandMessage message = new DeleteCommandMessage(getNamespace(), writeConcern, removeList,
394394
DefaultDBEncoder.FACTORY.create(), encoder,
395-
getMessageSettings(port.getAddress()));
395+
getMessageSettings(port));
396396
return writeWithCommandProtocol(port, REMOVE, message, writeConcern);
397397
}
398398

@@ -402,7 +402,7 @@ private BulkWriteResult updateWithCommandProtocol(final List<ModifyRequest> upda
402402
final DBEncoder encoder, final DBPort port) {
403403
BaseWriteCommandMessage message = new UpdateCommandMessage(getNamespace(), writeConcern, updates,
404404
DefaultDBEncoder.FACTORY.create(), encoder,
405-
getMessageSettings(port.getAddress()));
405+
getMessageSettings(port));
406406
return writeWithCommandProtocol(port, UPDATE, message, writeConcern);
407407
}
408408

@@ -438,13 +438,19 @@ private boolean useWriteCommands(final WriteConcern concern, final DBPort port)
438438
db.getConnector().getServerDescription(port.getAddress()).getVersion().compareTo(new ServerVersion(2, 6)) >= 0;
439439
}
440440

441-
private MessageSettings getMessageSettings(final ServerAddress address) {
442-
ServerDescription serverDescription = db.getConnector().getServerDescription(address);
443-
return MessageSettings.builder().maxDocumentSize(serverDescription.getMaxDocumentSize()).maxMessageSize(serverDescription
444-
.getMaxMessageSize())
441+
private MessageSettings getMessageSettings(final DBPort port) {
442+
ServerDescription serverDescription = db.getConnector().getServerDescription(port.getAddress());
443+
return MessageSettings.builder()
444+
.maxDocumentSize(serverDescription.getMaxDocumentSize())
445+
.maxMessageSize(serverDescription.getMaxMessageSize())
446+
.maxWriteBatchSize(serverDescription.getMaxWriteBatchSize())
445447
.build();
446448
}
447449

450+
private int getMaxWriteBatchSize(final DBPort port) {
451+
return db.getConnector().getServerDescription(port.getAddress()).getMaxWriteBatchSize();
452+
}
453+
448454
private MongoNamespace getNamespace() {
449455
return new MongoNamespace(getDB().getName(), getName());
450456
}
@@ -551,7 +557,7 @@ public OrderedRunGenerator(final List<WriteRequest> writeRequests, final WriteCo
551557
this.writeRequests = writeRequests;
552558
this.writeConcern = writeConcern.continueOnError(false);
553559
this.encoder = encoder;
554-
this.maxBatchWriteSize = db.getConnector().getServerDescription(port.getAddress()).getMaxWriteBatchSize();
560+
this.maxBatchWriteSize = getMaxWriteBatchSize(port);
555561
}
556562

557563
@Override
@@ -605,7 +611,7 @@ public UnorderedRunGenerator(final List<WriteRequest> writeRequests, final Write
605611
this.writeRequests = writeRequests;
606612
this.writeConcern = writeConcern.continueOnError(true);
607613
this.encoder = encoder;
608-
this.maxBatchWriteSize = db.getConnector().getServerDescription(port.getAddress()).getMaxWriteBatchSize();
614+
this.maxBatchWriteSize = getMaxWriteBatchSize(port);
609615
}
610616

611617
@Override

src/main/com/mongodb/DeleteCommandMessage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected BaseWriteCommandMessage writeTheWrites(final OutputBuffer buffer, fina
5151
writer.writeInt32("limit", remove.isMulti() ? 0 : 1);
5252
writer.popMaxDocumentSize();
5353
writer.writeEndDocument();
54-
if (maximumCommandDocumentSizeExceeded(buffer, commandStartPosition)) {
54+
if (exceedsLimits(buffer.getPosition() - commandStartPosition, i + 1)) {
5555
writer.reset();
5656
nextMessage = new DeleteCommandMessage(getWriteNamespace(), getWriteConcern(), deletes.subList(i, deletes.size()),
5757
getCommandEncoder(), queryEncoder, getSettings());

src/main/com/mongodb/InsertCommandMessage.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@ protected InsertCommandMessage writeTheWrites(final OutputBuffer buffer, final i
4343
for (int i = 0; i < documents.size(); i++) {
4444
writer.mark();
4545
writer.encodeDocument(encoder, documents.get(i));
46-
if (maximumCommandDocumentSizeExceeded(buffer, commandStartPosition)) {
46+
if (exceedsLimits(buffer.getPosition() - commandStartPosition, i + 1)) {
4747
writer.reset();
48-
nextMessage = new InsertCommandMessage(getWriteNamespace(), getWriteConcern(),
49-
documents.subList(i, documents.size()),
48+
nextMessage = new InsertCommandMessage(getWriteNamespace(), getWriteConcern(), documents.subList(i, documents.size()),
5049
getCommandEncoder(), encoder, getSettings());
5150
break;
5251
}

src/main/com/mongodb/MessageSettings.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
final class MessageSettings {
2323
private static final int DEFAULT_MAX_DOCUMENT_SIZE = 0x1000000; // 16MB
2424
private static final int DEFAULT_MAX_MESSAGE_SIZE = 0x2000000; // 32MB
25+
private static final int DEFAULT_MAX_WRITE_BATCH_SIZE = 1000;
2526

2627
private final int maxDocumentSize;
2728
private final int maxMessageSize;
29+
private final int maxWriteBatchSize;
2830

2931
public static Builder builder() {
3032
return new Builder();
@@ -33,6 +35,7 @@ public static Builder builder() {
3335
static final class Builder {
3436
private int maxDocumentSize = DEFAULT_MAX_DOCUMENT_SIZE;
3537
private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
38+
private int maxWriteBatchSize = DEFAULT_MAX_WRITE_BATCH_SIZE;
3639

3740
public MessageSettings build() {
3841
return new MessageSettings(this);
@@ -48,6 +51,12 @@ public Builder maxMessageSize(final int maxMessageSize) {
4851
this.maxMessageSize = maxMessageSize;
4952
return this;
5053
}
54+
55+
public Builder maxWriteBatchSize(final int maxWriteBatchSize) {
56+
this.maxWriteBatchSize = maxWriteBatchSize;
57+
return this;
58+
}
59+
5160
// CHECKSTYLE:ON
5261
}
5362

@@ -59,8 +68,13 @@ public int getMaxMessageSize() {
5968
return maxMessageSize;
6069
}
6170

71+
public int getMaxWriteBatchSize() {
72+
return maxWriteBatchSize;
73+
}
74+
6275
MessageSettings(final Builder builder) {
6376
this.maxDocumentSize = builder.maxDocumentSize;
6477
this.maxMessageSize = builder.maxMessageSize;
78+
this.maxWriteBatchSize = builder.maxWriteBatchSize;
6579
}
6680
}

src/main/com/mongodb/UpdateCommandMessage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected UpdateCommandMessage writeTheWrites(final OutputBuffer buffer, final i
5454
}
5555
writer.popMaxDocumentSize();
5656
writer.writeEndDocument();
57-
if (maximumCommandDocumentSizeExceeded(buffer, commandStartPosition)) {
57+
if (exceedsLimits(buffer.getPosition() - commandStartPosition, i + 1)) {
5858
writer.reset();
5959
nextMessage = new UpdateCommandMessage(getWriteNamespace(), getWriteConcern(), updates.subList(i, updates.size()),
6060
getCommandEncoder(), encoder, getSettings());

src/test/com/mongodb/DBCollectionTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.junit.Test;
2222

2323
import java.net.UnknownHostException;
24+
import java.util.ArrayList;
2425
import java.util.Arrays;
2526
import java.util.HashSet;
2627
import java.util.List;
@@ -49,6 +50,17 @@ public void testMultiInsert() {
4950
c.insert(inserted1,inserted2);
5051
}
5152

53+
@Test
54+
public void testLargeMultiInsert() {
55+
List<DBObject> documents = new ArrayList<DBObject>();
56+
for (int i = 0; i < 1001; i++) {
57+
documents.add(new BasicDBObject());
58+
}
59+
60+
collection.insert(documents);
61+
assertEquals(1001, collection.count());
62+
}
63+
5264
@Test
5365
public void testCappedCollection() {
5466
String collectionName = "testCapped";

0 commit comments

Comments
 (0)