ZhuLinsen/daily_stock_analysis · warning · TimeoutError

{task_name} ranking fetch cooling down after previous timeou

Error message

{task_name} ranking fetch cooling down after previous timeout

What it means

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.

Source

Thrown at src/services/market_hotspot_service.py:467

            ) from exc

    @classmethod
    def _get_or_submit_ranking_fetch(
        cls,
        task: Callable[[], Any],
        *,
        inflight_key: Hashable,
        task_name: str,
    ) -> Future:
        submitted: Future
        worker: threading.Thread
        with cls._ranking_fetch_futures_lock:
            retry_entry = cls._ranking_fetch_retry_after.get(inflight_key)
            now = time.monotonic()
            if retry_entry is not None:
                retry_future, retry_after = retry_entry
                if retry_after > now:
                    raise TimeoutError(
                        f"{task_name} ranking fetch cooling down after previous timeout"
                    )
                cls._ranking_fetch_retry_after.pop(inflight_key, None)
                if cls._ranking_fetch_futures.get(inflight_key) is retry_future:
                    cls._ranking_fetch_futures.pop(inflight_key, None)
                    if retry_future.done() or retry_future.cancelled():
                        cls._ranking_fetch_slots.release()
                    else:
                        cls._ranking_fetch_detached_futures.add(retry_future)

            current = cls._ranking_fetch_futures.get(inflight_key)
            if current is not None:
                if not current.done():
                    return current
                cls._ranking_fetch_futures.pop(inflight_key, None)
                cls._ranking_fetch_slots.release()

            if not cls._ranking_fetch_slots.acquire(blocking=False):

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Back off and retry after the cooldown: sleep/retry with a delay >= max(timeout_value, 0.2s) before calling again
  2. Catch TimeoutError at the call site and serve cached/last-known ranking data during cooldown
  3. If cooldowns are too aggressive relative to your polling cadence, lower the configured timeout or re-evaluate the retry delay constant
  4. Ensure only one scheduler (not several) triggers the same ranking fetch to avoid overlapping retry storms

Example fix

# before: immediate retry hammers the circuit breaker
try:
    data = fetch_ranking()
except TimeoutError:
    data = fetch_ranking()  # raises 'cooling down'

# after: back off past the cooldown window
try:
    data = fetch_ranking()
except TimeoutError:
    time.sleep(0.5)
    data = fetch_ranking()
Defensive patterns

Strategy: retry

Try / catch

try:
    data = fetch_ranking(key)
except TimeoutError as exc:
    if "cooling down" in str(exc):
        time.sleep(max(0.2, configured_timeout))  # past cooldown window
        data = fetch_ranking(key)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/04e84afdcc1fe946. Report an issue: GitHub.