Skip to content

Show chain of references in Ractor errors - #15983

Open
etiennebarrie wants to merge 3 commits into
ruby:masterfrom
etiennebarrie:ractor-error-reference-chain
Open

etiennebarrie wants to merge 3 commits into
ruby:masterfrom
etiennebarrie:ractor-error-reference-chain

Conversation

@etiennebarrie

@etiennebarrie etiennebarrie commented Jan 27, 2026 •

Copy link
Copy Markdown
Contributor

When an object fails to be made shareable with Ractor.make_shareable or when an unshareable object is accessed through module constants or module instance variables, the error message now includes the chain of references that leads to the unshareable value.

For example with this code:

class A; attr_accessor :b; end
class B; attr_accessor :c; end
class C; attr_accessor :d; end
class D; attr_accessor :e; end

a = A.new
a.b = b = B.new
b.c = c = C.new
c.d = d = D.new
d.e = ->{}

Ractor.make_shareable a

Currently you get:

$ ruby -v test.rb
ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin25]
test.rb:12:in 'Ractor.make_shareable': Proc's self is not shareable: #<Proc:0x0000000124a1c898 test.rb:10 (lambda)> (Ractor::IsolationError)
	from test.rb:12:in '<main>'

This PR turns the error message into:

$ ./ruby -v test.rb 
ruby 4.1.0dev (2026-01-27T16:02:28Z ractor-error-refer.. f44c1602bb) +PRISM [arm64-darwin25]
last_commit=Show chain of references in Ractor errors
test.rb:12:in 'Ractor.make_shareable': Proc's self is not shareable: #<Proc:0x000000010893d860 test.rb:10 (lambda)> (Ractor::IsolationError)
  from block's self (an instance of Object)
  from instance variable @e of an instance of D
  from instance variable @d of an instance of C
  from instance variable @c of an instance of B
  from instance variable @b of an instance of A
	from test.rb:12:in '<main>'

I chose not to display individual objects except in the "block self" case, but maybe we shouldn't even in that case (in this example main is pretty short). Including the objects' #inspect output causes the lines to easily become super long and the overall output to be messy.

@cloudbees-smart-tests

This comment has been minimized.

@luke-gru luke-gru left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If chain is guaranteed to be non-nil when printing, I think an assertion that chain != Qnil would be helpful. Otherwise lgtm!

Comment thread test/ruby/test_ractor.rb Outdated
Comment thread test/ruby/test_ractor.rb Outdated
Comment thread test/ruby/test_ractor.rb Outdated
@luke-gruber

luke-gruber commented Jan 27, 2026 •

Copy link
Copy Markdown
Contributor

I think for code like this:

str = "hi"
str.instance_variable_set("@b", proc { })
obj = Object.new
obj.instance_variable_set("@a", str)
ary = [obj]
Ractor.make_shareable(ary)

It would be nice to get more type info like:

./miniruby -I../ruby/lib -I. -I.ext/common  -r./arm64-darwin25-fake  ../ruby/test.rb
../ruby/test.rb:9:in 'Ractor.make_shareable': Proc's self is not shareable: #<Proc:0x0000000121c420b0 ../ruby/test.rb:6> (Ractor::IsolationError)
  from block's self (main)
  from instance variable @b on String (Proc)
  from instance variable @a on Object (String)
  from Array element at index 0 (Object)
	from ../ruby/test.rb:9:in '<main>'

The "on Type" part requires tracking the data->parent, and setting it back during each return path in obj_traverse_i . A bit annoying, but it's probably worth it.

@byroot

byroot commented Jan 28, 2026

Copy link
Copy Markdown
Member

I was about to suggest including the type as well.

@etiennebarrie
etiennebarrie force-pushed the ractor-error-reference-chain branch 4 times, most recently from ee0ecd3 to eceb099 Compare January 30, 2026 10:47
@etiennebarrie

Copy link
Copy Markdown
Contributor Author

It would be nice to get more type info like:

I find it's a bit too redundant, each type is listed twice, once on the instance itself, and then on the object that refers to that instance on the line below.

Here's what I got now for that example:

../../test.rb:6:in 'Ractor.make_shareable': Proc's self is not shareable: #<Proc:0x00000001034c20b0 ../../test.rb:2> (Ractor::IsolationError)
  from block's self (an instance of Object)
  from instance variable @b of an instance of String
  from instance variable @a of an instance of Object
  from Array element at index 0
	from ../../test.rb:6:in '<main>'

"main" is now "an instance of Object" which is not great but in a lot of cases, proc's self will be of another class, the one where the proc was defined.

I also changed "from struct member :foo" to "from member :foo of an instance of S" because S might be a Data.

The updated result for the example at the top:

test.rb:12:in 'Ractor.make_shareable': Proc's self is not shareable: #<Proc:0x000000010893d860 test.rb:10 (lambda)> (Ractor::IsolationError)
  from block's self (an instance of Object)
  from instance variable @e of an instance of D
  from instance variable @d of an instance of C
  from instance variable @c of an instance of B
  from instance variable @b of an instance of A
	from test.rb:12:in '<main>'

@byroot

byroot commented Jan 30, 2026

Copy link
Copy Markdown
Member

I like it. I guess the only nitpick I'd have would be that perhaps all that should be in #detailed_message rather than message. But that's not a strong opinion.

@etiennebarrie

Copy link
Copy Markdown
Contributor Author

Yeah I'm not sure, it seems #detailed_message is more about enhancing the error message (ANSI codes/class name on first line) than adding more information. I could be convinced but the message without those details is really often not enough so I would lean towards keeping it.

@ko1

ko1 commented Feb 4, 2026

Copy link
Copy Markdown
Contributor
  1. The patch allocate one String object per traversing. I think it is not acceptable for normal path.
  2. Also I'm afraid that this information can be explored on deep nesting data structure and I'm not sure it is allowed to put such huge lines in an error message.

Ractor::IsolationError#path (or better name) is another idea?

@byroot

byroot commented Feb 4, 2026

Copy link
Copy Markdown
Member
  1. The patch allocate one String object per traversing. I think it is not acceptable for normal path.

That's only on the error path though.

I'm not sure it is allowed to put such huge lines in an error message.

Yeah, I already suggested storing it in the exception and surfacing it via #detailed_message

@etiennebarrie
etiennebarrie force-pushed the ractor-error-reference-chain branch from eceb099 to bd6d236 Compare February 5, 2026 17:10
@etiennebarrie

Copy link
Copy Markdown
Contributor Author

I've made this script to check the allocations:

class A; attr_accessor :b; end
class B; attr_accessor :c; end
class C; attr_accessor :d; end
class D; attr_accessor :e; end

def a(leaf = -> {})
  a = A.new
  a.b = b = B.new
  b.c = c = C.new
  c.d = d = D.new
  d.e = leaf
  a
end

def foo(desc, obj)
  before = GC.stat(:total_allocated_objects)
  Ractor.make_shareable(obj) rescue nil
  puts "#{desc}: #{GC.stat(:total_allocated_objects) - before}"
end

foo "warmup no traversal", nil
foo "no traversal", nil
foo "warmup no traversal not shareable", ->{}
foo "no traversal not shareable", ->{}
foo "warmup shareable", a(nil)
foo "shareable", a(nil)
foo "warmup not shareable", a
foo "not shareable", a

On main:

warmup no traversal: 3
no traversal: 0
warmup no traversal not shareable: 11
no traversal not shareable: 5
warmup shareable: 6
shareable: 1
warmup not shareable: 6
not shareable: 6

On this branch:

warmup no traversal: 3
no traversal: 0
warmup no traversal not shareable: 14
no traversal not shareable: 7
warmup shareable: 6
shareable: 1
warmup not shareable: 16
not shareable: 12

We can see when the object is shareable, we still only allocate rec_hash to prevent infinite loops. When it's not shareable we do allocate a few more strings to build the chain of references.

When an object fails to be made shareable with `Ractor.make_shareable`
or when an unshareable object is accessed through module constants or
module instance variables, the error message now includes the chain of
references that leads to the unshareable value.
@etiennebarrie
etiennebarrie force-pushed the ractor-error-reference-chain branch from bd6d236 to d00928a Compare April 2, 2026 14:07
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.

5 participants