ZhuLinsen/daily_stock_analysis · warning · TimeoutError

题材新闻搜索等待超过调用截止时间

Error message

题材新闻搜索等待超过调用截止时间

What it means

TimeoutError from the request-coalescing wait loop in the search cache: this caller lost the race to own the cache key, waited on the owner's event, and the overall caller-supplied deadline passed before either a cached result appeared or ownership became available. Checked before waiting (remaining <= 0) — so it can fire immediately if the deadline is already exhausted.

Source

Thrown at src/search_service.py:2754

        deadline: Optional[float] = None,
    ) -> Tuple[Optional['SearchResponse'], bool, Optional[threading.Event], bool]:
        """Return a cache hit or exclusive ownership of one cache fill.

        Waiters never proceed to provider work without becoming the owner. If
        an owner finishes without a cacheable response, all waiters compete for
        the next reservation and the losers keep waiting on that new owner.
        """
        waited = False
        while True:
            cached, cache_owner, cache_event = self._get_cached_or_reserve(key)
            if cached is not None or cache_owner:
                return cached, cache_owner, cache_event, waited
            if cache_event is None:  # Defensive: the reservation API promises an event here.
                raise RuntimeError("搜索缓存请求合并状态异常")
            waited = True
            remaining = None if deadline is None else deadline - time.monotonic()
            if remaining is not None and remaining <= 0:
                raise TimeoutError("题材新闻搜索等待超过调用截止时间")
            if remaining is None:
                cached = self._wait_for_cached(key, cache_event)
            else:
                cached = self._wait_for_cached(key, cache_event, timeout_seconds=remaining)
            if cached is not None:
                return cached, False, None, waited
            if deadline is not None and time.monotonic() >= deadline:
                raise TimeoutError("题材新闻搜索等待超过调用截止时间")

    def _put_cache(self, key: str, response: 'SearchResponse') -> None:
        """Store a successful SearchResponse in cache."""
        with self._cache_lock:
            # Hard cap: evict oldest entries when cache exceeds limit
            _MAX_CACHE_SIZE = 500
            if len(self._cache) >= _MAX_CACHE_SIZE:
                now = time.time()
                # First pass: remove expired entries
                expired = [k for k, (ts, _) in self._cache.items() if now - ts > self._cache_ttl]

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Increase or remove the caller's deadline so followers can wait for the in-flight result instead of timing out
  2. Retry the call — by then the owner has usually populated the cache and the retry returns instantly
  3. Warm the cache ahead of burst windows (prefetch the topic) so followers never wait
  4. If deadlines are strict by design, treat this as normal backpressure and degrade gracefully (skip topic news) rather than retrying
Defensive patterns

Strategy: retry

Validate before calling

def has_deadline_slack(deadline: float, min_seconds: float = 1.0) -> bool:
    return deadline is None or deadline - time.monotonic() >= min_seconds

Try / catch

try:
    resp = search_topic_news(topic, deadline=deadline)
except TimeoutError as exc:
    if "调用截止时间" in str(exc):
        resp = get_cached_topic_news(topic)  # usually populated by now; else degrade
    else:
        raise

Prevention

When it happens

Trigger: Two or more concurrent searches for the same topic key with a deadline: the loser blocks on the owner's cache event; if the owner takes longer than the loser's remaining deadline, the loser raises this instead of starting its own search. Also fires when a caller passes a deadline that is already in the past while the key is contended.

Common situations: Fan-out analysis requesting the same topic concurrently under tight per-request deadlines; a slow owner search stretching beyond followers' budgets; retry storms piling onto one key; deadlines computed from a shared request clock that has nearly expired.

Related errors


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