Tasks and coordination¶
The types that make coroutines cooperate: the task and process handles, and
the four coordination primitives. All in namespace cpptb::coro.
Tasks and concurrency teaches the patterns;
this page is the API surface.
Shared properties of Event, Queue, Lock, and Semaphore: all four
are non-copyable, all four take an optional name used in wait-graph and
deadlock reports, and destroying one while coroutines are still parked on
it is a runtime error naming the type.
Handles¶
Task¶
template <typename T> class Task; // the coroutine return type
Any coroutine in a cpptb testbench returns Task<T> (usually
Task<void>). Move-only. co_await yields the task’s T and propagates
an exception thrown inside the child to the awaiting parent. The awaited
task must be a temporary or moved: co_await helper(dut) or
co_await std::move(named_task).
Process¶
bool valid() const;
bool done() const; // completed or cancelled
bool cancelled() const; // finished BECAUSE it was cancelled
void cancel() const;
co_await process; // yields void; rethrows the process's exception
The handle test.spawn(...) returns. Unlike Task, a Process is
copyable — a reference-counted handle, safe to store and pass around.
cancel() on an already-finished handle is a no-op. Awaiting a
default-constructed handle is a runtime error; check valid() first if
one might be empty.
Join¶
co_await Join{drive(dut), monitor(dut), scoreboard(test)};
Runs two or more Task<void> children and resumes when all of them have
finished; an exception from any child is rethrown. Children are moved in.
For racing rather than joining, see
First; for children with independent lifetimes, use
test.spawn(...) and await the Process handles.
Coordination primitives¶
Event¶
explicit Event(std::string_view name = {});
bool is_set() const noexcept;
void set(); // wakes ALL waiters and latches
void clear(); // clears the latch; wakes nobody
co_await event; // or:
co_await event.wait();
A latching broadcast: after set(), waiters resume and the flag stays set
until clear(), so awaiting an already-set event completes without
suspending. event.wait() and bare co_await event behave identically —
wait() records the call site in wait-graph diagnostics, the bare form
records the declaration site; prefer wait() where the report should name
the waiting line.
Queue¶
explicit Queue(size_t maxsize = 0, std::string_view name = {});
Task<void> put(T value); // blocks while full
Task<T> get(); // blocks while empty
bool put_nowait(T value); // false instead of blocking
std::optional<T> get_nowait(); // nullopt instead of blocking
size_t size(); bool empty(); bool full(); size_t maxsize() const;
Typed FIFO in both directions: items come out in insertion order and
blocked callers are served oldest-first. maxsize == 0 (the default)
means unbounded — full() is then always false and put() never blocks.
T must be move-constructible. The _nowait pair is the only safe form
outside a coroutine. Wrapping queue.get() in
with_timeout is safe: an abandoned wait
releases its reservation and wakes the next waiter.
Lock¶
explicit Lock(std::string_view name = {});
co_await lock.acquire(); // FIFO; no bare co_await form
bool try_acquire();
void release();
bool locked();
Binary and non-reentrant, with no owner tracking — any process may call
release(), and releasing an unlocked lock is a runtime error. When
waiters are queued, release() hands ownership directly to the oldest
waiter, so a third party polling try_acquire() cannot barge in between.
Semaphore¶
explicit Semaphore(size_t permits = 0, std::string_view name = {});
co_await sem.acquire(); // consumes one permit; FIFO
bool try_acquire();
void release(size_t permits = 1);
size_t available();
A counting semaphore whose initial permit count defaults to zero — the
default-constructed form is an empty credit counter, not a mutex; pass an
initial count for resource-limiting use. release(n) gives permits to the
oldest waiters first and banks the surplus only when no waiter is left.
See also¶
TestContext and checks —
spawn(), process ownership, and what happens to running processes when a test ends.Triggers and phase waits — everything time-shaped that these primitives compose with.