• v1.9.7 7b1c6f6772

    Fix preview pool permanently wedging after worker stderr pipe fills

    LeoVasanko released this 2026-08-11 04:57:20 +00:00 | 27 commits to main since this release

    Production symptom: previews of all types (pdf/pyvips/onlyoffice) start
    hitting the 10s timeout and never recover until server restart, while the
    rest of the server stays healthy.

    Root cause (reproduced on Python 3.12): workers were spawned with
    stderr=PIPE that nothing drained after startup. Once the OS pipe buffer
    filled from accumulated worker tracebacks and library warnings, asyncio
    flow control stopped the parent reading it and the worker blocked forever
    mid-request on a stderr write. The 10s timeout then fired, but
    _replace_worker hung forever in proc.wait() even after kill() — the
    flow-control-paused pipe transport never sees EOF — permanently wedging
    one dispatcher per stuck worker. Once all dispatchers were stuck, every
    preview request timed out. Restart cleared it.

    Fixes:

    • Spawn workers with inherited stderr (stderr=None) so worker diagnostics
      go straight to the server log and no undrained pipe can exist.
    • Bound proc.wait() in worker kill() with a 5s grace timeout so a wedged
      transport can never hang a dispatcher; log the worker pid instead.
    • Guard the dispatch loop with an outer exception handler so a dispatcher
      can never die silently and shrink pool capacity.
    • Retry failed worker replacement spawns with 1s-30s backoff instead of
      silently shrinking the pool.
    • Fix latent crash: except-tuple referenced msgspec.json.DecodeError,
      which does not exist in the installed msgspec; any protocol failure
      would itself raise AttributeError. Use msgspec.DecodeError.
    • Worker: redirect Python-level sys.stdout to stderr in persistent mode
      and keep the raw buffer solely for the binary protocol, so a library
      print() can never corrupt the command channel again (cf. the pymupdf
      deprecation warning that crashed workers at startup).
    • Worker: close the pymupdf document explicitly in process_pdf.
    • Log worker pid on timeout/protocol/checksum failures, and log failed
      kills and replacement retries, for future production diagnostics.

    Add tests/test_preview_pool.py with an end-to-end regression recreating
    the wedged-worker setup (piped, undrained stderr) plus kill-grace,
    respawn-retry and dispatcher-survival tests.

    Downloads