From b9817d714c372df86dea9b924c4a712872b0bf4e Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 20 Mar 2026 20:45:41 +0800 Subject: [PATCH 1/2] Check the errno with != 0 in close impls --- Lib/test/test_devpoll.py | 10 ++++++++++ Lib/test/test_epoll.py | 9 +++++++++ Lib/test/test_kqueue.py | 9 +++++++++ Modules/selectmodule.c | 15 +++++++++------ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_devpoll.py b/Lib/test/test_devpoll.py index 85e0accb611b1d..5951f8817ab1c1 100644 --- a/Lib/test/test_devpoll.py +++ b/Lib/test/test_devpoll.py @@ -2,6 +2,7 @@ # Initial tests are copied as is from "test_poll.py" +import errno import os import random import select @@ -112,6 +113,15 @@ def test_close(self): self.assertRaises(ValueError, devpoll.register, fd, select.POLLIN) self.assertRaises(ValueError, devpoll.unregister, fd) + def test_close_error(self): + # gh-146205: close() should raise OSError if underlying fd is invalid + devpoll = select.devpoll() + fd = devpoll.fileno() + os.close(fd) + with self.assertRaises(OSError) as cm: + devpoll.close() + self.assertEqual(cm.exception.errno, errno.EBADF) + def test_fd_non_inheritable(self): devpoll = select.devpoll() self.addCleanup(devpoll.close) diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py index c94946a6ae6b7c..5e6a4ab0166a86 100644 --- a/Lib/test/test_epoll.py +++ b/Lib/test/test_epoll.py @@ -259,6 +259,15 @@ def test_close(self): self.assertRaises(ValueError, epoll.register, fd, select.EPOLLIN) self.assertRaises(ValueError, epoll.unregister, fd) + def test_close_error(self): + # gh-146205: close() should raise OSError if underlying fd is invalid + epoll = select.epoll() + fd = epoll.fileno() + os.close(fd) + with self.assertRaises(OSError) as cm: + epoll.close() + self.assertEqual(cm.exception.errno, errno.EBADF) + def test_fd_non_inheritable(self): epoll = select.epoll() self.addCleanup(epoll.close) diff --git a/Lib/test/test_kqueue.py b/Lib/test/test_kqueue.py index d2ab45c4a5b1ea..2cf99be9e2c3ba 100644 --- a/Lib/test/test_kqueue.py +++ b/Lib/test/test_kqueue.py @@ -254,6 +254,15 @@ def test_close(self): # operations must fail with ValueError("I/O operation on closed ...") self.assertRaises(ValueError, kqueue.control, None, 4) + def test_close_error(self): + # gh-146205: close() should raise OSError if underlying fd is invalid + kqueue = select.kqueue() + fd = kqueue.fileno() + os.close(fd) + with self.assertRaises(OSError) as cm: + kqueue.close() + self.assertEqual(cm.exception.errno, errno.EBADF) + def test_fd_non_inheritable(self): kqueue = select.kqueue() self.addCleanup(kqueue.close) diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 137bf2ca55bbf8..4dd544c6ee8d34 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -1118,8 +1118,9 @@ static PyObject * select_devpoll_close_impl(devpollObject *self) /*[clinic end generated code: output=26b355bd6429f21b input=408fde21a377ccfb]*/ { - errno = devpoll_internal_close(self); - if (errno < 0) { + int err = devpoll_internal_close(self); + if (err != 0) { + errno = err; PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1446,8 +1447,9 @@ static PyObject * select_epoll_close_impl(pyEpoll_Object *self) /*[clinic end generated code: output=ee2144c446a1a435 input=f626a769192e1dbe]*/ { - errno = pyepoll_internal_close(self); - if (errno < 0) { + int err = pyepoll_internal_close(self); + if (err != 0) { + errno = err; PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -2263,8 +2265,9 @@ static PyObject * select_kqueue_close_impl(kqueue_queue_Object *self) /*[clinic end generated code: output=d1c7df0b407a4bc1 input=6d763c858b17b690]*/ { - errno = kqueue_queue_internal_close(self); - if (errno < 0) { + int err = kqueue_queue_internal_close(self); + if (err != 0) { + errno = err; PyErr_SetFromErrno(PyExc_OSError); return NULL; } From 53d6b3a3361091bb5aaf5ce57513b14a00d5a4bf Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:53:05 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-20-12-52-55.gh-issue-146205.M4yKdf.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-20-12-52-55.gh-issue-146205.M4yKdf.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-20-12-52-55.gh-issue-146205.M4yKdf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-20-12-52-55.gh-issue-146205.M4yKdf.rst new file mode 100644 index 00000000000000..e9d95cdf836dba --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-20-12-52-55.gh-issue-146205.M4yKdf.rst @@ -0,0 +1,2 @@ +Fixed a bug where :meth:`select.epoll.close`, :meth:`select.kqueue.close`, +and :meth:`select.devpoll.close` silently ignored errors.