1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

relax Fiber#transfer's restriction

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). 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.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.

[Bug #17221]

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).
This commit is contained in:
Koichi Sasada 2020-10-08 01:15:32 +09:00
parent 2fd71112fb
commit bf3b2a4374
Notes: git 2020-10-12 22:59:06 +09:00
7 changed files with 269 additions and 91 deletions

View file

@ -42,10 +42,22 @@ describe "Fiber.current" do
fiber3 = Fiber.new do
states << :fiber3
fiber2.transfer
flunk
ruby_version_is '3.0' do
states << :fiber3_terminated
end
ruby_version_is '' ... '3.0' do
flunk
end
end
fiber3.resume
states.should == [:fiber3, :fiber2, :fiber]
ruby_version_is "" ... "3.0" do
states.should == [:fiber3, :fiber2, :fiber]
end
ruby_version_is "3.0" do
states.should == [:fiber3, :fiber2, :fiber, :fiber3_terminated]
end
end
end

View file

@ -3,10 +3,21 @@ require_relative '../../spec_helper'
require 'fiber'
describe "Fiber#resume" do
it "raises a FiberError if the Fiber has transferred control to another Fiber" do
fiber1 = Fiber.new { true }
fiber2 = Fiber.new { fiber1.transfer; Fiber.yield }
fiber2.resume
-> { fiber2.resume }.should raise_error(FiberError)
ruby_version_is '' ... '3.0' do
it "raises a FiberError if the Fiber has transferred control to another Fiber" do
fiber1 = Fiber.new { true }
fiber2 = Fiber.new { fiber1.transfer; Fiber.yield }
fiber2.resume
-> { fiber2.resume }.should raise_error(FiberError)
end
end
ruby_version_is '3.0' do
it "can work with Fiber#transfer" do
fiber1 = Fiber.new { true }
fiber2 = Fiber.new { fiber1.transfer; Fiber.yield 10 ; Fiber.yield 20; raise }
fiber2.resume.should == 10
fiber2.resume.should == 20
end
end
end

View file

@ -11,7 +11,13 @@ describe "Fiber#transfer" do
it "transfers control from one Fiber to another when called from a Fiber" do
fiber1 = Fiber.new { :fiber1 }
fiber2 = Fiber.new { fiber1.transfer; :fiber2 }
fiber2.resume.should == :fiber1
ruby_version_is '' ... '3.0' do
fiber2.resume.should == :fiber1
end
ruby_version_is '3.0' do
fiber2.resume.should == :fiber2
end
end
it "returns to the root Fiber when finished" do
@ -34,12 +40,24 @@ describe "Fiber#transfer" do
states.should == [:start, :end]
end
it "can transfer control to a Fiber that has transferred to another Fiber" do
states = []
fiber1 = Fiber.new { states << :fiber1 }
fiber2 = Fiber.new { states << :fiber2_start; fiber1.transfer; states << :fiber2_end}
fiber2.resume.should == [:fiber2_start, :fiber1]
fiber2.transfer.should == [:fiber2_start, :fiber1, :fiber2_end]
ruby_version_is '' ... '3.0' do
it "can transfer control to a Fiber that has transferred to another Fiber" do
states = []
fiber1 = Fiber.new { states << :fiber1 }
fiber2 = Fiber.new { states << :fiber2_start; fiber1.transfer; states << :fiber2_end}
fiber2.resume.should == [:fiber2_start, :fiber1]
fiber2.transfer.should == [:fiber2_start, :fiber1, :fiber2_end]
end
end
ruby_version_is '3.0' do
it "can not transfer control to a Fiber that has suspended by Fiber.yield" do
states = []
fiber1 = Fiber.new { states << :fiber1 }
fiber2 = Fiber.new { states << :fiber2_start; Fiber.yield fiber1.transfer; states << :fiber2_end}
fiber2.resume.should == [:fiber2_start, :fiber1]
-> { fiber2.transfer }.should raise_error(FiberError)
end
end
it "raises a FiberError when transferring to a Fiber which resumes itself" do
@ -83,4 +101,28 @@ describe "Fiber#transfer" do
thread.join
states.should == [0, 1, 2, 3]
end
ruby_version_is "" ... "3.0" do
it "runs until Fiber.yield" do
obj = mock('obj')
obj.should_not_receive(:do)
fiber = Fiber.new { 1 + 2; Fiber.yield; obj.do }
fiber.transfer
end
it "resumes from the last call to Fiber.yield on subsequent invocations" do
fiber = Fiber.new { Fiber.yield :first; :second }
fiber.transfer.should == :first
fiber.transfer.should == :second
end
it "sets the block parameters to its arguments on the first invocation" do
first = mock('first')
first.should_receive(:arg).with(:first).twice
fiber = Fiber.new { |arg| first.arg arg; Fiber.yield; first.arg arg; }
fiber.transfer :first
fiber.transfer :second
end
end
end