{"record":{"id":"512108040b26a28c","repo":"ZhuLinsen/daily_stock_analysis","slug":"task-name-ranking-fetch-timeout-after-timeout-v","errorCode":null,"errorMessage":"{task_name} ranking fetch timeout after {timeout_value:g}s","messagePattern":"(.+?) ranking fetch timeout after (.+?)s","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"warning","filePath":"src/services/market_hotspot_service.py","lineNumber":447,"sourceCode":"\n        effective_inflight_key = inflight_key or task_name\n        future = cls._get_or_submit_ranking_fetch(\n            task,\n            inflight_key=effective_inflight_key,\n            task_name=task_name,\n        )\n        try:\n            return future.result(timeout=timeout_value)\n        except FutureTimeoutError as exc:\n            if future.done() and future.exception(timeout=0) is exc:\n                raise\n            cls._mark_ranking_fetch_timeout(\n                effective_inflight_key,\n                future,\n                retry_after=time.monotonic()\n                + max(timeout_value, RANKING_FETCH_TIMEOUT_RETRY_DELAY_SECONDS),\n            )\n            raise TimeoutError(\n                f\"{task_name} ranking fetch timeout after {timeout_value:g}s\"\n            ) from exc\n\n    @classmethod\n    def _get_or_submit_ranking_fetch(\n        cls,\n        task: Callable[[], Any],\n        *,\n        inflight_key: Hashable,\n        task_name: str,\n    ) -> Future:\n        submitted: Future\n        worker: threading.Thread\n        with cls._ranking_fetch_futures_lock:\n            retry_entry = cls._ranking_fetch_retry_after.get(inflight_key)\n            now = time.monotonic()\n            if retry_entry is not None:\n                retry_future, retry_after = retry_entry","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/market_hotspot_service.py#L429-L465","documentation":"TimeoutError(f'{task_name} ranking fetch timeout after {timeout_value:g}s') is raised in MarketHotspotService._call_with_timeout when future.result(timeout=timeout_value) raises FutureTimeoutError — i.e. the ranking fetch worker thread did not complete within the allotted wall-clock seconds, and the future itself did not already contain that exception. The inflight key is then put into a cooldown (retry_after = now + max(timeout, RANKING_FETCH_TIMEOUT_RETRY_DELAY_SECONDS)) so repeated calls back off instead of hammering the slow source.","triggerScenarios":"A ranking data source (e.g. EastMoney/THS ranking endpoints behind MarketHotspotService) responding slower than the configured timeout; network degradation; the source hanging without reading the socket; or the timeout set lower than the source's typical latency.","commonSituations":"Upstream ranking API slowdowns during peak trading hours, cross-border network latency, a stale in-flight future from an earlier request still holding the slot, or timeouts tuned too tight after a provider change.","solutions":["Measure the actual source latency with curl -w '%{time_total}' against the ranking endpoint and set the timeout to ~2-3x that","Retry after the cooldown window (RANKING_FETCH_TIMEOUT_RETRY_DELAY_SECONDS = 0.2s minimum) rather than immediately re-calling","Check network egress/proxy health if all task_names time out simultaneously","If only one task_name fails, that specific data provider is degraded — rely on the service's per-source fallback instead of raising the global timeout","Look for leaked in-flight futures (detached futures never completing) if timeouts persist even after the source recovers"],"exampleFix":"# before\nresult = svc._call_with_timeout(fetch, timeout_seconds=1.5, task_name=\"eastmoney\")\n\n# after: sized to observed p99 latency\nresult = svc._call_with_timeout(fetch, timeout_seconds=6.0, task_name=\"eastmoney\")","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    data = MarketHotspotService._call_with_timeout(fetch, timeout_seconds=6.0, task_name=name)\nexcept TimeoutError as exc:\n    if \"after\" in str(exc):\n        logger.warning(\"%s slow; cooling down, will retry later\", name)\n        data = cached_ranking(name)  # fallback to last good data","preventionTips":["Size timeouts from measured p99 latency of each ranking provider","Keep a last-known-good cache of ranking data for timeout fallback","Respect the built-in cooldown instead of hammering retries"],"tags":["timeout","network","market-data","concurrency","retry"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}