Merge pull request #41862 from BerriAI/litellm_settings_store_clear_hang

* fix(proxy): make SettingsStore.clear() terminate when the config file owns a key

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): compare the cleared settings store against an unmutated refill mapping

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): keep the resolved values of config-owned keys across SettingsStore.clear()

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng-berri 2026-09-18 13:29:53 -07:00 committed by GitHub
commit 176ac4cc2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 0 deletions

View file

@ -87,6 +87,12 @@ class SettingsStore(MutableMapping[str, JsonValue]):
)
self._deleted_runtime_keys = self._deleted_runtime_keys | frozenset((key,))
def clear(self) -> None:
self._deleted_runtime_keys = frozenset(key for key in self._keys() if not self.owned_by_config(key))
self._runtime_values = MappingProxyType(
{key: value for key, value in self._runtime_values.items() if self.owned_by_config(key)}
)
def __iter__(self) -> Iterator[str]:
return iter(
key

View file

@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Final
from unittest.mock import patch
import pytest
@ -168,6 +169,54 @@ def test_settings_store_refuses_a_runtime_write_to_a_config_owned_key() -> None:
assert store.source("max_parallel_requests") == "config"
@pytest.mark.timeout(10)
def test_settings_store_clear_removes_every_key_the_config_file_does_not_own() -> None:
store: Final = SettingsStore("general_settings")
store.load_yaml({"master_key": "os.environ/MASTER_KEY"})
store.apply_db_row("general_settings", {"max_parallel_requests": 3, "alerting": ["slack"]})
store.apply_runtime_values({"master_key": "sk-resolved", "alerting": ["slack"]})
store["allow_requests_on_db_unavailable"] = True
del store["alerting"]
store.clear()
assert dict(store) == {"master_key": "sk-resolved"}
assert "alerting" not in store
with pytest.raises(KeyError):
store["max_parallel_requests"]
@pytest.mark.timeout(10)
def test_settings_store_clear_then_refill_matches_a_plain_dict() -> None:
refilled: Final[dict[str, JsonValue]] = {"alerting": ["email"], "max_parallel_requests": 11}
store: Final = SettingsStore("general_settings")
store.update({"max_parallel_requests": 3, "alerting": ["slack"]})
store.clear()
store.update(refilled)
assert dict(store) == refilled
assert tuple(store) == tuple(refilled)
assert len(store) == len(refilled)
@pytest.mark.timeout(10)
@pytest.mark.parametrize("clear", (False, True))
def test_settings_store_survives_a_patch_dict_round_trip_when_the_config_file_owns_a_key(clear: bool) -> None:
store: Final = SettingsStore("general_settings")
store.load_yaml({"master_key": "os.environ/MASTER_KEY"})
store.apply_db_row("general_settings", {"max_parallel_requests": 3})
store.apply_runtime_values({"master_key": "sk-resolved", "max_parallel_requests": 3})
before: Final = dict(store)
with patch.dict(store, {"allow_requests_on_db_unavailable": True}, clear=clear):
assert store["allow_requests_on_db_unavailable"] is True
assert store["master_key"] == "sk-resolved"
assert ("max_parallel_requests" in store) is not clear
assert dict(store) == before
def test_settings_store_reports_the_config_owned_keys_a_write_would_change() -> None:
store: Final = SettingsStore("general_settings")
store.load_yaml({"max_parallel_requests": 3, "ui_access_mode": "admin_only"})