Library
Cancellation, timeouts and structured concurrency
Cancellation is an exception delivered at the next await; awaitTimeout turns a timeout into a None result; TaskGroup keeps tasks inside their scope.
since 0.1.0-alpha.1linuxwindows
Description
Cancellation is an exception delivered at park points, and only there. A running task is
never interrupted in the middle of what it is doing. When a task is cancelled it is marked, and the
mark takes effect at its next await that has to wait (see lang.await): that await throws a
CancelledException. A task that never waits cannot be cancelled until it does.
CancelledException is an ordinary exception. It extends Exception, implements
ICancelledException, and is caught like any other, so there is no second error channel:
interface ICancelledException : IException { }
class CancelledException : Exception, ICancelledException { }
A task may catch the exception, tidy up, and either rethrow it or carry on. If it carries on, it
has refused cancellation, which is allowed and visible. An uncaught CancelledException in a task
that belongs to a TaskGroup is absorbed at the group's boundary, because cancelling a task is not a
program error. An uncaught exception of any other type is still an uncaught program error.
awaitTimeout makes a timeout an outcome instead of a failure:
T? awaitTimeout<T>(Promise<T> work, int ms)
It waits for work for at most ms milliseconds. It returns the value if work settles first, and
None if the time runs out. It does not throw on timeout. Note what it does not do: it stops
waiting, it does not cancel the work. A Worker is never cancelled by a timeout either, because
a thread cannot be stopped by the clock. To combine the two, cancel the surrounding group yourself:
int? r = awaitTimeout(p, 5000); if (r == None) g.cancelAll();.
awaitTimeout returns None on timeout
Promise<int> after(int ms, int value) {
Promise<int> p = Promise();
std::sysTimerStart(ms, 0, (n) => p.resolve(value));
return p;
}
int? quick = awaitTimeout(after(10, 7), 1000);
console.writeln(quick ?? -1);
int? slow = awaitTimeout(after(500, 8), 20);
console.writeln("slow result is None: ${slow == None}");
7
slow result is None: true
Rules
- Cancellation takes effect at the next
awaitthat actually waits, never earlier. Work that does not wait is never interrupted. - A cancelled
awaitthrowsCancelledExceptionwith the messagetask cancelled. - A task may catch
CancelledExceptionand refuse to stop. It is then still running, and a group'sclose()keeps waiting for it. awaitTimeout(work, ms)returns the value ofworkorNone; it never throws on timeout. It can still rethrow a failure ofworkifworksettles with one.awaitTimeoutnever cancelswork, and cannot stop aWorker.- While a task is running its own
TaskGroupcleanup (theclose()that ausingruns), further cancellation is held back until that cleanup is over, sousinginside a cancelled task cannot deadlock. - Cancellation is for tasks on the same thread. Workers started with
spawnare separate threads and are not cancelled.
Examples
A timeout used as a kill switch for a group: the slow answer takes too long, so the group is told
to stop its task. Leaving the using block waits for the task to finish cancelling:
Timeout, then cancel the group
Promise<int> after(int ms, int value) {
Promise<int> p = Promise();
std::sysTimerStart(ms, 0, (n) => p.resolve(value));
return p;
}
void run() {
using TaskGroup g = TaskGroup();
g.run(() => {
console.writeln("worker task: waiting for a slow answer");
int v = await after(10000, 1);
console.writeln("worker task: never reached");
});
Promise<int> slow = after(500, 2);
int? answer = awaitTimeout(slow, 20);
if (answer == None) {
console.writeln("timed out, cancelling the group");
g.cancelAll();
}
}
run();
console.writeln("done");
worker task: waiting for a slow answer
timed out, cancelling the group
done
Notes
See std.TaskGroup for starting, cancelling and joining tasks as a group.
See also
- TaskGroup — A set of tasks that live and end together.
- CancelledException — The error delivered to a task that has been cancelled.
- Promise — A value that arrives later.
- Worker — The handle to a value that another worker is computing.