nautechsystems/nautilus_trader · error

submit first

Error message

submit first

What it means

Test panic from `.expect("submit first")` on the first `EventStoreWriter::submit`, which returns `SubmitError::Closed` (writer shut down, thread exited, or prior halt latched) or `SubmitError::HaltSignaled` (channel full longer than `halt_threshold`). Under madsim, thread/timing semantics differ, making writer-thread liveness the usual failure point.

Source

Thrown at crates/event_store/src/writer/mod.rs:1874

    }

    #[rstest]
    fn record_snapshot_anchor_records_current_watermark_under_madsim() {
        let (wrapper, shared) = SharedMemory::new();
        shared
            .lock()
            .open_run(manifest("run-anchor"))
            .expect("open");

        let writer = EventStoreWriter::spawn(
            Box::new(wrapper),
            get_atomic_clock_static(),
            noop_halt(),
            WriterConfig::default(),
        )
        .expect("spawn");

        writer.submit(entry_draft(10)).expect("submit first");
        writer.submit(entry_draft(11)).expect("submit second");
        let anchor = writer
            .record_snapshot_anchor("cache://position-snapshots/P-1/0", "blake3:abc")
            .expect("record anchor");

        let backend = shared.lock();
        assert_eq!(anchor.high_watermark, 2);
        assert_eq!(
            backend.latest_snapshot_anchor().expect("latest anchor"),
            Some(anchor),
        );
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Confirm `spawn` succeeded on a backend with an open run before the first submit.
  2. Under madsim, ensure the writer thread can run (check madsim thread model compatibility of the batcher loop).
  3. Raise `halt_threshold` if simulated latency legitimately exceeds it.
  4. Inspect the `SubmitError` variant to distinguish `Closed` from `HaltSignaled`.

Example fix

// before
writer.submit(entry_draft(10)).expect("submit first");
// after
writer.submit(entry_draft(10))
    .unwrap_or_else(|e| panic!("submit first failed: {e:?}"));
Defensive patterns

Strategy: retry

Validate before calling

if halted.load(Ordering::Acquire) { return Err(anyhow!("writer halted")); }

Type guard

fn is_closed(e: &SubmitError) -> bool { matches!(e, SubmitError::Closed) }

Try / catch

if let Err(SubmitError::Closed) = writer.submit(draft) {
    log::warn!("writer closed; re-spawn required before further submits");
}

Prevention

When it happens

Trigger: Submitting immediately after spawn when the writer thread failed to start; a halt already latched from setup; the bounded channel never draining because the writer thread is dead, blocking until `halt_threshold`.

Common situations: madsim test runs where thread spawn/scheduling behaves differently; halt threshold set too low relative to simulated commit latency; backend without an open run killing the writer thread at startup.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/f411e31424005808. Report an issue: GitHub.