Move worker task logic into a new TaskPool class.
This commit is contained in:
parent
f9372446b1
commit
40713db075
3 changed files with 494 additions and 314 deletions
|
@ -107,6 +107,13 @@ struct Task {
|
|||
Base64.encode(md.finish()[0 .. 3], dst);
|
||||
if (!this.running) dst.put("-fin");
|
||||
}
|
||||
string getDebugID()
|
||||
@trusted {
|
||||
import std.array : appender;
|
||||
auto app = appender!string;
|
||||
getDebugID(app);
|
||||
return app.data;
|
||||
}
|
||||
|
||||
bool opEquals(in ref Task other) const @safe nothrow { return m_fiber is other.m_fiber && m_taskCounter == other.m_taskCounter; }
|
||||
bool opEquals(in Task other) const @safe nothrow { return m_fiber is other.m_fiber && m_taskCounter == other.m_taskCounter; }
|
||||
|
@ -338,6 +345,7 @@ final package class TaskFiber : Fiber {
|
|||
while (true) {
|
||||
while (!m_taskFunc.func) {
|
||||
try {
|
||||
logTrace("putting fiber to sleep waiting for new task...");
|
||||
Fiber.yield();
|
||||
} catch (Exception e) {
|
||||
logWarn("CoreTaskFiber was resumed with exception but without active task!");
|
||||
|
@ -427,11 +435,12 @@ final package class TaskFiber : Fiber {
|
|||
void join(bool interruptiple)(size_t task_counter)
|
||||
@trusted {
|
||||
auto cnt = m_onExit.emitCount;
|
||||
while (m_running && m_taskCounter == task_counter)
|
||||
while (m_running && m_taskCounter == task_counter) {
|
||||
static if (interruptiple)
|
||||
cnt = m_onExit.wait(1.seconds, cnt);
|
||||
cnt = m_onExit.wait(cnt);
|
||||
else
|
||||
cnt = m_onExit.waitUninterruptible(1.seconds, cnt);
|
||||
cnt = m_onExit.waitUninterruptible(cnt);
|
||||
}
|
||||
}
|
||||
|
||||
/** Throws an InterruptExeption within the task as soon as it calls an interruptible function.
|
||||
|
@ -480,8 +489,52 @@ final package class TaskFiber : Fiber {
|
|||
|
||||
package struct TaskFuncInfo {
|
||||
void function(TaskFuncInfo*) func;
|
||||
void[2*size_t.sizeof] callable = void;
|
||||
void[maxTaskParameterSize] args = void;
|
||||
void[2*size_t.sizeof] callable;
|
||||
void[maxTaskParameterSize] args;
|
||||
|
||||
static TaskFuncInfo make(CALLABLE, ARGS...)(ref CALLABLE callable, ref ARGS args)
|
||||
{
|
||||
import std.algorithm : move;
|
||||
import std.traits : hasElaborateAssign;
|
||||
|
||||
static struct TARGS { ARGS expand; }
|
||||
|
||||
static assert(CALLABLE.sizeof <= TaskFuncInfo.callable.length);
|
||||
static assert(TARGS.sizeof <= maxTaskParameterSize,
|
||||
"The arguments passed to run(Worker)Task must not exceed "~
|
||||
maxTaskParameterSize.to!string~" bytes in total size.");
|
||||
|
||||
static void callDelegate(TaskFuncInfo* tfi) {
|
||||
assert(tfi.func is &callDelegate, "Wrong callDelegate called!?");
|
||||
|
||||
// copy original call data to stack
|
||||
CALLABLE c;
|
||||
TARGS args;
|
||||
move(*(cast(CALLABLE*)tfi.callable.ptr), c);
|
||||
move(*(cast(TARGS*)tfi.args.ptr), args);
|
||||
|
||||
// reset the info
|
||||
tfi.func = null;
|
||||
|
||||
// make the call
|
||||
mixin(callWithMove!ARGS("c", "args.expand"));
|
||||
}
|
||||
|
||||
return () @trusted {
|
||||
TaskFuncInfo tfi;
|
||||
tfi.func = &callDelegate;
|
||||
|
||||
static if (hasElaborateAssign!CALLABLE) tfi.initCallable!CALLABLE();
|
||||
static if (hasElaborateAssign!TARGS) tfi.initArgs!TARGS();
|
||||
tfi.typedCallable!CALLABLE = callable;
|
||||
foreach (i, A; ARGS) {
|
||||
static if (needsMove!A) args[i].move(tfi.typedArgs!TARGS.expand[i]);
|
||||
else tfi.typedArgs!TARGS.expand[i] = args[i];
|
||||
}
|
||||
return tfi;
|
||||
} ();
|
||||
}
|
||||
|
||||
|
||||
@property ref C typedCallable(C)()
|
||||
{
|
||||
|
@ -678,7 +731,9 @@ package struct TaskScheduler {
|
|||
auto thisthr = thist ? thist.thread : () @trusted { return Thread.getThis(); } ();
|
||||
assert(t.thread is thisthr, "Cannot switch to a task that lives in a different thread.");
|
||||
if (thist == Task.init) {
|
||||
logTrace("switch to task from global context");
|
||||
resumeTask(t);
|
||||
logTrace("task yielded control back to global context");
|
||||
} else {
|
||||
auto tf = () @trusted { return t.taskFiber; } ();
|
||||
auto thistf = () @trusted { return thist.taskFiber; } ();
|
||||
|
@ -690,7 +745,7 @@ package struct TaskScheduler {
|
|||
assert(!tf.m_queue, "Task removed from queue, but still has one set!?");
|
||||
}
|
||||
|
||||
logTrace("Switching tasks");
|
||||
logDebugV("Switching tasks (%s already in queue)", m_taskQueue.length);
|
||||
m_taskQueue.insertFront(thistf);
|
||||
m_taskQueue.insertFront(tf);
|
||||
doYield(thist);
|
||||
|
@ -725,7 +780,9 @@ package struct TaskScheduler {
|
|||
while (m_taskQueue.front !is m_markerTask) {
|
||||
auto t = m_taskQueue.front;
|
||||
m_taskQueue.popFront();
|
||||
logTrace("resuming task");
|
||||
resumeTask(t.task);
|
||||
logTrace("task out");
|
||||
|
||||
assert(!m_taskQueue.empty, "Marker task got removed from tasks queue!?");
|
||||
if (m_taskQueue.empty) return ScheduleStatus.idle; // handle gracefully in release mode
|
||||
|
@ -734,6 +791,8 @@ package struct TaskScheduler {
|
|||
// remove marker task
|
||||
m_taskQueue.popFront();
|
||||
|
||||
logDebugV("schedule finished - %s tasks left in queue", m_taskQueue.length);
|
||||
|
||||
return m_taskQueue.empty ? ScheduleStatus.allProcessed : ScheduleStatus.busy;
|
||||
}
|
||||
|
||||
|
@ -742,7 +801,9 @@ package struct TaskScheduler {
|
|||
{
|
||||
import std.encoding : sanitize;
|
||||
|
||||
logTrace("task fiber resume");
|
||||
auto uncaught_exception = () @trusted nothrow { return t.fiber.call!(Fiber.Rethrow.no)(); } ();
|
||||
logTrace("task fiber yielded");
|
||||
|
||||
if (uncaught_exception) {
|
||||
auto th = cast(Throwable)uncaught_exception;
|
||||
|
@ -850,3 +911,59 @@ private struct FLSInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// mixin string helper to call a function with arguments that potentially have
|
||||
// to be moved
|
||||
package string callWithMove(ARGS...)(string func, string args)
|
||||
{
|
||||
import std.string;
|
||||
string ret = func ~ "(";
|
||||
foreach (i, T; ARGS) {
|
||||
if (i > 0) ret ~= ", ";
|
||||
ret ~= format("%s[%s]", args, i);
|
||||
static if (needsMove!T) ret ~= ".move";
|
||||
}
|
||||
return ret ~ ");";
|
||||
}
|
||||
|
||||
private template needsMove(T)
|
||||
{
|
||||
template isCopyable(T)
|
||||
{
|
||||
enum isCopyable = __traits(compiles, (T a) { return a; });
|
||||
}
|
||||
|
||||
template isMoveable(T)
|
||||
{
|
||||
enum isMoveable = __traits(compiles, (T a) { return a.move; });
|
||||
}
|
||||
|
||||
enum needsMove = !isCopyable!T;
|
||||
|
||||
static assert(isCopyable!T || isMoveable!T,
|
||||
"Non-copyable type "~T.stringof~" must be movable with a .move property.");
|
||||
}
|
||||
|
||||
unittest {
|
||||
enum E { a, move }
|
||||
static struct S {
|
||||
@disable this(this);
|
||||
@property S move() { return S.init; }
|
||||
}
|
||||
static struct T { @property T move() { return T.init; } }
|
||||
static struct U { }
|
||||
static struct V {
|
||||
@disable this();
|
||||
@disable this(this);
|
||||
@property V move() { return V.init; }
|
||||
}
|
||||
static struct W { @disable this(); }
|
||||
|
||||
static assert(needsMove!S);
|
||||
static assert(!needsMove!int);
|
||||
static assert(!needsMove!string);
|
||||
static assert(!needsMove!E);
|
||||
static assert(!needsMove!T);
|
||||
static assert(!needsMove!U);
|
||||
static assert(needsMove!V);
|
||||
static assert(!needsMove!W);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue