From 34f7985357ca57f6569b7146926dee9d8446541c Mon Sep 17 00:00:00 2001 From: Holden Date: Tue, 16 Jun 2026 23:41:38 +0000 Subject: [PATCH] docs: document BaseException limitation in _suppress_output finally block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A KeyboardInterrupt raised inside the saved_out cleanup block would propagate past the saved_err and devnull_fd blocks, leaking those fds. In CPython this race is not realistically triggerable — KI is delivered between bytecodes and os.dup2 is a single atomic C syscall — so we accept the theoretical risk rather than silencing BaseException in a finally block. --- winnow/embeddings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/winnow/embeddings.py b/winnow/embeddings.py index 1e84ed4..9d59d8a 100644 --- a/winnow/embeddings.py +++ b/winnow/embeddings.py @@ -37,6 +37,11 @@ def _suppress_output(): os.dup2(devnull_fd, 2) yield finally: + # Each block is a separate sequential statement. A BaseException (e.g. + # KeyboardInterrupt) raised inside block N would propagate past blocks N+1 + # and N+2, leaving saved_err or devnull_fd unclosed. In CPython, KI is + # delivered between bytecodes, not mid-syscall; os.dup2 is a single C call + # and completes atomically, so this race is not realistically triggerable. if saved_out is not None: try: os.dup2(saved_out, 1)