Skip to content

Commit 1df7523

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

File tree

8 files changed

+116
-2
lines changed

8 files changed

+116
-2
lines changed

ext/json/ext/generator/generator.c

Lines changed: 14 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,6 +1855,7 @@ 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);
18511860
state->strict = RTEST(enable);
18521861
return Qnil;
@@ -1871,6 +1880,7 @@ static VALUE cState_allow_nan_p(VALUE self)
18711880
*/
18721881
static VALUE cState_allow_nan_set(VALUE self, VALUE enable)
18731882
{
1883+
rb_check_frozen(self);
18741884
GET_STATE(self);
18751885
state->allow_nan = RTEST(enable);
18761886
return Qnil;
@@ -1895,6 +1905,7 @@ static VALUE cState_ascii_only_p(VALUE self)
18951905
*/
18961906
static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
18971907
{
1908+
rb_check_frozen(self);
18981909
GET_STATE(self);
18991910
state->ascii_only = RTEST(enable);
19001911
return Qnil;
@@ -1932,6 +1943,7 @@ static VALUE cState_depth(VALUE self)
19321943
*/
19331944
static VALUE cState_depth_set(VALUE self, VALUE depth)
19341945
{
1946+
rb_check_frozen(self);
19351947
GET_STATE(self);
19361948
state->depth = long_config(depth);
19371949
return Qnil;
@@ -1965,6 +1977,7 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_
19651977
*/
19661978
static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length)
19671979
{
1980+
rb_check_frozen(self);
19681981
GET_STATE(self);
19691982
buffer_initial_length_set(state, buffer_initial_length);
19701983
return Qnil;
@@ -2031,6 +2044,7 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con
20312044

20322045
static VALUE cState_configure(VALUE self, VALUE opts)
20332046
{
2047+
rb_check_frozen(self);
20342048
GET_STATE(self);
20352049
configure_state(state, self, opts);
20362050
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)

java/src/json/ext/GeneratorState.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
283283

284284
@JRubyMethod(name="[]=")
285285
public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) {
286+
checkFrozen();
286287
String name = vName.asJavaString();
287288
String nameWriter = name + "=";
288289
if (getMetaClass().isMethodBound(nameWriter, true)) {
@@ -304,6 +305,7 @@ public RubyString indent_get(ThreadContext context) {
304305

305306
@JRubyMethod(name="indent=")
306307
public IRubyObject indent_set(ThreadContext context, IRubyObject indent) {
308+
checkFrozen();
307309
this.indent = prepareByteList(context, indent);
308310
return indent;
309311
}
@@ -319,6 +321,7 @@ public RubyString space_get(ThreadContext context) {
319321

320322
@JRubyMethod(name="space=")
321323
public IRubyObject space_set(ThreadContext context, IRubyObject space) {
324+
checkFrozen();
322325
this.space = prepareByteList(context, space);
323326
return space;
324327
}
@@ -335,6 +338,7 @@ public RubyString space_before_get(ThreadContext context) {
335338
@JRubyMethod(name="space_before=")
336339
public IRubyObject space_before_set(ThreadContext context,
337340
IRubyObject spaceBefore) {
341+
checkFrozen();
338342
this.spaceBefore = prepareByteList(context, spaceBefore);
339343
return spaceBefore;
340344
}
@@ -351,6 +355,7 @@ public RubyString object_nl_get(ThreadContext context) {
351355
@JRubyMethod(name="object_nl=")
352356
public IRubyObject object_nl_set(ThreadContext context,
353357
IRubyObject objectNl) {
358+
checkFrozen();
354359
this.objectNl = prepareByteList(context, objectNl);
355360
return objectNl;
356361
}
@@ -367,6 +372,7 @@ public RubyString array_nl_get(ThreadContext context) {
367372
@JRubyMethod(name="array_nl=")
368373
public IRubyObject array_nl_set(ThreadContext context,
369374
IRubyObject arrayNl) {
375+
checkFrozen();
370376
this.arrayNl = prepareByteList(context, arrayNl);
371377
return arrayNl;
372378
}
@@ -382,6 +388,7 @@ public IRubyObject as_json_get(ThreadContext context) {
382388

383389
@JRubyMethod(name="as_json=")
384390
public IRubyObject as_json_set(ThreadContext context, IRubyObject asJSON) {
391+
checkFrozen();
385392
if (asJSON.isNil() || asJSON == context.getRuntime().getFalse()) {
386393
this.asJSON = null;
387394
} else {
@@ -402,6 +409,7 @@ public RubyInteger max_nesting_get(ThreadContext context) {
402409

403410
@JRubyMethod(name="max_nesting=")
404411
public IRubyObject max_nesting_set(IRubyObject max_nesting) {
412+
checkFrozen();
405413
maxNesting = RubyNumeric.fix2int(max_nesting);
406414
return max_nesting;
407415
}
@@ -420,6 +428,7 @@ public RubyBoolean script_safe_get(ThreadContext context) {
420428

421429
@JRubyMethod(name="script_safe=", alias="escape_slash=")
422430
public IRubyObject script_safe_set(IRubyObject script_safe) {
431+
checkFrozen();
423432
scriptSafe = script_safe.isTrue();
424433
return script_safe.getRuntime().newBoolean(scriptSafe);
425434
}
@@ -443,6 +452,7 @@ public RubyBoolean strict_get(ThreadContext context) {
443452

444453
@JRubyMethod(name="strict=")
445454
public IRubyObject strict_set(IRubyObject isStrict) {
455+
checkFrozen();
446456
strict = isStrict.isTrue();
447457
return isStrict.getRuntime().newBoolean(strict);
448458
}
@@ -472,6 +482,7 @@ public RubyInteger buffer_initial_length_get(ThreadContext context) {
472482

473483
@JRubyMethod(name="buffer_initial_length=")
474484
public IRubyObject buffer_initial_length_set(IRubyObject buffer_initial_length) {
485+
checkFrozen();
475486
int newLength = RubyNumeric.fix2int(buffer_initial_length);
476487
if (newLength > 0) bufferInitialLength = newLength;
477488
return buffer_initial_length;
@@ -488,6 +499,7 @@ public RubyInteger depth_get(ThreadContext context) {
488499

489500
@JRubyMethod(name="depth=")
490501
public IRubyObject depth_set(IRubyObject vDepth) {
502+
checkFrozen();
491503
depth = RubyNumeric.fix2int(vDepth);
492504
return vDepth;
493505
}
@@ -532,6 +544,7 @@ public boolean getDeprecateDuplicateKey() {
532544
*/
533545
@JRubyMethod(visibility=Visibility.PRIVATE)
534546
public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) {
547+
checkFrozen();
535548
OptionsReader opts = new OptionsReader(context, vOpts);
536549

537550
ByteList indent = opts.getString("indent");

java/src/json/ext/ParserConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRuby
172172

173173
@JRubyMethod(visibility = Visibility.PRIVATE)
174174
public IRubyObject initialize(ThreadContext context, IRubyObject options) {
175+
checkFrozen();
175176
Ruby runtime = context.runtime;
176177

177178
OptionsReader opts = new OptionsReader(context, options);

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_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/json_parser_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,13 @@ def test_parse_whitespace_after_newline
820820
assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]")
821821
end
822822

823+
def test_frozen
824+
parser_config = JSON::Parser::Config.new({}).freeze
825+
assert_raise FrozenError do
826+
parser_config.send(:initialize, {})
827+
end
828+
end
829+
823830
private
824831

825832
def assert_equal_float(expected, actual, delta = 1e-2)

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)