fix: make every client close what it built and nothing else
A client used to close whatever transport, recorder or cache it happened to hold, injected or not, so the first client to shut down killed the backend its siblings were still using. That is why the explicit-sharing path the architecture prescribes was unusable in practice and downstream projects fell back to one private instance per client. The mirror image of the same gap: the redis clients the factories build for the limiter and the breaker were never closed at all, because nobody kept a reference to them once they were handed to the retry middleware. Ownership is now stated once, the way RedisLimiter already stated it: whoever builds a resource closes it, injected ones are left alone. The constructor is the full-injection path, so it owns nothing by default and only the factories mark what they built. RedisCache gains the same rule for its own client, and the three copies of the "probe for aclose, fall back to close" dance collapse into a single helper so the next correction cannot land in only one of them.
This commit is contained in:
@@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from polygateway.client import _aclose_component
|
||||
from polygateway.config import EmbeddingSettings
|
||||
from polygateway.errors import (
|
||||
AllSourcesExhausted,
|
||||
@@ -128,6 +129,15 @@ class EmbeddingClient:
|
||||
TelemetryEmitter(telemetry, pricing=pricing, text_cap=text_cap) if telemetry else None
|
||||
)
|
||||
self._telemetry = telemetry
|
||||
# 限流/熔断后端在此之外只以 QuotaGate/BreakerGate 的形态存在,自持一份
|
||||
# 引用才关得到自建的 redis 客户端(设计 §3.4)
|
||||
self._limiter_backend = limiter
|
||||
self._breaker_backend = breaker
|
||||
# 所有权默认"不拥有": `__init__` 是全量注入路径,只有工厂自建时才置 True
|
||||
self._owns_transport = False
|
||||
self._owns_telemetry = False
|
||||
self._owns_limiter = False
|
||||
self._owns_breaker = False
|
||||
self._pricing = pricing
|
||||
self._batch_size = batch_size
|
||||
self._normalize = normalize
|
||||
@@ -445,20 +455,18 @@ class EmbeddingClient:
|
||||
return sum(known) if known else None
|
||||
|
||||
async def aclose(self) -> None:
|
||||
"""幂等释放 transport 连接池与遥测连接(与 GatewayClient 对称)。"""
|
||||
"""幂等释放**自建**资源(与 GatewayClient 对称);注入的组件一律不碰。"""
|
||||
if self._closed:
|
||||
return
|
||||
self._closed = True
|
||||
transport_aclose = getattr(self._transport, "aclose", None)
|
||||
if transport_aclose is not None:
|
||||
await transport_aclose()
|
||||
telemetry_aclose = getattr(self._telemetry, "aclose", None)
|
||||
if telemetry_aclose is not None:
|
||||
await telemetry_aclose()
|
||||
else:
|
||||
telemetry_close = getattr(self._telemetry, "close", None)
|
||||
if telemetry_close is not None:
|
||||
telemetry_close()
|
||||
if self._owns_transport:
|
||||
await _aclose_component(self._transport)
|
||||
if self._owns_telemetry:
|
||||
await _aclose_component(self._telemetry)
|
||||
if self._owns_limiter:
|
||||
await _aclose_component(self._limiter_backend)
|
||||
if self._owns_breaker:
|
||||
await _aclose_component(self._breaker_backend)
|
||||
|
||||
async def __aenter__(self) -> EmbeddingClient:
|
||||
return self
|
||||
@@ -484,18 +492,19 @@ class EmbeddingClient:
|
||||
_build_limiter,
|
||||
_build_selector,
|
||||
_build_telemetry,
|
||||
_mark_owned_components,
|
||||
)
|
||||
from polygateway.pricing import PricingTable
|
||||
from polygateway.transports.openai_compat import OpenAICompatTransport
|
||||
|
||||
gw = settings.gateway
|
||||
sources = list(gw.sources)
|
||||
return cls(
|
||||
client = cls(
|
||||
scope=gw.scope,
|
||||
sources=sources,
|
||||
selector=_build_selector(gw.selector),
|
||||
limiter=limiter or _build_limiter(gw, sources),
|
||||
breaker=breaker or _build_breaker(gw),
|
||||
limiter=limiter if limiter is not None else _build_limiter(gw, sources),
|
||||
breaker=breaker if breaker is not None else _build_breaker(gw),
|
||||
transport=OpenAICompatTransport(registry=registry),
|
||||
retry=gw.retry,
|
||||
backpressure=gw.backpressure,
|
||||
@@ -512,6 +521,8 @@ class EmbeddingClient:
|
||||
normalize=settings.normalize,
|
||||
expected_dim=settings.expected_dim,
|
||||
)
|
||||
_mark_owned_components(client, limiter=limiter, breaker=breaker, telemetry=telemetry)
|
||||
return client
|
||||
|
||||
@classmethod
|
||||
def from_env(
|
||||
|
||||
Reference in New Issue
Block a user