Clocks API
Experimental process-local RoomClock and SharedPoll API.
On this page
Import from djust.clocks. Experimental in 1.3.x for at least one minor
release; see API stability.
Phases 1–3 are process-local; there is no distributed lease or ownership API.
The guide explains authorization, delivery,
tenants, membership and testing.
RoomClock
RoomClock(*, name, interval, step, ...) is an opt-in function-of-key engine.
name is a nonempty ASCII slug; keys are nonempty strings of at most 512
characters. All intervals are seconds. The constructor creates no tasks or pool.
| Argument | Default | Contract |
|---|---|---|
name, interval, step | required | Namespace, fixed-rate seconds, callable receiving ClockTick. |
publish | None | Publish destination; truthy step results send a doorbell. |
alive | None | Sync/async callable of scoped key; otherwise use ensure heartbeats. |
idle_stop | 5.0 | Seconds idle before stopping; liveness polling cadence is one second. |
trailing | 1.0 | One final resend after last publishing beat; zero disables it. |
on_overrun | "skip" | Skip missed slots or bounded "catch_up". |
max_catch_up | 3 | Maximum catch-up steps per dispatch. |
max_clocks | 256 | Maximum live keys per definition, across loops. |
max_clocks_per_tenant | 64 | Sub-cap for resolved tenants only; untenanted rooms use max_clocks. |
max_consecutive_errors | 10 | Threshold for exponential retry backoff. |
max_backoff | 30.0 | Maximum retry delay; success resets it. |
step_timeout | None | Async cancellation; sync timeout reports stuck and waits. |
validate_key | None | Optional server-side predicate of raw key; rejection raises ValueError. |
on_stop | None | Sync/async (scope, reason) callback, skipped on cancellation. |
log_details | False | Opt-in exception text and traceback. |
time_source, executor | production implementations | Injection seams for deterministic tests. |
The interval floor is 0.02 seconds. A process-wide pool uses ten threads by
default, configurable by LIVEVIEW_CONFIG["clock_workers"] (integer >= 2).
It is separate from the session worker pool. A definition gets at most three
quarters of those threads; other eligible due calls can proceed.
| Method | Result and behavior |
|---|---|
scope(view, key) | Canonical namespace and percent-encoded raw key, plus the resolved view's presence tenant prefix. Use this exact string for push subscriptions and state keys. |
ensure(view, key) | Sync bridge for authorized WebSocket lifecycle code; returns True if running, False at capacity, when disabled in tests, or after the one-second stopping-run wait. |
await aensure(view, key) | Async equivalent; refuses an unregistered/non-serving loop. A join during a requested or step-returned stop waits up to one second of real time for retirement, then starts a fresh run or returns False without cancelling the old worker. |
running(scope) | Whether the scoped run is active, including an unfinished sync step. |
stop(scope, reason="requested") | Graceful stop request, safe from a foreign thread/loop; returns whether a live run was found. |
pause(scope), resume(scope) | Suppress/resume steps while keeping membership checks; return whether a live run was found. |
set_interval(scope, seconds) | Apply a valid interval on the next slot; return whether a live run was found. |
stats() | Mapping of scopes to live stats and bounded recent stopped records. |
room_clock is a reserved LiveView configuration attribute, excluded from
serialized state and template context on every LiveView. On PresenceMixin,
set it to a RoomClock (or None) to opt into automatic tracking/restoration
binding; use another attribute name for application state.
PresenceMixin.room_clock binds tracking and restoration automatically. Its
raw key is the formatted presence key without the tenant prefix. Existing push
subscriptions are preserved; the clock adds one scope. The optional
presence_key= keyword on ensure is framework binding plumbing: application
code normally supplies alive or uses the mixin. It accepts only that view's
actual tenant-scoped presence key.
ClockTick, Stop and Publish
ClockTick is immutable: key (raw), scope (tenant-scoped namespace), seq
(attempted steps within the run), run_id, dt, skipped, scheduled_at,
started_at, and fence (None, the deferred ownership seam). Monotonic
scheduled/start times are process-local seconds.
Stop(reason="step stopped") ends a run when returned by step. The reason is
stored in stats; exception and reason values are not interpolated into default
clock logs. Cancellation is recorded as "cancelled" and sends no trailing push.
Publish(view_path, handler, payload=None) sends through apush_to_view with
the engine's scoped key. The optional payload factory receives the raw key and
is called once per run in the shared clock worker pool, outside the registry
lock. First-join ensure waits for the result, including pool queue delay; keep
factories short. This initialization wait is separate from the bounded
stopping-run wait. Its default is {"key": key}. It must return a dict
whose contents remain beat-invariant. There is no state or sequence in the
engine-generated doorbell. Returning a truthy step result publishes; a falsy
result is quiet.
SharedPoll
SharedPoll(*, fetch, **clock_options) accepts all RoomClock options except
step. fetch(ClockTick) can be sync or async. Every successful fetch stores
its result under the scoped key and publishes. latest(scope) returns
(version, result) or None. Versions increase across runs while a result
remains retained. Results and reads share a lock; consumers must treat stored
objects as read-only. Retention is bounded; recently stopped results can be
removed to make room for active keys.
Only use scoped keys derived from an authorized view. Include every visibility dimension (role, object permission, etc.) in the key. Tenant context is bound; no configuring request/user context is inherited by the clock task.
Test helpers
djust.testing exports ManualClock, DeterministicClockExecutor, and
clocks_disabled. Manual time supports async advance(seconds) and settle();
the deterministic executor runs sync calls inline rather than in a thread.
The context manager clocks_disabled() makes sync and async ensure return
False before validating serving-loop or WebSocket context, allowing ordinary
LiveViewTestClient mounts.