Skip to main content

OpDispenser

Trait OpDispenser 

Source
pub trait OpDispenser: Send + Sync {
    // Required method
    fn execute<'a>(
        &'a self,
        cycle: u64,
        ctx: &'a ExecCtx<'a>,
    ) -> Pin<Box<dyn Future<Output = Result<OpResult, ExecutionError>> + Send + 'a>>;

    // Provided methods
    fn describe(&self) -> Option<String> { ... }
    fn describe_resolved(&self, wires: &dyn WireSource) -> Option<String> { ... }
    fn canonical_kernel(&self) -> Option<&Arc<dyn Kernel>> { ... }
    fn adapter_metrics(&self) -> Vec<(String, Labels, MetricValue)> { ... }
    fn status_counters(&self) -> Vec<(&str, u64)> { ... }
    fn rows_per_op(&self) -> usize { ... }
    fn inner_dispenser(&self) -> Option<&dyn OpDispenser> { ... }
}
Expand description

A per-template op factory. Created at init time by the adapter’s map_op(), called per-cycle to bind values and execute operations.

The dispenser captures template-specific state (prepared statement, field names, bind-point indices, metrics) so the per-cycle path is minimal: bind resolved values and execute.

Dispensers are shared across fibers and must be thread-safe.

Required Methods§

Source

fn execute<'a>( &'a self, cycle: u64, ctx: &'a ExecCtx<'a>, ) -> Pin<Box<dyn Future<Output = Result<OpResult, ExecutionError>> + Send + 'a>>

Execute an operation for the given cycle.

The ctx bundle carries:

  • ctx.fields — op-field substitution view for the inner adapter (positional / by-name access matching the prepared statement).
  • ctx.pulls — wrapper-facing handle-indexed view of Polydat values (used by validation / conditional / throttle wrappers; adapters ignore this).

See SRD 32 §“ExecCtx — cycle-time bundle” for the design.

Provided Methods§

Source

fn describe(&self) -> Option<String>

One-line description of the op this dispenser represents — typically the statement text / request shape with bind-point placeholders left unresolved ({table}, {key}, ?, …).

Used by the runtime’s error-capture path to attach the actual op identity to a phase-stop diagnostic. Without this, an error like [validation_failed] op 'indexes_present_cass' … only names the op template; the operator can’t see what statement the dispenser is actually firing without grepping the workload yaml. With this hooked, the captured reason carries the rendered statement so the operator can immediately tell whether (a) it’s the wrong dialect’s branch firing, (b) the bindpoints resolved to something unexpected, (c) the statement is malformed in the workload, etc.

Returns None for dispensers whose op shape isn’t usefully one-lineable (composed wrappers that delegate, dispensers whose body is multi-paragraph HTTP, etc.). The runtime falls through to the inner-dispenser chain when this returns None. Default: delegate to inner dispenser if any, else None.

Source

fn describe_resolved(&self, wires: &dyn WireSource) -> Option<String>

Render the actual op the dispenser would fire for the given cycle — the dryrun-equivalent view of the statement after every bind point is interpolated.

Pairs with Self::describe: describe() returns the op-template (placeholders intact) so the operator can match the failure to the workload yaml; describe_resolved(wires) returns what was actually sent for this cycle, so the operator can immediately tell whether bindpoints resolved to expected values, whether the wrong dialect’s branch fired, whether quoting / escaping is broken, etc.

SRD-68 Push 5: takes the dispenser’s bound WireSource (same surface adapters use at cycle time) so the rendered view comes from the canonical resolution path — no synthesis-layer ResolvedFields detour.

Returns None when the dispenser can’t usefully render the resolved form (e.g. opaque request bodies, dispensers without per-cycle interpolation). The default delegates to the inner dispenser; leaf dispensers should override when they have a useful per-cycle rendering.

Source

fn canonical_kernel(&self) -> Option<&Arc<dyn Kernel>>

SRD-68 invariant I-3 — the dispenser’s canonical Polydat Kernel, established at construction by map_op from its parent — the parent itself, or a kernel bound under it. Any engine: the executor finds the program to bind per fiber by program_id. Returns None for dispensers that don’t own a kernel (adapters with no Polydat needs, or wrappers that delegate to an inner dispenser).

The executor walks Some returns at fiber spawn to materialise per-fiber subscope kernels — one per dispenser, indexed parallel to the dispenser registry. At cycle time the firing fiber’s slot for this dispenser is handed in via ExecCtx::wires so cycle-time reads stay on the SRD-68 I-1 single resolution surface.

Default: delegate to inner dispenser if any, else None.

Source

fn adapter_metrics(&self) -> Vec<(String, Labels, MetricValue)>

Snapshot adapter-specific metrics for inclusion in the capture snapshot.

Called by the metrics scheduler alongside the standard activity metrics. Adapters return additional (family_name, labels, MetricValue) triples that represent adapter-internal state (e.g., rows/s for batched CQL). These appear in the summary report.

The OpenMetrics-shaped runtime model lives under nmbrs_metrics::snapshot — adapters typically build MetricValue::Counter / MetricValue::Histogram / MetricValue::Gauge directly. Default: no additional metrics.

Source

fn status_counters(&self) -> Vec<(&str, u64)>

Adapter-specific status line entries (cumulative, non-destructive read).

Unlike adapter_metrics() which snapshots delta timers, this method returns cumulative counters safe to read from the progress thread without interfering with the metrics pipeline. Returns (display_name, cumulative_count) pairs.

A name with a leading underscore (_batch_writes) is an INTERNAL counter: published only to back a derived display metric (the rows/batch average divides rows_inserted by _batch_writes), it is looked up by name for that computation but never rendered as its own <name>/s throughput chip. See crate::readout_context::is_internal_counter — the single predicate every chip-rendering surface filters on. Default: delegates to inner dispenser (for wrapper chains).

Source

fn rows_per_op(&self) -> usize

The op’s uniform per-invocation cursor consumption — how many consecutive wire ordinals one execute call reads and covers.

The executor drives the phase cursor with Σ rows_per_op over the stanza’s ops (instead of the raw stanza length) and hands each op a contiguous sub-run of exactly this size, so a batch op that reads N rows per call also advances the cursor by N — consecutive stanzas then cover disjoint ordinal runs (SRD-22 “phase extent = cursor exhaustion”, cover-once). A batch dispenser overrides this to return its fixed stride N; every ordinary op keeps the default 1 (identical to the pre-batching model).

The default delegates to the inner dispenser so a wrapped batch op (retry / result / metrics layers) still reports its leaf stride — the executor sees the outermost wrapper, and the stride must reach it through the chain (mirrors describe / adapter_metrics). Leaves with no inner fall through to 1.

This is the NOMINAL stride (what sets reserve(N)); at the cursor tail the reservation may be shorter, and the executor passes the actual (possibly-short) run length via crate::fixture::ExecCtx::run_len so the op inserts exactly the ordinals reserved — never over-reading, never dropping the remainder.

Source

fn inner_dispenser(&self) -> Option<&dyn OpDispenser>

Returns the wrapped dispenser when this is a wrapper, None when this is a leaf (the adapter’s base dispenser — e.g. CQL raw / prepared / batch, HTTP, stdout, plotter, …).

Default returns None, which is correct for every leaf. Adapter-base dispensers should rely on the default — they have no inner to expose, and asking them to write fn inner_dispenser(&self) -> None is pure boilerplate.

Wrappers MUST override to return Some(self.inner.as_ref()). Several pieces of cross-cutting machinery walk this chain:

  • adapter_metrics and status_counters delegate through wrapper layers via the default implementations on this trait, which call inner_dispenser() to find the wrapped layer.
  • describe() walks inward to surface the runtime op shape (CQL statement text) for error-context dumps; missing this on a wrapper silently breaks the walk and the error loses its op-shape line.

The WrappingDispenser marker trait below is the type-system signal that flags “this is a wrapper”; wrappers should implement both. Future composition machinery (SRD-32a) will use the WrappingDispenser bound to require the override at the registration boundary.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§