{"record":{"id":"04e84afdcc1fe946","repo":"ZhuLinsen/daily_stock_analysis","slug":"task-name-ranking-fetch-cooling-down-after-previ","errorCode":null,"errorMessage":"{task_name} ranking fetch cooling down after previous timeout","messagePattern":"(.+?) ranking fetch cooling down after previous timeout","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"warning","filePath":"src/services/market_hotspot_service.py","lineNumber":467,"sourceCode":"            ) 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\n                if retry_after > now:\n                    raise TimeoutError(\n                        f\"{task_name} ranking fetch cooling down after previous timeout\"\n                    )\n                cls._ranking_fetch_retry_after.pop(inflight_key, None)\n                if cls._ranking_fetch_futures.get(inflight_key) is retry_future:\n                    cls._ranking_fetch_futures.pop(inflight_key, None)\n                    if retry_future.done() or retry_future.cancelled():\n                        cls._ranking_fetch_slots.release()\n                    else:\n                        cls._ranking_fetch_detached_futures.add(retry_future)\n\n            current = cls._ranking_fetch_futures.get(inflight_key)\n            if current is not None:\n                if not current.done():\n                    return current\n                cls._ranking_fetch_futures.pop(inflight_key, None)\n                cls._ranking_fetch_slots.release()\n\n            if not cls._ranking_fetch_slots.acquire(blocking=False):","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/market_hotspot_service.py#L449-L485","documentation":"TimeoutError(f'{task_name} ranking fetch cooling down after previous timeout') is raised by MarketHotspotService._get_or_submit_ranking_fetch when an inflight key is still inside its cooldown window (retry_after > now) after a previous timeout. It is a deliberate circuit-breaker: calls made too soon after a timeout fail fast instead of stacking new requests against a slow source.","triggerScenarios":"Calling the same ranking fetch (same inflight_key) again within the cooldown period — max(previous timeout value, RANKING_FETCH_TIMEOUT_RETRY_DELAY_SECONDS=0.2s) — after that key previously hit 'ranking fetch timeout after ...s'. Higher configured timeouts mean longer cooldowns.","commonSituations":"Retry loops that immediately re-invoke after a timeout (no backoff), a frontend polling interval shorter than the cooldown, or multiple concurrent requests for the same ranking that all funnel into one timed-out key and then fail fast in quick succession.","solutions":["Back off and retry after the cooldown: sleep/retry with a delay >= max(timeout_value, 0.2s) before calling again","Catch TimeoutError at the call site and serve cached/last-known ranking data during cooldown","If cooldowns are too aggressive relative to your polling cadence, lower the configured timeout or re-evaluate the retry delay constant","Ensure only one scheduler (not several) triggers the same ranking fetch to avoid overlapping retry storms"],"exampleFix":"# before: immediate retry hammers the circuit breaker\ntry:\n    data = fetch_ranking()\nexcept TimeoutError:\n    data = fetch_ranking()  # raises 'cooling down'\n\n# after: back off past the cooldown window\ntry:\n    data = fetch_ranking()\nexcept TimeoutError:\n    time.sleep(0.5)\n    data = fetch_ranking()","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    data = fetch_ranking(key)\nexcept TimeoutError as exc:\n    if \"cooling down\" in str(exc):\n        time.sleep(max(0.2, configured_timeout))  # past cooldown window\n        data = fetch_ranking(key)","preventionTips":["Always back off at least max(timeout, 0.2s) after a timeout before retrying","Poll less frequently than the cooldown window","Serve cached data during cooldown instead of failing the request"],"tags":["timeout","circuit-breaker","retry","market-data","concurrency"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}