From 1651c84c76d50e3a718f90e6467590d009e80996 Mon Sep 17 00:00:00 2001 From: AZero13 Date: Mon, 8 Dec 2025 00:50:31 -0500 Subject: [PATCH] gh-142403: Avoid leaking file descriptor in socket.py --- Lib/socket.py | 75 ++++++++++--------- ...-12-10-09-16-46.gh-issue-142403.MCyTuL.rst | 1 + 2 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-10-09-16-46.gh-issue-142403.MCyTuL.rst diff --git a/Lib/socket.py b/Lib/socket.py index 3073c012b19877..09e92aa8c34a72 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -376,45 +376,46 @@ def _sendfile_zerocopy(self, zerocopy_func, giveup_exc_type, file, # poll/select have the advantage of not requiring any # extra file descriptor, contrarily to epoll/kqueue # (also, they require a single syscall). - if hasattr(selectors, 'PollSelector'): - selector = selectors.PollSelector() - else: - selector = selectors.SelectSelector() - selector.register(sockno, selectors.EVENT_WRITE) - total_sent = 0 - # localize variable access to minimize overhead - selector_select = selector.select try: - while True: - if timeout and not selector_select(timeout): - raise TimeoutError('timed out') - if count: - blocksize = min(count - total_sent, blocksize) - if blocksize <= 0: - break - try: - sent = zerocopy_func(fileno, offset, blocksize) - except BlockingIOError: - if not timeout: - # Block until the socket is ready to send some - # data; avoids hogging CPU resources. - selector_select() - continue - except OSError as err: - if total_sent == 0: - # We can get here for different reasons, the main - # one being 'file' is not a regular mmap(2)-like - # file, in which case we'll fall back on using - # plain send(). - raise giveup_exc_type(err) - raise err from None - else: - if sent == 0: - break # EOF - offset += sent - total_sent += sent - return total_sent + with ( + selectors.PollSelector() + if hasattr(selectors, 'PollSelector') + else selectors.SelectSelector() + ) as selector: + selector.register(sockno, selectors.EVENT_WRITE) + + # localize variable access to minimize overhead + selector_select = selector.select + while True: + if timeout and not selector_select(timeout): + raise TimeoutError('timed out') + if count: + blocksize = min(count - total_sent, blocksize) + if blocksize <= 0: + break + try: + sent = zerocopy_func(fileno, offset, blocksize) + except BlockingIOError: + if not timeout: + # Block until the socket is ready to send some + # data; avoids hogging CPU resources. + selector_select() + continue + except OSError as err: + if total_sent == 0: + # We can get here for different reasons, the main + # one being 'file' is not a regular mmap(2)-like + # file, in which case we'll fall back on using + # plain send(). + raise giveup_exc_type(err) + raise err from None + else: + if sent == 0: + break # EOF + offset += sent + total_sent += sent + return total_sent finally: if total_sent > 0 and hasattr(file, 'seek'): file.seek(offset) diff --git a/Misc/NEWS.d/next/Library/2025-12-10-09-16-46.gh-issue-142403.MCyTuL.rst b/Misc/NEWS.d/next/Library/2025-12-10-09-16-46.gh-issue-142403.MCyTuL.rst new file mode 100644 index 00000000000000..896b6fae4fe39a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-10-09-16-46.gh-issue-142403.MCyTuL.rst @@ -0,0 +1 @@ +Stop leaking the selector file descriptor, and release it when done.