Skip to content

Commit c51d824

Browse files
happycubeclaude
andcommitted
parallel: fix Ctrl-C hang once the worker pool is running
A terminal Ctrl-C sends SIGINT to the whole foreground process group, so each ProcessPoolExecutor worker gets it too. A KeyboardInterrupt raised inside a worker's compiled demod (or while it holds a multiprocessing queue lock) wedged the worker in futex_wait; the parent, after writing the .tbc.db cleanly, then hung in do_wait joining that worker at exit. Two changes: - _demod_worker_init sets SIGINT to SIG_IGN, so only the main process handles the interrupt and workers never die mid-task holding a lock. - DemodBlockCache.close() terminate()s the pool's worker processes before shutdown(wait=False), so the interpreter's atexit join doesn't block on a worker still in a long demod. Their results are no longer needed. Reproduced with `ld-decode testdata/ntsc/ve-monitor.ldf mon -t 8` plus a group SIGINT ~15-24s in: before, the parent hung indefinitely (do_wait on futex-stuck workers); after, it exits ~0.5s post-INT at every interrupt point tried (11/13/15/18/20/24s) with an integrity-ok .tbc.db. A normal uninterrupted -t8 decode still completes and exits 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a23b2a3 commit c51d824

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

lddecode/parallel.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import copy
3232
import multiprocessing
33+
import signal
3334
import threading
3435
from concurrent.futures import Future, ProcessPoolExecutor, ThreadPoolExecutor
3536

@@ -47,6 +48,14 @@ def _demod_worker_init(rf_opts, decoder_params, field_cfg=None):
4748
import os
4849
import time as _time
4950

51+
# A terminal Ctrl-C delivers SIGINT to the whole foreground process
52+
# group, workers included. A KeyboardInterrupt raised inside a
53+
# worker's compiled demod - or while it holds a multiprocessing queue
54+
# lock - wedges the worker (futex_wait) and the parent then hangs
55+
# joining it at exit. Ignore SIGINT here so only the main process
56+
# handles the interrupt; it tears the pool down (see DemodBlockCache).
57+
signal.signal(signal.SIGINT, signal.SIG_IGN)
58+
5059
from . import utils_logging as logs
5160
from .rfdecode import RFDecode
5261

@@ -570,6 +579,15 @@ def close(self):
570579
self.flush()
571580
self._pool.shutdown(wait=False, cancel_futures=True)
572581
if self._procs is not None:
582+
# shutdown(wait=False) leaves the interpreter's atexit handler
583+
# to join the workers (wait=True); one still in a long compiled
584+
# demod would block that join. Their results are no longer
585+
# needed, so terminate them outright for a prompt exit.
586+
for p in list(getattr(self._procs, "_processes", {}).values()):
587+
try:
588+
p.terminate()
589+
except Exception:
590+
pass
573591
self._procs.shutdown(wait=False, cancel_futures=True)
574592
self._procs = None
575593

0 commit comments

Comments
 (0)