nautechsystems/nautilus_trader · error
registered callback lease was not acquired
Error message
registered callback lease was not acquired
What it means
`LiveTimer::start` splits dispatch into direct and registered-callback modes. For a registered callback (identified by token) it must have acquired a `registered_lease` from the callback registry; the code asserts this invariant with `.expect`. Missing lease means the timer was configured for a registered callback but the lease was never created/fetched before firing.
Source
Thrown at crates/common/src/live/timer.rs:354
break;
}
None
};
if sender.is_some()
&& let Some(following_next_time_ns) = following_next_time_ns
{
next_time_atomic
.store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
}
match (&sender, &worker_dispatch) {
(Some(sender), WorkerDispatch::Direct(factory)) => {
sender.send(factory.message(event));
}
(Some(sender), WorkerDispatch::Registered(token)) => {
let lease =
registered_lease.expect("registered callback lease was not acquired");
if expires_after_fire {
token.close();
}
sender.send(TimeEventMessage::registered(event, lease));
}
#[cfg(feature = "python")]
(None, WorkerDispatch::SenderlessPython(callback)) => callback.call(event),
_ => unreachable!("timer callback dispatch did not match its sender"),
}
if sender.is_none()
&& let Some(following_next_time_ns) = following_next_time_ns
{
next_time_atomic
.store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure the registered callback is registered (lease acquired) before `set_timer_ns`/`set_time_alert_ns` fires
- Use a direct (closure) callback instead of a registered token if you do not need named registration
- Do not close/cancel the registration before the timer fires; close after fire as the code does via `token.close()`
- Update/restart the timer after any registry change so a fresh lease is acquired
Example fix
// before let token = registry.register(name, None); // no lease returned/held timer.start(event); // after let (token, lease) = registry.register(name, callback); timer.start(event); // registered_lease = Some(lease)
Defensive patterns
Strategy: type-guard
Validate before calling
if timer_is_registered_callback() && registered_lease.is_none() {
panic-avoid: re-acquire lease or fall back to direct callback before start();
} Type guard
fn ready_to_start(sender: &Option<Sender>, lease: &Option<Lease>, dispatch: &WorkerDispatch) -> bool {
!matches!(dispatch, WorkerDispatch::Registered(_)) || lease.is_some()
} Prevention
- Register the callback and hold its lease before setting any timer that references it
- Do not close/cancel registrations while timers referencing them are live
- Use direct closure callbacks unless named registration is required
- After cancelling registrations, recreate the timer rather than restarting it
When it happens
Trigger: Starting a live timer whose callback is a registered token while `registered_lease` is None — e.g. a senderless/registered-callback configuration path that skipped lease acquisition, or an internal race where the registry entry was removed before start.
Common situations: Custom timer setup mixing direct and registered callback APIs; global-runtime dispatch where lease acquisition is skipped; tests exercising registered callbacks after terminal events closed the registration.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Callback should exist
- Event '{}' should have associated handler
- timer event sender was unset for Rust callback system
- task slot is already occupied
- Python on_time_event failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ae9fd6f5853b75f1.
Report an issue: GitHub.