ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
@ 2020-10-07 17:28 ko1
  2020-10-07 17:57 ` [ruby-core:100335] " ko1
                   ` (13 more replies)
  0 siblings, 14 replies; 15+ messages in thread
From: ko1 @ 2020-10-07 17:28 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been reported by ko1 (Koichi Sasada).

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

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

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

(2) can not transfer to the yielding fiber.

```ruby
require 'fiber'

f1 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

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

(3) can not resume transferring fiber.

```ruby
require 'fiber'

f1 = f2 = nil

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

# root
# |
# v
# f1 <-+
# |    |
# v    | resume
# f2 --+

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

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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



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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100335] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
@ 2020-10-07 17:57 ` ko1
  2020-10-07 20:03 ` [ruby-core:100338] " eregontp
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-07 17:57 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).

File clipboard-202010080257-u2lbv.png added
File clipboard-202010080248-9wdyk.png added

## Implementation note

Each fiber has "resuming_fiber" and we can check (1).

![](clipboard-202010080257-u2lbv.png)


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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

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

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

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

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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-202010080248-9wdyk.png (20 KB)
clipboard-202010080257-u2lbv.png (25.5 KB)


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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100338] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
  2020-10-07 17:57 ` [ruby-core:100335] " ko1
@ 2020-10-07 20:03 ` eregontp
  2020-10-07 22:31 ` [ruby-core:100339] " samuel
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: eregontp @ 2020-10-07 20:03 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by Eregon (Benoit Daloze).


This sounds great to me!

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

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

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

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

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100339] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
  2020-10-07 17:57 ` [ruby-core:100335] " ko1
  2020-10-07 20:03 ` [ruby-core:100338] " eregontp
@ 2020-10-07 22:31 ` samuel
  2020-10-07 23:46 ` [ruby-core:100340] " ko1
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: samuel @ 2020-10-07 22:31 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ioquatix (Samuel Williams).


Great work everyone!

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

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

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

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

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100340] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (2 preceding siblings ...)
  2020-10-07 22:31 ` [ruby-core:100339] " samuel
@ 2020-10-07 23:46 ` ko1
  2020-10-08  0:48 ` [ruby-core:100341] " ko1
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-07 23:46 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).


Note for (2). It can break compatibility, but transferred fibers can not be resumed more, so maybe nobody rely on mixing them.

```ruby
require 'fiber'

f = Fiber.new do
  loop do
    Fiber.yield :ok
  end
end

p f.resume
p f.transfer #=> 3.0  `transfer': attempt to transfer to an yielding fiber (FiberError)
p f.resume   #=> 2.7  `resume': cannot resume transferred Fiber (FiberError)
```


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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

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

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

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

and remove current restriction. The first example works fine:

```ruby
require 'fiber'
f1 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100341] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (3 preceding siblings ...)
  2020-10-07 23:46 ` [ruby-core:100340] " ko1
@ 2020-10-08  0:48 ` ko1
  2020-10-08  9:34 ` [ruby-core:100342] " eregontp
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-08  0:48 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).

Description updated

I found (4) can not yield from not-resumed fiber, so I updated the proposal body with (4).

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100342] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (4 preceding siblings ...)
  2020-10-08  0:48 ` [ruby-core:100341] " ko1
@ 2020-10-08  9:34 ` eregontp
  2020-10-09 19:18 ` [ruby-core:100351] " ko1
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: eregontp @ 2020-10-08  9:34 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by Eregon (Benoit Daloze).


For (2), can we use the error message `attempt to transfer to a yielding fiber (FiberError)` (yielding instead of resuming)? That seems clearer since f2 is not resuming but waiting for a `resume` and inside `Fiber.yield` (= yielding).

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> attempt to transfer to a resuming fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100351] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (5 preceding siblings ...)
  2020-10-08  9:34 ` [ruby-core:100342] " eregontp
@ 2020-10-09 19:18 ` ko1
  2020-10-09 19:19 ` [ruby-core:100352] " ko1
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-09 19:18 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).

Description updated

Eregon (Benoit Daloze) wrote in #note-8:
> For (2), can we use the error message `attempt to transfer to a yielding fiber (FiberError)` (yielding instead of resuming)? That seems clearer since f2 is not resuming but waiting for a `resume` and inside `Fiber.yield` (= yielding).

It was my mistake. Implementation says

>  `transfer': attempt to transfer to an yielding fiber (FiberError)

Description is updated.

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100352] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (6 preceding siblings ...)
  2020-10-09 19:18 ` [ruby-core:100351] " ko1
@ 2020-10-09 19:19 ` ko1
  2020-10-09 20:53 ` [ruby-core:100354] " eregontp
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-09 19:19 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).


BTW, "a yielding"? "an yielding"?

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100354] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (7 preceding siblings ...)
  2020-10-09 19:19 ` [ruby-core:100352] " ko1
@ 2020-10-09 20:53 ` eregontp
  2020-10-11  3:13 ` [ruby-core:100364] " duerst
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: eregontp @ 2020-10-09 20:53 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by Eregon (Benoit Daloze).


I believe it's `a yielding Fiber` (the first sound in yield-ing is not a vowel sound)

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100364] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (8 preceding siblings ...)
  2020-10-09 20:53 ` [ruby-core:100354] " eregontp
@ 2020-10-11  3:13 ` duerst
  2020-10-12  0:16 ` [ruby-core:100376] " ko1
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: duerst @ 2020-10-11  3:13 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by duerst (Martin Dürst).


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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100376] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (9 preceding siblings ...)
  2020-10-11  3:13 ` [ruby-core:100364] " duerst
@ 2020-10-12  0:16 ` ko1
  2020-10-12  0:20 ` [ruby-core:100377] " ko1
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-12  0:16 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).


Thanks!


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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100377] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (10 preceding siblings ...)
  2020-10-12  0:16 ` [ruby-core:100376] " ko1
@ 2020-10-12  0:20 ` ko1
  2020-10-12  9:31 ` [ruby-core:100385] " ko1
  2020-10-12 16:16 ` [ruby-core:100388] " shannonskipper
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-12  0:20 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).


Next English question :)

"attempt to a yielding fiber" or "attempt to the yielding fiber" (a/the) on error message (error message when calling `fib.transfer` and `fib` is yielding).

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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100385] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (11 preceding siblings ...)
  2020-10-12  0:20 ` [ruby-core:100377] " ko1
@ 2020-10-12  9:31 ` ko1
  2020-10-12 16:16 ` [ruby-core:100388] " shannonskipper
  13 siblings, 0 replies; 15+ messages in thread
From: ko1 @ 2020-10-12  9:31 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by ko1 (Koichi Sasada).


BTW I already got Matz's approval so I'll merge it as soon as possible.


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

* 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 (once `Fiber#transfer` is called for a fiber, the fiber can not be resumed more).

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [ruby-core:100388] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation
  2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
                   ` (12 preceding siblings ...)
  2020-10-12  9:31 ` [ruby-core:100385] " ko1
@ 2020-10-12 16:16 ` shannonskipper
  13 siblings, 0 replies; 15+ messages in thread
From: shannonskipper @ 2020-10-12 16:16 UTC (permalink / raw)
  To: ruby-core

Issue #17221 has been updated by shan (Shannon Skipper).


It seems like both "a" and "the" work here. I might say, "cannot transfer to a yielding Fiber" or "attempted transfer to a yielding Fiber."

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

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

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

This restriction was introduced to protect the resume/yield chain, but we realized 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 = Fiber.current
f1 = f2 = nil

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

p f1.transfer #=> 10

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


# horizontal direction: resume
# vertical direction: transfer

p f1.transfer #=> 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 = f2 = nil

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

p f1.transfer #=> 10

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

p f2.transfer #=> `transfer': attempt to transfer to an yielding fiber (FiberError)

# 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 = f2 = nil

f2 = Fiber.new{
  f1.resume #=> attempt to resume the transferring fiber (FiberError)
}
f1 = 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 = Fiber.new do
  Fiber.yield #=> `yield': attempt to yield on not resumed fiber (FiberError)
end

f1 = 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 = nil
f2 = Fiber.new{
  f1.transfer
}
f1 = Fiber.new{
  f2.transfer
  Fiber.yield 10
  Fiber.yield 20
}
p f1.resume #=> 10
p f1.resume #=> 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 chain 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 another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume chain from 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 = Fiber.new{
  10
}
f2 = Fiber.new{
  f3.transfer + 20
}
f1 = Fiber.new{
  f2.resume
}
p f1.resume #=> 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/

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2020-10-12 16:16 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-07 17:28 [ruby-core:100332] [Ruby master Bug#17221] Relax the Fiber#transfer's limitation ko1
2020-10-07 17:57 ` [ruby-core:100335] " ko1
2020-10-07 20:03 ` [ruby-core:100338] " eregontp
2020-10-07 22:31 ` [ruby-core:100339] " samuel
2020-10-07 23:46 ` [ruby-core:100340] " ko1
2020-10-08  0:48 ` [ruby-core:100341] " ko1
2020-10-08  9:34 ` [ruby-core:100342] " eregontp
2020-10-09 19:18 ` [ruby-core:100351] " ko1
2020-10-09 19:19 ` [ruby-core:100352] " ko1
2020-10-09 20:53 ` [ruby-core:100354] " eregontp
2020-10-11  3:13 ` [ruby-core:100364] " duerst
2020-10-12  0:16 ` [ruby-core:100376] " ko1
2020-10-12  0:20 ` [ruby-core:100377] " ko1
2020-10-12  9:31 ` [ruby-core:100385] " ko1
2020-10-12 16:16 ` [ruby-core:100388] " shannonskipper

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).