Bug #9664


cannot resume transferred Fiber even if it should resume

Added by royaltm (Rafał Michalski) about 8 years ago. Updated over 2 years ago.

Target version:
ruby -v:
2.1.0 2.0.0-p353


The simplified case:

root = Fiber.current
f = {
  puts 'transfer'
  puts 'yield'
  puts 'ok'

First we resume into a new fiber, then the fiber f transfers control back to root and then root transfers control back to f.
Then the fiber f yields back to root and when root tries to resume fiber f the unexpected error is raised.

In ruby MRI 1.9.(1-3) it works as expected and the result is:


However since MRI 2.0 we get:

-:11:in `resume': cannot resume transferred Fiber (FiberError)
        from -:11:in `<main>'

In ruby #transfer docs one reads:

The fiber which receives the transfer call is treats it much like a resume call. Arguments passed to transfer are treated like those passed to resume.

You cannot resume a fiber that transferred control to another one. This will cause a double resume error. You need to transfer control back to this fiber before it can yield and resume.

But it looks like since 2.0 you can't yield and resume fiber after transfering control from or back to it.
This looks like if a fiber has ever transfered control with #transfer then it is somehow marked and resuming to it is no more possible.

This bug kills possibility to use e.g. fiber-synchronized async frameworks together with enumerators.

Related issues 1 (0 open1 closed)

Has duplicate Ruby master - Bug #12555: Cannot resume a fiber that was earlier transferred from, then transferred back to, and then yieldedRejectedActions

Updated by royaltm (Rafał Michalski) about 8 years ago

More straightforward example and a convenient way to detect the bug:

begin { Fiber.yield Fiber.current }.transfer.resume
  puts "Cool I'm in ruby 1.9.x. I've got fibers!"
rescue FiberError
  puts "Too bad, I'm in 2.x and no transfer/yield/resume for me. Is it supposed to be a feature? :("

Updated by cremes (Chuck Remes) over 4 years ago

This bug or limitation still exists on Ruby 2.4.2. Any plans to address it?

This thread on identifies a possible bug in the C code.

fib->transferred = 1;

The transferred flag is set to 1 but it is never cleared under any circumstance.

Actions #3

Updated by jeremyevans0 (Jeremy Evans) over 2 years ago

  • Has duplicate Bug #12555: Cannot resume a fiber that was earlier transferred from, then transferred back to, and then yielded added

Updated by jeremyevans0 (Jeremy Evans) over 2 years ago

  • Assignee set to ioquatix (Samuel Williams)
  • Status changed from Open to Assigned

This bug still exists in the master branch, and I agree with @cremes (Chuck Remes) as to the cause. When calling Fiber#transfer, the transferred flag needs to be set on the source fiber, not the target fiber, and it should be unset on the target fiber. I've added a pull request to fix this:

Actions #5

Updated by jeremyevans (Jeremy Evans) over 2 years ago

  • Status changed from Assigned to Closed

Applied in changeset git|fa8ac91e957a076f6df1adaecad7896817138009.

Fix Fiber#transfer

Fiber#transfer previously made it impossible to resume the fiber
if it was transferred to (no resuming the target of Fiber#transfer).
However, the documentation specifies that you cannot resume a fiber
that has transferred to another fiber (no resuming the source of
Fiber#transfer), unless control is transferred back.

Fix the code by setting the transferred flag on the current/source
fiber, and unsetting the transferred flag on the target fiber.

Fixes [Bug #9664]
Fixes [Bug #12555]

Updated by ko1 (Koichi Sasada) over 2 years ago

  • Assignee changed from ioquatix (Samuel Williams) to ko1 (Koichi Sasada)
  • Status changed from Closed to Rejected

The reported behavior is intentional.

Fibers are separated to two categories:

(1) Semi-coroutine: transition by resume/yiled. There is a "parent/child" relationship like function call (caller/callee).
(2) Coroutine: transition by Fiber#transfer. There is no relationship between transfer.

(2) is the traditional coroutine. However, we designed (1) because it is easy to retrieve (at least I believe it). For example, exception caused by a hild fiber, all parents fibers receive the exception.

(2) is optional, like callcc. It should be difficult to use and we believe (1) and (2) should not be mixed (a Fiber should be used by one).

(10 years ago, we found good example to show how it is danger behavior, but I can't remember it. sorry)


Also available in: Atom PDF