Issue #17221 has been updated by duerst (Martin D=FCrst).


Eregon (Benoit Daloze) wrote in #note-11:
> I believe it's `a yielding Fiber` (the first sound in yield-ing is not a =
vowel sound)

A Yes indeed for this question. (not an yes :-)

----------------------------------------
Bug #17221: Relax the Fiber#transfer's limitation
https://bugs.ruby-lang.org/issues/17221#change-87975

* Author: ko1 (Koichi Sasada)
* Status: Open
* Priority: Normal
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN
----------------------------------------
Using `Fiber#transfer` with `Fiber#resume` for a same Fiber is limited (onc=
e `Fiber#transfer` is called for a fiber, the fiber can not be resumed more=
).

```ruby
require 'fiber'
f1 =3D nil
f2 =3D Fiber.new{
  f1.transfer
}
f1 =3D Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=3D> 10
p f1.resume #=3D> `resume': cannot resume transferred Fiber (FiberError)
```

This restriction was introduced to protect the resume/yield chain, but we r=
ealized that it is too much to protect the chain.

Instead of the current restriction, we introduce some other protections.

(1) can not transfer to the resuming fiber.

```ruby
require 'fiber'

root =3D Fiber.current
f1 =3D f2 =3D nil

f1 =3D Fiber.new{
  f2 =3D Fiber.new{
    root.transfer(10)
  }
  f2.resume
}

p f1.transfer #=3D> 10

# root <-----+
#  |         |
#  v         | transfer
#  f1 -> f2 -+ # resume/yield chain


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=3D> attempt to transfer to a resuming fiber (FiberError)

# f1 has it's own resume/yield chain, and f1.transfer breaks the chain

# root <-----+
#  || (error)|
#  vv        |
#  f1 -> f2 -+ # resume/yield chain
```

(2) can not transfer to the yielding fiber.

```ruby
require 'fiber'

f1 =3D f2 =3D nil

f1 =3D Fiber.new{
  f2 =3D Fiber.new{
    Fiber.yield
  }
  f2.resume
  10
}

p f1.transfer #=3D> 10

# root =

# | ^
# | | transfer
# v |
# f1 --> f2 # resume/yield chain
#    <--

p f2.transfer #=3D> `transfer': attempt to transfer to an yielding fiber (F=
iberError)

# f2 is waiting for the resume, so the transfer is not allowed.

# root --+
# | ^    | transfer (error)
# | |    |
# v |    v
# f1 --> f2 # resume/yield chain
#    <--

```

(3) can not resume transferring fiber.

```ruby
require 'fiber'

f1 =3D f2 =3D nil

f2 =3D Fiber.new{
  f1.resume #=3D> attempt to resume the transferring fiber (FiberError)
}
f1 =3D Fiber.new{
  f2.transfer
}
f1.transfer

# root
# |
# v
# f1 <-+
# |    |
# v    | resume (error)
# f2 --+

# f1 seems waiting for transfer from other fibers.
```

(4) can not yield from not-resumed fiber

```
require 'fiber'

f2 =3D Fiber.new do
  Fiber.yield #=3D> `yield': attempt to yield on not resumed fiber (FiberEr=
ror)
end

f1 =3D Fiber.new
  f2.transfer
end

p f1.transfer

#     root
#     |
#     v
#     f1
#     |
#     v
#  <- f2
#  yield to where ...? (2.7 switches to root fiber)
```

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 =3D nil
f2 =3D Fiber.new{
  f1.transfer
}
f1 =3D Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=3D> 10
p f1.resume #=3D> 20


# root -> f1 <-+
#         |    |
#         v    |
#         f2 --+
```

The basic idea is respect *programmer's intention*.

For (1), resuming fiber should be switched by the `Fiber.yield`.
For (2), yielding fiber should be switched by the `Fiber#resume`.
For (3), transferring fiber should be switched by the `Fiber#transfer`.

Mainly (1) can keep the resume/yield chain. Also (2) and (3) makes the chai=
n and relationships with fibers cleanly.

----

Also at the end of a transferred fiber, it had continued on root fiber.

However, if the root fiber resumed a fiber (and that fiber can resumed anot=
her fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain fr=
om root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to f3 and=
 f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).

```ruby
require 'fiber'
f3 =3D Fiber.new{
  10
}
f2 =3D Fiber.new{
  f3.transfer + 20
}
f1 =3D Fiber.new{
  f2.resume
}
p f1.resume #=3D> 30

# without this patch:
#
# root -> f1 -> f2
#  ^             |
#  | exit        v
#  +----------- f3

# with this patch:
#
# root -> f1 -> f2 <-+  # keep resume/yield chain
#               |    |
#               v    |
#               f3 --+ exit
```

The patch is: https://github.com/ruby/ruby/pull/3636

---Files--------------------------------
clipboard-202010080257-u2lbv.png (25.5 KB)


-- =

https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=3Dunsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>