fix(llm): HostPool.check merges in-flight counts; guard release (refs #568)

This commit is contained in:
kert
2026-07-17 09:28:22 -04:00
parent f1f5b35e58
commit f630a9349b
2 changed files with 24 additions and 2 deletions

View File

@@ -53,7 +53,7 @@ class HostPool:
except httpx.HTTPError: except httpx.HTTPError:
continue continue
with self._lock: with self._lock:
self._in_flight = {h: 0 for h in alive} self._in_flight = {h: self._in_flight.get(h, 0) for h in alive}
if not alive: if not alive:
raise RuntimeError( raise RuntimeError(
f"no Ollama host in pool serves {model!r} — " f"no Ollama host in pool serves {model!r} — "
@@ -70,7 +70,8 @@ class HostPool:
yield host yield host
finally: finally:
with self._lock: with self._lock:
self._in_flight[host] -= 1 if host in self._in_flight:
self._in_flight[host] -= 1
def embed_texts( def embed_texts(

View File

@@ -85,6 +85,27 @@ class TestEmbedTexts:
embed_texts(HostPool([H1]), "m", ["a"]) embed_texts(HostPool([H1]), "m", ["a"])
class TestCheckAcquireInterleave:
@patch("llm.pool.httpx.Client")
def test_check_drops_held_host_without_corruption(self, MockClient):
client = MockClient.return_value.__enter__.return_value
pool = HostPool([H1, H2])
with pool.acquire() as held:
survivor = H2 if held == H1 else H1
def fake_get(url, **kw):
if url.startswith(held):
return _resp({"models": []})
return _resp({"models": [{"name": "m:latest"}]})
client.get.side_effect = fake_get
assert pool.check("m") == [survivor]
# exiting acquire() above must not raise despite the dropped host
with pool.acquire() as again:
assert again == survivor
assert pool._in_flight[survivor] == 0
class TestPoolEmbeddings: class TestPoolEmbeddings:
@patch("llm.pool.embed_texts") @patch("llm.pool.embed_texts")
def test_adapter_delegates(self, mock_embed): def test_adapter_delegates(self, mock_embed):