threads/raising_into_threads.rb

tests/_examples/threads/raising_into_threads.rb in the metorex repository.

# `Thread#raise` builds the exception the way `raise` does, in the thread that
# asks, and the other thread raises it where it stands. On the thread running
# now it raises at once, and a thread that has ended takes nothing.
def refusal
  yield
rescue StandardError => problem
  [problem.class, problem.message]
end

p refusal { Thread.current.raise ArgumentError, "at once" }
p refusal { Thread.current.raise Object.new }

done = Thread.new { :over }
done.join
p done.raise("late")

# The cause is settled where the exception is built, from what that thread
# was handling, rather than from what the raising thread was handling.
caught = nil
sleeper = Thread.new do
  Thread.current.report_on_exception = false
  begin
    begin
      raise "inside"
    rescue
      sleep
    end
  rescue => caught
  end
end
Thread.pass until sleeper.stop?
p $!
sleeper.raise "handed over"
sleeper.join
p caught.message
p caught.cause

# An exception handed with a message is asked for a copy through its own
# `exception`.
logged = []
tracked = Class.new(StandardError) do
  define_method(:exception) do |*given|
    logged << given
    super(*given)
  end
end
waiting = Thread.new do
  Thread.current.report_on_exception = false
  sleep
end
Thread.pass until waiting.stop?
waiting.raise tracked.new, "with a message"
p refusal { waiting.join }[1]
p logged.first(2)

# `rescue => name` inside a block sets the variable the block can see.
seen = nil
[1].each do
  begin
    raise "in a block"
  rescue => seen
  end
end
p seen.message

# Each fiber keeps its own `$!`.
begin
  raise "outer"
rescue
  fiber = Fiber.new { $! }
  p fiber.resume
  p $!.message
end

Output:

[ArgumentError, "at once"]
[TypeError, "exception class/object expected"]
nil
nil
"handed over"
nil
"with a message"
[["with a message"], []]
"in a block"
nil
"outer"