Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coverage Ledger — Python: Language Semantics, Standard Library, Process APIs, and Subprocesses (G4 schema)

Chapter: ./python.md — Section G, process runtime. This ledger inventories every documented API and behavior claim in the chapter as one row each, ordered by chapter section (§1 syntax/semantics behavior rows first, then §2–§6 API/behavior rows in order of first appearance).

Scope. CPython 3.15.0, the current stable release per python.org/downloads (“Download Python 3.15.0”; the previous line 3.14.8 confirmed at docs.python.org/3.14/). Every signature was verified verbatim against the docs.python.org/3 library/reference pages as served at retrieval (2026-10-13; docs.python.org/3 serves the 3.15.0 documentation). CPython-specific behavioral claims are anchored to those pages and, where a numeric fact exists only in the implementation, to the CPython source tree (github.com/python/cpython main: Python/pylifecycle.c, Lib/subprocess.py, Lib/threading.py). PEPs 475/446/442 (peps.python.org) are cited as normative references for EINTR retry, fd inheritance, and finalization. Platform layer: Linux primary; POSIX availability markers as documented; Windows deltas labeled per row. No source was unreachable at retrieval time — there are no source-limited rows.

Status definitions.

  • documented — the claim is documented in ./python.md and anchored to a fetched authoritative source (docs.python.org page, CPython source file, or PEP) exactly as the chapter cites it.
  • source-limited — documented but primary wording unreachable at retrieval time. Unused here (Remaining: 0).
  • not-started / researched — defined by the repo ledger convention; unused in this ledger.

Rollup: Inventoried: 80 / Documented: 80 / Remaining: 0 / Source-limited: 0

Section layout: §1 → PL-PY-001–012 · §2 → PL-PY-013–031 · §3 → PL-PY-032–040 · §4 → PL-PY-041–049 · §5 → PL-PY-050–071 · §6 → PL-PY-072–080.

IDLanguage/API or behaviorCanonical signatureDocumentation pathAuthoritative referenceVersion/platform scopeStatus
PL-PY-001§1.1 Name→object binding model; assignment rebinds, never copies; fully specified evaluation order: left to right, RHS of assignment before LHS; short-circuit and/or/conditional are the only operand-skipping forms (no unsequenced-evaluation UB class)—./python.mdPython reference, Expressions §6.16 “Evaluation order” + §4.2.1 Binding of names, docs.python.org/3/reference/expressions.html, /3/reference/executionmodel.html (3.15.0 docs)Python language (all 3.x)documented
PL-PY-002§1.2 Scope resolution: nearest enclosing scope (local → enclosing functions → global → builtins); NameError/UnboundLocalError semantics; block-static binding determined by scanning the block; global/nonlocal; runtime resolution of free variables—./python.mdPython reference, Execution model §4.2.2 “Resolution of names”, docs.python.org/3/reference/executionmodel.htmlPython language (all 3.x)documented
PL-PY-003§1.3 Object lifetime: CPython deterministic refcount destruction + generational cycle GC; PEP 442 (3.4+) safe finalization of cycles, __del__ at most once; language guarantee limited: no __del__ timing or shutdown-invocation guarantee; gc.garbage empty-by-default era—./python.mdData model §3.1.13 object.__del__, docs.python.org/3/reference/datamodel.html; PEP 442 (peps.python.org); gc module docs[language] weak guarantee + [CPython] refcounting/cycle GC; PEP 442 → 3.4+documented
PL-PY-004§1.4 BaseException vs Exception split: SystemExit, KeyboardInterrupt, GeneratorExit inherit BaseException so broad except Exception: cannot swallow exit/interrupt requests—./python.mdExceptions docs (BaseException hierarchy; SystemExit, KeyboardInterrupt), docs.python.org/3/library/exceptions.htmlPython language (all 3.x)documented
PL-PY-005§1.5 try/except/else/finally: finally runs on every exit path incl. SystemExit; exception/return in finally replaces the in-flight exception (chained via __context__); with calls __exit__ on every path, return-true suppresses—./python.mdPython reference (try statement) + Exceptions docs + Data model (context managers), docs.python.org/3Python language (all 3.x)documented
PL-PY-006§1.6 Exception chaining: implicit __context__ set when raising during handling; raise X from Y sets __cause__ + __suppress_context__; default traceback renders both chains; __traceback__ carried on every exceptionBaseException.__context__ / BaseException.__cause__ / BaseException.__suppress_context__ / BaseException.__traceback__./python.mdExceptions docs, docs.python.org/3/library/exceptions.html (exception chaining section)Python language (3.0+ chaining)documented
PL-PY-007§1.7 Generator cleanup: close() raises GeneratorExit at the suspension point; finally in the generator runs; yielding again after GeneratorExit → RuntimeError; aclose() async analogue; unawaited-coroutine warninggenerator.close() / coroutine.aclose()./python.mdData model (coroutine/generator close semantics), docs.python.org/3/reference/datamodel.htmlPython language (2.5+ generators; 3.5+ async)documented
PL-PY-008§1.8 Normal vs exceptional termination vocabulary: script end → shutdown/status 0; sys.exit → shutdown/status=arg; uncaught exception → traceback + status 1; uncaught SystemExit → no traceback; os._exit/os.abort/external signal → no Python shutdown—./python.mdData model + sys/exceptions docs (this chapter’s §1.8 table)Python language (all 3.x)documented
PL-PY-009§1.9 In-scope unspecified areas: __del__ timing; no defined memory model (GIL is implementation); string interning unspecified except sys.intern(); handler-vs-bytecode interleaving unspecified; shutdown destruction order beyond the underscore rule—./python.mdPython reference (data model, execution model) read as specification-silence; chapter §1.9Python language (all 3.x); specifics [CPython]documented
PL-PY-010§1.10 Version deltas 3.9→3.15 in scope: 3.9 group/user/extra_groups/umask; 3.11 process_group + exception groups; 3.12 threaded-fork DeprecationWarning + subinterpreter RuntimeError; 3.15 event-driven Popen.wait timeout (Linux ≥ 5.3 pidfd, macOS/BSD kqueue); free-threaded build makes delivery/interleaving claims build-dependent—./python.mddocs.python.org/3 library versionchanged/versionadded notes (subprocess, os, signal, threading)CPython 3.9 → 3.15.0documented
PL-PY-011§1.11 Misconceptions corrected: sys.exit is catchable (SystemExit); GC is not deterministic for cycles/__del__; finally runs for SystemExit but not under os._exit and not in killed daemon threads; shell=False passes metacharacters safely; negative returncode = killed-by-signal—./python.mdsys/exceptions/subprocess/threading docs (docs.python.org/3)Python language + CPython (all in-scope versions)documented
PL-PY-012§1.5 worked example: Guard.__exit__ runs on sys.exit(0) (prints enter/exit), SystemExit not suppressed with return False → status 0__exit__(self, exc_type, exc, tb) -> bool./python.mdData model (with statement context managers), docs.python.org/3/reference/datamodel.htmlPython language (2.6+ with)documented
PL-PY-013sys.exit: raises SystemExit to signal intent; int arg → status (0 = success); None → 0; other object → printed to stderr + status 1; cleanup honored; catchablesys.exit(arg=0)./python.mdsys docs, docs.python.org/3/library/sys.html#sys.exit (3.15.0, retrieved 2026-10-13)Python 3.x, all platformsdocumented
PL-PY-014SystemExit: inherits BaseException not Exception; unhandled → interpreter exits, no traceback; constructor arg = sys.exit arg; .code attributeSystemExit(code=0) (exception class)./python.mdExceptions docs, docs.python.org/3/library/exceptions.html#SystemExitPython 3.x, all platformsdocumented
PL-PY-015os._exit: exit process with status n without cleanup handlers, stdio flush, etc.; docs: normally only used in child after fork()os._exit(n, /)./python.mdos docs, docs.python.org/3/library/os.html#os._exitPython 3.x, all platforms (POSIX semantics per OS)documented
PL-PY-016os.abort: generates SIGABRT to current process; Unix default → core dump, Windows → exit code 3; does NOT call a Python SIGABRT handler; bypasses Python shutdownos.abort()./python.mdos docs, docs.python.org/3/library/os.html#os.abortPython 3.x; Unix core-dump / Windows exit-3 deltadocumented
PL-PY-017atexit.register: handler runs at interpreter shutdown in reverse (LIFO) order; same func registerable repeatedly; returns func (decorator)atexit.register(func, *args, **kwargs)./python.mdatexit docs, docs.python.org/3/library/atexit.html#atexit.registerPython 3.x, all platformsdocumented
PL-PY-018atexit.unregister: removes all ==-equal registrations; silent no-op if absent; registering/unregistering from within a running cleanup function is documented undefinedatexit.unregister(func)./python.mdatexit docs, docs.python.org/3/library/atexit.html#atexit.unregisterPython 3.x, all platformsdocumented
PL-PY-019signal.signal: set handler (2-arg callable or SIG_IGN/SIG_DFL), returns previous; main-thread-of-main-interpreter only (else ValueError); Windows limited to SIGABRT/SIGFPE/SIGILL/SIGINT/SIGSEGV/SIGTERM/SIGBREAKsignal.signal(signalnum, handler)./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.signalPython 3.x; Windows signal set deltadocumented
PL-PY-020signal.getsignal: return current handler — callable, SIG_IGN, SIG_DFL, or None (not installed from Python)signal.getsignal(signalnum)./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.getsignalPython 3.x, all platformsdocumented
PL-PY-021signal.set_wakeup_fd: writes signal number as one byte on each caught signal; fd must be non-blocking; -1 disables; returns old fd; main-thread-only; warn_on_full_buffer=True (3.8+) warns on dropped bytessignal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.set_wakeup_fdPython 3.x; warn_on_full_buffer 3.8+documented
PL-PY-022signal.pthread_sigmask: fetch/change calling thread’s signal mask (SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK), returns old mask set; Availability: Unixsignal.pthread_sigmask(how, mask)./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.pthread_sigmaskPython 3.3+; Unixdocumented
PL-PY-023signal.sigpending: set of signals pending for delivery to calling thread; Availability: Unixsignal.sigpending()./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.sigpendingPython 3.3+; Unixdocumented
PL-PY-024signal.sigwait: suspend calling thread until one of sigset is deliverable; accepts (removes from pending) and returns signal number; Availability: Unixsignal.sigwait(sigset)./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.sigwaitPython 3.3+; Unixdocumented
PL-PY-025signal.raise_signal (send signal to calling process, 3.8+) and signal.valid_signals (set of valid signal numbers, 3.8+); SIG_DFL/SIG_IGN constantssignal.raise_signal(signum) / signal.valid_signals()./python.mdsignal docs, docs.python.org/3/library/signal.html#signal.raise_signal, #signal.valid_signalsPython 3.8+documented
PL-PY-026subprocess.Popen full constructor surface (verified verbatim incl. 3.11 process_group, 3.10 pipesize); execvpe-like on POSIX, CreateProcess on Windows; no env_merge parameter exists — env is a complete mappingsubprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None, pipesize=-1, process_group=None)./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#subprocess.PopenCPython 3.11+/3.15.0 surface; POSIX/Windows split as documenteddocumented
PL-PY-027subprocess stdio constants: PIPE, DEVNULL, STDOUT (stderr→stdout merge)subprocess.PIPE / subprocess.DEVNULL / subprocess.STDOUT./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#subprocess.PIPEPython 3.3+ (DEVNULL)documented
PL-PY-028subprocess.run: high-level one-shot → CompletedProcess; timeout/input/check/capture_output not passed through; timeout → child killed and waited for, then TimeoutExpired re-raised; process creation itself not interruptible on many platformssubprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs) -> CompletedProcess./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#subprocess.runPython 3.5+; capture_output 3.7+documented
PL-PY-029CompletedProcess: run()’s result record (.args/.returncode/.stdout/.stderr; .check_returncode()); negative returncode = killed by signal N (POSIX only)CompletedProcess(args, returncode, stdout=None, stderr=None)./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#subprocess.CompletedProcessPython 3.5+documented
PL-PY-030Legacy trio call / check_call / check_output: return returncode / raise CalledProcessError on non-zero / capture stdout and raise with .output; docs direct users to run()subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs); check_call(...same...); check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None, **other_popen_kwargs)./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#subprocess.call / #subprocess.check_call / #subprocess.check_outputPython 3.5+ (wrappers over run)documented
PL-PY-031Exception family: SubprocessError base (3.3+); CalledProcessError (.returncode/.cmd/.output), TimeoutExpired (.cmd/.timeout, partial output); invalid Popen args → ValueErrorSubprocessError / CalledProcessError / TimeoutExpired (exception classes)./python.mdsubprocess docs, docs.python.org/3/library/subprocess.html#exceptionsPython 3.3+documented
PL-PY-032§3.2 exit-status derivation: int → C exit() (OS truncates to low 8 bits on POSIX: ≥256 wraps, negatives wrap); docs: most systems require 0–127; non-int → stderr print + 1; None → 0—./python.mdsys docs (#sys.exit, retrieved 2026-10-13) + POSIX exit() layer; decoder: ../../linux/process-exit-signal/languages/python.md[language] arg mapping + [POSIX/Linux] status-byte truncationdocumented
PL-PY-033§3.3 SystemExit propagation: runs finally/exit on the way out; except BaseException catches; re-raise resumes exit; swallowing cancels the exit; in non-main thread ends only that thread—./python.mdsys docs (“only exit the process when called from the main thread”) + exceptions docsPython language (all 3.x)documented
PL-PY-034§3.4 Uncaught exception → traceback to stderr + exit status 1; normal shutdown sequence still runs afterwards; KeyboardInterrupt same unless decoded via 128+N from actual signal death—./python.mdexceptions docs + sys docs + chapter §3.4Python language + CPythondocumented
PL-PY-035§3.5 os._exit semantics: no atexit, no threading._shutdown, no stdio flush, no module teardown, no __del__; buffered data (Python + C stdio) lost; sanctioned fork-child exitos._exit(n, /) (behavior row)./python.mdos docs + exceptions docs cross-referencePython 3.x, all platformsdocumented
PL-PY-036§3.6 os.abort semantics: SIGABRT to self bypassing Python handler machinery; shell status 128+6=134 with core/caveat; no shutdown sequenceos.abort() (behavior row)./python.mdos docs (#os.abort) + POSIX SIGABRT dispositionPython 3.x; Unix / Windows exit-3 deltadocumented
PL-PY-037§3.7 Interpreter shutdown sequence (CPython, verified in pylifecycle.c): threading._shutdown joins non-daemon threads → pending calls → atexit LIFO (looped until quiescent) → module teardown + final GC/del finalization → stdio flush (failure after SystemExit caught → exit status 120) → C exit(sts); daemon threads not joined; per-interpreter atexit stacks (3.12+)—./python.mddocs.python.org/3/library/sys.html#sys.exit versionchanged 3.6 (status 120); CPython Python/pylifecycle.c (wait_for_thread_shutdown, _PyAtExit_Call, sts = 120); atexit docs (multi-interpreter)[CPython] 3.15.0 source + 3.6+ documented status-120 ruledocumented
PL-PY-038§3.10 atexit exact behavior: LIFO order (A,B,C → C,B,A); handler exception → traceback printed (unless SystemExit), info saved, all handlers still run, last exception re-raised at the end (→ status 1); registering from a cleanup function undefinedatexit.register(func, *args, **kwargs) (behavior row)./python.mdatexit docs, docs.python.org/3/library/atexit.html (retrieved 2026-10-13, verbatim quotes)Python 3.x, all platformsdocumented
PL-PY-039§3.9 Module teardown: __del__ may run during shutdown when globals (incl. modules) may be deleted or set to None; only guarantee: underscore-prefixed globals deleted first; pre-3.4 all globals were forcibly None’d; exceptions escaping __del__ printed as “Exception ignored in:”, never change exit status—./python.mdData model §3.1.13 (object.del, docs.python.org/3/reference/datamodel.html)Python language (3.4+ semantics; CPython teardown)documented
PL-PY-040§3.8 thread vs process: sys.exit in a thread raises SystemExit in that thread only (thread machinery swallows it); main thread’s SystemExit → process exit; children (fork/multiprocessing/subprocess) have independent exit statuses reaped by the parent—./python.mdsys docs + threading docs + chapter §3.8Python language + CPythondocumented
PL-PY-041§4.1 Signal delivery model: C-level handler only sets a flag; Python handler runs at a later point (e.g. next bytecode) in the main thread; SIGFPE/SIGSEGV catching futile; long C computations delay handlers; handler exceptions raised “out of thin air” in main thread—./python.mdsignal docs (“Execution of Python signal handlers”), docs.python.org/3/library/signal.html[CPython] implementation model, documented; build-dependent on free-threadeddocumented
PL-PY-042§4.2 Defaults and persistence: SIGPIPE ignored (→ BrokenPipeError etc.), SIGINT → KeyboardInterrupt (unless parent changed it); BSD-style persistent handlers regardless of platform; SIGCHLD follows the underlying implementation—./python.mdsignal docs (“General rules”), docs.python.org/3/library/signal.html[CPython] all 3.x; per-OS SIGCHLD deltadocumented
PL-PY-043§4.3 Main-thread-only: handlers always executed in main Python thread of main interpreter (signals unusable for inter-thread communication); signal.signal/set_wakeup_fd raise ValueError off-main; locks must not be acquired inside handlers—./python.mdsignal docs (“Signals and threads” + per-function notes)[CPython] all 3.xdocumented
PL-PY-044§4.4 EINTR / PEP 475: since 3.5, EINTR-failing syscalls are retried automatically (no InterruptedError leakage); time.sleep sleeps at least the requested duration across signal interruptions unless the handler raises; pre-3.5 catch-InterruptedError obsolete—./python.mdPEP 475 (peps.python.org/pep-0475) + time docs versionchanged 3.5, docs.python.org/3/library/time.htmlCPython 3.5+documented
PL-PY-045§4.5 set_wakeup_fd pattern: one-byte signal-number writes wake poll/select/loops; non-blocking fd required; buffer full → bytes lost (+ warn_on_full_buffer); drain before next poll; the bridge under every event-loop signal integrationsignal.set_wakeup_fd(fd, *, warn_on_full_buffer=True) (behavior row)./python.mdsignal docs (#signal.set_wakeup_fd)[CPython] 3.8+ full form; Unix/Windows per availabilitydocumented
PL-PY-046§4.6 POSIX trio pattern: pthread_sigmask + sigpending + sigwait; dedicated-signal-thread idiom moves handler logic to a normal thread; raise_signal for self-delivery (e.g. re-raise SIGINT after logging)signal.pthread_sigmask(how, mask) / signal.sigpending() / signal.sigwait(sigset) / signal.raise_signal(signum) (behavior row)./python.mdsignal docs (per-API) + PEP 475 contextPython 3.3+/3.8+; Unixdocumented
PL-PY-047§4.7 Handler exceptions in main thread; documented SIGTERM→raise idiom; loop.add_signal_handler(signum, callback, *args) runs the callback as a normal loop callback (loop-interaction allowed), built over set_wakeup_fd, main-loop-thread only (ValueError/RuntimeError per docs), remove via remove_signal_handlerloop.add_signal_handler(signum, callback, *args)./python.mdasyncio event-loop docs, docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.add_signal_handler[CPython] Unix event loopsdocumented
PL-PY-048§4.8 Windows deltas: signal() limited set; console-control emulation; os.kill special-cases CTRL_C_EVENT/CTRL_BREAK_EVENT (shared console, CREATE_NEW_PROCESS_GROUP) else TerminateProcess; send_signal maps SIGTERM→terminate(); pthread_sigmask/sigwait Unix-only—./python.mdsignal docs + os docs (#os.kill) + subprocess docs (send_signal note)Windows (deltas), all in-scope versionsdocumented
PL-PY-049§4.2/§5.5 restore-signals rationale: Python-parent SIGPIPE-ignored inheritance into children is why restore_signals=True exists (child gets SIG_DFL pre-exec)—./python.mdsignal docs (SIGPIPE ignored) + subprocess docs (#subprocess.Popen restore_signals)CPython + POSIXdocumented
PL-PY-050§5.1 Popen args/shell semantics: sequence args recommended; shell=True on POSIX ≡ Popen(['/bin/sh', '-c', args[0], args[1], ...]) — extra sequence items are shell args (trap); executable= swaps program keeping argv[0]; bufsize maps to open() (0/1/-1/positive); text/encoding/errors—./python.mdsubprocess docs (#subprocess.Popen, Frequently Used Arguments), retrieved 2026-10-13Python 3.5+; POSIX /bin/shdocumented
PL-PY-051§5.1 stdio wiring: stdin/stdout/stderr ∈ None (inherit)/PIPE/DEVNULL/fd/file object; stderr=STDOUT merge; cwd= child chdir (POSIX: affects relative executable resolution); env= complete replacement—./python.mdsubprocess docs (#subprocess.Popen)Python 3.x, POSIX+Windowsdocumented
PL-PY-052§5.2 preexec_fn hazard: called in child just before exec; “NOT SAFE to use in the presence of threads … could deadlock before exec”; docs redirect to env/start_new_session/process_group; RuntimeError in subinterpreters (3.8+)Popen(..., preexec_fn=None) (behavior row)./python.mdsubprocess docs (#subprocess.Popen, Warning blocks)POSIX only; CPython 3.8+/3.12 subinterpreter rulesdocumented
PL-PY-053§5.3 fd inheritance: PEP 446 (3.4) non-inheritable-by-default; close_fds=True default since 3.2 closes all but 0/1/2; pass_fds forces close_fds=True and is the opt-in allowlist; os.pipe() results documented non-inheritablePopen(..., close_fds=True, pass_fds=()) (behavior row)./python.mdPEP 446 (peps.python.org/pep-0446) + subprocess docs + os docs (#os.pipe)CPython 3.4+documented
PL-PY-054§5.4 pre-exec parameter set: start_new_session=True → setsid() (3.2); process_group → setpgid(0, value) (3.11); group/extra_groups/user → setregid/setgroups/setreuid, strings via grp/pwd, user does not drop supplementary groups unless extra_groups=() (3.9); umask → umask() (3.9); all POSIX-onlyPopen(..., start_new_session=False, process_group=None, group=None, extra_groups=None, user=None, umask=-1) (behavior row)./python.mdsubprocess docs (#subprocess.Popen, per-parameter text + Added in version notes)CPython 3.2/3.9/3.11 additions; POSIXdocumented
PL-PY-055§5.5 restore_signals=True (default): SIG_IGN’d signals (currently SIGPIPE, SIGXFZ, SIGXFSZ per docs) restored to SIG_DFL in child pre-execPopen(..., restore_signals=True)./python.mdsubprocess docs (#subprocess.Popen)CPython 3.2+; POSIXdocumented
PL-PY-056§5.6 poll/wait semantics: poll() non-blocking (None or returncode); wait() deadlock warning verbatim (PIPE + full child output); timeout waits: event-driven on Linux ≥ 5.3 (pidfd_open+poll) and macOS/BSD (kqueue) per 3.15 docs, else poll-sleep fallback; TimeoutExpired safe to catch and retryPopen.poll() / Popen.wait(timeout=None)./python.mdsubprocess docs (#subprocess.Popen.poll, #subprocess.Popen.wait incl. 3.15 versionchanged)CPython 3.15.0 docs; Linux/macOS/BSD fast pathsdocumented
PL-PY-057§5.7 communicate(): concurrent drain of stdout/stderr + feed stdin + wait; timeout does NOT kill the child; retry after TimeoutExpired loses no output; post-timeout input is undefined behavior; documented kill-then-communicate cleanup patternPopen.communicate(input=None, timeout=None) -> (stdout_data, stderr_data)./python.mdsubprocess docs (#subprocess.Popen.communicate)Python 3.3+ timeoutdocumented
PL-PY-058§5.8 send_signal/terminate/kill/pid: send_signal no-op on completed process; terminate → SIGTERM (POSIX)/TerminateProcess (Windows); kill → SIGKILL (POSIX)/alias of terminate (Windows); returncode negative -N = signal N (POSIX only)Popen.send_signal(sig) / Popen.terminate() / Popen.kill() / Popen.pid / .returncode./python.mdsubprocess docs (per-method + returncode attribute)Python 3.x; POSIX/Windows splitdocumented
PL-PY-059§5.9 Popen as context manager: on exit closes stdio fds and waits for the process (3.2+); __del__ emits ResourceWarning if child still running (3.6+); abandoned objects parked on module _active list and reaped by _cleanup() at next Popen creation (source-verified)Popen context manager + Popen.__del__ (behavior row)./python.mdsubprocess docs (context manager section + 3.6/3.8 versionchanged); CPython Lib/subprocess.py (del, _active, _cleanup)CPython 3.2+ (ctx mgr), 3.6+ (warning), source main 2026-10-13documented
PL-PY-060§5.10 run() timeout contract: child killed and waited for, then TimeoutExpired re-raised (unlike bare communicate); check=True → CalledProcessError with .returncode/.cmd/captured output; creation time not interruptible on many platformssubprocess.run(..., timeout=None, check=False) (behavior row)./python.mdsubprocess docs (#subprocess.run)Python 3.5+documented
PL-PY-061§5.12 Child-side pre-exec failure: exceptions raised in the child before the new program starts are re-raised in the parent (most commonly OSError/FileNotFoundError); caveat: with the posix_spawn fast path (3.8+), WSL/QEMU and friends report missing programs as non-zero child returncode instead—./python.mdsubprocess docs (Exceptions raised in the child process + 3.8 versionchanged)CPython 3.8+ nuancedocumented
PL-PY-062§5.11 shell=True injection surface: library “will not implicitly choose to call a system shell” — shell=False passes metacharacters safely; shell=True makes quoting the application’s responsibility; shlex.split tokenizes, shlex.join (3.8+) inverts splitshlex.split(s) / shlex.join(split_command)./python.mdsubprocess docs (Security Considerations) + shlex docs, docs.python.org/3/library/shlex.htmlPython 3.x; shlex.join 3.8+documented
PL-PY-063§5.14 os.fork: 0 in child / pid in parent, OSError on failure; DeprecationWarning when multiple threads detected (3.12); RuntimeError in subinterpreters (3.8+); macOS safety caveats; TLS warning; os.forkpty: fork + new pty controlling terminal, master fd non-inheritableos.fork() / os.forkpty()./python.mdos docs (#os.fork, #os.forkpty incl. versionchanged notes)POSIX; CPython 3.12 warningdocumented
PL-PY-064§5.14 exec family: execv/execve/execvp/execvpe (+execl*) replace the process image, do not return, errors → OSError in caller; open file objects/descriptors not flushed — flush before execos.execv(path, args) / os.execve(path, args, env) / os.execvp(file, args) / os.execvpe(file, args, env)./python.mdos docs (#os.execv family)Python 3.x, POSIX semantics (Windows variants limited)documented
PL-PY-065§5.14 wait family + status decoding: os.wait() → (pid, raw 16-bit status); os.waitpid(pid, options) with WNOHANG; os.waitid(idtype, id, options) P_PID/P_PGID/P_ALL/P_PIDFD(Linux), WEXITED/WSTOPPED/WCONTINUED, WNOHANG → None when nothing ready; os.waitstatus_to_exitcode: WIFEXITED → status, WIFSIGNALED → -signum; unreaped children = zombies until parent exitos.wait() / os.waitpid(pid, options, /) / os.waitid(idtype, id, options, /) / os.waitstatus_to_exitcode(status)./python.mdos docs (#os.wait, #os.waitpid, #os.waitid, #os.waitstatus_to_exitcode)Python 3.x/3.9+ (waitstatus_to_exitcode); Unixdocumented
PL-PY-066§5.14 posix_spawn pair: os.posix_spawn(path, argv, env, *, file_actions=None, setpgroup=None, resetids=False, setsid=False, setsigmask=(), setsigdef=(), scheduler=None) wraps posix_spawn(3) — fork+exec fast path with child setup expressed as POSIX_SPAWN_OPEN/CLOSE/DUP2 file_actions, no Python-in-child; posix_spawnp resolves via PATH; docs: “Most users should use subprocess.run() instead”; Popen uses it as fast path (3.8+)os.posix_spawn(path, argv, env, *, file_actions=None, setpgroup=None, resetids=False, setsid=False, setsigmask=(), setsigdef=(), scheduler=None) / os.posix_spawnp(path, argv, env, *, ...)./python.mdos docs (#os.posix_spawn, #os.posix_spawnp)Python 3.8+documented
PL-PY-067§5.14 os.spawn* legacy family: spawnl/spawnle/spawnlp/spawnlpe/spawnv/spawnve/spawnvp/spawnvpe with mode P_WAIT/P_NOWAIT; prefer subprocessos.spawnl(mode, path, ...) (+ variants)./python.mdos docs (#os.spawnl family)Python 3.xdocumented
PL-PY-068§5.14 kill/killpg: os.kill(pid, sig), os.killpg(pgid, sig); Windows: CTRL_C_EVENT/CTRL_BREAK_EVENT special, anything else → unconditional TerminateProcessos.kill(pid, sig, /) / os.killpg(pgid, sig, /)./python.mdos docs (#os.kill, #os.killpg)Python 3.x; Unix / Windows deltadocumented
PL-PY-069§5.14 pipes and dup2: os.pipe() → (r, w) non-inheritable (PEP 446); os.pipe2(flags) sets O_NONBLOCK/O_CLOEXEC atomically (Unix); os.dup2(fd, fd2, inheritable=True) → fd2, result inheritable by defaultos.pipe() / os.pipe2(flags, /) / os.dup2(fd, fd2, inheritable=True)./python.mdos docs (#os.pipe, #os.pipe2, #os.dup2)Python 3.3+ (pipe2), 3.4+ (inheritable kw)documented
PL-PY-070§5.3 fd controls: os.get_inheritable(fd) → bool / os.set_inheritable(fd, inheritable) — the per-fd PEP 446 controls; setsid() for new sessionsos.get_inheritable(fd, /) / os.set_inheritable(fd, inheritable, /) / os.setsid()./python.mdos docs (#os.get_inheritable, #os.set_inheritable, #os.setsid)Python 3.4+; Unix (setsid)documented
PL-PY-071§5.13/§5.14 asyncio + multiprocessing: create_subprocess_exec/create_subprocess_shell → Process with coroutine communicate()/wait(), same terminate/kill mapping, StreamReader limit=65536, no run()-style auto-kill on timeout; multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None): fork/spawn/forkserver start methods, .daemon killed at parent exit, .exitcode None-while-running/negative-signalasyncio.create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None, limit=65536, **kwds) / asyncio.create_subprocess_shell(cmd, ...) / multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)./python.mdasyncio-subprocess docs + multiprocessing docs (docs.python.org/3/library/asyncio-subprocess.html, /3/library/multiprocessing.html)Python 3.x; start-method semantics per platformdocumented
PL-PY-072§6.1 GIL effects in scope: one bytecode-executing thread at a time; signal handling needs main-thread bytecode; GIL-holding C extensions delay delivery; memory visibility between threads is implementation artifact (free-threaded build changes it)—./python.mdsignal docs + chapter §6.1; companions ../python/gil.md, ../python/free-threaded.md[CPython]; build-dependent (free-threaded 3.13+)documented
PL-PY-073§6.2 fork × threads: 3.12 DeprecationWarning when multi-threaded detected (verbatim docs quote; “never been safe to mix threading with os.fork() on POSIX”); os.register_at_fork(*, before=None, after_in_parent=None, after_in_child=None) as the sanctioned hook set (3.7+); multiprocessing fork start method inherits the hazard, spawn avoids itos.register_at_fork(*, before=None, after_in_parent=None, after_in_child=None) (behavior row)./python.mdos docs (#os.fork versionchanged 3.12, #os.register_at_fork)CPython 3.7+/3.12+; POSIXdocumented
PL-PY-074§6.3 buffering × fork: fork duplicates userspace buffers (Python file objects + C stdio); exec does not flush them (docs quote) → flush before exec; child must _exit or exec, never return through parent cleanup; Popen immune by construction (child stdio re-wired pre-exec)—./python.mdos docs (#os.execvpe flush note) + chapter §6.3POSIX + CPythondocumented
PL-PY-075§6.4 fd ownership rule: non-inheritable-by-default prevents socket/lock leakage into children; dup2/pipe2(O_CLOEXEC) are the opt-in mechanisms for raw fork/exec code; CPython sets the flags at creation per PEP 446—./python.mdPEP 446 + os docs (inheritable API)CPython 3.4+documented
PL-PY-076§6.5 daemon threads: abruptly stopped at shutdown; “Their resources … may not be released properly”; no finally/exit/del for in-flight work; program exits when no alive non-daemon threads remain (threading._shutdown joins them)threading.Thread(..., daemon=None) (behavior row)./python.mdthreading docs, docs.python.org/3/library/threading.html (daemon note + _shutdown behavior)Python language + CPythondocumented
PL-PY-077§6.6 deadlocks: wait()+PIPE vs child filling the OS pipe buffer (docs warning verbatim); symmetric join()-vs-full-pipe shutdown deadlock; pipe capacity is OS-level (Linux default 64 KiB; pipesize=/F_SETPIPE_SZ hints); asyncio StreamReaders bound by limit instead of threads—./python.mdsubprocess docs (wait note) + chapter §6.6 + os docs (pipe2/pipesize)POSIX/Linux + CPythondocumented
PL-PY-078§6.7 races: preexec_fn races parent threads for duplicated runtime state; subprocess._cleanup’s own documented thread race on _active.remove (harmless, source comment); guidance: single owner thread for Popen objects—./python.mdCPython Lib/subprocess.py (_cleanup comment) + subprocess docs (preexec_fn warning)CPython main 2026-10-13documented
PL-PY-079§6.8 leaks: unclosed parent-side pipe fds → ResourceWarning; abandoned running Popen → ResourceWarning at del + _active reaping at next Popen creation + final reaping at interpreter exit; raw os.fork parents must wait/waitpid or zombies accumulate until parent exit; GC/finalization is backstop (PEP 442), not the release mechanism—./python.mdsubprocess docs (3.6 versionchanged) + CPython Lib/subprocess.py + gc docsCPython 3.6+documented
PL-PY-080§6.9 cleanup hierarchy: with (deterministic, exception-safe) → try/finally → explicit close() on error paths → atexit (process lifetime, LIFO) → del/GC backstop → OS reclamation at _exit/death; Popen with closes stdio fds and waits (blocking) on block exit—./python.mdData model + atexit + subprocess docs (context manager)Python language + CPythondocumented