{"record":{"id":"e217ab6cb728b8e2","repo":"nautechsystems/nautilus_trader","slug":"bybit-rate-limit-window-missing-after-planning","errorCode":null,"errorMessage":"Bybit rate-limit window missing after planning","messagePattern":"Bybit rate-limit window missing after planning","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/bybit/src/common/rate_limit.rs","lineNumber":180,"sourceCode":"                            limit,\n                            reservation.key.label(),\n                        ));\n                    }\n                    let used = u32::try_from(window.timestamps.len()).unwrap_or(u32::MAX);\n                    if used.saturating_add(reservation.weight) > limit {\n                        let needed = used.saturating_add(reservation.weight) - limit;\n                        let index =\n                            usize::try_from(needed - 1).expect(\"reservation index overflow\");\n                        let ready_at = window.timestamps[index] + window.period;\n                        wait = wait.max(ready_at.duration_since(now));\n                    }\n                }\n\n                if wait.is_zero() {\n                    for reservation in reservations {\n                        let window = windows\n                            .get_mut(reservation.key)\n                            .expect(\"Bybit rate-limit window missing after planning\");\n                        window\n                            .timestamps\n                            .extend(std::iter::repeat_n(now, reservation.weight as usize));\n                    }\n                }\n\n                wait\n            };\n\n            if wait.is_zero() {\n                return Ok(());\n            }\n            tokio::time::sleep(wait).await;\n        }\n    }\n\n    fn observe(\n        &self,","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/bybit/src/common/rate_limit.rs#L162-L198","documentation":"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.","triggerScenarios":"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.","commonSituations":"Only seen by maintainers refactoring SlidingWindows::acquire, e.g. splitting the pass across awaits or adding cleanup that drops idle windows inside the loop.","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"],"exampleFix":"// before\nlet window = windows.get_mut(reservation.key)\n    .expect(\"Bybit rate-limit window missing after planning\");\n// after\nlet window = windows.get_mut(reservation.key)\n    .ok_or_else(|| anyhow::anyhow!(\"window evicted during acquire\"))?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// acquire surfaces quota errors as Err(String); handle instead of unwrapping\nif let Err(e) = limiter.acquire(&reservations).await {\n    return Err(AdapterError::RateLimit(e));\n}","preventionTips":["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"],"tags":["panic","invariant","rate-limit","concurrency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}