Skip to content

Pool workers exit with the server; shutdown stops the scan pool - #477

Merged
jeffcrouse merged 2 commits into
mainfrom
fix/workers-exit-with-parent
Nov 10, 2026
Merged

jeffcrouse merged 2 commits into
mainfrom
fix/workers-exit-with-parent

Conversation

@jeffcrouse

Copy link
Copy Markdown
Member

The leak

Four Python processes, each orphaned to PID 1, held test databases open for a day. They blocked recreating familiar_test (#474). They came from E2E servers stopped with pkill (SIGTERM).

Reproduced:

  1. Start a server on a scratch DB and run a sync over the fixture audio.
  2. SIGTERM it. The server logs a clean shutdown.
  3. The library-scan pool's worker survives, re-parented to PID 1 with its Postgres connection open. The per-track analysis workers do exit.

Why:

  • An idle multiprocessing pool worker waits on its task queue, and it holds a write end of that queue itself, so it never sees EOF when the parent dies. It leaves only if the pool sends it a stop message.
  • The scan pool (library_sync._get_scan_executor) is a module singleton whose worker outlives each sync. BackgroundManager.shutdown never stopped it; only an atexit shutdown(wait=False) did, and the worker never received that stop.
  • A crash or SIGKILL never sends a stop message at all.

Who else it affects: Familiar Server stops and restarts the Python server with SIGTERM, then SIGKILL after 15 s (ServerController.stopServer). So a Mac install probably gained an orphan holding a Postgres connection on every stop or restart after a sync. I haven't checked this on a real Familiar Server install. Docker is unaffected, because stopping a container ends every process in it.

Fix

  • process_setup.exit_with_parent: a daemon thread in every pool worker calls os._exit once its parent PID changes, checking once a second. macOS has no PR_SET_PDEATHSIG. Both scan_worker_init and analysis_worker_init start it, so this holds however the server dies.
  • BackgroundManager.shutdown now also stops the scan pool.

Tests

  • tests/test_workers_exit_with_parent.py, run on the real scan pool and on the analysis initialiser: each takes a task, then the parent SIGKILLs itself. The worker must be gone within 10 s.
    • Against the previous code, both fail with "worker … outlived its killed parent". The test cleans up any orphan it finds.
    • On this branch, both pass in about 2.5 s.
  • The original scenario: a real server, synced, then SIGTERMed. No children and no database connections remain.
  • Full backend suite: 2,627 passed, 45 skipped. Ruff, mypy and the profile lint are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UpUascYWiPxuH2vtav72eV

jeffcrouse and others added 2 commits November 10, 2026 10:38
…an pool

An idle multiprocessing pool worker holds a write end of its own task queue,
so it never sees EOF when its parent dies; it leaves only on the pool's stop
message. The library-scan pool's worker outlives each sync, and nothing
stopped that pool at shutdown (only an atexit handler, which did not deliver
the message): after a SIGTERM it was re-parented to PID 1 with its database
connection open. Four such orphans held test databases for a day. Familiar
Server stops and restarts the server the same way.

- process_setup.exit_with_parent: a daemon thread in every pool worker
  _exits once its parent PID changes (macOS has no PR_SET_PDEATHSIG).
  scan_worker_init and analysis_worker_init both start it.
- BackgroundManager.shutdown stops the scan pool too.
- tests/test_workers_exit_with_parent.py: the real scan pool and the
  analysis initialiser each take a task, the parent is SIGKILLed, the worker
  must be gone within 10 s. Against the previous code both fail ("worker
  outlived its killed parent"). A real server synced then SIGTERMed leaves no
  child and no database connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpUascYWiPxuH2vtav72eV
@jeffcrouse
jeffcrouse merged commit 46c8d3e into main Nov 10, 2026
16 checks passed
@jeffcrouse
jeffcrouse deleted the fix/workers-exit-with-parent branch November 10, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant