{"record":{"id":"09e41f7a66eb75a1","repo":"ZhuLinsen/daily_stock_analysis","slug":"error-09e41f","errorCode":null,"errorMessage":"题材新闻搜索并发已满，请稍后重试","messagePattern":"题材新闻搜索并发已满，请稍后重试","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"warning","filePath":"src/search_service.py","lineNumber":106,"sourceCode":"            except BaseException:\n                pass\n    finally:\n        conn.close()\n\n\ndef _call_topic_news_in_subprocess(\n    *,\n    constructor_kwargs: Dict[str, Any],\n    topic: str,\n    max_results: int,\n    focus_keywords: Optional[List[str]],\n    timeout_seconds: float,\n    deadline: Optional[float] = None,\n) -> \"SearchResponse\":\n    \"\"\"Execute a topic-news provider chain with a hard, process-level deadline.\"\"\"\n    wait_seconds = max(0.01, float(timeout_seconds))\n    if not _SEARCH_TIMEOUT_WORKER_SLOTS.acquire(blocking=False):\n        raise RuntimeError(\"题材新闻搜索并发已满，请稍后重试\")\n\n    process: Any = None\n    process_started = False\n    parent_conn: Any = None\n    child_conn: Any = None\n    try:\n        try:\n            multiprocessing.freeze_support()\n            ctx = multiprocessing.get_context(_SEARCH_TIMEOUT_PROCESS_START_METHOD)\n            parent_conn, child_conn = ctx.Pipe(duplex=False)\n            process = ctx.Process(\n                target=_search_topic_news_process_worker,\n                args=(\n                    child_conn,\n                    constructor_kwargs,\n                    topic,\n                    max_results,\n                    focus_keywords,","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/search_service.py#L88-L124","documentation":"RuntimeError from _call_topic_news_in_subprocess when the module-level _SEARCH_TIMEOUT_WORKER_SLOTS semaphore is exhausted before a new topic-news subprocess can start. It is an admission-control failure: concurrent topic-news searches are capped process-wide, and non-blocking acquire failing means the cap is already busy. Backpressure, not a data error.","triggerScenarios":"Issuing more concurrent topic/sector news searches than the configured slot count (acquire(blocking=False) fails), e.g. a burst of market-review requests each spawning a topic-news subprocess while earlier ones are still within their timeout window.","commonSituations":"Batch analysis of many topics in parallel; long provider timeouts keeping slots occupied; a previous search subprocess hung near its deadline; load tests or schedulers overlapping with user-triggered searches.","solutions":["Retry after a short delay — slots free up as in-flight searches finish or time out","Reduce client-side concurrency (limit parallel topic searches to the slot count)","Tune the search timeout down so stuck subprocesses release slots sooner","If this recurs at steady load, raise the slot cap in configuration or scale horizontally"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"from src.search_service import _SEARCH_TIMEOUT_WORKER_SLOTS\n\ndef search_slot_available() -> bool:\n    return _SEARCH_TIMEOUT_WORKER_SLOTS._value > 0  # advisory only, racy by nature","typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        resp = search_topic_news(topic)\n        break\n    except RuntimeError as exc:\n        if \"并发已满\" in str(exc) and attempt < 2:\n            time.sleep(0.5 * (attempt + 1))\n            continue\n        raise","preventionTips":["Cap your own parallel topic searches below the slot limit","Add jitter/backoff to bursty batch jobs that fan out topic searches","Tune search timeouts so slots cannot be held by hung workers"],"tags":["concurrency","search","backpressure","retry"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}