fix(gateway): per-server lock in OpcConnectionPool to avoid duplicate connects

This commit is contained in:
PedroHMCosme
2026-07-14 09:50:14 -03:00
parent a08fd88c80
commit 0b29857089
2 changed files with 76 additions and 6 deletions

View File

@@ -29,6 +29,9 @@ class OpcConnectionPool(SientiaMonitoring):
self.metadata = metadata or {}
self.clock = MonotonicClock()
self._connections: dict[str, tuple[Client, float]] = {}
# ponytail: one Lock per server_id, never cleaned up — server set is
# small and bounded. Add eviction of stale locks only if it grows.
self._locks: dict[str, asyncio.Lock] = {}
SientiaMonitoring.__init__(
self,
@@ -38,13 +41,28 @@ class OpcConnectionPool(SientiaMonitoring):
)
async def get(self, server_id: str, server_cfg: dict) -> Client:
"""Return the cached client for `server_id`, connecting one if needed."""
cached = self._connections.get(server_id)
if cached is not None:
client, _ = cached
self._connections[server_id] = (client, self.clock.elapsed())
return client
"""Return the cached client for `server_id`, connecting one if needed.
Double-checked under a per-server lock so concurrent callers for the
same new server share one connection (no leaked, untracked sessions)."""
client = self._cached(server_id)
if client is not None:
return client
async with self._locks.setdefault(server_id, asyncio.Lock()):
client = self._cached(server_id) # re-check: a racer may have connected
if client is not None:
return client
return await self._open(server_id, server_cfg)
def _cached(self, server_id: str) -> Client | None:
entry = self._connections.get(server_id)
if entry is None:
return None
client, _ = entry
self._connections[server_id] = (client, self.clock.elapsed())
return client
async def _open(self, server_id: str, server_cfg: dict) -> Client:
try:
client = await self._connect(server_cfg)
except Exception: