From e47947fb6926e884a670bcf5c54e86a4dd41c063 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 16 May 2026 03:52:45 +0900 Subject: [PATCH] fix(signal_v2): await cancelled poll_task + public cache_size Code review fixes: - main.py lifespan: await poll_task after cancel() to avoid client close racing with mid-fetch task (CRITICAL). - stock_client: add public cache_size() method; main.py /health uses it instead of private _cache attribute (IMPORTANT). 19 tests still pass. Deferred to Phase 7 backlog: - _ctx singleton test isolation (importlib.reload provides isolation in practice) - poll_loop interval floor (interval >= 60 by design) - shutdown logging - response schema validation Co-Authored-By: Claude Opus 4.7 (1M context) --- signal_v2/main.py | 6 +++++- signal_v2/stock_client.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/signal_v2/main.py b/signal_v2/main.py index e9281d7..6f7a75e 100644 --- a/signal_v2/main.py +++ b/signal_v2/main.py @@ -50,6 +50,10 @@ async def lifespan(app: FastAPI): await asyncio.wait_for(_ctx.poll_task, timeout=5.0) except asyncio.TimeoutError: _ctx.poll_task.cancel() + try: + await _ctx.poll_task + except asyncio.CancelledError: + pass if _ctx.client is not None: await _ctx.client.close() @@ -66,5 +70,5 @@ async def health(): "status": "online", "stock_api_url": settings.stock_api_url, "last_poll": state_mod.state.last_updated, - "cache_size": len(_ctx.client._cache) if _ctx.client is not None else 0, + "cache_size": _ctx.client.cache_size() if _ctx.client is not None else 0, } diff --git a/signal_v2/stock_client.py b/signal_v2/stock_client.py index bfe4428..668b62c 100644 --- a/signal_v2/stock_client.py +++ b/signal_v2/stock_client.py @@ -34,6 +34,10 @@ class StockClient: async def close(self) -> None: await self._client.aclose() + def cache_size(self) -> int: + """Number of cached endpoint responses (public surface for /health).""" + return len(self._cache) + async def get_portfolio(self) -> dict: return await self._cached_request( "portfolio", "GET", "/api/webai/portfolio"