nautechsystems/nautilus_trader · error
Bybit rate-limit window missing after planning
Error message
Bybit rate-limit window missing after planning
What it means
During acquire(), the planning pass creates each rate-limit window via entry().or_insert_with, and when wait is zero the commit pass re-fetches the same windows with get_mut. Since nothing can remove windows between the two passes (both run under one mutex hold), the window must exist; the expect documents that invariant. Hitting it means the map was mutated between planning and commit, which the current locking prevents.
Source
Thrown at crates/adapters/bybit/src/common/rate_limit.rs:180
limit,
reservation.key.label(),
));
}
let used = u32::try_from(window.timestamps.len()).unwrap_or(u32::MAX);
if used.saturating_add(reservation.weight) > limit {
let needed = used.saturating_add(reservation.weight) - limit;
let index =
usize::try_from(needed - 1).expect("reservation index overflow");
let ready_at = window.timestamps[index] + window.period;
wait = wait.max(ready_at.duration_since(now));
}
}
if wait.is_zero() {
for reservation in reservations {
let window = windows
.get_mut(reservation.key)
.expect("Bybit rate-limit window missing after planning");
window
.timestamps
.extend(std::iter::repeat_n(now, reservation.weight as usize));
}
}
wait
};
if wait.is_zero() {
return Ok(());
}
tokio::time::sleep(wait).await;
}
}
fn observe(
&self,View on GitHub (pinned to 18893faf8b)
Solutions
- Keep planning and commit inside the same mutex guard scope
- Never drop or evict windows between the entry() pass and the get_mut() pass
- Refactor to store the &mut Window from the first pass instead of re-looking it up
- If hit, audit recent changes to rate_limit.rs for lock-scope changes
Example fix
// before
let window = windows.get_mut(reservation.key)
.expect("Bybit rate-limit window missing after planning");
// after
let window = windows.get_mut(reservation.key)
.ok_or_else(|| anyhow::anyhow!("window evicted during acquire"))?; Defensive patterns
Strategy: try-catch
Try / catch
// acquire surfaces quota errors as Err(String); handle instead of unwrapping
if let Err(e) = limiter.acquire(&reservations).await {
return Err(AdapterError::RateLimit(e));
} Prevention
- Do not add window eviction between the planning and commit passes when extending this code
- Keep both passes under one mutex guard
- Write concurrency tests around SlidingWindows::acquire before refactoring
When it happens
Trigger: Unreachable with the current single-lock implementation; could only fire if window eviction/removal is added between the planning and commit loops, or the lock is released mid-function.
Common situations: Only seen by maintainers refactoring SlidingWindows::acquire, e.g. splitting the pass across awaits or adding cleanup that drops idle windows inside the loop.
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
- reservation index overflow
- Close command should not be drained
- Flush command should not be drained
- Order invariant violated: first event must be OrderInitializ
- validated proxy target hash
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e217ab6cb728b8e2.
Report an issue: GitHub.