Skip to content

Commit c3d6367

Browse files
authored
Merge pull request #347 from Shopify/341-store-mapped-strings-on-stack
Packer: store the parent buffer mapped strings on the stack
2 parents 79027da + c47abb8 commit c3d6367

5 files changed

Lines changed: 94 additions & 1 deletion

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
fail-fast: false
5353
matrix:
5454
os: [ubuntu]
55-
ruby: ['jruby-9.2', 'jruby-9.3', 'jruby-9.4', 'truffleruby']
55+
ruby: ['jruby-9.3', 'jruby-9.4', 'truffleruby']
5656
runs-on: ${{ matrix.os }}-latest
5757
steps:
5858
- uses: actions/checkout@v2

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Fix a potential GC bug when packing data using recursive extensions and buffers containing over 512KkiB of data (See #341).
12
* Fix a regression where feeding an empty string to an Unpacker would be considered like the end of the buffer.
23

34
2023-05-19 1.7.1:

bin/rspec

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
#
5+
# This file was generated by Bundler.
6+
#
7+
# The application 'rspec' is installed as part of a gem, and
8+
# this file is here to facilitate running it.
9+
#
10+
11+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12+
13+
bundle_binstub = File.expand_path("bundle", __dir__)
14+
15+
if File.file?(bundle_binstub)
16+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17+
load(bundle_binstub)
18+
else
19+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21+
end
22+
end
23+
24+
require "rubygems"
25+
require "bundler/setup"
26+
27+
load Gem.bin_path("rspec-core", "rspec")

ext/msgpack/packer.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,40 @@ bool msgpack_packer_try_write_with_ext_type_lookup(msgpack_packer_t* pk, VALUE v
114114
}
115115

116116
if(ext_flags & MSGPACK_EXT_RECURSIVE) {
117+
// HACK: While we call the proc, the current pk->buffer won't be reachable
118+
// as it will be stored on the stack.
119+
// To ensure all the `mapped_string` reference in that buffer are properly
120+
// marked and pined, we copy them all on the stack.
121+
VALUE* mapped_strings = NULL;
122+
size_t mapped_strings_count = 0;
123+
msgpack_buffer_chunk_t* c = pk->buffer.head;
124+
while (c != &pk->buffer.tail) {
125+
if (c->mapped_string != NO_MAPPED_STRING) {
126+
mapped_strings_count++;
127+
}
128+
c = c->next;
129+
}
130+
if (c->mapped_string != NO_MAPPED_STRING) {
131+
mapped_strings_count++;
132+
}
133+
134+
if (mapped_strings_count > 0) {
135+
mapped_strings = ALLOCA_N(VALUE, mapped_strings_count);
136+
mapped_strings_count = 0;
137+
c = pk->buffer.head;
138+
while (c != &pk->buffer.tail) {
139+
if (c->mapped_string != NO_MAPPED_STRING) {
140+
mapped_strings[mapped_strings_count] = c->mapped_string;
141+
mapped_strings_count++;
142+
}
143+
c = c->next;
144+
}
145+
if (c->mapped_string != NO_MAPPED_STRING) {
146+
mapped_strings[mapped_strings_count] = c->mapped_string;
147+
mapped_strings_count++;
148+
}
149+
}
150+
117151
msgpack_buffer_t parent_buffer = pk->buffer;
118152
msgpack_buffer_init(PACKER_BUFFER_(pk));
119153

@@ -132,6 +166,10 @@ bool msgpack_packer_try_write_with_ext_type_lookup(msgpack_packer_t* pk, VALUE v
132166
pk->buffer = parent_buffer;
133167
msgpack_packer_write_ext(pk, ext_type, payload);
134168
}
169+
170+
if (mapped_strings_count > 0) {
171+
RB_GC_GUARD(mapped_strings[0]);
172+
}
135173
} else {
136174
VALUE payload = rb_proc_call_with_block(proc, 1, &v, Qnil);
137175
StringValue(payload);

spec/packer_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,4 +589,31 @@ def to_msgpack_ext
589589
GC.stress = stress
590590
end
591591
end
592+
593+
it "doesn't crash when using recursive packers concurrently" do
594+
hash_with_indifferent_access = Class.new(Hash)
595+
msgpack = MessagePack::Factory.new
596+
msgpack.register_type(
597+
0x02,
598+
hash_with_indifferent_access,
599+
packer: ->(value, packer) do
600+
packer.write("a".b * 600_0000) # Over MSGPACK_BUFFER_STRING_WRITE_REFERENCE_DEFAULT
601+
packer.write(value.to_h)
602+
GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
603+
end,
604+
unpacker: ->(unpacker) { hash_with_indifferent_access.new(unpacker.read) },
605+
recursive: true
606+
)
607+
608+
packer = msgpack.packer
609+
610+
top_hash = hash = hash_with_indifferent_access.new
611+
10.times do
612+
hash["a"] = new_hash = hash_with_indifferent_access.new
613+
hash = new_hash
614+
end
615+
packer.write(top_hash)
616+
packer.write(top_hash)
617+
packer.full_pack
618+
end
592619
end

0 commit comments

Comments
 (0)