mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(proxy): retry lock-timed-out daily spend batches in place so the shutdown flush keeps them (#45273)
* fix(proxy): retry lock-timed-out daily spend batches in place so the shutdown flush keeps them Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): annotate the lock-timeout retry test and carry its context in assert messages Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): mark the remaining lock-timeout test local Final Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
487164f762
commit
b9ff2d66a4
2 changed files with 53 additions and 3 deletions
|
|
@ -2589,9 +2589,11 @@ class DBSpendUpdateWriter:
|
|||
PrismaDBExceptionHandler,
|
||||
)
|
||||
|
||||
is_retryable = isinstance(
|
||||
e, DB_RETRY_SAFE_ERROR_TYPES
|
||||
) or PrismaDBExceptionHandler.is_deadlock_error(e)
|
||||
is_retryable = (
|
||||
isinstance(e, DB_RETRY_SAFE_ERROR_TYPES)
|
||||
or PrismaDBExceptionHandler.is_deadlock_error(e)
|
||||
or PrismaDBExceptionHandler.is_lock_timeout_error(e)
|
||||
)
|
||||
if not is_retryable:
|
||||
raise
|
||||
if i >= n_retry_times:
|
||||
|
|
|
|||
|
|
@ -537,6 +537,54 @@ async def test_update_daily_spend_retries_connect_errors(monkeypatch):
|
|||
assert len(prisma_client.db.statements) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_daily_spend_retries_lock_timeout_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _lock_timeout_error() -> PrismaDataError:
|
||||
return PrismaDataError(
|
||||
data={
|
||||
"user_facing_error": {
|
||||
"is_panic": False,
|
||||
"message": "Error querying the database: canceling statement due to lock timeout",
|
||||
"meta": {"code": "55P03", "message": "canceling statement due to lock timeout"},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
outcomes: Final = iter([_lock_timeout_error(), None])
|
||||
|
||||
def first_attempt_locks_out() -> int:
|
||||
outcome: Final = next(outcomes)
|
||||
if outcome is not None:
|
||||
raise outcome
|
||||
return 1
|
||||
|
||||
prisma_client: Final = _RecordingPrisma(execute_raw=first_attempt_locks_out)
|
||||
proxy_logging: Final = MagicMock()
|
||||
proxy_logging.failure_handler = AsyncMock()
|
||||
|
||||
async def fake_sleep(seconds: float) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.db.db_spend_update_writer.asyncio.sleep", fake_sleep)
|
||||
daily_spend_transactions: Final = {"k1": _daily_txn()}
|
||||
await DBSpendUpdateWriter._update_daily_spend(
|
||||
n_retry_times=3,
|
||||
prisma_client=prisma_client,
|
||||
proxy_logging_obj=proxy_logging,
|
||||
daily_spend_transactions=daily_spend_transactions,
|
||||
entity_type="user",
|
||||
entity_id_field="user_id",
|
||||
)
|
||||
|
||||
assert len(prisma_client.db.statements) == 2, (
|
||||
"a 55P03 lock_timeout cancels the upsert before it applies, so the writer must "
|
||||
"resend it in place instead of only requeueing it for a next tick a shutdown "
|
||||
"flush never gets"
|
||||
)
|
||||
assert daily_spend_transactions == {}, "the retried batch must drain the transactions dict"
|
||||
proxy_logging.failure_handler.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_daily_spend_sorting():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue