nautechsystems/nautilus_trader · error

Callback should exist

Error message

Callback should exist

What it means

This panic comes from `.expect("Callback should exist")` in `LiveClock::set_time_alert_ns` in crates/common/src/live/clock.rs. When no new callback is supplied, the method assumes a callback was previously registered under `name` and tries to fetch it from the callback registry; if the registry has no entry for that name, the invariant is broken and the clock panics.

Source

Thrown at crates/common/src/live/clock.rs:168

    ) -> anyhow::Result<()> {
        let ts_now = self.get_time_ns();
        let (name, alert_time_ns) =
            validate_and_prepare_time_alert(name, alert_time_ns, allow_past, ts_now)?;

        check_predicate_true(
            callback.is_some() | self.callbacks.has_any_callback(&name),
            "No callbacks provided",
        )?;

        self.replace_existing_timer_if_needed(&name);

        let callback = if let Some(callback) = callback {
            self.callbacks.register_callback(name, callback.clone());
            callback
        } else {
            self.callbacks
                .get_callback(&name)
                .expect("Callback should exist")
        };

        // Safe to calculate interval now that we've ensured alert_time_ns >= ts_now
        let interval_ns = create_valid_interval(alert_time_ns - ts_now);
        let fire_immediately = alert_time_ns == ts_now;
        let sender = self.resolve_time_event_sender();

        let mut timer = LiveTimer::new(
            name,
            interval_ns,
            ts_now,
            Some(alert_time_ns),
            callback,
            fire_immediately,
            sender,
        );

        timer.start();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Always pass a callback (Some(...)) the first time you register a given time-alert name
  2. Ensure the name matches exactly a previously registered callback; verify with `callback_registered(&name)` before passing None
  3. If the timer may have expired and cleaned up its callback, re-register with a fresh callback instead of None
  4. Check that no code path cancels the timer/callback between registration and re-use

Example fix

// before
clock.set_time_alert_ns("my_alert", alert_time_ns, None); // panics if callback was cleaned up
// after
if !clock.callback_registered(&"my_alert".into()) {
    clock.set_time_alert_ns("my_alert", alert_time_ns, Some(Callback::from(|event| { /* handle */ })));
} else {
    clock.set_time_alert_ns("my_alert", alert_time_ns, None);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if !clock.callback_registered(&name) {
    // register with Some(callback) instead of None
}

Type guard

fn can_rearm(clock: &LiveClock, name: &str) -> bool { clock.callback_registered(&name.into()) }

Prevention

When it happens

Trigger: Calling `live_clock.set_time_alert_ns(name, alert_time_ns, None)` (or the `_` variant) for a `name` that was never registered with a callback — e.g. the alert was set with None before any registration, the callback was removed via `cancel_timer`/registry clearing, or the name is mistyped.

Common situations: Re-registering a persistent time alert after the timer expired and was cleaned up (callback registry entry dropped), typos in timer names between calls, or actors re-arming alerts in `on_stop`/`on_start` cycles where the previous callback was cancelled.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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