Skip to content

Commit 77c03ae

Browse files
committed
Ractor-shareable JSON::Coder
1 parent ab5efca commit 77c03ae

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

ext/json/ext/generator/generator.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,7 @@ static VALUE string_config(VALUE config)
16301630
*/
16311631
static VALUE cState_indent_set(VALUE self, VALUE indent)
16321632
{
1633+
rb_check_frozen(self);
16331634
GET_STATE(self);
16341635
RB_OBJ_WRITE(self, &state->indent, string_config(indent));
16351636
return Qnil;
@@ -1655,6 +1656,7 @@ static VALUE cState_space(VALUE self)
16551656
*/
16561657
static VALUE cState_space_set(VALUE self, VALUE space)
16571658
{
1659+
rb_check_frozen(self);
16581660
GET_STATE(self);
16591661
RB_OBJ_WRITE(self, &state->space, string_config(space));
16601662
return Qnil;
@@ -1678,6 +1680,7 @@ static VALUE cState_space_before(VALUE self)
16781680
*/
16791681
static VALUE cState_space_before_set(VALUE self, VALUE space_before)
16801682
{
1683+
rb_check_frozen(self);
16811684
GET_STATE(self);
16821685
RB_OBJ_WRITE(self, &state->space_before, string_config(space_before));
16831686
return Qnil;
@@ -1703,6 +1706,7 @@ static VALUE cState_object_nl(VALUE self)
17031706
*/
17041707
static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
17051708
{
1709+
rb_check_frozen(self);
17061710
GET_STATE(self);
17071711
RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl));
17081712
return Qnil;
@@ -1726,6 +1730,7 @@ static VALUE cState_array_nl(VALUE self)
17261730
*/
17271731
static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
17281732
{
1733+
rb_check_frozen(self);
17291734
GET_STATE(self);
17301735
RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl));
17311736
return Qnil;
@@ -1749,6 +1754,7 @@ static VALUE cState_as_json(VALUE self)
17491754
*/
17501755
static VALUE cState_as_json_set(VALUE self, VALUE as_json)
17511756
{
1757+
rb_check_frozen(self);
17521758
GET_STATE(self);
17531759
RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc"));
17541760
return Qnil;
@@ -1791,6 +1797,7 @@ static long long_config(VALUE num)
17911797
*/
17921798
static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
17931799
{
1800+
rb_check_frozen(self);
17941801
GET_STATE(self);
17951802
state->max_nesting = long_config(depth);
17961803
return Qnil;
@@ -1816,6 +1823,7 @@ static VALUE cState_script_safe(VALUE self)
18161823
*/
18171824
static VALUE cState_script_safe_set(VALUE self, VALUE enable)
18181825
{
1826+
rb_check_frozen(self);
18191827
GET_STATE(self);
18201828
state->script_safe = RTEST(enable);
18211829
return Qnil;
@@ -1847,7 +1855,9 @@ static VALUE cState_strict(VALUE self)
18471855
*/
18481856
static VALUE cState_strict_set(VALUE self, VALUE enable)
18491857
{
1858+
rb_check_frozen(self);
18501859
GET_STATE(self);
1860+
rb_check_frozen(self);
18511861
state->strict = RTEST(enable);
18521862
return Qnil;
18531863
}
@@ -1871,6 +1881,7 @@ static VALUE cState_allow_nan_p(VALUE self)
18711881
*/
18721882
static VALUE cState_allow_nan_set(VALUE self, VALUE enable)
18731883
{
1884+
rb_check_frozen(self);
18741885
GET_STATE(self);
18751886
state->allow_nan = RTEST(enable);
18761887
return Qnil;
@@ -1895,6 +1906,7 @@ static VALUE cState_ascii_only_p(VALUE self)
18951906
*/
18961907
static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
18971908
{
1909+
rb_check_frozen(self);
18981910
GET_STATE(self);
18991911
state->ascii_only = RTEST(enable);
19001912
return Qnil;
@@ -1932,6 +1944,7 @@ static VALUE cState_depth(VALUE self)
19321944
*/
19331945
static VALUE cState_depth_set(VALUE self, VALUE depth)
19341946
{
1947+
rb_check_frozen(self);
19351948
GET_STATE(self);
19361949
state->depth = long_config(depth);
19371950
return Qnil;
@@ -1965,6 +1978,7 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_
19651978
*/
19661979
static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length)
19671980
{
1981+
rb_check_frozen(self);
19681982
GET_STATE(self);
19691983
buffer_initial_length_set(state, buffer_initial_length);
19701984
return Qnil;
@@ -2031,6 +2045,7 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con
20312045

20322046
static VALUE cState_configure(VALUE self, VALUE opts)
20332047
{
2048+
rb_check_frozen(self);
20342049
GET_STATE(self);
20352050
configure_state(state, self, opts);
20362051
return self;

ext/json/ext/parser/parser.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,7 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts)
14671467
*/
14681468
static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
14691469
{
1470+
rb_check_frozen(self);
14701471
GET_PARSER_CONFIG;
14711472

14721473
parser_config_init(config, opts);
@@ -1554,6 +1555,11 @@ static size_t JSON_ParserConfig_memsize(const void *ptr)
15541555
return sizeof(JSON_ParserConfig);
15551556
}
15561557

1558+
#ifndef HAVE_RB_EXT_RACTOR_SAFE
1559+
# undef RUBY_TYPED_FROZEN_SHAREABLE
1560+
# define RUBY_TYPED_FROZEN_SHAREABLE 0
1561+
#endif
1562+
15571563
static const rb_data_type_t JSON_ParserConfig_type = {
15581564
"JSON::Ext::Parser/ParserConfig",
15591565
{
@@ -1562,7 +1568,7 @@ static const rb_data_type_t JSON_ParserConfig_type = {
15621568
JSON_ParserConfig_memsize,
15631569
},
15641570
0, 0,
1565-
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
1571+
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
15661572
};
15671573

15681574
static VALUE cJSON_parser_s_allocate(VALUE klass)

lib/json/common.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ def initialize(options = nil, &as_json)
10481048
options[:as_json] = as_json if as_json
10491049

10501050
@state = State.new(options).freeze
1051-
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options))
1051+
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze
10521052
end
10531053

10541054
# call-seq:

test/json/json_coder_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,18 @@ def test_nesting_recovery
141141
end
142142
assert_equal '{"a":1}', coder.dump({ a: 1 })
143143
end
144+
145+
def test_frozen
146+
coder = JSON::Coder.new
147+
parser_config = coder.instance_variable_get(:@parser_config)
148+
assert_instance_of JSON::Parser::Config, parser_config
149+
assert_raise FrozenError do
150+
parser_config.send(:initialize, {})
151+
end
152+
state = coder.instance_variable_get(:@state)
153+
assert_instance_of JSON::State, state
154+
assert_raise FrozenError do
155+
state.send(:configure, { max_nesting: 1 })
156+
end
157+
end
144158
end

test/json/json_generator_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,4 +901,18 @@ def test_generate_duplicate_keys_disallowed
901901
end
902902
assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message
903903
end
904+
905+
def test_frozen
906+
state = JSON::State.new.freeze
907+
assert_raise(FrozenError) do
908+
state.configure(max_nesting: 1)
909+
end
910+
setters = state.methods.grep(/\w=$/)
911+
assert_not_empty setters
912+
setters.each do |setter|
913+
assert_raise(FrozenError) do
914+
state.send(setter, 1)
915+
end
916+
end
917+
end
904918
end

test/json/ractor_test.rb

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

0 commit comments

Comments
 (0)