Skip to content

Commit 7b1fb27

Browse files
committed
Ractor-shareable JSON::Coder
1 parent 4f1adb1 commit 7b1fb27

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

ext/json/ext/parser/parser.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,11 @@ static size_t JSON_ParserConfig_memsize(const void *ptr)
15551555
return sizeof(JSON_ParserConfig);
15561556
}
15571557

1558+
#ifndef HAVE_RB_EXT_RACTOR_SAFE
1559+
# undef RUBY_TYPED_FROZEN_SHAREABLE
1560+
# define RUBY_TYPED_FROZEN_SHAREABLE 0
1561+
#endif
1562+
15581563
static const rb_data_type_t JSON_ParserConfig_type = {
15591564
"JSON::Ext::Parser/ParserConfig",
15601565
{
@@ -1563,7 +1568,7 @@ static const rb_data_type_t JSON_ParserConfig_type = {
15631568
JSON_ParserConfig_memsize,
15641569
},
15651570
0, 0,
1566-
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
1571+
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
15671572
};
15681573

15691574
static VALUE cJSON_parser_s_allocate(VALUE klass)

lib/json/common.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,12 @@ def initialize(options = nil, &as_json)
10511051
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options))
10521052
end
10531053

1054+
def freeze
1055+
@state.freeze
1056+
@parser_config.freeze
1057+
super
1058+
end
1059+
10541060
# call-seq:
10551061
# dump(object) -> String
10561062
# dump(object, io) -> io

test/json/ractor_test.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,68 @@ def test_generate
5252
_, status = Process.waitpid2(pid)
5353
assert_predicate status, :success?
5454
end
55+
56+
module Store
57+
extend self
58+
attr_accessor :coder
59+
end
60+
61+
def test_coder
62+
Store.coder = JSON::Coder.new.freeze
63+
assert Ractor.shareable?(Store.coder)
64+
pid = fork do
65+
Warning[:experimental] = false
66+
r = Ractor.new do
67+
json = Store.coder.dump({
68+
'a' => 2,
69+
'b' => 3.141,
70+
'c' => 'c',
71+
'd' => [ 1, "b", 3.14 ],
72+
'e' => { 'foo' => 'bar' },
73+
'g' => "\"\0\037",
74+
'h' => 1000.0,
75+
'i' => 0.001
76+
})
77+
Store.coder.load(json)
78+
end
79+
expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
80+
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}')
81+
actual_json = r.value
82+
83+
if expected_json == actual_json
84+
exit 0
85+
else
86+
puts "Expected:"
87+
puts expected_json
88+
puts "Actual:"
89+
puts actual_json
90+
puts
91+
exit 1
92+
end
93+
end
94+
_, status = Process.waitpid2(pid)
95+
assert_predicate status, :success?
96+
end
97+
98+
class NonNative
99+
def initialize(value)
100+
@value = value
101+
end
102+
end
103+
104+
def test_coder_proc
105+
block = Ractor.shareable_proc { |value| value.as_json }
106+
Store.coder = JSON::Coder.new(&block).freeze
107+
assert Ractor.shareable?(Store.coder)
108+
109+
pid = fork do
110+
Warning[:experimental] = false
111+
assert_equal [{}], Ractor.new {
112+
Store.coder.load('[{}]')
113+
}.value
114+
end
115+
116+
_, status = Process.waitpid2(pid)
117+
assert_predicate status, :success?
118+
end if Ractor.respond_to?(:shareable_proc)
55119
end if defined?(Ractor) && Process.respond_to?(:fork)

0 commit comments

Comments
 (0)