Skip to content

Commit 06d206d

Browse files
committed
feat(timer): add the standard timer host module
Add the self-contained timer host-function module with `timer::{at,every,pending_count,running_count}`, the `TimerBackend` contract, `TimerConfig`/`TimerRegistration` admission limits, and the `TimerExtension` composition/install entry point. The module owns callback ownership transfer, the private callback VM, callback state (Ready/Running/Waiting/Yielded/Complete/Cancelled), error isolation, per-callback async bridges, and transactional registration rollback. The VM core stays timer-free apart from the generic owned-value registry. Admission limits travel with every registration and are MUST-enforce obligations on the backend; `timer::pending_count` / `timer::running_count` are pure backend queries and the module keeps no counters of its own. The callback parameter is typed `fn(bool) -> unknown`: the callback observes the `premature` flag and its return value is discarded, so the declared result is `unknown` -- any `int`/`string`/`map`/`bool`/`null` body compiles and runs, and the value is dropped instead of accumulating. This is the one deliberate dynamic occurrence in the public timer surface; the typed-catalog guard records it as its narrow discarded-callable-result policy exception. Downstream adapter surface: `register_owned_timer` lets an embedding expose the same contract under its own exact host name and schema -- it takes the owned call, the callback argument index, and a checked `Duration` plus an optional repeating `Duration`, and reuses the runtime lookup, callback type/provenance checks, fresh isolated callback VM, admission limits, backend registration and rollback of the standard adapters, so `OwnedTimerCallback::new` never needs to become public. `installed_timer_counts` exposes the installed backend's synchronous pending/running counts for thin adapter count functions. Both helpers are name-independent and unit-independent; `timer::at` / `timer::every` are millisecond adapters over the same path. The builtin runtime composes the timer surface into the standard catalog, so a compiled program resolves the imports and each call fails with the documented installation error until backend state is installed. The module's own tests cover the callback state machine through the private callback VM: waiting rounds on independent per-callback bridges, cancellation of a waiting round, panic and error isolation with recovery for `start` and for the resumed/polled round, the discarded callback return (no result or operand accumulates), and overlapping-round rejection.
1 parent 769da0b commit 06d206d

3 files changed

Lines changed: 1767 additions & 0 deletions

File tree

‎src/builtins/runtime/mod.rs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,21 @@ pub(crate) mod regex;
3737
#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
3838
pub(crate) mod sqlite;
3939
pub(crate) mod standard_composition;
40+
mod timer;
4041
mod typed;
4142

4243
pub use jit::{
4344
jit_host_catalog, register_jit_builtin_module, register_jit_builtin_module_from_catalog,
4445
};
4546
#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
4647
pub use sqlite::{register_sqlite_builtin_module, register_sqlite_builtin_module_from_catalog};
48+
pub use timer::{
49+
DEFAULT_MAX_PENDING_TIMERS, DEFAULT_MAX_RUNNING_TIMERS, OwnedTimerCallback, TIMER_CALLBACK_ARG,
50+
TimerBackend, TimerCallbackError, TimerCallbackState, TimerCallbackStatus, TimerConfig,
51+
TimerCounts, TimerExtension, TimerHostExt, TimerHostState, TimerRegistration,
52+
installed_timer_counts, register_owned_timer, register_timer_builtin_module,
53+
register_timer_builtin_module_from_catalog, timer_host_catalog,
54+
};
4755

4856
/// Returns the editor/compiler catalog for the built-in host extensions.
4957
///
@@ -375,6 +383,21 @@ pub fn standard_host_catalog() -> Arc<HostApiCatalog> {
375383
builder.function(function.clone());
376384
}
377385
}
386+
{
387+
// The standard timer surface: exact catalog entries whose owned
388+
// callbacks are registered by `TimerExtension` /
389+
// `register_timer_builtin_module`, exactly like the other exact
390+
// adapter surfaces. A compiled program resolves the imports from
391+
// this catalog; each call then fails at the runtime boundary until
392+
// backend state is installed.
393+
let timer_catalog = timer_host_catalog();
394+
for schema in timer_catalog.structs() {
395+
builder.named_struct(schema.clone());
396+
}
397+
for function in timer_catalog.functions() {
398+
builder.function(function.clone());
399+
}
400+
}
378401
Arc::new(builder.build().expect("standard host catalog is valid"))
379402
}))
380403
}

0 commit comments

Comments
 (0)