mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-17 04:34:25 +00:00
fix(proxy): cap the opt-in startup cleanup delay at the configured retention interval
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
69f30e26c2
commit
5f7f66d50b
3 changed files with 55 additions and 2 deletions
|
|
@ -108,16 +108,24 @@ def first_cleanup_run_kwargs(settings: Mapping[str, object], now: datetime) -> F
|
|||
"""
|
||||
Extra ``add_job`` kwargs for the interval cleanup job. Empty unless
|
||||
``maximum_spend_logs_cleanup_run_on_startup`` is true, so the first run otherwise stays one interval out.
|
||||
The startup delay is capped at the configured interval so opting in never makes the first run later.
|
||||
"""
|
||||
if (
|
||||
settings.get("maximum_spend_logs_cleanup_cron")
|
||||
or settings.get("maximum_spend_logs_cleanup_run_on_startup") is not True
|
||||
):
|
||||
return {}
|
||||
delay_seconds: Final = SPEND_LOG_CLEANUP_STARTUP_DELAY_SECONDS + random.randint(
|
||||
retention_interval: Final = settings.get("maximum_spend_logs_retention_interval", "1d")
|
||||
if not isinstance(retention_interval, str):
|
||||
return {}
|
||||
try:
|
||||
interval_seconds: Final = duration_in_seconds(retention_interval)
|
||||
except (ValueError, OverflowError):
|
||||
return {}
|
||||
startup_delay_seconds: Final = SPEND_LOG_CLEANUP_STARTUP_DELAY_SECONDS + random.randint(
|
||||
0, SPEND_LOG_CLEANUP_STARTUP_JITTER_SECONDS
|
||||
)
|
||||
return {"next_run_time": now + timedelta(seconds=delay_seconds)}
|
||||
return {"next_run_time": now + timedelta(seconds=min(startup_delay_seconds, interval_seconds))}
|
||||
|
||||
|
||||
class SpendLogCleanup:
|
||||
|
|
|
|||
|
|
@ -4259,6 +4259,28 @@ async def test_ProxyConfig__reschedule_spend_log_cleanup_job_first_run_only_move
|
|||
real_scheduler.shutdown(wait=False)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ProxyConfig__reschedule_spend_log_cleanup_job_opt_in_never_delays_a_short_interval(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
real_scheduler: Final = _paused_scheduler(monkeypatch)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{
|
||||
"maximum_health_check_retention_period": "90d",
|
||||
"maximum_spend_logs_retention_interval": "30s",
|
||||
"maximum_spend_logs_cleanup_run_on_startup": True,
|
||||
},
|
||||
)
|
||||
before: Final = datetime.now(timezone.utc)
|
||||
try:
|
||||
await ProxyConfig()._reschedule_spend_log_cleanup_job()
|
||||
delay: Final = (real_scheduler.get_job("spend_log_cleanup_job").next_run_time - before).total_seconds()
|
||||
assert delay <= 30 + 1, delay
|
||||
finally:
|
||||
real_scheduler.shutdown(wait=False)
|
||||
|
||||
|
||||
def _scheduler_whose_first_add_job_raises(monkeypatch):
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,29 @@ def test_first_cleanup_run_kwargs_runs_soon_after_startup_when_opted_in():
|
|||
assert len(delays) > 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interval, interval_seconds", [("30s", 30), ("2m", 120)])
|
||||
def test_first_cleanup_run_kwargs_never_runs_later_than_a_short_interval(interval: str, interval_seconds: int):
|
||||
now: Final = datetime(2026, 1, 1, tzinfo=timezone.utc)
|
||||
settings: Final = {
|
||||
"maximum_spend_logs_cleanup_run_on_startup": True,
|
||||
"maximum_spend_logs_retention_interval": interval,
|
||||
}
|
||||
delays: Final = {
|
||||
(first_cleanup_run_kwargs(settings, now)["next_run_time"] - now).total_seconds() for _ in range(200)
|
||||
}
|
||||
assert max(delays) <= interval_seconds, delays
|
||||
assert min(delays) >= min(SPEND_LOG_CLEANUP_STARTUP_DELAY_SECONDS, interval_seconds), delays
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interval", ["not-a-duration", 3600])
|
||||
def test_first_cleanup_run_kwargs_leaves_an_invalid_interval_to_the_caller(interval: object):
|
||||
settings: Final = {
|
||||
"maximum_spend_logs_cleanup_run_on_startup": True,
|
||||
"maximum_spend_logs_retention_interval": interval,
|
||||
}
|
||||
assert dict(first_cleanup_run_kwargs(settings, datetime(2026, 1, 1, tzinfo=timezone.utc))) == {}
|
||||
|
||||
|
||||
def _far_deadline() -> float:
|
||||
"""A run deadline far enough out that only the other bounds can stop a batch loop."""
|
||||
return time.monotonic() + 3600
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue