Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Synchronous monitor lifecycle

DataflowProgram is an immutable compiled definition that can be reused to create independent DataflowMonitor sessions. This supports embedded callers that evaluate many traces without repeating DSRV parsing, type checking, or Dataflow compilation.

Ownership

DataflowProgram is a shallow-clone handle. Its definition data is shared through Rc, while every monitor owns its mutable runtime state:

  • input, computed, and retained environment values;
  • temporal histories and delayed writes;
  • lazy and reconfigurable evaluator state;
  • expression activation and scheduler state;
  • monitor and interface revisions;
  • failure and staged-tick state;
  • quickened plans, caches, counters, and integrated JIT state.

The handle is cheap to clone within one thread. It does not add a Send or Sync guarantee. Creating a monitor still allocates its private execution arena, scheduler, and histories.

Creating independent monitors

Retain the compiled program and clone its handle for each monitor:

fn reuse_a_compiled_program_for_independent_traces() -> anyhow::Result<()> {
    use trustworthiness_checker::dataflow::{DataflowMonitor, DataflowProgram};
    use trustworthiness_checker::{ElaboratedDsrvSpecification, TypeCheckOptions, Value};

    let source = "in x: Int\n\
        out total: Int\n\
        total = default(total[1], 0) + x";
    let (spec, warnings) =
        ElaboratedDsrvSpecification::parse_with(source, TypeCheckOptions::GRADUAL)?.into_parts();
    assert!(warnings.is_empty(), "{warnings:?}");
    let spec = spec.map_err(|errors| anyhow::anyhow!("{errors:?}"))?;
    let program = DataflowProgram::compile_checked(spec)?;

    let mut first = DataflowMonitor::from_program(program.clone());
    let mut second = DataflowMonitor::from_program(program);
    let mut first_rows = Vec::new();
    first.evaluate_trace([[Value::Int(2)], [Value::Int(3)]], &mut first_rows)?;
    assert_eq!(first_rows, vec![vec![Value::Int(2)], vec![Value::Int(5)]]);

    let mut second_rows = Vec::new();
    second.evaluate_trace([[Value::Int(10)]], &mut second_rows)?;
    assert_eq!(second_rows, vec![vec![Value::Int(10)]]);

    first.reset();
    let mut restarted_rows = Vec::new();
    first.evaluate_trace([[Value::Int(3)]], &mut restarted_rows)?;
    assert_eq!(restarted_rows, vec![vec![Value::Int(3)]]);
    Ok(())
}

Each monitor starts from the same definition but has independent temporal and execution state. Evaluating or resetting one monitor cannot affect another.

For integrated JIT execution, use DataflowMonitor::from_program_with_jit. The selected JIT activation policy belongs to the monitor configuration, not the shared compiled program.

Evaluating traces

DataflowMonitor::evaluate_trace accepts an iterator of input rows and appends one complete output row for each successful logical tick. Rows contain the existing runtime-tagged Value type and follow input_vars() and output_vars() order.

The method preserves every output cell, including NoVal and Deferred. Application timestamps are ordinary input values: equal timestamps on successive rows remain distinct ticks because call order establishes logical time.

The trace helper is an ordered loop over DataflowMonitor::evaluate:

  • existing rows in the caller's output vector are retained;
  • zero-output ticks append empty rows;
  • the first evaluation error is returned unchanged;
  • successful rows before an error remain available;
  • no later input row is consumed after an error;
  • exhausting the iterator does not add a final tick or resolve pending state;
  • evaluation does not reset the monitor implicitly.

Call evaluate directly with a reusable output buffer when accumulating an entire output timeline would use too much memory.

Reset

DataflowMonitor::reset ends the current execution session and reconstructs the monitor from its original compiled program. It uses the same initialization path as a fresh monitor rather than trying to clear individual caches or histories.

Reset restores:

  • initial environment and retained values;
  • initial temporal histories and history depths;
  • initial lazy, dynamic, and deferred-expression state;
  • initial activation and dependency scheduling;
  • the original root definition and interface;
  • initial monitor and interface revisions;
  • a healthy monitor with no staged tick;
  • cold quickening and integrated JIT state.

The latest selected quickening, integrated-JIT, and internal context-transfer configuration is retained, but session-derived plans, artifacts, counters, reports, replay records, and cached runtime expressions are discarded. An eager JIT configuration may therefore compile native code again during reset.

Evaluation failures remain terminal until reset. Input or output arity errors that fail before a tick begins retain their existing non-poisoning behavior.

Reconfiguration

Dynamic and deferred expressions are supported because reset reconstructs their initial activation, scheduler, retained environment, and history topology.

Root replacement changes the active definition only for the current session. Reset returns to the program originally used to construct the monitor. A caller that wants a replacement definition to become a new reset baseline must create a new monitor from that compiled program.

Context transfer remains a separate state-preserving operation and is not used to implement reset.

Runtime boundaries

The lifecycle API applies to in-memory DataflowMonitor evaluation, including canonical, quickened, and integrated-JIT Value execution under their existing feature gates.

It does not add lifecycle control to:

  • TypedDataflowMonitor, TypedJitMonitor, or extracted direct-JIT monitors;
  • asynchronous, semi-synchronous, reconfigurable semi-synchronous, distributed, or MSTLO runtimes;
  • MQTT, Redis, ROS, file, or other transport sessions.

The CLI retains its existing behavior. Its synchronous Dataflow mode remains an external compatibility path rather than the embedded lifecycle API.