Bug #22442
closedLazy loading of error_highlight etc. is skipped in threads and Ractors
Description
#21951 made error_highlight / did_you_mean / syntax_suggest lazily loaded.
When the lazy loading is triggered concurrently from threads or Ractors, the decoration may be skipped.
# repro.rb
rs = 2.times.map do
Ractor.new do
nil.upcase
rescue => e
e.detailed_message(highlight: false)
end
end
rs.each { puts it.value, "" }
Expected:
$ ruby repro.rb
undefined method 'upcase' for nil (NoMethodError)
nil.upcase
^^^^^^^
undefined method 'upcase' for nil (NoMethodError)
nil.upcase
^^^^^^^
Actual:
$ ruby repro.rb
undefined method 'upcase' for nil (NoMethodError)
nil.upcase
^^^^^^^
undefined method 'upcase' for nil (NoMethodError)
Cause: the first caller of detailed_message sets a flag when it starts loading. So when another thread or Ractor calls it during the load, it returns without decoration instead of waiting for the load to complete.
Fix: fire the autoload instead of calling rb_require_string, so that the autoload's synchronization prevents the skip.
https://github.com/ruby/ruby/pull/19352
Also, because rb_require_string was used, a newer version installed by gem install error_highlight was not used.
Firing the autoload respects the monkeypatch of Kernel#require, so the newer version of error_highlight will be used.
Note: this is why the snippet is not shown in #22439.
Updated by mame (Yusuke Endoh) 1 day ago
- Related to Bug #22439: Segfault in `Thread::Backtrace::Location#syntax_tree` (via `Exception#detailed_message` / error_highlight) when called from multiple Ractors added
Updated by mame (Yusuke Endoh) about 16 hours ago
- Status changed from Assigned to Closed
Applied in changeset git|d193fd3217a87b1015b73a4d31c0f6e116bc3cdd.
Load the error decoration gems by firing their autoloads
The lazy loading of error_highlight, did_you_mean, and syntax_suggest
claimed the load by an atomic exchange before the load started. So a
caller in another Ractor or thread during the load returned the
undecorated message without waiting for the load to complete.
rs = 2.times.map do
Ractor.new do
nil.upcase
rescue => e
e.detailed_message(highlight: false)
end
end
rs.each { puts it.value, "" }
# One of them lacks the snippet by error_highlight
Load the gems with rb_autoload_load instead of rb_require_string, as a
constant reference would. The autoload's own synchronization makes
concurrent callers wait until the load is done. The flag to load the
gems only once is no longer needed: rb_autoload_load does nothing once
the gems are loaded, and whether to re-dispatch is now decided by whether
the load has just put a decorator above Exception#detailed_message.
This also makes the gems loaded through RubyGems' Kernel#require, so a
newer version of the gems installed by the user is activated, as it was
before they were lazily loaded.
[Bug #22442]
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com