ZhuLinsen/daily_stock_analysis · error · RuntimeError

FastAPI 服务在 {timeout_seconds:.1f}s 内未完成启动: {host}:{port}

Error message

FastAPI 服务在 {timeout_seconds:.1f}s 内未完成启动: {host}:{port}

What it means

The uvicorn thread is still alive after the 3-second deadline but uvicorn_server.started never became True — startup is proceeding but too slowly. The fixed 3.0s timeout is short for cold starts: first-time imports of the FastAPI app (pandas, LLM SDKs), lifespan hooks loading indices or connecting to external services can exceed it on slow disks or constrained CI runners.

Source

Thrown at main.py:1274

            raise RuntimeError(
                f"FastAPI server failed to start: {host}:{port}; {startup_error[0]}"
            )
        if uvicorn_server.started:
            logger.info(f"FastAPI 服务已启动: http://{host}:{port}")
            return
        if not thread.is_alive():
            break
        time.sleep(0.05)

    if startup_error:
        raise RuntimeError(f"FastAPI server failed to start: {host}:{port}; {startup_error[0]}")
    if uvicorn_server.started:
        logger.info(f"FastAPI 服务已启动: http://{host}:{port}")
        return
    if not thread.is_alive():
        raise RuntimeError(f"FastAPI 服务器启动后立即退出: {host}:{port}")

    raise RuntimeError(f"FastAPI 服务在 {timeout_seconds:.1f}s 内未完成启动: {host}:{port}")


def _is_truthy_env(var_name: str, default: str = "true") -> bool:
    """Parse common truthy / falsy environment values."""
    value = os.getenv(var_name, default).strip().lower()
    return value not in {"0", "false", "no", "off"}


def start_bot_stream_clients(config: Config) -> None:
    """Start bot stream clients when enabled in config."""
    # 启动钉钉 Stream 客户端
    if config.dingtalk_stream_enabled:
        try:
            from bot.platforms import start_dingtalk_stream_background, DINGTALK_STREAM_AVAILABLE
            if DINGTALK_STREAM_AVAILABLE:
                if start_dingtalk_stream_background():
                    logger.info("[Main] Dingtalk Stream client started in background.")
                else:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Confirm the server eventually starts by running uvicorn server:app directly — if it comes up in e.g. 8s, raise timeout_seconds in main.py accordingly (or make it configurable).
  2. Speed up app import: pre-warm imports in the main thread before spawning the server thread (imports are then cached).
  3. Move slow lifespan work (index loads, network calls) to background tasks so started flips quickly.
  4. On CI, use a longer timeout or a readiness poll against /health instead of this fixed window.

Example fix

# before
    timeout_seconds = 3.0

# after
    timeout_seconds = float(os.getenv("FASTAPI_STARTUP_TIMEOUT", "10"))
Defensive patterns

Strategy: retry

Validate before calling

import time, urllib.request

# pre-warm imports in the main thread so the server thread starts fast
import server  # noqa: F401  (caches heavy modules)
time.sleep(0)  # optional: let page cache warm on cold hosts

Try / catch

for timeout in (3.0, 15.0):
    try:
        run_fastapi_with_timeout(host, port, config, timeout)
        break
    except RuntimeError as e:
        if "未完成启动" not in str(e):
            raise
else:
    raise  # still slow: profile app import / lifespan hooks

Prevention

When it happens

Trigger: Heavy import chain (src/, data_provider, pandas) taking >3s on first run; lifespan startup doing network I/O (stock index refresh, DB connect) with no cache; machine under load (CI runner, low-CPU container); cold page cache after reboot.

Common situations: First --serve invocation in Docker where dependencies are imported lazily; CI smoke tests starting the server on shared runners; slow NFS/network filesystems holding the venv.

Understand the failure class

Related errors


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