Skip to content

Commit 8638265

Browse files
mvanhornclaude
andcommitted
gh-145638: Add signal.get_wakeup_fd()
Add signal.get_wakeup_fd() to return the current wakeup file descriptor without modifying it. This avoids the race condition inherent in the current workaround of calling set_wakeup_fd(-1) followed by set_wakeup_fd(fd) to read the current value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 66eafc9 commit 8638265

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

Doc/library/signal.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,19 @@ The :mod:`!signal` module defines the following functions:
592592
.. versionchanged:: 3.7
593593
Added ``warn_on_full_buffer`` parameter.
594594

595+
.. function:: get_wakeup_fd()
596+
597+
Return the current wakeup file descriptor, or -1 if no wakeup fd is
598+
currently set. Unlike :func:`set_wakeup_fd`, this function does not modify
599+
the wakeup fd.
600+
601+
When threads are enabled, this function can only be called
602+
from :ref:`the main thread of the main interpreter <signals-and-threads>`;
603+
attempting to call it from other threads will cause a :exc:`ValueError`
604+
exception to be raised.
605+
606+
.. versionadded:: 3.15
607+
595608
.. function:: siginterrupt(signalnum, flag)
596609

597610
Change system call restart behaviour: if *flag* is :const:`False`, system

Lib/test/test_signal.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,26 @@ def test_set_wakeup_fd_socket_result(self):
288288
self.assertEqual(signal.set_wakeup_fd(-1), fd2)
289289
self.assertEqual(signal.set_wakeup_fd(-1), -1)
290290

291+
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
292+
def test_get_wakeup_fd(self):
293+
# gh-145638: get_wakeup_fd should return the current wakeup fd
294+
# without modifying it.
295+
self.assertEqual(signal.get_wakeup_fd(), -1)
296+
297+
r, w = os.pipe()
298+
self.addCleanup(os.close, r)
299+
self.addCleanup(os.close, w)
300+
301+
if hasattr(os, 'set_blocking'):
302+
os.set_blocking(w, False)
303+
304+
signal.set_wakeup_fd(w)
305+
self.assertEqual(signal.get_wakeup_fd(), w)
306+
# Calling get_wakeup_fd again should return the same value
307+
self.assertEqual(signal.get_wakeup_fd(), w)
308+
signal.set_wakeup_fd(-1)
309+
self.assertEqual(signal.get_wakeup_fd(), -1)
310+
291311
# On Windows, files are always blocking and Windows does not provide a
292312
# function to test if a socket is in non-blocking mode.
293313
@unittest.skipIf(sys.platform == "win32", "tests specific to POSIX")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added :func:`signal.get_wakeup_fd` to return the current wakeup file
2+
descriptor without modifying it, avoiding the race condition inherent in
3+
using :func:`signal.set_wakeup_fd` to read and restore the current value.

Modules/clinic/signalmodule.c.h

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/signalmodule.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,39 @@ PySignal_SetWakeupFd(int fd)
847847
}
848848

849849

850+
/*[clinic input]
851+
signal.get_wakeup_fd
852+
853+
Returns the current wakeup fd.
854+
855+
Returns the file descriptor previously set by set_wakeup_fd(), or -1 if
856+
no wakeup fd is currently set. Unlike set_wakeup_fd(), this function does
857+
not modify the wakeup fd.
858+
[clinic start generated code]*/
859+
860+
static PyObject *
861+
signal_get_wakeup_fd_impl(PyObject *module)
862+
/*[clinic end generated code: output=4a38f1468baa700c input=c3ef2d4ee38352fd]*/
863+
{
864+
PyThreadState *tstate = _PyThreadState_GET();
865+
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
866+
_PyErr_SetString(tstate, PyExc_ValueError,
867+
"get_wakeup_fd only works in main thread "
868+
"of the main interpreter");
869+
return NULL;
870+
}
871+
872+
#ifdef MS_WINDOWS
873+
if (wakeup.fd != INVALID_FD)
874+
return PyLong_FromSocket_t((SOCKET_T)wakeup.fd);
875+
else
876+
return PyLong_FromLong(-1);
877+
#else
878+
return PyLong_FromLong(wakeup.fd);
879+
#endif
880+
}
881+
882+
850883
#ifdef HAVE_SETITIMER
851884
/*[clinic input]
852885
@permit_long_docstring_body
@@ -1357,6 +1390,7 @@ static PyMethodDef signal_methods[] = {
13571390
SIGNAL_STRSIGNAL_METHODDEF
13581391
SIGNAL_GETSIGNAL_METHODDEF
13591392
SIGNAL_SET_WAKEUP_FD_METHODDEF
1393+
SIGNAL_GET_WAKEUP_FD_METHODDEF
13601394
SIGNAL_SIGINTERRUPT_METHODDEF
13611395
SIGNAL_PAUSE_METHODDEF
13621396
SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF

0 commit comments

Comments
 (0)