caliper.jobs.v1¶
Service id caliper.jobs.v1 — background compute with progress + cancel (PLATFORM.md §7.5). This page embeds the header verbatim; the docs build fails if the file moves.
#pragma once
/* caliper.jobs.v1 — background compute with progress + cancel (PLATFORM.md
* §7.5). IMMUTABLE once published: new capability = jobs_v2, alongside.
*
* THREADING HONESTY (§15): job functions run on HOST WORKER THREADS as
* trusted code. They are NOT crash-guarded — the signal guard is
* UI-thread-only by documented precondition — so a fault in a job takes the
* process down. Cancellation is cooperative: poll cancelled() in your inner
* loop and return promptly. */
#include <stdint.h>
#include <stdbool.h>
#define CALIPER_JOBS_V1 "caliper.jobs.v1"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CaliperJobControl CaliperJobControl;
struct CaliperJobControl {
uint32_t struct_size;
/* Poll in loops; return promptly when true. */
bool (*cancelled)(const CaliperJobControl* ctl);
/* frac in [0,1]; msg_utf8 may be NULL. Surfaced in the host jobs tray. */
void (*progress)(const CaliperJobControl* ctl, float frac,
const char* msg_utf8);
};
/* Runs on a host worker thread. user must outlive the job. */
typedef void (*CaliperJobFn)(void* user, const CaliperJobControl* ctl);
typedef struct CaliperJobsV1 {
uint32_t struct_size;
/* Returns a job id; 0 = error (never a valid id). */
uint64_t (*submit)(const char* label_utf8, CaliperJobFn fn, void* user);
void (*request_cancel)(uint64_t job);
bool (*is_running)(uint64_t job);
float (*progress_of)(uint64_t job); /* last reported frac; 0 if none */
} CaliperJobsV1;
#ifdef __cplusplus
}
#endif
Semantics¶
Threading. Job functions do not run on the frame thread — that is the whole
point of the service. You call submit(label, fn, user) from on_frame (or
anywhere), and the host runs fn on a host worker thread. Your frame stays
fluid while the work grinds. The corollary, stated in the header and not
softened: job functions run on host worker threads as trusted code, and they
are not crash-guarded — the signal guard is UI-thread-only by documented
precondition (see the trust model) — so a
fault inside a job takes the whole process down. Keep job code as disciplined as
you would keep any code that runs without a net.
Cancellation is cooperative. request_cancel(job) sets a flag; it does not
interrupt your function. Your job must poll ctl->cancelled(ctl) in its inner
loop and return promptly when it reads true. The framework contract (PLATFORM.md
§16) is that a well-behaved job honours cancel within ≤ 100 ms — this is a
tested guarantee, not advice, and the exemplar's teardown relies on it (below).
A job that ignores cancelled() and runs for a minute is a bug in the applet,
not the host.
user must outlive the job. submit takes a raw void* user and the
worker dereferences it for the job's entire lifetime. If you pass this (the
common case — see the exemplar), then this must not be destroyed while the job
is still running. Because the host destroys your applet object right after
on_cleanup() returns, an applet with a live job must, in on_cleanup,
request cancel and then bounded-wait on is_running(job) before returning — so
the worker has exited before destroy() frees the object out from under it. The
≤ 100 ms cancel contract is what makes that wait bounded: a short poll loop
(e.g. up to 300 ms) cannot hang teardown.
Ids. submit returns a job id; 0 is never a valid id — it means the
submission failed (for example, a headless host that does not vend the service).
is_running/progress_of on an unknown or finished id return false/0.
The canonical consumer is the MLScope exemplar
(examples/ml_scope/): it submits an MLP training loop, polls cancelled() each
epoch, publishes loss under a mutex, and does the bounded-wait teardown described
above.