Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/natalie/fiber_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class FiberObject : public Object {
static Value set_scheduler(Env *, Value);
Value set_storage(Env *, Value);
Value storage(Env *) const;
Value transfer(Env *env, Args &&args);
void swap_to_previous(Env *env, size_t arg_size, Value *arg_data);

mco_coro *coroutine() { return m_coroutine; }
Expand Down
1 change: 1 addition & 0 deletions lib/natalie/compiler/binding_gen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ def generate_name
gen.binding('Fiber', 'storage', 'FiberObject', 'storage', argc: 0, pass_env: true, pass_block: false, return_type: :Value)
gen.binding('Fiber', 'storage=', 'FiberObject', 'set_storage', argc: 1, pass_env: true, pass_block: false, return_type: :Value)
gen.binding('Fiber', 'to_s', 'FiberObject', 'inspect', argc: 0, pass_env: true, pass_block: false, aliases: ['inspect'], return_type: :Value)
gen.binding('Fiber', 'transfer', 'FiberObject', 'transfer', argc: :any, pass_env: true, pass_block: false, return_type: :Value)

gen.static_binding_as_class_method('File', 'absolute_path', 'FileObject', 'absolute_path', argc: 1..2, pass_env: true, pass_block: false, return_type: :Value)
gen.static_binding_as_class_method('File', 'absolute_path?', 'FileObject', 'is_absolute_path', argc: 1, pass_env: true, pass_block: false, return_type: :bool)
Expand Down
4 changes: 2 additions & 2 deletions spec/core/fiber/current_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
root.should be_an_instance_of(Fiber)
# We can always transfer to the root Fiber; it will never die
5.times do
NATFIXME 'Implement Fiber#transfer', exception: NoMethodError, message: "undefined method 'transfer'" do
NATFIXME 'Implement Fiber#transfer', exception: FiberError, message: 'attempt to resume a resuming fiber' do
root.transfer.should be_nil
end
root.alive?.should be_true
Expand All @@ -24,7 +24,7 @@
end

it "returns the current Fiber when called from a Fiber that transferred to another" do
NATFIXME 'Implement Fiber#transfer', exception: NoMethodError, message: "undefined method 'transfer'" do
NATFIXME 'Implement Fiber#transfer', exception: SpecFailedException do
states = []
fiber = Fiber.new do
states << :fiber
Expand Down
6 changes: 2 additions & 4 deletions spec/core/fiber/inspect_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
end

it "is resumed for a Fiber which was transferred" do
NATFIXME 'Implement Fiber#transfer', exception: NoMethodError, message: "undefined method 'transfer'" do
inspected = Fiber.new { Fiber.current.inspect }.transfer
inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/
end
inspected = Fiber.new { Fiber.current.inspect }.transfer
inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/
end

it "is suspended for a Fiber which was resumed and yielded" do
Expand Down
8 changes: 3 additions & 5 deletions spec/core/fiber/resume_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,9 @@

it "can work with Fiber#transfer" do
fiber1 = Fiber.new { true }
NATFIXME 'Implement Fiber#transfer', exception: NoMethodError, message: "undefined method 'transfer' for an instance of Fiber" do
fiber2 = Fiber.new { fiber1.transfer; Fiber.yield 10 ; Fiber.yield 20; raise }
fiber2.resume.should == 10
fiber2.resume.should == 20
end
fiber2 = Fiber.new { fiber1.transfer; Fiber.yield 10 ; Fiber.yield 20; raise }
fiber2.resume.should == 10
fiber2.resume.should == 20
end

it "raises a FiberError if the Fiber attempts to resume a resuming fiber" do
Expand Down
97 changes: 97 additions & 0 deletions spec/core/fiber/transfer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require_relative '../../spec_helper'
require_relative 'shared/resume'

describe "Fiber#transfer" do
it_behaves_like :fiber_resume, :transfer
end

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 == :fiber2
end

it "returns to the root Fiber when finished" do
f1 = Fiber.new { :fiber_1 }
f2 = Fiber.new { f1.transfer; :fiber_2 }

NATFIXME 'it returns to the root Fiber when finished', exception: SpecFailedException do
f2.transfer.should == :fiber_1
f2.transfer.should == :fiber_2
end
end

it "can be invoked from the same Fiber it transfers control to" do
states = []
NATFIXME 'it can be invoked from the same Fiber it transfers control to', exception: FiberError, message: 'attempt to resume the current fiber' do
fiber = Fiber.new { states << :start; fiber.transfer; states << :end }
fiber.transfer
states.should == [:start, :end]

states = []
fiber = Fiber.new { states << :start; fiber.transfer; states << :end }
fiber.resume
states.should == [:start, :end]
end
end

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]
NATFIXME 'it can not transfer control to a Fiber that has suspended by Fiber.yield', exception: SpecFailedException, message: /but instead raised nothing/ do
-> { fiber2.transfer }.should raise_error(FiberError)
end
end

it "raises a FiberError when transferring to a Fiber which resumes itself" do
fiber = Fiber.new { fiber.resume }
-> { fiber.transfer }.should raise_error(FiberError)
end

it "works if Fibers in different Threads each transfer to a Fiber in the same Thread" do
# This catches a bug where Fibers are running on a thread-pool
# and Fibers from a different Ruby Thread reuse the same native thread.
# Caching the Ruby Thread based on the native thread is not correct in that case,
# and the check for "fiber called across threads" in Fiber#transfer
# might be incorrect based on that.
2.times do
Thread.new do
NATFIXME 'it works if Fibers in different Threads each transfer to a Fiber in the same Thread', exception: FiberError, message: 'attempt to resume a resuming fiber' do
# This catches a bug where Fibers are running on a thread-pool
io_fiber = Fiber.new do |calling_fiber|
calling_fiber.transfer
end
io_fiber.transfer(Fiber.current)
value = Object.new
io_fiber.transfer(value).should equal value
end
end.join
end
end

it "transfers control between a non-main thread's root fiber to a child fiber and back again" do
states = []
thread = Thread.new do
NATFIXME "it transfers control between a non-main thread's root fiber to a child fiber and back again", exception: FiberError, message: 'attempt to resume a resuming fiber' do
f1 = Fiber.new do |f0|
states << 0
value2 = f0.transfer(1)
states << value2
3
end

value1 = f1.transfer(Fiber.current)
states << value1
value3 = f1.transfer(2)
states << value3
end
end
thread.join
NATFIXME "it transfers control between a non-main thread's root fiber to a child fiber and back again", exception: SpecFailedException do
states.should == [0, 1, 2, 3]
end
end
end
5 changes: 5 additions & 0 deletions src/fiber_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ Value FiberObject::storage(Env *env) const {
return fiber->m_storage;
}

// NATFIXME: For now, it is just the same as Fiber#resume
Value FiberObject::transfer(Env *env, Args &&args) {
return resume(env, std::move(args));
}

NO_SANITIZE_ADDRESS Value FiberObject::yield(Env *env, Args args) {
auto current_fiber = FiberObject::current();
auto previous_fiber = current_fiber->m_previous_fiber;
Expand Down
Loading