Skip to content

Commit 44184e1

Browse files
author
Emily Giurleo
committed
RUBY-2234 Fix error on large bulk writes with zlib (#2026)
1 parent 898a869 commit 44184e1

File tree

3 files changed

+53
-12
lines changed

3 files changed

+53
-12
lines changed

lib/mongo/server/connection_base.rb

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,6 @@ def deliver(message, client, options = {})
171171
end
172172

173173
def serialize(message, client, buffer = BSON::ByteBuffer.new)
174-
start_size = 0
175-
final_message = message.maybe_compress(compressor, options[:zlib_compression_level])
176-
177174
# Driver specifications only mandate the fixed 16MiB limit for
178175
# serialized BSON documents. However, the server returns its
179176
# active serialized BSON document size limit in the ismaster response,
@@ -198,12 +195,41 @@ def serialize(message, client, buffer = BSON::ByteBuffer.new)
198195
max_bson_size += MAX_BSON_COMMAND_OVERHEAD
199196
end
200197

201-
final_message.serialize(buffer, max_bson_size)
202-
if max_message_size &&
203-
(buffer.length - start_size) > max_message_size
204-
then
205-
raise Error::MaxMessageSize.new(max_message_size)
198+
# RUBY-2234: It is necessary to check that the message size does not
199+
# exceed the maximum bson object size before compressing and serializing
200+
# the final message.
201+
#
202+
# This is to avoid the case where the user performs a bulk write
203+
# larger than 16MiB which, when compressed, becomes smaller than 16MiB.
204+
# If the driver does not split the bulk writes prior to compression,
205+
# the entire operation will be sent to the server, which will raise an
206+
# error because the uncompressed operation exceeds the maximum bson size.
207+
#
208+
# To address this problem, we serialize the message prior to compression
209+
# and raise an exception if the serialized message exceeds the maximum
210+
# bson size.
211+
if max_message_size
212+
# Create a separate buffer that contains the un-compressed message
213+
# for the purpose of checking its size. Write any pre-existing contents
214+
# from the original buffer into the temporary one.
215+
temp_buffer = BSON::ByteBuffer.new
216+
217+
# TODO: address the fact that this line mutates the buffer.
218+
temp_buffer.put_bytes(buffer.get_bytes(buffer.length))
219+
220+
message.serialize(temp_buffer, max_bson_size)
221+
if temp_buffer.length > max_message_size
222+
raise Error::MaxMessageSize.new(max_message_size)
223+
end
206224
end
225+
226+
# RUBY-2335: When the un-compressed message is smaller than the maximum
227+
# bson size limit, the message will be serialized twice. The operations
228+
# layer should be refactored to allow compression on an already-
229+
# serialized message.
230+
final_message = message.maybe_compress(compressor, options[:zlib_compression_level])
231+
final_message.serialize(buffer, max_bson_size)
232+
207233
buffer
208234
end
209235
end

spec/integration/bulk_write_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'spec_helper'
2+
3+
describe 'Bulk writes' do
4+
before do
5+
authorized_collection.drop
6+
end
7+
8+
context 'when bulk write is larger than 48MB' do
9+
let(:operations) do
10+
[ { insert_one: { text: 'a' * 1000 * 1000 } } ] * 48
11+
end
12+
13+
it 'succeeds' do
14+
expect do
15+
authorized_collection.bulk_write(operations)
16+
end.not_to raise_error
17+
end
18+
end
19+
end

spec/integration/size_limit_spec.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,6 @@
8181
end
8282

8383
it 'allows bulk writes of multiple documents of exactly 16 MiB each' do
84-
if SpecConfig.instance.compressors
85-
pending "RUBY-2234"
86-
end
87-
8884
documents = []
8985
1.upto(3) do |index|
9086
document = { key: 'a' * (max_document_size - 28), _id: "in#{index}" }

0 commit comments

Comments
 (0)