Skip to content

Conversation

@etiennebarrie
Copy link
Contributor

I want to be able to reuse an instance of JSON::Coder across Ractors. We need to add RUBY_TYPED_FROZEN_SHAREABLE to the parser config typed data definition and freeze the instance variables, after which it's possible to use a frozen coder across Ractors.

@byroot
Copy link
Member

byroot commented Nov 18, 2025

CI needs some fixing but otherwise 👍

},
0, 0,
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems correct because all the fields in

typedef struct JSON_ParserStruct {
VALUE on_load_proc;
VALUE decimal_class;
ID decimal_method_id;
enum duplicate_key_action on_duplicate_key;
int max_nesting;
bool allow_nan;
bool allow_trailing_comma;
bool parsing_name;
bool symbolize_names;
bool freeze;
} JSON_ParserConfig;

are only written in parser_config_init().
However we need to make sure that's not called multiple times, so there should be a rb_check_frozen() at the beginning of parser_config_init().

BTW bool parsing_name; seems unused.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW bool parsing_name; seems unused.

Good catch: ab5efca

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon Thanks for the review! I added a call to rb_check_frozen() in cParserConfig_initialize.

>> JSON::Coder.new.freeze.instance_variable_get(:@parser_config).send(:initialize, {})
(irb):1:in 'JSON::Ext::ParserConfig#initialize': can't modify frozen JSON::Ext::ParserConfig: #<JSON::Ext::ParserConfig:0x0000000125000d40> (FrozenError)

I wasn't sure whether to add a test for that, it looked like this:

    coder = JSON::Coder.new.freeze
    parser_config = coder.instance_variable_get(:@parser_config)
    assert_instance_of JSON::Parser::Config, parser_config
    assert_raise FrozenError do
      parser_config.send(:initialize, {})
    end

BTW I just realized that the Generator::State object is already frozen in initialize, I'll actually do the same for the Parser::Config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll also need to make sure a frozen Ext::Generator::State is not modified:

>> state = JSON::Coder.new.instance_variable_get(:@state)
=> #<JSON::Ext::Generator::State:0x000000012557c7d8>
>> state.frozen?
=> true
>> state.max_nesting
=> 100
>> state.max_nesting=1
=> 1
>> state.max_nesting
=> 1

Copy link
Member

@eregon eregon Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that's already marked as RUBY_TYPED_FROZEN_SHAREABLE but it's not correct since it can still be mutated. So I guess we need rb_check_frozen() in all methods that can mutate a Ext::Generator::State then.
For Ext::Generator::State fields which don't contain a shareable value (e.g. strings) it would break the Ractor invariant (unshareable objects must always stay in a single Ractor) and probably segfault. And it shouldn't be visible as mutable anyway if marked as RUBY_TYPED_FROZEN_SHAREABLE.

@etiennebarrie etiennebarrie force-pushed the ractor-shareable-json-coder branch 5 times, most recently from 77c03ae to 7ea7a05 Compare November 19, 2025 14:06
@etiennebarrie
Copy link
Contributor Author

I can't repro the errors with JRuby on my machine. I don't have the same JDK but still…

$ ruby -v
jruby 9.4.14.0 (3.1.7) 2025-08-28 ddda6d5992 OpenJDK 64-Bit Server VM 24.0.1 on 24.0.1 +jit [arm64-darwin]

vs. https://github.com/ruby/json/actions/runs/19504076937/job/55825471457#step:3:39

/Users/runner/.rubies/jruby-9.4.14.0/bin/ruby --version
  jruby 9.4.14.0 (3.1.7) 2025-08-28 ddda6d5992 OpenJDK 64-Bit Server VM 21.0.9+10-LTS on 21.0.9+10-LTS +jit [arm64-darwin]

@etiennebarrie etiennebarrie force-pushed the ractor-shareable-json-coder branch from 7ea7a05 to 76ed0f4 Compare November 19, 2025 14:49
@byroot
Copy link
Member

byroot commented Nov 20, 2025

I can't repro the errors with JRuby

Weird, I can't either.

@eregon
Copy link
Member

eregon commented Nov 20, 2025

It seems somewhat transient as it doesn't happen on all platforms.
I wonder if maybe sometimes the wrong json jar gets picked and the one from stdlib is used instead?

@etiennebarrie
Copy link
Contributor Author

The path seems correct:

/Users/runner/work/json/json/lib/json/ext/generator/state.rb
/Users/runner/work/json/json/lib/json/ext/generator.jar

https://github.com/ruby/json/actions/runs/19541236496/job/55947865782#step:5:24

@byroot
Copy link
Member

byroot commented Nov 20, 2025

Maybe it’s not worth testing this closely anyways?

@etiennebarrie
Copy link
Contributor Author

Yeah at first I didn't have the test at all but then when I found out that the existing State#freeze was subtly incorrect, I felt like it was useful. But the important test is the high-level ractor test. I'll clean that up.

@etiennebarrie etiennebarrie force-pushed the ractor-shareable-json-coder branch from 0c34822 to 7ca198c Compare November 20, 2025 15:32
@eregon
Copy link
Member

eregon commented Nov 20, 2025

I would suggest to keep the test (it was a pretty subtle and important bug) and skip it on JRuby, it sounds like a JRuby bug to me, maybe something @headius could investigate when he has some time.

@eregon
Copy link
Member

eregon commented Nov 20, 2025

Re-reading the diff, the test for JSON::State is kept but the one for JSON::Parser::Config is removed.
I think it's important to add it back, because without it, RUBY_TYPED_FROZEN_SHAREABLE is quite unsafe, it could regress (missing frozen check) and e.g. cause segfaults.

@etiennebarrie etiennebarrie force-pushed the ractor-shareable-json-coder branch from 7ca198c to 1df7523 Compare November 21, 2025 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants