Convert Waitable from struct to pure template to avoid heap closures.
Also fixes a case where ThreadLocalWaiter.emitSingle overlaps a call to .emit.
This commit is contained in:
parent
e32d818873
commit
9fe9783443
|
@ -1010,14 +1010,17 @@ struct FileDescriptorEvent {
|
||||||
|
|
||||||
assert((which & m_trigger) == Trigger.read, "Waiting for write event not yet supported.");
|
assert((which & m_trigger) == Trigger.read, "Waiting for write event not yet supported.");
|
||||||
|
|
||||||
Waitable!(IOCallback,
|
bool got_data;
|
||||||
|
|
||||||
|
alias readwaiter = Waitable!(IOCallback,
|
||||||
cb => eventDriver.sockets.waitForData(m_socket, cb),
|
cb => eventDriver.sockets.waitForData(m_socket, cb),
|
||||||
cb => eventDriver.sockets.cancelRead(m_socket)
|
cb => eventDriver.sockets.cancelRead(m_socket),
|
||||||
) readwaiter;
|
(StreamSocketFD fd, IOStatus st, size_t nb) { got_data = st == IOStatus.ok; }
|
||||||
|
);
|
||||||
|
|
||||||
asyncAwaitAny!true(timeout, readwaiter);
|
asyncAwaitAny!(true, readwaiter)(timeout);
|
||||||
|
|
||||||
return !readwaiter.cancelled;
|
return got_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ NetworkAddress resolveHost(string host, ushort address_family, bool use_dns = tr
|
||||||
enforce(use_dns, "Malformed IP address string.");
|
enforce(use_dns, "Malformed IP address string.");
|
||||||
NetworkAddress res;
|
NetworkAddress res;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Waitable!(DNSLookupCallback,
|
alias waitable = Waitable!(DNSLookupCallback,
|
||||||
cb => eventDriver.dns.lookupHost(host, cb),
|
cb => eventDriver.dns.lookupHost(host, cb),
|
||||||
(cb, id) => eventDriver.dns.cancelLookup(id),
|
(cb, id) => eventDriver.dns.cancelLookup(id),
|
||||||
(DNSLookupID, DNSStatus status, scope RefAddress[] addrs) {
|
(DNSLookupID, DNSStatus status, scope RefAddress[] addrs) {
|
||||||
|
@ -63,9 +63,9 @@ NetworkAddress resolveHost(string host, ushort address_family, bool use_dns = tr
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) waitable;
|
);
|
||||||
|
|
||||||
asyncAwaitAny!true(waitable);
|
asyncAwaitAny!(true, waitable);
|
||||||
|
|
||||||
enforce(success, "Failed to lookup host '"~host~"'.");
|
enforce(success, "Failed to lookup host '"~host~"'.");
|
||||||
return res;
|
return res;
|
||||||
|
@ -542,22 +542,27 @@ mixin(tracer);
|
||||||
if (m_context.readBuffer.length > 0) return true;
|
if (m_context.readBuffer.length > 0) return true;
|
||||||
auto mode = timeout <= 0.seconds ? IOMode.immediate : IOMode.once;
|
auto mode = timeout <= 0.seconds ? IOMode.immediate : IOMode.once;
|
||||||
|
|
||||||
Waitable!(IOCallback,
|
bool cancelled;
|
||||||
|
IOStatus status;
|
||||||
|
size_t nbytes;
|
||||||
|
|
||||||
|
alias waiter = Waitable!(IOCallback,
|
||||||
cb => eventDriver.sockets.read(m_socket, m_context.readBuffer.peekDst(), mode, cb),
|
cb => eventDriver.sockets.read(m_socket, m_context.readBuffer.peekDst(), mode, cb),
|
||||||
cb => eventDriver.sockets.cancelRead(m_socket)
|
(cb) { cancelled = true; eventDriver.sockets.cancelRead(m_socket); },
|
||||||
) waiter;
|
(sock, st, nb) { assert(sock == m_socket); status = st; nbytes = nb; }
|
||||||
|
);
|
||||||
|
|
||||||
asyncAwaitAny!true(timeout, waiter);
|
asyncAwaitAny!(true, waiter)(timeout);
|
||||||
|
|
||||||
if (waiter.cancelled) return false;
|
if (cancelled) return false;
|
||||||
|
|
||||||
logTrace("Socket %s, read %s bytes: %s", waiter.results[0], waiter.results[2], waiter.results[1]);
|
logTrace("Socket %s, read %s bytes: %s", m_socket, nbytes, status);
|
||||||
|
|
||||||
assert(m_context.readBuffer.length == 0);
|
assert(m_context.readBuffer.length == 0);
|
||||||
m_context.readBuffer.putN(waiter.results[2]);
|
m_context.readBuffer.putN(nbytes);
|
||||||
switch (waiter.results[1]) {
|
switch (status) {
|
||||||
default:
|
default:
|
||||||
logDebug("Error status when waiting for data: %s", waiter.results[1]);
|
logDebug("Error status when waiting for data: %s", status);
|
||||||
break;
|
break;
|
||||||
case IOStatus.ok: break;
|
case IOStatus.ok: break;
|
||||||
case IOStatus.wouldBlock: assert(mode == IOMode.immediate); break;
|
case IOStatus.wouldBlock: assert(mode == IOMode.immediate); break;
|
||||||
|
@ -837,20 +842,21 @@ struct UDPConnection {
|
||||||
|
|
||||||
IOStatus status;
|
IOStatus status;
|
||||||
size_t nbytes;
|
size_t nbytes;
|
||||||
|
bool cancelled;
|
||||||
|
|
||||||
Waitable!(DatagramIOCallback,
|
alias waitable = Waitable!(DatagramIOCallback,
|
||||||
cb => eventDriver.sockets.send(m_socket, data, IOMode.once, peer_address ? addrc : null, cb),
|
cb => eventDriver.sockets.send(m_socket, data, IOMode.once, peer_address ? addrc : null, cb),
|
||||||
cb => eventDriver.sockets.cancelSend(m_socket),
|
(cb) { cancelled = true; eventDriver.sockets.cancelSend(m_socket); },
|
||||||
(DatagramSocketFD, IOStatus status_, size_t nbytes_, scope RefAddress addr)
|
(DatagramSocketFD, IOStatus status_, size_t nbytes_, scope RefAddress addr)
|
||||||
{
|
{
|
||||||
status = status_;
|
status = status_;
|
||||||
nbytes = nbytes_;
|
nbytes = nbytes_;
|
||||||
}
|
}
|
||||||
) waitable;
|
);
|
||||||
|
|
||||||
asyncAwaitAny!true(waitable);
|
asyncAwaitAny!(true, waitable);
|
||||||
|
|
||||||
enforce(!waitable.cancelled && status == IOStatus.ok, "Failed to send packet.");
|
enforce(!cancelled && status == IOStatus.ok, "Failed to send packet.");
|
||||||
enforce(nbytes == data.length, "Packet was only sent partially.");
|
enforce(nbytes == data.length, "Packet was only sent partially.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,10 +879,11 @@ struct UDPConnection {
|
||||||
|
|
||||||
IOStatus status;
|
IOStatus status;
|
||||||
size_t nbytes;
|
size_t nbytes;
|
||||||
|
bool cancelled;
|
||||||
|
|
||||||
Waitable!(DatagramIOCallback,
|
alias waitable = Waitable!(DatagramIOCallback,
|
||||||
cb => eventDriver.sockets.receive(m_socket, buf, IOMode.once, cb),
|
cb => eventDriver.sockets.receive(m_socket, buf, IOMode.once, cb),
|
||||||
cb => eventDriver.sockets.cancelReceive(m_socket),
|
(cb) { cancelled = true; eventDriver.sockets.cancelReceive(m_socket); },
|
||||||
(DatagramSocketFD, IOStatus status_, size_t nbytes_, scope RefAddress addr)
|
(DatagramSocketFD, IOStatus status_, size_t nbytes_, scope RefAddress addr)
|
||||||
{
|
{
|
||||||
status = status_;
|
status = status_;
|
||||||
|
@ -886,10 +893,10 @@ struct UDPConnection {
|
||||||
catch (Exception e) logWarn("Failed to store datagram source address: %s", e.msg);
|
catch (Exception e) logWarn("Failed to store datagram source address: %s", e.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) waitable;
|
);
|
||||||
|
|
||||||
asyncAwaitAny!true(timeout, waitable);
|
asyncAwaitAny!(true, waitable)(timeout);
|
||||||
enforce(!waitable.cancelled, "Receive timeout.");
|
enforce(!cancelled, "Receive timeout.");
|
||||||
enforce(status == IOStatus.ok, "Failed to receive packet.");
|
enforce(status == IOStatus.ok, "Failed to receive packet.");
|
||||||
return buf[0 .. nbytes];
|
return buf[0 .. nbytes];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1236,15 +1236,14 @@ private final class ThreadLocalWaiter(bool EVENT_TRIGGERED) {
|
||||||
private {
|
private {
|
||||||
static struct TaskWaiter {
|
static struct TaskWaiter {
|
||||||
TaskWaiter* prev, next;
|
TaskWaiter* prev, next;
|
||||||
Task task;
|
|
||||||
void delegate() @safe nothrow notifier;
|
void delegate() @safe nothrow notifier;
|
||||||
bool cancelled;
|
|
||||||
|
|
||||||
void wait(void delegate() @safe nothrow del) @safe nothrow {
|
void wait(void delegate() @safe nothrow del) @safe nothrow {
|
||||||
assert(notifier is null, "Local waiter is used twice!");
|
assert(notifier is null, "Local waiter is used twice!");
|
||||||
notifier = del;
|
notifier = del;
|
||||||
}
|
}
|
||||||
void cancel() @safe nothrow { cancelled = true; notifier = null; }
|
void cancel() @safe nothrow { notifier = null; }
|
||||||
|
void emit() @safe nothrow { auto n = notifier; notifier = null; n(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
static if (EVENT_TRIGGERED) {
|
static if (EVENT_TRIGGERED) {
|
||||||
|
@ -1301,13 +1300,15 @@ private final class ThreadLocalWaiter(bool EVENT_TRIGGERED) {
|
||||||
target_timeout = now + timeout;
|
target_timeout = now + timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
Waitable!(typeof(TaskWaiter.notifier),
|
bool cancelled;
|
||||||
cb => waiter.wait(cb),
|
|
||||||
cb => waiter.cancel(),
|
|
||||||
) waitable;
|
|
||||||
|
|
||||||
if (evt != EventID.invalid) {
|
alias waitable = Waitable!(typeof(TaskWaiter.notifier),
|
||||||
Waitable!(EventCallback,
|
(cb) { waiter.wait(cb); },
|
||||||
|
(cb) { cancelled = true; waiter.cancel(); },
|
||||||
|
() {}
|
||||||
|
);
|
||||||
|
|
||||||
|
alias ewaitable = Waitable!(EventCallback,
|
||||||
(cb) {
|
(cb) {
|
||||||
eventDriver.events.wait(evt, cb);
|
eventDriver.events.wait(evt, cb);
|
||||||
// check for exit condition *after* starting to wait for the event
|
// check for exit condition *after* starting to wait for the event
|
||||||
|
@ -1317,14 +1318,17 @@ private final class ThreadLocalWaiter(bool EVENT_TRIGGERED) {
|
||||||
cb(evt);
|
cb(evt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cb => eventDriver.events.cancelWait(evt, cb)
|
(cb) { eventDriver.events.cancelWait(evt, cb); },
|
||||||
) ewaitable;
|
(EventID) {}
|
||||||
asyncAwaitAny!interruptible(timeout, waitable, ewaitable);
|
);
|
||||||
|
|
||||||
|
if (evt != EventID.invalid) {
|
||||||
|
asyncAwaitAny!(interruptible, waitable, ewaitable)(timeout);
|
||||||
} else {
|
} else {
|
||||||
asyncAwaitAny!interruptible(timeout, waitable);
|
asyncAwaitAny!(interruptible, waitable)(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitable.cancelled) {
|
if (cancelled) {
|
||||||
assert(waiter.next !is null, "Cancelled waiter not in queue anymore!?");
|
assert(waiter.next !is null, "Cancelled waiter not in queue anymore!?");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1363,6 +1367,19 @@ private final class ThreadLocalWaiter(bool EVENT_TRIGGERED) {
|
||||||
bool emitSingle()
|
bool emitSingle()
|
||||||
@safe nothrow {
|
@safe nothrow {
|
||||||
if (m_waiters.empty) return false;
|
if (m_waiters.empty) return false;
|
||||||
|
|
||||||
|
TaskWaiter* pivot = () @trusted { return &m_emitPivot; } ();
|
||||||
|
|
||||||
|
if (pivot.next) { // another emit in progress?
|
||||||
|
// shift pivot to the right, so that the other emit call will process another waiter
|
||||||
|
if (pivot !is m_waiters.back) {
|
||||||
|
auto n = pivot.next;
|
||||||
|
m_waiters.remove(pivot);
|
||||||
|
m_waiters.insertAfter(pivot, n);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
emitWaiter(m_waiters.front);
|
emitWaiter(m_waiters.front);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1373,8 +1390,7 @@ private final class ThreadLocalWaiter(bool EVENT_TRIGGERED) {
|
||||||
|
|
||||||
if (w.notifier !is null) {
|
if (w.notifier !is null) {
|
||||||
logTrace("notify task %s %s %s", cast(void*)w, () @trusted { return cast(void*)w.notifier.funcptr; } (), w.notifier.ptr);
|
logTrace("notify task %s %s %s", cast(void*)w, () @trusted { return cast(void*)w.notifier.funcptr; } (), w.notifier.ptr);
|
||||||
w.notifier();
|
w.emit();
|
||||||
w.notifier = null;
|
|
||||||
} else logTrace("notify callback is null");
|
} else logTrace("notify callback is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,85 +8,88 @@ import vibe.core.log;
|
||||||
import core.time : Duration, seconds;
|
import core.time : Duration, seconds;
|
||||||
|
|
||||||
|
|
||||||
auto asyncAwait(Callback, alias action, alias cancel, string func = __FUNCTION__)()
|
auto asyncAwait(Callback, alias action, alias cancel)(string func = __FUNCTION__)
|
||||||
if (!is(Object == Duration)) {
|
if (!is(Object == Duration)) {
|
||||||
Waitable!(Callback, action, cancel) waitable;
|
ParameterTypeTuple!Callback results;
|
||||||
asyncAwaitAny!(true, func)(waitable);
|
alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; });
|
||||||
return tuple(waitable.results);
|
asyncAwaitAny!(true, waitable)(func);
|
||||||
|
return tuple(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto asyncAwait(Callback, alias action, alias cancel, string func = __FUNCTION__)(Duration timeout)
|
auto asyncAwait(Callback, alias action, alias cancel)(Duration timeout, string func = __FUNCTION__)
|
||||||
{
|
{
|
||||||
Waitable!(Callback, action, cancel) waitable;
|
|
||||||
asyncAwaitAny!(true, func)(timeout, waitable);
|
|
||||||
static struct R {
|
static struct R {
|
||||||
bool completed;
|
bool completed = true;
|
||||||
typeof(waitable.results) results;
|
typeof(waitable.results) results;
|
||||||
}
|
}
|
||||||
return R(!waitable.cancelled, waitable.results);
|
R ret;
|
||||||
|
alias waitable = Waitable!(Callback,
|
||||||
|
action,
|
||||||
|
(cb) { ret.completed = false; cancel(cb); },
|
||||||
|
(ParameterTypeTuple!Callback r) { ret.results = r; }
|
||||||
|
);
|
||||||
|
asyncAwaitAny!(true, waitable)(timeout, func);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto asyncAwaitUninterruptible(Callback, alias action, string func = __FUNCTION__)()
|
auto asyncAwaitUninterruptible(Callback, alias action)(string func = __FUNCTION__)
|
||||||
nothrow {
|
nothrow {
|
||||||
static if (is(typeof(action(Callback.init)) == void)) void cancel(Callback) { assert(false, "Action cannot be cancelled."); }
|
static if (is(typeof(action(Callback.init)) == void)) void cancel(Callback) { assert(false, "Action cannot be cancelled."); }
|
||||||
else void cancel(Callback, typeof(action(Callback.init))) { assert(false, "Action cannot be cancelled."); }
|
else void cancel(Callback, typeof(action(Callback.init))) @safe @nogc nothrow { assert(false, "Action cannot be cancelled."); }
|
||||||
Waitable!(Callback, action, cancel) waitable;
|
ParameterTypeTuple!Callback results;
|
||||||
asyncAwaitAny!(false, func)(waitable);
|
alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; });
|
||||||
return tuple(waitable.results);
|
asyncAwaitAny!(false, waitable)(func);
|
||||||
|
return tuple(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto asyncAwaitUninterruptible(Callback, alias action, alias cancel, string func = __FUNCTION__)(Duration timeout)
|
auto asyncAwaitUninterruptible(Callback, alias action, alias cancel)(Duration timeout, string func = __FUNCTION__)
|
||||||
nothrow {
|
nothrow {
|
||||||
Waitable!(Callback, action, cancel) waitable;
|
ParameterTypeTuple!Callback results;
|
||||||
asyncAwaitAny!(false, func)(timeout, waitable);
|
alias waitable = Waitable!(Callback, action, cancel, (ParameterTypeTuple!Callback r) { results = r; });
|
||||||
return tuple(waitable.results);
|
asyncAwaitAny!(false, waitable)(timeout, func);
|
||||||
|
return tuple(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Waitable(CB, alias wait, alias cancel, on_result...)
|
template Waitable(CB, alias WAIT, alias CANCEL, alias DONE)
|
||||||
if (on_result.length <= 1)
|
|
||||||
{
|
{
|
||||||
import std.traits : ReturnType;
|
import std.traits : ReturnType;
|
||||||
|
|
||||||
alias Callback = CB;
|
static assert(is(typeof(WAIT(CB.init))), "WAIT must be callable with a parameter of type "~CB.stringof);
|
||||||
|
static if (is(typeof(WAIT(CB.init)) == void))
|
||||||
static if (on_result.length == 0) {
|
static assert(is(typeof(CANCEL(CB.init))),
|
||||||
static assert(!hasAnyScopeParameter!Callback, "Need to retrieve results with a callback because of scoped parameter");
|
"CANCEL must be callable with a parameter of type "~CB.stringof);
|
||||||
ParameterTypeTuple!Callback results;
|
|
||||||
void setResult(ref ParameterTypeTuple!Callback r) { this.results = r; }
|
|
||||||
} else {
|
|
||||||
import std.format : format;
|
|
||||||
alias PTypes = ParameterTypeTuple!Callback;
|
|
||||||
mixin(q{void setResult(%s) { on_result[0](%s); }}.format(generateParamDecls!Callback, generateParamNames!Callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cancelled;
|
|
||||||
auto waitCallback(Callback cb) nothrow { return wait(cb); }
|
|
||||||
|
|
||||||
static if (is(ReturnType!waitCallback == void))
|
|
||||||
void cancelCallback(Callback cb) nothrow { cancel(cb); }
|
|
||||||
else
|
else
|
||||||
void cancelCallback(Callback cb, ReturnType!waitCallback r) nothrow { cancel(cb, r); }
|
static assert(is(typeof(CANCEL(CB.init, typeof(WAIT(CB.init)).init))),
|
||||||
|
"CANCEL must be callable with parameters ("~CB.stringof~", "~typeof(WAIT(CB.init)).stringof~")");
|
||||||
|
static assert(is(typeof(DONE(ParameterTypeTuple!CB.init))),
|
||||||
|
"DONE must be callable with types "~ParameterTypeTuple!CB.stringof);
|
||||||
|
|
||||||
|
alias Callback = CB;
|
||||||
|
alias wait = WAIT;
|
||||||
|
alias cancel = CANCEL;
|
||||||
|
alias done = DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncAwaitAny(bool interruptible, string func = __FUNCTION__, Waitables...)(Duration timeout, ref Waitables waitables)
|
void asyncAwaitAny(bool interruptible, Waitables...)(Duration timeout, string func = __FUNCTION__)
|
||||||
{
|
{
|
||||||
if (timeout == Duration.max) asyncAwaitAny!(interruptible, func)(waitables);
|
if (timeout == Duration.max) asyncAwaitAny!(interruptible, Waitables)(func);
|
||||||
else {
|
else {
|
||||||
import eventcore.core;
|
import eventcore.core;
|
||||||
|
|
||||||
auto tm = eventDriver.timers.create();
|
auto tm = eventDriver.timers.create();
|
||||||
eventDriver.timers.set(tm, timeout, 0.seconds);
|
eventDriver.timers.set(tm, timeout, 0.seconds);
|
||||||
scope (exit) eventDriver.timers.releaseRef(tm);
|
scope (exit) eventDriver.timers.releaseRef(tm);
|
||||||
Waitable!(TimerCallback,
|
alias timerwaitable = Waitable!(TimerCallback,
|
||||||
cb => eventDriver.timers.wait(tm, cb),
|
cb => eventDriver.timers.wait(tm, cb),
|
||||||
cb => eventDriver.timers.cancelWait(tm)
|
cb => eventDriver.timers.cancelWait(tm),
|
||||||
) timerwaitable;
|
(tid) {}
|
||||||
asyncAwaitAny!(interruptible, func)(timerwaitable, waitables);
|
);
|
||||||
|
asyncAwaitAny!(interruptible, timerwaitable, Waitables)(func);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncAwaitAny(bool interruptible, string func = __FUNCTION__, Waitables...)(ref Waitables waitables)
|
void asyncAwaitAny(bool interruptible, Waitables...)(string func = __FUNCTION__)
|
||||||
if (Waitables.length >= 1 && !is(Waitables[0] == Duration))
|
if (Waitables.length >= 1)
|
||||||
{
|
{
|
||||||
import std.meta : staticMap;
|
import std.meta : staticMap;
|
||||||
import std.algorithm.searching : any;
|
import std.algorithm.searching : any;
|
||||||
|
@ -94,59 +97,59 @@ void asyncAwaitAny(bool interruptible, string func = __FUNCTION__, Waitables...)
|
||||||
import std.meta : AliasSeq;
|
import std.meta : AliasSeq;
|
||||||
import std.traits : ReturnType;
|
import std.traits : ReturnType;
|
||||||
|
|
||||||
/*scope*/ staticMap!(CBDel, Waitables) callbacks; // FIXME: avoid heap delegates
|
|
||||||
|
|
||||||
bool[Waitables.length] fired;
|
bool[Waitables.length] fired;
|
||||||
ScopeGuard[Waitables.length] scope_guards;
|
|
||||||
bool any_fired = false;
|
bool any_fired = false;
|
||||||
Task t;
|
Task t;
|
||||||
|
|
||||||
bool still_inside = true;
|
bool still_inside = true;
|
||||||
scope (exit) still_inside = false;
|
scope (exit) still_inside = false;
|
||||||
|
|
||||||
debug(VibeAsyncLog) logDebugV("Performing %s async operations in %s", waitables.length, func);
|
debug(VibeAsyncLog) logDebugV("Performing %s async operations in %s", Waitables.length, func);
|
||||||
|
|
||||||
() @trusted { logDebugV("si %x", &still_inside); } ();
|
() @trusted { logDebugV("si %x", &still_inside); } ();
|
||||||
|
|
||||||
|
static string waitableCode()
|
||||||
|
{
|
||||||
|
string ret;
|
||||||
foreach (i, W; Waitables) {
|
foreach (i, W; Waitables) {
|
||||||
alias PTypes = ParameterTypeTuple!(CBDel!W);
|
alias PTypes = ParameterTypeTuple!(CBDel!W);
|
||||||
/*scope*/auto cb = mixin(q{(%s) @safe nothrow {
|
ret ~= q{
|
||||||
|
alias PT%1$s = ParameterTypeTuple!(Waitables[%1$s].Callback);
|
||||||
|
scope callback_%1$s = (%2$s) @safe nothrow {
|
||||||
() @trusted { logDebugV("siw %%x", &still_inside); } ();
|
() @trusted { logDebugV("siw %%x", &still_inside); } ();
|
||||||
debug(VibeAsyncLog) logDebugV("Waitable %%s in %%s fired (istask=%%s).", i, func, t != Task.init);
|
debug(VibeAsyncLog) logDebugV("Waitable %%s in %%s fired (istask=%%s).", %1$s, func, t != Task.init);
|
||||||
assert(still_inside, "Notification fired after asyncAwait had already returned!");
|
assert(still_inside, "Notification fired after asyncAwait had already returned!");
|
||||||
fired[i] = true;
|
fired[%1$s] = true;
|
||||||
any_fired = true;
|
any_fired = true;
|
||||||
static if (PTypes.length)
|
Waitables[%1$s].done(%3$s);
|
||||||
waitables[i].setResult(%s);
|
|
||||||
if (t != Task.init) switchToTask(t);
|
if (t != Task.init) switchToTask(t);
|
||||||
}}.format(generateParamDecls!(CBDel!W), generateParamNames!(CBDel!W)));
|
|
||||||
callbacks[i] = cb;
|
|
||||||
|
|
||||||
debug(VibeAsyncLog) logDebugV("Starting operation %s", i);
|
|
||||||
static if (is(ReturnType!(W.waitCallback) == void))
|
|
||||||
waitables[i].waitCallback(callbacks[i]);
|
|
||||||
else
|
|
||||||
auto wr = waitables[i].waitCallback(callbacks[i]);
|
|
||||||
|
|
||||||
scope ccb = () @safe nothrow {
|
|
||||||
if (!fired[i]) {
|
|
||||||
debug(VibeAsyncLog) logDebugV("Cancelling operation %s", i);
|
|
||||||
static if (is(ReturnType!(W.waitCallback) == void))
|
|
||||||
waitables[i].cancelCallback(callbacks[i]);
|
|
||||||
else
|
|
||||||
waitables[i].cancelCallback(callbacks[i], wr);
|
|
||||||
waitables[i].cancelled = true;
|
|
||||||
any_fired = true;
|
|
||||||
fired[i] = true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
scope_guards[i] = ScopeGuard(ccb);
|
|
||||||
|
debug(VibeAsyncLog) logDebugV("Starting operation %%s", %1$s);
|
||||||
|
alias WR%1$s = typeof(Waitables[%1$s].wait(callback_%1$s));
|
||||||
|
static if (is(WR%1$s == void)) Waitables[%1$s].wait(callback_%1$s);
|
||||||
|
else auto wr%1$s = Waitables[%1$s].wait(callback_%1$s);
|
||||||
|
|
||||||
|
scope (exit) {
|
||||||
|
if (!fired[%1$s]) {
|
||||||
|
debug(VibeAsyncLog) logDebugV("Cancelling operation %%s", %1$s);
|
||||||
|
static if (is(WR%1$s == void)) Waitables[%1$s].cancel(callback_%1$s);
|
||||||
|
else Waitables[%1$s].cancel(callback_%1$s, wr%1$s);
|
||||||
|
any_fired = true;
|
||||||
|
fired[%1$s] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (any_fired) {
|
if (any_fired) {
|
||||||
debug(VibeAsyncLog) logDebugV("Returning to %s without waiting.", func);
|
debug(VibeAsyncLog) logDebugV("Returning to %%s without waiting.", func);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}.format(i, generateParamDecls!(CBDel!W)(format("PT%s", i)), generateParamNames!(CBDel!W));
|
||||||
}
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin(waitableCode());
|
||||||
|
|
||||||
debug(VibeAsyncLog) logDebugV("Need to wait in %s (%s)...", func, interruptible ? "interruptible" : "uninterruptible");
|
debug(VibeAsyncLog) logDebugV("Need to wait in %s (%s)...", func, interruptible ? "interruptible" : "uninterruptible");
|
||||||
|
|
||||||
|
@ -173,9 +176,7 @@ void asyncAwaitAny(bool interruptible, string func = __FUNCTION__, Waitables...)
|
||||||
debug(VibeAsyncLog) logDebugV("Return result for %s.", func);
|
debug(VibeAsyncLog) logDebugV("Return result for %s.", func);
|
||||||
}
|
}
|
||||||
|
|
||||||
private alias CBDel(Waitable) = Waitable.Callback;
|
private alias CBDel(alias Waitable) = Waitable.Callback;
|
||||||
|
|
||||||
private struct ScopeGuard { @safe nothrow: void delegate() op; ~this() { if (op !is null) op(); } }
|
|
||||||
|
|
||||||
@safe nothrow /*@nogc*/ unittest {
|
@safe nothrow /*@nogc*/ unittest {
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
@ -186,40 +187,45 @@ private struct ScopeGuard { @safe nothrow: void delegate() op; ~this() { if (op
|
||||||
|
|
||||||
@safe nothrow /*@nogc*/ unittest {
|
@safe nothrow /*@nogc*/ unittest {
|
||||||
int a, b, c;
|
int a, b, c;
|
||||||
Waitable!(
|
int w1r, w2r;
|
||||||
|
alias w1 = Waitable!(
|
||||||
void delegate(int) @safe nothrow,
|
void delegate(int) @safe nothrow,
|
||||||
(cb) { a++; cb(42); },
|
(cb) { a++; cb(42); },
|
||||||
(cb) { assert(false); }
|
(cb) { assert(false); },
|
||||||
) w1;
|
(i) { w1r = i; }
|
||||||
Waitable!(
|
);
|
||||||
|
alias w2 = Waitable!(
|
||||||
void delegate(int) @safe nothrow,
|
void delegate(int) @safe nothrow,
|
||||||
(cb) { b++; },
|
(cb) { b++; },
|
||||||
(cb) { c++; }
|
(cb) { c++; },
|
||||||
) w2;
|
(i) { w2r = i; }
|
||||||
Waitable!(
|
);
|
||||||
|
alias w3 = Waitable!(
|
||||||
void delegate(int) @safe nothrow,
|
void delegate(int) @safe nothrow,
|
||||||
(cb) { c++; cb(42); },
|
(cb) { c++; cb(42); },
|
||||||
(cb) { assert(false); },
|
(cb) { assert(false); },
|
||||||
(int n) { assert(n == 42); }
|
(int n) { assert(n == 42); }
|
||||||
) w3;
|
);
|
||||||
|
|
||||||
asyncAwaitAny!false(w1, w2);
|
asyncAwaitAny!(false, w1, w2);
|
||||||
assert(w1.results[0] == 42 && w2.results[0] == 0);
|
assert(w1r == 42 && w2r == 0);
|
||||||
assert(a == 1 && b == 0 && c == 0);
|
assert(a == 1 && b == 0 && c == 0);
|
||||||
|
|
||||||
asyncAwaitAny!false(w2, w1);
|
asyncAwaitAny!(false, w2, w1);
|
||||||
assert(w1.results[0] == 42 && w2.results[0] == 0);
|
assert(w1r == 42 && w2r == 0);
|
||||||
assert(a == 2 && b == 1 && c == 1);
|
assert(a == 2 && b == 1 && c == 1);
|
||||||
|
|
||||||
asyncAwaitAny!false(w3);
|
asyncAwaitAny!(false, w3);
|
||||||
assert(c == 2);
|
assert(c == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string generateParamDecls(Fun)()
|
private string generateParamDecls(Fun)(string ptypes_name = "PTypes")
|
||||||
{
|
{
|
||||||
import std.format : format;
|
import std.format : format;
|
||||||
import std.traits : ParameterTypeTuple, ParameterStorageClass, ParameterStorageClassTuple;
|
import std.traits : ParameterTypeTuple, ParameterStorageClass, ParameterStorageClassTuple;
|
||||||
|
|
||||||
|
if (!__ctfe) assert(false);
|
||||||
|
|
||||||
alias Types = ParameterTypeTuple!Fun;
|
alias Types = ParameterTypeTuple!Fun;
|
||||||
alias SClasses = ParameterStorageClassTuple!Fun;
|
alias SClasses = ParameterStorageClassTuple!Fun;
|
||||||
string ret;
|
string ret;
|
||||||
|
@ -229,7 +235,7 @@ private string generateParamDecls(Fun)()
|
||||||
static if (SClasses[i] & ParameterStorageClass.scope_) ret ~= "scope ";
|
static if (SClasses[i] & ParameterStorageClass.scope_) ret ~= "scope ";
|
||||||
static if (SClasses[i] & ParameterStorageClass.out_) ret ~= "out ";
|
static if (SClasses[i] & ParameterStorageClass.out_) ret ~= "out ";
|
||||||
static if (SClasses[i] & ParameterStorageClass.ref_) ret ~= "ref ";
|
static if (SClasses[i] & ParameterStorageClass.ref_) ret ~= "ref ";
|
||||||
ret ~= format("PTypes[%s] param_%s", i, i);
|
ret ~= format("%s[%s] param_%s", ptypes_name, i, i);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -237,6 +243,7 @@ private string generateParamDecls(Fun)()
|
||||||
private string generateParamNames(Fun)()
|
private string generateParamNames(Fun)()
|
||||||
{
|
{
|
||||||
import std.format : format;
|
import std.format : format;
|
||||||
|
if (!__ctfe) assert(false);
|
||||||
|
|
||||||
string ret;
|
string ret;
|
||||||
foreach (i, T; ParameterTypeTuple!Fun) {
|
foreach (i, T; ParameterTypeTuple!Fun) {
|
||||||
|
|
Loading…
Reference in a new issue