Skip to content

Qirava DMS docs

The execute model & function scope

execute() is the single funnel through which every registered function runs — from a request, a scheduler, or re-entrantly from inside another function. It resolves the key to a Public function, admits the call against a shared resource budget (deny if over), schedules it on the bounded worker pool, and hands back a typed FunctionResponse. Internal helpers never go through execute(); a running function reaches them with ctx.call / chain / all, each of which enforces the helper's scope and reserves its budget too. This page is the complete reference for the execution funnel, the FunctionScope model, resource budgets, and FIFO routing.

The one funnel#

FunctionRuntime::execute is the only way to run a registered function. Its signature and contract are stable.

rust
pub fn execute(&self, function_key: &str, input: &[u8], fifo: bool,
               execution_key: Option<ExecutionKey>) -> FunctionCall;
let response: FunctionResponse = runtime.execute("qql.read.key", b"...", false, None).wait();
function_key
Must name a registered PUBLIC function. If it is internal, execute returns AccessDenied; if unknown, PublicFunctionNotFound.
input
The function's input bytes (raw body, rendered QQL, or an encoded Record depending on the caller).
fifo
false (default) = concurrent. true = serialize this call on execution_key. A function registered FIFO always serializes regardless of this flag.
execution_key
Optional. None makes the system generate a unique key (matters only under FIFO). For record mutations the caller passes the record's _key so writes to the same record serialize.

Two siblings exist for special callers: execute_detached (no waitable handle; an on_complete callback fires on the worker thread — used by the event-loop reactor so it never blocks on .wait()), and call_inline (runs a public function on the CALLING thread, under the same budget, for re-entrant inline QQL like function_key(...) evaluated mid-query, bounded to MAX_INLINE_DEPTH = 64 to prevent stack overflow on recursion).

The FunctionScope model#

Every function is registered with one of four scopes. The scope is what makes the registry deny-by-default and is checked at two places: at execute() (only Public is schedulable) and inside ctx.call (check_internal_access).

ScopeReachable by execute()?Reachable via ctx.call from a function?
Publicyesno (a Public function cannot be called through the internal helper path)
InternalGlobalno (AccessDenied)yes, from anyone
InternalScoped { owner }noonly when the call tree's root_key == owner
InternalPrivate { owner }noonly when the immediate caller current_key == owner

The registration methods mirror this: register_public, register_internal_global, register_internal_scoped(owner, ..), register_internal_private(owner, ..), plus _with_resources variants that declare a budget, and register_user_* variants that register the same shapes with FunctionOrigin::UserDefined. Functions can also be added/replaced at runtime via register_dynamic / set_dynamic (used to load user-defined QQL functions through the worker layer); both kinds dispatch identically through the same executor.

Request

// a planner helper is internal — a worker cannot schedule it directly
let r = runtime.execute("planner.query", b"select", false, Some(ExecutionKey(1))).wait();

Response

assert!(!r.ok);
assert_eq!(r.code, ResponseCode::AccessDenied);

Output

// The public root qql.planner.query exists for callers; it CALLS planner.query internally.

Composing helpers: call / chain / all#

From inside a function body you reach helpers ONLY through the FunctionContext. There is no other way to run a function from within a function, and every path reserves the helper's registered budget.

ctx.call(key, input)
Run one helper. Canonical. Enforces scope, reserves the helper's budget (RAII lease released on return), increments helper_calls.
ctx.chain(&[keys], input)
Sequential composition: feed each helper's output as the next one's input; a non-ok response short-circuits and is returned.
ctx.all(&[HelperRequest..])
Run each request sequentially, honoring per-call resource overrides.
ctx.all_concurrent(&[HelperRequest..])
Fan independent helpers across the helper pool (one runs inline on the worker so it participates). Falls back to sequential all() if there is <=1 call, no helper pool, or any call is .fifo(). Under resource/helper-pool pressure a call runs inline rather than failing the group.
rust
// The planner root is just a one-line public wrapper that calls its internal helper:
builder.register_public(ROOT_PLANNER_QUERY, ResourceRequest::empty(), false,
    |context, input| context.call(HELPER_PLANNER_QUERY, input))?;

Resource budgets#

No function runs outside the budget — public, internal, inline, or fan-out helper, every path reserves from the SAME shared ResourcePool before the body runs, and releases on return (RAII). A ResourceRequest declares memory_bytes and storage_bytes; a zero-byte request is free (no atomics). If a reservation exceeds the budget the call is refused with ResourceLimit.

Request

let config = ExecConfig { max_memory_bytes: Some(64), ..ExecConfig::default_for(1) };
// register "big" with ResourceRequest::memory(65)  (1 byte over the ceiling)
let r = runtime.execute("big", b"x", false, Some(ExecutionKey(1))).wait();

Response

assert!(!r.ok);
assert_eq!(r.code, ResponseCode::ResourceLimit);

Helpers obey the same budget on the sequential path: a public function chaining to a helper that requests more than the whole budget is refused — resource control reaches the internal helper path, not just public functions. A per-call HelperRequest::with_resources override takes precedence over the registered budget when non-empty.

FIFO routes and worker capacity#

By default calls are concurrent; the executor never runs more than worker_threads bodies at once. FIFO pins a (function, execution_key) pair to one worker in submission order, so same-key work runs strictly in the order submitted — used for record mutations where writes to one _key must serialize. A function can be registered FIFO, or any single call can force it with fifo=true.

Request

let key = ExecutionKey(7);
let mut calls = Vec::new();
for index in 0..20u8 { calls.push(runtime.execute("fifo.append", &[index], false, Some(key))); }
for call in calls { assert!(call.wait().ok); }

Response

// recorded order == 0,1,2,...,19 — FIFO preserves submission order on a single worker

Output

// Two FIFO calls with the same (function, execution_key) also land on the SAME worker_id.

When the pool is saturated, a pool submission is refused with WorkersBusy (HTTP 503) rather than queued unboundedly — load shedding. call_inline is the escape hatch: it runs on the calling thread regardless of pool saturation, so a re-entrant inline QQL call still completes even when every worker is busy.

Every FunctionResponse carries meta: root_key (the public function that started the call tree), worker_id (the worker that ran it; None for call_inline), and helper_calls (how many helpers ran beneath it).