From: "byroot (Jean Boussier) via ruby-core" Date: 2026-01-02T21:49:09+00:00 Subject: [ruby-core:124415] [Ruby Bug#21818] Thread backtraces cannot be communicated over Ractor ports Issue #21818 has been updated by byroot (Jean Boussier). > Is this intentional? I don't think so, more of an oversight. Looking at the implementation, I don't see a reason why it couldn't be marked as FROZEN_SHAREABLE. ---------------------------------------- Bug #21818: Thread backtraces cannot be communicated over Ractor ports https://bugs.ruby-lang.org/issues/21818#change-115933 * Author: dazuma (Daniel Azuma) * Status: Open * ruby -v: ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin25] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- It looks like Thread::Backtrace is confined to the Ractor that produced it: it cannot be copied or moved to another Ractor, nor can it be made shareable. This makes it difficult for a Ractor to communicate exceptions (e.g. the reason for its own failure) to other Ractors. Is this intentional? I would have expected that a Thread::Backtrace is just static data that should not have problems moving between Ractors. Details below: **Thread::Backtrace cannot be moved to another Ractor** Code: ``` def make_trace caller_locations end r1 = Ractor.new do port = receive trace = make_trace puts "**** Original: #{trace.inspect}" port.send(trace, move: true) # Fails here end port = Ractor::Port.new r1.send(port) trace = port.receive # Hangs here puts "**** Received: #{trace.inspect}" ``` Result: ``` **** Original: ["hello.rb:7:in 'block in
'"] # terminated with exception (report_on_exception is true): hello.rb:9:in 'Ractor::Port#send': can not move Thread::Backtrace::Location object. (Ractor::Error) from hello.rb:9:in 'block in
' ``` As a result, it is also not possible to move an Exception: ``` r1 = Ractor.new do port = receive begin raise "hello" rescue => e port.send(e, move: true) # Fails here end end port = Ractor::Port.new r1.send(port) e = port.receive # Hangs here ``` Result: ``` # terminated with exception (report_on_exception is true): hello.rb:6:in 'Ractor::Port#send': can not move Thread::Backtrace object. (Ractor::Error) from hello.rb:6:in 'block in
' ``` Note that Kernel#caller returns a string array that can be moved successfully: ``` def make_trace caller end r1 = Ractor.new do port = receive trace = make_trace puts "**** Original: #{trace.inspect}" port.send(trace, move: true) end port = Ractor::Port.new r1.send(port) trace = port.receive puts "**** Received: #{trace.inspect}" ``` Result: ``` **** Original: ["hello.rb:7:in 'block in
'"] **** Received: ["hello.rb:7:in 'block in
'"] ``` **Thread::Backtrace cannot be *copied* to another Ractor either** Code: ``` def make_trace caller_locations end r1 = Ractor.new do port = receive trace = make_trace puts "**** Original: #{trace.inspect}" port.send(trace, move: false) # Fails here end port = Ractor::Port.new r1.send(port) trace = port.receive # Hangs here puts "**** Received: #{trace.inspect}" ``` Result. Note the specific error message "allocator undefined" that might give a clue why this is all happening: ``` **** Original: ["hello.rb:7:in 'block in
'"] # terminated with exception (report_on_exception is true): hello.rb:9:in 'Ractor::Port#send': allocator undefined for Thread::Backtrace::Location (TypeError) port.send(trace, move: false) ^^^^^^^^^^^^^^^^^^ from hello.rb:9:in 'block in
' ``` Again, Kernel#caller returns a string array that copies successfully: ``` def make_trace caller end r1 = Ractor.new do port = receive trace = make_trace puts "**** Original: #{trace.inspect}" port.send(trace, move: false) # Fails here end port = Ractor::Port.new r1.send(port) trace = port.receive # Hangs here puts "**** Received: #{trace.inspect}" ``` Result: ``` **** Original: ["hello.rb:7:in 'block in
'"] **** Received: ["hello.rb:7:in 'block in
'"] ``` Interestingly, if an exception is copied when sent over a port, it doesn't fail with an undefined allocator, but the backtrace in the copy is empty: ``` r1 = Ractor.new do port = receive begin raise "hello" rescue => e puts "**** Original: #{e.backtrace.inspect}" port.send(e, move: false) end end port = Ractor::Port.new r1.send(port) e = port.receive puts "**** Received: #{e.backtrace.inspect}" ``` Result: ``` **** Original: ["hello.rb:4:in 'block in
'"] **** Received: [] ``` **Thread::Backtrace cannot be made shareable** Code: ``` def make_trace caller_locations end trace = Ractor.make_shareable(make_trace) # Fails here ``` Result: ``` hello.rb:5:in 'Ractor.make_shareable': can not make shareable object for "hello.rb:5:in '
'" (Ractor::Error) from hello.rb:5:in '
' ``` As a result, exceptions also cannot be made shareable: ``` begin raise "hello" rescue => e e = Ractor.make_shareable(e) # Fails here end ``` Result: ``` hello.rb: can not make shareable object for # (Ractor::Error) ``` Interestingly, if the backtrace is empty, make_shareable succeeds, suggesting that it's specifically the Thread::Backtrace::Location that cannot be made shareable: ``` trace = Ractor.make_shareable(caller_locations) puts trace.inspect ``` Result: ``` [] ``` And, of course, the result of Kernel#caller can be made shareable: ``` def make_trace caller end trace = Ractor.make_shareable(make_trace) puts trace.inspect ``` Result: ``` ["hello.rb:5:in '
'"] ``` -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/