diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index c3fe9943ba9d76..89d3bdd8b67a7b 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -592,6 +592,19 @@ The :mod:`!signal` module defines the following functions: .. versionchanged:: 3.7 Added ``warn_on_full_buffer`` parameter. +.. function:: get_wakeup_fd() + + Return the current wakeup file descriptor, or -1 if no wakeup fd is + currently set. Unlike :func:`set_wakeup_fd`, this function does not modify + the wakeup fd. + + When threads are enabled, this function can only be called + from :ref:`the main thread of the main interpreter `; + attempting to call it from other threads will cause a :exc:`ValueError` + exception to be raised. + + .. versionadded:: 3.15 + .. function:: siginterrupt(signalnum, flag) Change system call restart behaviour: if *flag* is :const:`False`, system diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index d6cc22558ec4fa..6a92097b718feb 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -288,6 +288,26 @@ def test_set_wakeup_fd_socket_result(self): self.assertEqual(signal.set_wakeup_fd(-1), fd2) self.assertEqual(signal.set_wakeup_fd(-1), -1) + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_get_wakeup_fd(self): + # gh-145638: get_wakeup_fd should return the current wakeup fd + # without modifying it. + self.assertEqual(signal.get_wakeup_fd(), -1) + + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + + if hasattr(os, 'set_blocking'): + os.set_blocking(w, False) + + signal.set_wakeup_fd(w) + self.assertEqual(signal.get_wakeup_fd(), w) + # Calling get_wakeup_fd again should return the same value + self.assertEqual(signal.get_wakeup_fd(), w) + signal.set_wakeup_fd(-1) + self.assertEqual(signal.get_wakeup_fd(), -1) + # On Windows, files are always blocking and Windows does not provide a # function to test if a socket is in non-blocking mode. @unittest.skipIf(sys.platform == "win32", "tests specific to POSIX") diff --git a/Misc/NEWS.d/next/Library/2026-03-09-00-00-01.gh-issue-145638.get-wakeup-fd.rst b/Misc/NEWS.d/next/Library/2026-03-09-00-00-01.gh-issue-145638.get-wakeup-fd.rst new file mode 100644 index 00000000000000..67586ddc1e13d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-09-00-00-01.gh-issue-145638.get-wakeup-fd.rst @@ -0,0 +1,3 @@ +Added :func:`signal.get_wakeup_fd` to return the current wakeup file +descriptor without modifying it, avoiding the race condition inherent in +using :func:`signal.set_wakeup_fd` to read and restore the current value. diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h index 9fd24d15bf2500..41e12a94013c2a 100644 --- a/Modules/clinic/signalmodule.c.h +++ b/Modules/clinic/signalmodule.c.h @@ -354,6 +354,28 @@ signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, return return_value; } +PyDoc_STRVAR(signal_get_wakeup_fd__doc__, +"get_wakeup_fd($module, /)\n" +"--\n" +"\n" +"Returns the current wakeup fd.\n" +"\n" +"Returns the file descriptor previously set by set_wakeup_fd(), or -1 if\n" +"no wakeup fd is currently set. Unlike set_wakeup_fd(), this function does\n" +"not modify the wakeup fd."); + +#define SIGNAL_GET_WAKEUP_FD_METHODDEF \ + {"get_wakeup_fd", (PyCFunction)signal_get_wakeup_fd, METH_NOARGS, signal_get_wakeup_fd__doc__}, + +static PyObject * +signal_get_wakeup_fd_impl(PyObject *module); + +static PyObject * +signal_get_wakeup_fd(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return signal_get_wakeup_fd_impl(module); +} + #if defined(HAVE_SETITIMER) PyDoc_STRVAR(signal_setitimer__doc__, @@ -794,4 +816,4 @@ signal_pidfd_send_signal(PyObject *module, PyObject *const *args, Py_ssize_t nar #ifndef SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF #define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF #endif /* !defined(SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF) */ -/*[clinic end generated code: output=42e20d118435d7fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9c8cdf6f5fe76c08 input=a9049054013a1b77]*/ diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 5060e4097d33c9..9291199f48f8ad 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -847,6 +847,41 @@ PySignal_SetWakeupFd(int fd) } +/*[clinic input] +signal.get_wakeup_fd + +Returns the current wakeup fd. + +Returns the file descriptor previously set by set_wakeup_fd(), or -1 if +no wakeup fd is currently set. Unlike set_wakeup_fd(), this function does +not modify the wakeup fd. +[clinic start generated code]*/ + +static PyObject * +signal_get_wakeup_fd_impl(PyObject *module) +/*[clinic end generated code: output=4a38f1468baa700c input=c3ef2d4ee38352fd]*/ +{ + PyThreadState *tstate = _PyThreadState_GET(); + if (!_Py_ThreadCanHandleSignals(tstate->interp)) { + _PyErr_SetString(tstate, PyExc_ValueError, + "get_wakeup_fd only works in main thread " + "of the main interpreter"); + return NULL; + } + +#ifdef MS_WINDOWS + if (wakeup.fd != INVALID_FD) { + return PyLong_FromSocket_t((SOCKET_T)wakeup.fd); + } + else { + return PyLong_FromLong(-1); + } +#else + return PyLong_FromLong(wakeup.fd); +#endif +} + + #ifdef HAVE_SETITIMER /*[clinic input] @permit_long_docstring_body @@ -1357,6 +1392,7 @@ static PyMethodDef signal_methods[] = { SIGNAL_STRSIGNAL_METHODDEF SIGNAL_GETSIGNAL_METHODDEF SIGNAL_SET_WAKEUP_FD_METHODDEF + SIGNAL_GET_WAKEUP_FD_METHODDEF SIGNAL_SIGINTERRUPT_METHODDEF SIGNAL_PAUSE_METHODDEF SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF