Repository navigation
fix(router): await redis flush before reading back budget spend - #32844
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
Greptile SummaryThis PR fixes a race condition in
Confidence Score: 5/5Safe to merge — the change is minimal, targeted, and directly addresses the documented race condition without altering any public interface or data format. The diff is a two-line behavioural change (fire-and-forget → await) plus a queue snapshot that prevents lost increments during the flush. The logic is straightforward, the ordering guarantee is proven by the new regression test, and the change does not touch any auth, schema, or critical-path code outside the Redis sync helper. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/router_strategy/budget_limiter.py | Converts fire-and-forget asyncio.create_task to an awaited call for the Redis increment pipeline; snapshots and clears the queue before the await to prevent dropped increments during the flush |
| tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py | Adds regression test with a fake Redis that records event order (pipeline_start → pipeline_end → batch_get), verifying the flush completes before the read-back and the read-back sees the incremented value |
Reviews (1): Last reviewed commit: "fix(router): await redis flush before re..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Relevant issues
Fixes #32614
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Type
🐛 Bug Fix
Changes
RouterBudgetLimiting._sync_in_memory_spend_with_redisruns two steps in order: (1) push the queued in-memory spend increments to Redis, then (2) read the current provider spend back from Redis to refresh the in-memory cache. The push step in_push_in_memory_increments_to_redisfired the Redis write as a background task and returned immediatelySo step 2 could read Redis before that fire-and-forget increment landed, get a stale (pre-increment) value, and then overwrite the fresher in-memory spend with it. Under load the in-memory provider spend regresses, which lets an over-budget provider slip back into routing until the next sync catches up
The fix awaits the pipeline so the write is guaranteed to complete before the read-back. The queue is snapshotted and cleared before the await so increments arriving during the flush aren't dropped
Added a regression test in
tests/test_litellm/router_strategy/test_budget_limiter_hotpath.pythat drives a full sync with a fake Redis recording event order; it asserts the increment pipeline completes (pipeline_end) before the read-back (batch_get) and that the read-back sees the just-written spend. It fails on the old fire-and-forget code and passes with the fixScreenshots / Proof of Fix
Live proxy against a real Redis, hitting the real xAI API (real $), captured at commit
0bdeea1dec. Config wiresrouter_settings.provider_budget_config.xaiwith a Redis-backed dual cache. Three realgrok-4.5calls, then the synced provider spend key in Redis matches the summed request cost with the queue fully drained (no stale overwrite)Proxy debug log confirms each increment is pushed and the queue drains to empty on subsequent ticks
Deterministic before/after on the specific ordering (the race is timing-dependent, so this is the reliable signal). Same working tree, only
litellm/router_strategy/budget_limiter.pydiffersBefore (fire-and-forget, commit
0bdeea1decwith the fix reverted):After (commit
0bdeea1dec):Link to Devin session: https://app.devin.ai/sessions/98a4449e087e43b1972219953850fef3