In order to make a Thread and the fiber it is resuming look like
a single unit, we must make sure that asynchronous exceptions
happening at that boundary propagate the right direction.
Exceptions raised in a thread waiting on Fiber#resume should go
into the fiber and be handled there. Exceptions raised on a fiber
waiting on Fiber.yield should go to the resuming thread and be
handled there. This is done by capturing exceptions in
Fiber#resume, transfer, and yield and propagating them to the
waiting thread/fiber if its queue is still active. If that queue
is not active (the fiber has exited), the exception is propagated
upward in the current fiber/thread.
The unknown case is what should happen to a fiber in which an
exception (for example, a timeout) has triggered.
In MRI, where there's really only one thread, the timeout would go
to the resuming thread and happen arbitrarily in its running code.
The originating fiber would be left undamaged as if nothing had
happened, and would be subject to GC if abandoned or resumable as
normal. In this patch, the original fiber performs the same
exception-propagation loop and goes back to its queue for a
resumed value; this should roughly match MRI's behavior. However,
I am not sure I agree with MRI's behavior, or if MRI's behavior is
correct, then Timeout should *never* be used around Fiber.yield,
since it is impossible to know where and when it will fire.