Skip to content

Commit a82c28a

Browse files
Drop task-completion transfer; rely on Fiber.blocking only.
The unconditional Fiber.scheduler&.transfer at task completion left task fibers parked (alive but finished) instead of terminating, which breaks Fiber#alive? assumptions in io-event (timers/waiters transfer into finished fibers) and caused downstream servers to hang. The Fiber.blocking change alone fixes the reported cases (Enumerator, bare Fiber.new) since those enter the reactor fiber via resume, so task termination returns control correctly. The transfer-based-scheduler case requires the Ruby-level fix in https://bugs.ruby-lang.org/issues/20081 and is not addressed here. Also fix RuboCop block delimiter spacing in tests. Assisted-By: devx/5f4cfab6-1d74-4100-8755-82d7c62c70ab
1 parent a5c5ee9 commit a82c28a

3 files changed

Lines changed: 3 additions & 45 deletions

File tree

lib/async/task.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,11 +532,6 @@ def schedule(&block)
532532
# Console.info(self) {"Task ensure $! = #{$!} with #{@children&.size.inspect} children!"}
533533
finish!
534534
end
535-
536-
# Transfer control back to the scheduler (which may not be the root
537-
# fiber). This is skipped when a critical exception propagates out of
538-
# the block above, so that it is not swallowed by the transfer.
539-
Fiber.scheduler&.transfer
540535
end
541536

542537
@fiber.async_task = self

releases.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
## v2.42.0
44

55
- `Sync` and `Async` can now be invoked from a non-blocking fiber that has no scheduler (e.g. inside an `Enumerator` or a bare `Fiber.new`). Previously this raised `RuntimeError: Running scheduler on non-blocking fiber!`. The reactor is now run within `Fiber.blocking`, so the scheduler always runs on a blocking fiber.
6-
- When a task finishes, it now explicitly transfers control back to the scheduler (`Fiber.scheduler&.transfer`). This ensures the event loop regains control even when it is running on a fiber that is not the thread's root fiber (e.g. a fiber owned by another transfer-based scheduler), rather than relying on fiber termination returning control to the correct fiber.
76

87
## v2.41.0
98

test/kernel/sync.rb

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
it "returns the result of the block" do
6161
result = Fiber.new do
62-
Sync{|task| value}
62+
Sync {|task| value}
6363
end.resume
6464

6565
expect(result).to be == value
@@ -98,7 +98,7 @@
9898
Fiber[:annotation] = "outer"
9999

100100
result = Fiber.new do
101-
Sync{|task| Fiber[:annotation]}
101+
Sync {|task| Fiber[:annotation]}
102102
end.resume
103103

104104
expect(result).to be == "outer"
@@ -131,49 +131,13 @@
131131
it "cannot yield to the enumerator from within the block" do
132132
# Yielding to the enumerator's consumer from inside `Sync` happens on the task fiber, which is not resumable, so it fails cleanly rather than hanging:
133133
enumerator = Enumerator.new do |yielder|
134-
Sync{yielder << value}
134+
Sync {yielder << value}
135135
end
136136

137137
expect do
138138
enumerator.next
139139
end.to raise_exception(FiberError)
140140
end
141-
142-
it "returns control to the scheduler when the loop fiber is entered via transfer" do
143-
# The reactor loop fiber may be entered via `Fiber#transfer` (e.g. owned
144-
# by another transfer-based scheduler), which places it off the resume
145-
# chain. When a task terminates, control must return to the loop fiber so
146-
# the reactor can finish - not to the resume-chain root. Without this,
147-
# the reactor is abandoned and the scenario deadlocks, so we run it in a
148-
# separate thread with a timeout:
149-
completed = nil
150-
151-
thread = Thread.new do
152-
driver = worker = nil
153-
154-
worker = Fiber.new do
155-
completed = Sync do |task|
156-
task.async{sleep(0.001)}.wait
157-
value
158-
end
159-
160-
driver.transfer
161-
end
162-
163-
driver = Fiber.new do
164-
# Enter the loop fiber via transfer, so it is off the resume chain:
165-
worker.transfer
166-
end
167-
168-
driver.resume
169-
end
170-
171-
finished = thread.join(2)
172-
thread.kill unless finished
173-
174-
expect(finished).not.to be_nil
175-
expect(completed).to be == value
176-
end
177141
end
178142

179143
it "cannot be worked around by running the scheduler on a nested blocking fiber" do

0 commit comments

Comments
 (0)