Various task scheduling fixes.
- the initial task yield() now is done in an uninterruptible way - switchToTask now handles switching to an already scheduled task gracefully - TaskScheduler.hibernate() now properly blocks when called form outside of a task - added yieldUninterruptible()
This commit is contained in:
parent
ed36531bd2
commit
f9579a5dd2
|
@ -333,7 +333,7 @@ final package class TaskFiber : Fiber {
|
||||||
debug if (ms_taskEventCallback) ms_taskEventCallback(TaskEvent.start, handle);
|
debug if (ms_taskEventCallback) ms_taskEventCallback(TaskEvent.start, handle);
|
||||||
if (!isEventLoopRunning) {
|
if (!isEventLoopRunning) {
|
||||||
logTrace("Event loop not running at task start - yielding.");
|
logTrace("Event loop not running at task start - yielding.");
|
||||||
vibe.core.core.yield();
|
vibe.core.core.taskScheduler.yieldUninterruptible();
|
||||||
logTrace("Initial resume of task.");
|
logTrace("Initial resume of task.");
|
||||||
}
|
}
|
||||||
task.func(&task);
|
task.func(&task);
|
||||||
|
@ -345,9 +345,17 @@ final package class TaskFiber : Fiber {
|
||||||
logDebug("Full error: %s", e.toString().sanitize());
|
logDebug("Full error: %s", e.toString().sanitize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_interrupt) {
|
||||||
|
logDebug("Task exited while an interrupt was in flight.");
|
||||||
|
m_interrupt = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.tidInfo.ident = Tid.init; // clear message box
|
this.tidInfo.ident = Tid.init; // clear message box
|
||||||
|
|
||||||
foreach (t; m_joiners) taskScheduler.switchTo(t);
|
foreach (t; m_joiners) {
|
||||||
|
logTrace("Resuming joining task.");
|
||||||
|
taskScheduler.switchTo(t);
|
||||||
|
}
|
||||||
m_joiners.length = 0;
|
m_joiners.length = 0;
|
||||||
m_joiners.assumeSafeAppend();
|
m_joiners.assumeSafeAppend();
|
||||||
|
|
||||||
|
@ -414,6 +422,7 @@ final package class TaskFiber : Fiber {
|
||||||
assert(caller != this.task, "A task cannot interrupt itself.");
|
assert(caller != this.task, "A task cannot interrupt itself.");
|
||||||
assert(caller.thread is this.thread, "Interrupting tasks in different threads is not yet supported.");
|
assert(caller.thread is this.thread, "Interrupting tasks in different threads is not yet supported.");
|
||||||
} else assert(Thread.getThis() is this.thread, "Interrupting tasks in different threads is not yet supported.");
|
} else assert(Thread.getThis() is this.thread, "Interrupting tasks in different threads is not yet supported.");
|
||||||
|
logTrace("Resuming task with interrupt flag.");
|
||||||
m_interrupt = true;
|
m_interrupt = true;
|
||||||
taskScheduler.switchTo(this.task);
|
taskScheduler.switchTo(this.task);
|
||||||
}
|
}
|
||||||
|
@ -428,10 +437,17 @@ final package class TaskFiber : Fiber {
|
||||||
@safe nothrow {
|
@safe nothrow {
|
||||||
assert(Task.getThis().fiber is this, "Handling interrupt outside of the corresponding fiber.");
|
assert(Task.getThis().fiber is this, "Handling interrupt outside of the corresponding fiber.");
|
||||||
if (m_interrupt && on_interrupt) {
|
if (m_interrupt && on_interrupt) {
|
||||||
|
logTrace("Handling interrupt flag.");
|
||||||
m_interrupt = false;
|
m_interrupt = false;
|
||||||
on_interrupt();
|
on_interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package void handleInterrupt()
|
||||||
|
@safe {
|
||||||
|
if (m_interrupt)
|
||||||
|
throw new InterruptException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
package struct TaskFuncInfo {
|
package struct TaskFuncInfo {
|
||||||
|
@ -470,11 +486,11 @@ package struct TaskScheduler {
|
||||||
TaskFiber m_markerTask;
|
TaskFiber m_markerTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe nothrow:
|
@safe:
|
||||||
|
|
||||||
@disable this(this);
|
@disable this(this);
|
||||||
|
|
||||||
@property size_t scheduledTaskCount() const { return m_taskQueue.length; }
|
@property size_t scheduledTaskCount() const nothrow { return m_taskQueue.length; }
|
||||||
|
|
||||||
/** Lets other pending tasks execute before continuing execution.
|
/** Lets other pending tasks execute before continuing execution.
|
||||||
|
|
||||||
|
@ -483,6 +499,20 @@ package struct TaskScheduler {
|
||||||
fírst-in-first-out manner.
|
fírst-in-first-out manner.
|
||||||
*/
|
*/
|
||||||
void yield()
|
void yield()
|
||||||
|
{
|
||||||
|
auto t = Task.getThis();
|
||||||
|
if (t == Task.init) return; // not really a task -> no-op
|
||||||
|
logTrace("Yielding (interrupt=%s)", t.taskFiber.m_interrupt);
|
||||||
|
t.taskFiber.handleInterrupt();
|
||||||
|
if (t.taskFiber.m_queue !is null) return; // already scheduled to be resumed
|
||||||
|
m_taskQueue.insertBack(t.taskFiber);
|
||||||
|
doYield(t);
|
||||||
|
t.taskFiber.handleInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
nothrow:
|
||||||
|
|
||||||
|
void yieldUninterruptible()
|
||||||
{
|
{
|
||||||
auto t = Task.getThis();
|
auto t = Task.getThis();
|
||||||
if (t == Task.init) return; // not really a task -> no-op
|
if (t == Task.init) return; // not really a task -> no-op
|
||||||
|
@ -502,7 +532,7 @@ package struct TaskScheduler {
|
||||||
if (thist == Task.init) {
|
if (thist == Task.init) {
|
||||||
assert(!isEventLoopRunning, "Event processing outside of a fiber should only happen before the event loop is running!?");
|
assert(!isEventLoopRunning, "Event processing outside of a fiber should only happen before the event loop is running!?");
|
||||||
static import vibe.core.core;
|
static import vibe.core.core;
|
||||||
vibe.core.core.processEvents();
|
vibe.core.core.runEventLoopOnce();
|
||||||
} else {
|
} else {
|
||||||
doYield(thist);
|
doYield(thist);
|
||||||
}
|
}
|
||||||
|
@ -516,12 +546,23 @@ package struct TaskScheduler {
|
||||||
void switchTo(Task t)
|
void switchTo(Task t)
|
||||||
{
|
{
|
||||||
auto thist = Task.getThis();
|
auto thist = Task.getThis();
|
||||||
|
|
||||||
|
if (t == thist) return;
|
||||||
|
|
||||||
auto thisthr = thist ? thist.taskFiber.thread : () @trusted { return Thread.getThis(); } ();
|
auto thisthr = thist ? thist.taskFiber.thread : () @trusted { return Thread.getThis(); } ();
|
||||||
assert(t.thread is thisthr, "Cannot switch to a task that lives in a different thread.");
|
assert(t.thread is thisthr, "Cannot switch to a task that lives in a different thread.");
|
||||||
if (thist == Task.init) {
|
if (thist == Task.init) {
|
||||||
resumeTask(t);
|
resumeTask(t);
|
||||||
} else {
|
} else {
|
||||||
assert(!thist.taskFiber.m_queue, "Task already scheduled to be resumed... FIXME: should this really be an error?");
|
assert(!thist.taskFiber.m_queue, "Calling task is running, but scheduled to be resumed!?");
|
||||||
|
if (t.taskFiber.m_queue) {
|
||||||
|
logTrace("Task to switch to is already scheduled. Moving to front of queue.");
|
||||||
|
assert(t.taskFiber.m_queue is &m_taskQueue, "Task is already enqueued, but not in the main task queue.");
|
||||||
|
m_taskQueue.remove(t.taskFiber);
|
||||||
|
assert(!t.taskFiber.m_queue, "Task removed from queue, but still has one set!?");
|
||||||
|
}
|
||||||
|
|
||||||
|
logTrace("Switching tasks");
|
||||||
m_taskQueue.insertFront(thist.taskFiber);
|
m_taskQueue.insertFront(thist.taskFiber);
|
||||||
m_taskQueue.insertFront(t.taskFiber);
|
m_taskQueue.insertFront(t.taskFiber);
|
||||||
doYield(thist);
|
doYield(thist);
|
||||||
|
@ -604,7 +645,7 @@ private struct TaskFiberQueue {
|
||||||
|
|
||||||
void insertFront(TaskFiber task)
|
void insertFront(TaskFiber task)
|
||||||
{
|
{
|
||||||
assert(task.m_queue == null, "Task is already scheduled to be resumed!");
|
assert(task.m_queue is null, "Task is already scheduled to be resumed!");
|
||||||
assert(task.m_prev is null, "Task has m_prev set without being in a queue!?");
|
assert(task.m_prev is null, "Task has m_prev set without being in a queue!?");
|
||||||
assert(task.m_next is null, "Task has m_next set without being in a queue!?");
|
assert(task.m_next is null, "Task has m_next set without being in a queue!?");
|
||||||
task.m_queue = &this;
|
task.m_queue = &this;
|
||||||
|
@ -621,7 +662,7 @@ private struct TaskFiberQueue {
|
||||||
|
|
||||||
void insertBack(TaskFiber task)
|
void insertBack(TaskFiber task)
|
||||||
{
|
{
|
||||||
assert(task.m_queue == null, "Task is already scheduled to be resumed!");
|
assert(task.m_queue is null, "Task is already scheduled to be resumed!");
|
||||||
assert(task.m_prev is null, "Task has m_prev set without being in a queue!?");
|
assert(task.m_prev is null, "Task has m_prev set without being in a queue!?");
|
||||||
assert(task.m_next is null, "Task has m_next set without being in a queue!?");
|
assert(task.m_next is null, "Task has m_next set without being in a queue!?");
|
||||||
task.m_queue = &this;
|
task.m_queue = &this;
|
||||||
|
|
Loading…
Reference in a new issue