{"record":{"id":"a4c1e0b09620e976","repo":"ZhuLinsen/daily_stock_analysis","slug":"call-name-wait-seconds-g-s","errorCode":null,"errorMessage":"{call_name} 调用超过 {wait_seconds:g}s，已放弃等待","messagePattern":"(.+?) 调用超过 (.+?)s，已放弃等待","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"data_provider/akshare_fetcher.py","lineNumber":350,"sourceCode":"    wait_seconds = _AKSHARE_HISTORY_CALL_TIMEOUT if timeout is None else float(timeout)\n\n    multiprocessing.freeze_support()\n    ctx = multiprocessing.get_context(_AKSHARE_TIMEOUT_PROCESS_START_METHOD)\n    parent_conn, child_conn = ctx.Pipe(duplex=False)\n    process = ctx.Process(\n        target=_akshare_timeout_worker,\n        args=(child_conn, func, args, kwargs),\n        name=f\"akshare-{call_name}\",\n        daemon=True,\n    )\n\n    process.start()\n    child_conn.close()\n\n    try:\n        if not parent_conn.poll(wait_seconds):\n            _terminate_akshare_process(process)\n            raise TimeoutError(f\"{call_name} 调用超过 {wait_seconds:g}s，已放弃等待\")\n\n        try:\n            ok, value = parent_conn.recv()\n        except EOFError as exc:\n            raise RuntimeError(f\"{call_name} 调用进程未返回结果\") from exc\n    finally:\n        parent_conn.close()\n        process.join(_AKSHARE_TIMEOUT_PROCESS_JOIN_GRACE)\n        _terminate_akshare_process(process)\n\n    if ok:\n        return value\n    raise value\n\n\ndef _akshare_timeout_worker(conn, func, args, kwargs) -> None:\n    try:\n        conn.send((True, func(*args, **kwargs)))","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/akshare_fetcher.py#L332-L368","documentation":"TimeoutError raised by AkshareFetcher's subprocess watchdog: each akshare call runs in a dedicated child process (multiprocessing with a pipe), and if the child does not produce a result within wait_seconds, the parent gives up, terminates the child, and raises this error. It exists because akshare's network calls can hang indefinitely with no internal timeout.","triggerScenarios":"Any AkshareFetcher fetch path wrapped by the timeout worker (A-share/ETF/HK history via akshare) where the akshare HTTP call stalls longer than the configured wait_seconds — e.g. slow or unresponsive Eastmoney/Sina endpoints, network black-holing, or DNS hang.","commonSituations":"Running batch analysis for many stocks during peak hours when the data source is slow; the deployment machine has restricted egress so TCP connections hang instead of failing fast; an akshare version whose underlying endpoint changed and now stalls.","solutions":["Retry the request — transient upstream slowness is the most common cause; the fetcher design already isolates each call in a fresh process so a retry is safe.","Raise the timeout (wait_seconds / akshare timeout configuration) if the data source is legitimately slow.","Check egress connectivity to akshare's hosts (eastmoney, sina) from the deployment host.","Upgrade/downgrade akshare if an endpoint regression makes calls hang (check akshare changelog).","In multi-source setups, let the provider fallback chain route to the next fetcher (e.g. yfinance) on this TimeoutError."],"exampleFix":"# before\nak.stock_zh_a_hist(symbol='600519', period='daily')  # may hang forever\n\n# after (pattern the fetcher itself uses)\nfrom data_provider.akshare_fetcher import AkshareFetcher\nAkshareFetcher().fetch_stock_data('600519', '2024-01-01', '2024-12-31')  # bounded by subprocess watchdog","handlingStrategy":"retry","validationCode":"# cheap reachability check before a long fetch\nimport requests\nrequests.head('https://push2his.eastmoney.com', timeout=5)  # raises fast if egress is dead","typeGuard":"def is_akshare_timeout(exc: Exception) -> bool:\n    return isinstance(exc, TimeoutError) and '调用超过' in str(exc)","tryCatchPattern":"for attempt in range(2):\n    try:\n        df = fetcher.fetch_stock_data(code, start, end)\n        break\n    except TimeoutError as e:\n        if attempt == 1:\n            df = fallback_fetcher.fetch_stock_data(code, start, end)  # e.g. yfinance","preventionTips":["Treat akshare calls as untrusted latency: always run them under the fetcher's watchdog or your own timeout.","Cache fetched DataFrames to disk so timeouts during batch runs resume without refetching everything.","Configure the provider fallback chain so akshare timeouts degrade to the next source instead of failing the analysis."],"tags":["akshare","timeout","network","data-provider","python"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}