From 41c18aef743cdca2e0538c4ce804f21bd632a81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 16 Jun 2019 21:30:18 +0200 Subject: [PATCH 1/2] Add test for issue #161. --- tests/issue-161-multiple-joiners.d | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/issue-161-multiple-joiners.d diff --git a/tests/issue-161-multiple-joiners.d b/tests/issue-161-multiple-joiners.d new file mode 100644 index 0000000..c0ebc78 --- /dev/null +++ b/tests/issue-161-multiple-joiners.d @@ -0,0 +1,31 @@ +/+ dub.sdl: + name "tests" + dependency "vibe-core" path=".." + debugVersions "VibeTaskLog" "VibeAsyncLog" ++/ +module tests; + +import vibe.core.core; +import vibe.core.log; +import vibe.core.sync; +import core.time; +import core.stdc.stdlib : exit; + + +void main() +{ + setTimer(5.seconds, { logError("Test has hung."); exit(1); }); + + Task t; + + runTask({ + t = runTask({ sleep(100.msecs); }); + t.join(); + }); + + yield(); + + assert(t && t.running); + + t.join(); +} From 8c63f79ea77e2f0e08a2bd41103602992d746ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 16 Jun 2019 21:31:39 +0200 Subject: [PATCH 2/2] Avoid yielding in the task finalization phase. Fixes #161. Makes sure that the task finalization finishes (including notifying possibly multiple joiners) before the fiber yields, because it won't be resumed by the scheduler before the next task gets assigned to the fiber. --- source/vibe/core/task.d | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/source/vibe/core/task.d b/source/vibe/core/task.d index ad8f704..7950da9 100644 --- a/source/vibe/core/task.d +++ b/source/vibe/core/task.d @@ -362,7 +362,7 @@ final package class TaskFiber : Fiber { import std.algorithm.mutation : swap; import std.concurrency : Tid, thisTid; import std.encoding : sanitize; - import vibe.core.core : isEventLoopRunning, recycleFiber, taskScheduler, yield; + import vibe.core.core : isEventLoopRunning, recycleFiber, taskScheduler, yield, yieldLock; version (VibeDebugCatchAll) alias UncaughtException = Throwable; else alias UncaughtException = Exception; @@ -413,6 +413,17 @@ final package class TaskFiber : Fiber { this.tidInfo.ident = Tid.init; // clear message box debug (VibeTaskLog) logTrace("Notifying joining tasks."); + + // Issue #161: This fiber won't be resumed before the next task + // is assigned, because it is already marked as de-initialized. + // Since ManualEvent.emit() will need to switch tasks, this + // would mean that only the first waiter is notified before + // this fiber gets a new task assigned. + // Using a yield lock forces all corresponding tasks to be + // enqueued into the schedule queue and resumed in sequence + // at the end of the scope. + auto l = yieldLock(); + m_onExit.emit(); // make sure that the task does not get left behind in the yielder queue if terminated during yield() @@ -723,7 +734,7 @@ package struct TaskScheduler { auto t = Task.getThis(); if (t == Task.init) return; // not really a task -> no-op auto tf = () @trusted { return t.taskFiber; } (); - debug (VibeTaskLog) logTrace("Yielding (interrupt=%s)", tf.m_interrupt); + debug (VibeTaskLog) logTrace("Yielding (interrupt=%s)", () @trusted { return (cast(shared)tf).getTaskStatus().interrupt; } ()); tf.handleInterrupt(); if (tf.m_queue !is null) return; // already scheduled to be resumed m_taskQueue.insertBack(tf);