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

  1. Keep planning and commit inside the same mutex guard scope
  2. Never drop or evict windows between the entry() pass and the get_mut() pass
  3. Refactor to store the &mut Window from the first pass instead of re-looking it up
  4. 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

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


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