C · Receivers and Deferred Calls

Receivers and Deferred Calls

#include <uform/types.h> 4.0.0-rc.1 · 3ee5e534

Every call stores its result in the uform_outcome_t it names: a status, and a static message that is NULL exactly on success. uform_completion_blocking_k, the default, returns with the status final and every output readable. uform_completion_deferred_k returns once the executor’s own thread took the call, and the status reads uform_status_deferred_k until the call is final. uform_outcome_status polls with acquire ordering and uform_outcome_wait blocks until final, and these are the only ways to read a status another thread may still store. Submission copies the structure, while everything it points to stays borrowed until final. An executor runs one call at a time: a call finding another one holding its executor finishes uform_status_contended_k without running, so a caller orders dependent calls by waiting on the first. A call lets go of its arena, its handles and its executor before it stores the final status, so whoever reads it final may reuse all three at once. A call stops early only when its uform_progress_t handler returns uform_cancel_k, and then finishes uform_status_cancelled_k.

uform_outcome_t outcomes[4] = {0};
for (size_t shard = 0; shard != 4; ++shard) { // one executor per shard, each running one call at a time
    encodes[shard].outcome = &outcomes[shard];
    encodes[shard].executor = executors[shard];
    encodes[shard].completion = uform_completion_deferred_k;
    uform_image_encode(&encodes[shard]);
}
for (size_t shard = 0; shard != 4; ++shard) uform_outcome_wait(&outcomes[shard]);

§ Inclusive and Exclusive Executors

The threads of an executor either include the thread that calls it or work without it. A caller-inclusive pool needs its caller as one of its threads, so a deferred call on it runs at submission and is final when it returns. A caller-exclusive pool has threads of its own: a deferred call returns as soon as the pool’s thread 0 takes it, and that thread runs the call, forks included, and stores the status. uform_executor_for_ram and uform_executor_for_vram spawn caller-exclusive pools wherever the build has threads, and WebAssembly’s single thread is inclusive. Because a deferred call needs nobody to drive it, one thread can keep several executors busy, here across UForm, USearch and UStore, by only reading status words:

text_encode.completion = uform_completion_deferred_k;   // UForm, on a VRAM executor
dense_search.completion = usearch_completion_deferred_k; // USearch, on a RAM executor
blobs_read.completion = ustore_completion_deferred_k;    // UStore, on an NVMe executor
uform_text_encode(&text_encode);
usearch_dense_search(&dense_search);
ustore_blobs_read(&blobs_read);
while (uform_outcome_status(text_encode.outcome) == uform_status_deferred_k ||
       usearch_outcome_status(dense_search.outcome) == usearch_status_deferred_k ||
       ustore_outcome_status(blobs_read.outcome) == ustore_status_deferred_k)
    prepare_next_batch();

API

Types

uform_outcome_tOutcome of one call: a status every binding can branch on, and a static message carrying the detail the status cannot.
types.h:243 ↗
struct uform_outcome_t

Messages are static and don't need to be deallocated, and are NULL exactly when the status is uform_status_success_k.

Fields

statusuform_status_tThe status bindings branch on; uform_status_deferred_k until a deferred call settles.
messagechar const *The static detail, NULL exactly when status is uform_status_success_k.
uform_continuation_tWhether the call a handler is watching carries on or unwinds.
types.h:365 ↗
enum uform_continuation_t

Values

uform_continue_k0Carry on to the next unit of work.
uform_cancel_k1Stop and report uform_status_cancelled_k.
uform_progress_callback_tPeriodic report of a long call's advance, and the caller's chance to stop it.
types.h:385 ↗
typedef uform_continuation_t(*) uform_progress_callback_t(size_t completed, size_t total, void *state);

Counts are in the units the verb consumes - items for an encoding, tensors for a load.

Never invoked concurrently, and never after the call it was passed to stores its final status.

Arguments, in order:

  • completed: units finished so far, never decreasing across one call's reports.
  • total: units the call will finish if it runs to completion.
  • state: the opaque pointer registered alongside the handler, forwarded verbatim.
uform_progress_tHow a long call reports its advance: a handler, its state, and how many reports it hears.
types.h:396 ↗
struct uform_progress_t

A batch verb splits its items into steps nearly equal slices, hands each to its engine whole and reports after each; a load, whose engine counts its own tensors, forwards only the ticks that cross into the next slice. Without a handler the call runs as one slice and reports nothing.

Fields

callbackuform_progress_callback_tThe optional handler; NULL skips every report.
statevoid *Opaque pointer forwarded into every callback call.
stepssize_tReports a call makes at most, the last at completion; zero derives to ten.
uform_completion_tWhether a call returns with its final status, or once submitted to its executor.
types.h:431 ↗
enum uform_completion_t

Values

uform_completion_blocking_k0Return with the final status stored and every output readable; the default.
uform_completion_deferred_k1Return once submitted, the status deferred until final; the caller waits or polls.
uform_mood_tWhat an executor does with its threads or streams once it goes idle.
types.h:755 ↗
enum uform_mood_t

Values

uform_mood_parked_k0Sleep on futexes or events created with blocking sync, freeing the core.
uform_mood_spinning_k1Poll them, trading a core for the lowest wake-up latency.

Spellings

char const * uform_mood_name(uform_mood_t mood);
uform_mood_t uform_mood_from_name(uform_bytes_view_t name);

Functions

uform_outcome_waitBlocks on an atomic wait until outcome is final, with acquire ordering.
types.h:417 ↗
uform_status_t uform_outcome_wait(uform_outcome_t *outcome);

Never wait on a call's own outcome from inside its progress handler: the call finishes only after it returns.

Parameters

outcomeinAn outcome passed to a call, read until its status leaves uform_status_deferred_k.

Returns

The final status.

uform_outcome_statusReads the status of outcome without waiting, with the acquire ordering its final store pairs with, so a final status also publishes every output of the call.
types.h:428 ↗
uform_status_t uform_outcome_status(uform_outcome_t *outcome);

The one way to poll: the status word is atomic only by this contract, so a caller reading the field directly needs an atomic load its language may not offer on foreign memory.

Parameters

outcomeinAn outcome passed to a call.

Returns

uform_status_deferred_k while the call is not final, then its final status.