From fb80ee7de003acc7dcb54eb6d245a688b3bd4faf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 11:53:16 +0000 Subject: [PATCH 1/2] fix: prevent startup warmup from overwriting models list cache with stale data _build_models_list now skips the _models_list_cache write when called with only_if_empty=True and the cache is already populated. This prevents two races: 1. Within-test: the startup daemon thread (step 5 of _run_startup_tasks_once) overwrote a fresh cache built by list_models() on a concurrent request. 2. Cross-test: a daemon thread left over from a prior test (after monkeypatches were torn down) wrote an empty cache to the module dict after a module reload reset it to None, causing the next test's list_models() to see a stale cache hit. Also suppress startup daemon threads and interval probe checks in the test_proxy_display_id_format server fixture to eliminate remaining cross-test contamination from prior tests' threads writing with old lock instances. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RkRhX4Znv73oPcZy41bnyS --- llmproxy/server.py | 6 +++++- tests/test_proxy_display_id_format.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/llmproxy/server.py b/llmproxy/server.py index f05c79e..da1d614 100755 --- a/llmproxy/server.py +++ b/llmproxy/server.py @@ -2110,7 +2110,11 @@ def _build_models_list(providers: dict, config: dict, timeout: int, models_ttl: if models_ttl > 0: with _models_list_cache_lock: - _models_list_cache = (full_list, time.monotonic()) + # When called as a startup warmup (only_if_empty=True), skip the write + # if something already populated the cache — a concurrent request or a + # cross-test daemon thread from a previous test beat us here. + if not only_if_empty or _models_list_cache is None: + _models_list_cache = (full_list, time.monotonic()) return full_list diff --git a/tests/test_proxy_display_id_format.py b/tests/test_proxy_display_id_format.py index ad9db9a..891e891 100644 --- a/tests/test_proxy_display_id_format.py +++ b/tests/test_proxy_display_id_format.py @@ -24,6 +24,12 @@ def server(monkeypatch, minimal_config): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + # Suppress background daemon threads that can race with cache-sensitive tests: + # the startup warmup (step 5 of _run_startup_tasks_once) and the per-request + # interval probe check both write to _models_list_cache, and a stale thread + # from a prior test can overwrite the cache after the test sets it to None. + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod From ac010d80e2cb10a10b8249c1695c17c49a68be6b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 12:02:35 +0000 Subject: [PATCH 2/2] tests: suppress interval-probe daemon threads in all server-reloading fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _maybe_fire_interval_probes() spawns background daemon threads that run the full update pipeline. Under pytest-cov with concurrent threads, Python's coverage/collector.py:lock_data crashes with SIGSEGV (exit 139). After each importlib.reload(server_mod) the module globals reset, but old daemon threads from prior tests still run against the old lock instances — causing races. Patch both _run_startup_tasks_once and _maybe_fire_interval_probes to no-ops immediately after every reload in all 15 server-loading test helpers/fixtures. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RkRhX4Znv73oPcZy41bnyS --- tests/test_admin_api.py | 2 ++ tests/test_admin_auth.py | 2 ++ tests/test_admin_masking.py | 2 ++ tests/test_capabilities.py | 2 ++ tests/test_cycling_robustness.py | 2 ++ tests/test_dialects.py | 2 ++ tests/test_favorite_ordering.py | 2 ++ tests/test_fusion.py | 2 ++ tests/test_input_aware_routing.py | 2 ++ tests/test_loadbalanced.py | 2 ++ tests/test_local_provider_not_in_believed_free.py | 2 ++ tests/test_per_provider_virtual.py | 2 ++ tests/test_provider_env_runtime.py | 2 ++ tests/test_server_routes.py | 2 ++ tests/test_usage_endpoint.py | 2 ++ 15 files changed, 30 insertions(+) diff --git a/tests/test_admin_api.py b/tests/test_admin_api.py index dc72a13..ff9f9fd 100644 --- a/tests/test_admin_api.py +++ b/tests/test_admin_api.py @@ -45,6 +45,8 @@ def _make_server(monkeypatch, config_path: Path, config: dict): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) server_mod.app.config["TESTING"] = True return server_mod diff --git a/tests/test_admin_auth.py b/tests/test_admin_auth.py index 31feaf0..4170dc4 100644 --- a/tests/test_admin_auth.py +++ b/tests/test_admin_auth.py @@ -21,6 +21,8 @@ def _client(monkeypatch, tmp_path, admin_block=None): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + server_mod._run_startup_tasks_once = lambda *a, **k: None + server_mod._maybe_fire_interval_probes = lambda *a, **k: None server_mod.app.config["TESTING"] = True return server_mod.app.test_client() diff --git a/tests/test_admin_masking.py b/tests/test_admin_masking.py index 9d6de43..0531e4f 100644 --- a/tests/test_admin_masking.py +++ b/tests/test_admin_masking.py @@ -27,6 +27,8 @@ def client(monkeypatch, tmp_path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) server_mod.app.config["TESTING"] = True return server_mod.app.test_client(), cfg_path diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index b79f6aa..6293af0 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -16,6 +16,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_cycling_robustness.py b/tests/test_cycling_robustness.py index 78be3cd..d407e8d 100644 --- a/tests/test_cycling_robustness.py +++ b/tests/test_cycling_robustness.py @@ -22,6 +22,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_dialects.py b/tests/test_dialects.py index e014de4..93d02dd 100644 --- a/tests/test_dialects.py +++ b/tests/test_dialects.py @@ -66,6 +66,8 @@ def server(monkeypatch, tmp_path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) server_mod.app.config["TESTING"] = True return server_mod diff --git a/tests/test_favorite_ordering.py b/tests/test_favorite_ordering.py index 47dbfea..7db5a3d 100644 --- a/tests/test_favorite_ordering.py +++ b/tests/test_favorite_ordering.py @@ -17,6 +17,8 @@ def _reload_server(monkeypatch, tmp_path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) def _fn(): diff --git a/tests/test_fusion.py b/tests/test_fusion.py index ce05400..1d80971 100644 --- a/tests/test_fusion.py +++ b/tests/test_fusion.py @@ -99,6 +99,8 @@ def _load_server(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_input_aware_routing.py b/tests/test_input_aware_routing.py index a26eda9..1ff2db5 100644 --- a/tests/test_input_aware_routing.py +++ b/tests/test_input_aware_routing.py @@ -21,6 +21,8 @@ def _load_server(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_loadbalanced.py b/tests/test_loadbalanced.py index 76f176b..5f66317 100644 --- a/tests/test_loadbalanced.py +++ b/tests/test_loadbalanced.py @@ -23,6 +23,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_local_provider_not_in_believed_free.py b/tests/test_local_provider_not_in_believed_free.py index 5152fa5..f0c5284 100644 --- a/tests/test_local_provider_not_in_believed_free.py +++ b/tests/test_local_provider_not_in_believed_free.py @@ -34,6 +34,8 @@ def _load_server(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_per_provider_virtual.py b/tests/test_per_provider_virtual.py index 1856329..88980f6 100644 --- a/tests/test_per_provider_virtual.py +++ b/tests/test_per_provider_virtual.py @@ -20,6 +20,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_provider_env_runtime.py b/tests/test_provider_env_runtime.py index 0d65231..2a1dc61 100644 --- a/tests/test_provider_env_runtime.py +++ b/tests/test_provider_env_runtime.py @@ -43,6 +43,8 @@ def server_mod(monkeypatch, tmp_path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_server_routes.py b/tests/test_server_routes.py index daa3f97..896ce0c 100644 --- a/tests/test_server_routes.py +++ b/tests/test_server_routes.py @@ -20,6 +20,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod diff --git a/tests/test_usage_endpoint.py b/tests/test_usage_endpoint.py index 0ce9bab..64ab5c0 100644 --- a/tests/test_usage_endpoint.py +++ b/tests/test_usage_endpoint.py @@ -15,6 +15,8 @@ def _load_server_with_config(monkeypatch, config_path: Path): importlib.reload(config_mod) from llmproxy import server as server_mod importlib.reload(server_mod) + monkeypatch.setattr(server_mod, "_run_startup_tasks_once", lambda *a, **k: None) + monkeypatch.setattr(server_mod, "_maybe_fire_interval_probes", lambda *a, **k: None) return server_mod