diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index 12ad45f557e6db..042f2892571fa2 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -548,7 +548,41 @@ The :mod:`!signal` module defines the following functions: .. availability:: Unix. -.. function:: set_wakeup_fd(fd, *, warn_on_full_buffer=True) +.. function:: get_wakeup() + + Return a :class:`wakeup` snapshot of the current signal wakeup + configuration without changing it. This can be used with + :func:`set_wakeup_fd` to update the process-wide wakeup configuration only + if another owner has not changed it. + + 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.16 + + +.. class:: wakeup + + An immutable snapshot of the signal wakeup configuration. It has these + read-only attributes: + + .. attribute:: wakeup.fd + + The wakeup file descriptor, or ``-1`` if wakeups are disabled. + + .. attribute:: wakeup.warn_on_full_buffer + + Whether a full wakeup buffer reports warnings. + + .. attribute:: wakeup.is_socket + + Whether *fd* is used as a socket on Windows. This is always false on + other platforms. + + +.. function:: set_wakeup_fd(fd, *, warn_on_full_buffer=True, check_previous=None) Set the wakeup file descriptor to *fd*. When a signal your program has registered a signal handler for is received, the signal number is written as @@ -562,6 +596,11 @@ The :mod:`!signal` module defines the following functions: If not -1, *fd* must be non-blocking. It is up to the library to remove any bytes from *fd* before calling poll or select again. + If *check_previous* is a :class:`wakeup` snapshot, change the wakeup + configuration only if it is still equal to the current configuration. + Otherwise, raise :exc:`RuntimeError` without changing it. Pass ``None`` + (the default) to update unconditionally. + 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` @@ -594,6 +633,9 @@ The :mod:`!signal` module defines the following functions: .. versionchanged:: 3.7 Added ``warn_on_full_buffer`` parameter. + .. versionchanged:: 3.16 + Added the ``check_previous`` parameter. + .. function:: siginterrupt(signalnum, flag) Change system call restart behaviour: if *flag* is :const:`False`, system diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 0e2fab84aec0ae..a82185c0287e78 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -362,6 +362,7 @@ _PyStaticObjects_CheckAll(PyInterpreterState *interp) { _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(cb_type), "cb_type", 7); _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(certfile), "certfile", 8); _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(chain), "chain", 5); + _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(check_previous), "check_previous", 14); _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(check_same_thread), "check_same_thread", 17); _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(clamp), "clamp", 5); _PyStaticObject_CheckUnicodeSingleton((PyObject *)&_Py_ID(clear), "clear", 5); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index cbfa80f76b64cd..f5f22273daef55 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -374,6 +374,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(cb_type) STRUCT_FOR_ID(certfile) STRUCT_FOR_ID(chain) + STRUCT_FOR_ID(check_previous) STRUCT_FOR_ID(check_same_thread) STRUCT_FOR_ID(clamp) STRUCT_FOR_ID(clear) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 34dc6228a0bcb0..94d8e56091c551 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1649,6 +1649,7 @@ extern "C" { INIT_ID(cb_type), \ INIT_ID(certfile), \ INIT_ID(chain), \ + INIT_ID(check_previous), \ INIT_ID(check_same_thread), \ INIT_ID(clamp), \ INIT_ID(clear), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index f2f3b9de4a0d87..ebe9d70ee32e4c 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -326,6 +326,7 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { offsetof(struct _Py_global_strings, identifiers._py_cb_type._ascii.ob_base), offsetof(struct _Py_global_strings, identifiers._py_certfile._ascii.ob_base), offsetof(struct _Py_global_strings, identifiers._py_chain._ascii.ob_base), + offsetof(struct _Py_global_strings, identifiers._py_check_previous._ascii.ob_base), offsetof(struct _Py_global_strings, identifiers._py_check_same_thread._ascii.ob_base), offsetof(struct _Py_global_strings, identifiers._py_clamp._ascii.ob_base), offsetof(struct _Py_global_strings, identifiers._py_clear._ascii.ob_base), diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 8350e027604688..8ef6cf9f2bec81 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -663,11 +663,13 @@ def __init__(self, proactor): self._selector = proactor # convenient alias self._self_reading_future = None self._accept_futures = {} # socket file descriptor => Future + self._wakeup = None proactor.set_loop(self) self._make_self_pipe() if threading.current_thread() is threading.main_thread(): # wakeup fd can only be installed to a file descriptor from the main thread signal.set_wakeup_fd(self._csock.fileno()) + self._wakeup = signal.get_wakeup() def _make_socket_transport(self, sock, protocol, waiter=None, extra=None, server=None, context=None): @@ -715,8 +717,14 @@ def close(self): if self.is_closed(): return - if threading.current_thread() is threading.main_thread(): - signal.set_wakeup_fd(-1) + if (threading.current_thread() is threading.main_thread() + and self._wakeup is not None): + try: + signal.set_wakeup_fd(-1, check_previous=self._wakeup) + except RuntimeError: + # Another loop or host replaced the process-global wakeup fd. + pass + self._wakeup = None # Call these methods before closing the event loop (before calling # BaseEventLoop.close), because they can schedule callbacks with # call_soon(), which is forbidden when the event loop is closed. diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py index 2f229887ad62cc..e1d6b9b9eb7347 100644 --- a/Lib/test/test_asyncio/test_proactor_events.py +++ b/Lib/test/test_asyncio/test_proactor_events.py @@ -768,19 +768,24 @@ def setUp(self): with mock.patch('asyncio.proactor_events.socket.socketpair', return_value=(self.ssock, self.csock)): - with mock.patch('signal.set_wakeup_fd'): + with mock.patch('signal.set_wakeup_fd'), \ + mock.patch('signal.get_wakeup'): self.loop = BaseProactorEventLoop(self.proactor) + self.loop._wakeup = None self.set_event_loop(self.loop) @mock.patch('asyncio.proactor_events.socket.socketpair') def test_ctor(self, socketpair): ssock, csock = socketpair.return_value = ( mock.Mock(), mock.Mock()) - with mock.patch('signal.set_wakeup_fd'): + with mock.patch('signal.set_wakeup_fd'), \ + mock.patch('signal.get_wakeup'): loop = BaseProactorEventLoop(self.proactor) self.assertIs(loop._ssock, ssock) self.assertIs(loop._csock, csock) self.assertEqual(loop._internal_fds, 1) + # The wakeup state is mocked in this unit test. + loop._wakeup = None loop.close() def test_close_self_pipe(self): diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py index bb4ba74f19a17f..af9e50757fda41 100644 --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -78,6 +78,29 @@ def SIGINT_after_delay(): thread.join() +class ProactorWakeupOwnershipTests(WindowsEventsTestCase): + + def test_close_preserves_newer_loop_wakeup(self): + initial = signal.get_wakeup() + first = asyncio.ProactorEventLoop() + second = asyncio.ProactorEventLoop() + try: + newer_wakeup = signal.get_wakeup() + self.assertEqual(newer_wakeup.fd, second._csock.fileno()) + + first.close() + self.assertEqual(signal.get_wakeup(), newer_wakeup) + finally: + if not first.is_closed(): + first.close() + if not second.is_closed(): + second.close() + signal.set_wakeup_fd( + initial.fd, + warn_on_full_buffer=initial.warn_on_full_buffer, + ) + + class ProactorMultithreading(WindowsEventsTestCase): def test_run_from_nonmain_thread(self): finished = False diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 2cad18a69ff7c0..8894f7c274ead4 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -288,6 +288,48 @@ 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(support.has_socket_support, "needs working sockets.") + def test_wakeup_state_and_conditional_update(self): + first, second = socket.socketpair() + self.addCleanup(first.close) + self.addCleanup(second.close) + first.setblocking(False) + second.setblocking(False) + self.addCleanup(signal.set_wakeup_fd, -1) + + initial_state = signal.get_wakeup() + self.assertEqual(initial_state.fd, -1) + self.assertFalse(initial_state.is_socket) + signal.set_wakeup_fd(first.fileno(), warn_on_full_buffer=False) + first_state = signal.get_wakeup() + self.assertEqual(first_state.fd, first.fileno()) + self.assertFalse(first_state.warn_on_full_buffer) + self.assertEqual(first_state.is_socket, sys.platform == "win32") + + signal.set_wakeup_fd( + first.fileno(), + warn_on_full_buffer=True, + check_previous=first_state, + ) + changed_state = signal.get_wakeup() + self.assertEqual(changed_state.fd, first.fileno()) + self.assertTrue(changed_state.warn_on_full_buffer) + + with self.assertRaisesRegex(RuntimeError, + "signal wakeup configuration changed"): + signal.set_wakeup_fd(second.fileno(), check_previous=first_state) + self.assertEqual(signal.get_wakeup(), changed_state) + + signal.set_wakeup_fd(second.fileno(), check_previous=changed_state) + second_state = signal.get_wakeup() + self.assertEqual(second_state.fd, second.fileno()) + self.assertTrue(second_state.warn_on_full_buffer) + + with self.assertRaisesRegex(RuntimeError, + "signal wakeup configuration changed"): + signal.set_wakeup_fd(-1, check_previous=first_state) + self.assertEqual(signal.get_wakeup(), second_state) + # 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-10-09-00-00-01.gh-issue-145638.wakeup-state.rst b/Misc/NEWS.d/next/Library/2026-10-09-00-00-01.gh-issue-145638.wakeup-state.rst new file mode 100644 index 00000000000000..51ab9f590e0b1a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-09-00-00-01.gh-issue-145638.wakeup-state.rst @@ -0,0 +1,3 @@ +Add :func:`signal.get_wakeup` and conditional ``check_previous`` support +to :func:`signal.set_wakeup_fd`, allowing cooperating event loops and +hosts to avoid overwriting each other's signal wakeup configuration. diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h index 6125f253ac0380..a97f33153541de 100644 --- a/Modules/clinic/signalmodule.c.h +++ b/Modules/clinic/signalmodule.c.h @@ -281,8 +281,27 @@ signal_siginterrupt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* defined(HAVE_SIGINTERRUPT) */ +PyDoc_STRVAR(signal_get_wakeup__doc__, +"get_wakeup($module, /)\n" +"--\n" +"\n" +"Return the current signal wakeup configuration without modifying it."); + +#define SIGNAL_GET_WAKEUP_METHODDEF \ + {"get_wakeup", (PyCFunction)signal_get_wakeup, METH_NOARGS, signal_get_wakeup__doc__}, + +static PyObject * +signal_get_wakeup_impl(PyObject *module); + +static PyObject * +signal_get_wakeup(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return signal_get_wakeup_impl(module); +} + PyDoc_STRVAR(signal_set_wakeup_fd__doc__, -"set_wakeup_fd($module, fd, /, *, warn_on_full_buffer=True)\n" +"set_wakeup_fd($module, fd, /, *, warn_on_full_buffer=True,\n" +" check_previous=None)\n" "--\n" "\n" "Sets the fd to be written to (with the signal number) when a signal comes in.\n" @@ -290,14 +309,15 @@ PyDoc_STRVAR(signal_set_wakeup_fd__doc__, "A library can use this to wakeup select or poll.\n" "The previous fd or -1 is returned.\n" "\n" -"The fd must be non-blocking."); +"If check_previous is not None, it must equal the current signal.wakeup\n" +"value. The fd must be non-blocking."); #define SIGNAL_SET_WAKEUP_FD_METHODDEF \ {"set_wakeup_fd", _PyCFunction_CAST(signal_set_wakeup_fd), METH_FASTCALL|METH_KEYWORDS, signal_set_wakeup_fd__doc__}, static PyObject * signal_set_wakeup_fd_impl(PyObject *module, PyObject *fdobj, - int warn_on_full_buffer); + int warn_on_full_buffer, PyObject *check_previous); static PyObject * signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -305,7 +325,7 @@ signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 1 + #define NUM_KEYWORDS 2 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -314,7 +334,7 @@ signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(warn_on_full_buffer), }, + .ob_item = { &_Py_ID(warn_on_full_buffer), &_Py_ID(check_previous), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -323,17 +343,18 @@ signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "warn_on_full_buffer", NULL}; + static const char * const _keywords[] = {"", "warn_on_full_buffer", "check_previous", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "set_wakeup_fd", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[2]; + PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *fdobj; int warn_on_full_buffer = 1; + PyObject *check_previous = Py_None; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -344,12 +365,18 @@ signal_set_wakeup_fd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, if (!noptargs) { goto skip_optional_kwonly; } - warn_on_full_buffer = PyObject_IsTrue(args[1]); - if (warn_on_full_buffer < 0) { - goto exit; + if (args[1]) { + warn_on_full_buffer = PyObject_IsTrue(args[1]); + if (warn_on_full_buffer < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } } + check_previous = args[2]; skip_optional_kwonly: - return_value = signal_set_wakeup_fd_impl(module, fdobj, warn_on_full_buffer); + return_value = signal_set_wakeup_fd_impl(module, fdobj, warn_on_full_buffer, check_previous); exit: return return_value; @@ -807,4 +834,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=2a04ec31f49b1c93 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=89ccccba73bb278d input=a9049054013a1b77]*/ diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index bc5aef55648e07..016c5f0a64bceb 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -126,6 +126,7 @@ typedef struct { PyObject *itimer_error; #endif PyTypeObject *siginfo_type; + PyTypeObject *wakeup_type; } _signal_module_state; @@ -703,6 +704,107 @@ signal_siginterrupt_impl(PyObject *module, int signalnum, int flag) #endif +PyDoc_STRVAR(signal_wakeup__doc__, +"signal.wakeup: Current signal wakeup configuration."); + +static PyStructSequence_Field signal_wakeup_fields[] = { + {"fd", "file descriptor or socket handle used to wake the main thread"}, + {"warn_on_full_buffer", "whether a full wakeup buffer reports warnings"}, + {"is_socket", "whether the wakeup fd is a socket on Windows"}, + {0} +}; + +static PyStructSequence_Desc signal_wakeup_desc = { + "signal.wakeup", + signal_wakeup__doc__, + signal_wakeup_fields, + 3 +}; + +/*[clinic input] +signal.get_wakeup + +Return the current signal wakeup configuration without modifying it. +[clinic start generated code]*/ + +static PyObject * +signal_get_wakeup_impl(PyObject *module) +/*[clinic end generated code: output=3f3acef64b5f426c input=4b9da8dd3f679d8c]*/ +{ + PyThreadState *tstate = _PyThreadState_GET(); + if (!_Py_ThreadCanHandleSignals(tstate->interp)) { + _PyErr_SetString(tstate, PyExc_ValueError, + "get_wakeup only works in main thread " + "of the main interpreter"); + return NULL; + } + + _signal_module_state *modstate = get_signal_state(module); + PyObject *result = PyStructSequence_New(modstate->wakeup_type); + if (result == NULL) { + return NULL; + } + +#ifdef MS_WINDOWS + if (wakeup.fd == INVALID_FD) { + PyStructSequence_SET_ITEM(result, 0, PyLong_FromLong(-1)); + } + else { + PyStructSequence_SET_ITEM(result, 0, + PyLong_FromSocket_t((SOCKET_T)wakeup.fd)); + } +#else + PyStructSequence_SET_ITEM(result, 0, PyLong_FromLong(wakeup.fd)); +#endif + PyStructSequence_SET_ITEM(result, 1, + PyBool_FromLong(wakeup.warn_on_full_buffer)); +#ifdef MS_WINDOWS + PyStructSequence_SET_ITEM(result, 2, PyBool_FromLong(wakeup.use_send)); +#else + PyStructSequence_SET_ITEM(result, 2, PyBool_FromLong(0)); +#endif + if (PyErr_Occurred()) { + Py_DECREF(result); + return NULL; + } + return result; +} + + +static int +signal_check_wakeup(PyObject *module, PyObject *check_previous) +{ + if (check_previous == Py_None) { + return 0; + } + else { + _signal_module_state *modstate = get_signal_state(module); + if (!PyObject_TypeCheck(check_previous, modstate->wakeup_type)) { + PyErr_SetString(PyExc_TypeError, + "check_previous must be signal.wakeup or None"); + return -1; + } + + PyObject *current = signal_get_wakeup(module, NULL); + if (current == NULL) { + return -1; + } + int equal = PyObject_RichCompareBool(check_previous, current, Py_EQ); + Py_DECREF(current); + if (equal < 0) { + return -1; + } + if (equal) { + return 0; + } + } + + PyErr_SetString(PyExc_RuntimeError, + "signal wakeup configuration changed"); + return -1; +} + + /*[clinic input] @permit_long_summary signal.set_wakeup_fd @@ -711,19 +813,21 @@ signal.set_wakeup_fd / * warn_on_full_buffer: bool = True + check_previous: object = None Sets the fd to be written to (with the signal number) when a signal comes in. A library can use this to wakeup select or poll. The previous fd or -1 is returned. -The fd must be non-blocking. +If check_previous is not None, it must equal the current signal.wakeup +value. The fd must be non-blocking. [clinic start generated code]*/ static PyObject * signal_set_wakeup_fd_impl(PyObject *module, PyObject *fdobj, - int warn_on_full_buffer) -/*[clinic end generated code: output=2280d72dd2a54c4f input=1b914d48079e9274]*/ + int warn_on_full_buffer, PyObject *check_previous) +/*[clinic end generated code: output=c89866b8ec8e2573 input=cbb7cc5082881dfe]*/ { struct _Py_stat_struct status; #ifdef MS_WINDOWS @@ -751,6 +855,10 @@ signal_set_wakeup_fd_impl(PyObject *module, PyObject *fdobj, return NULL; } + if (signal_check_wakeup(module, check_previous) < 0) { + return NULL; + } + #ifdef MS_WINDOWS is_socket = 0; if (sockfd != INVALID_FD) { @@ -1356,6 +1464,7 @@ static PyMethodDef signal_methods[] = { SIGNAL_STRSIGNAL_METHODDEF SIGNAL_GETSIGNAL_METHODDEF SIGNAL_SET_WAKEUP_FD_METHODDEF + SIGNAL_GET_WAKEUP_METHODDEF SIGNAL_SIGINTERRUPT_METHODDEF SIGNAL_PAUSE_METHODDEF SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF @@ -1656,13 +1765,19 @@ signal_module_exec(PyObject *m) } #endif + modstate->wakeup_type = PyStructSequence_NewType(&signal_wakeup_desc); + if (modstate->wakeup_type == NULL) { + return -1; + } + if (PyModule_AddType(m, modstate->wakeup_type) < 0) { + return -1; + } + #if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) modstate->siginfo_type = PyStructSequence_NewType(&struct_siginfo_desc); if (modstate->siginfo_type == NULL) { return -1; } -#endif -#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) if (PyModule_AddType(m, modstate->siginfo_type) < 0) { return -1; } @@ -1680,13 +1795,15 @@ signal_module_exec(PyObject *m) } -#ifdef PYHAVE_ITIMER_ERROR static int _signal_module_traverse(PyObject *module, visitproc visit, void *arg) { _signal_module_state *state = get_signal_state(module); +#ifdef PYHAVE_ITIMER_ERROR Py_VISIT(state->itimer_error); +#endif Py_VISIT(state->siginfo_type); + Py_VISIT(state->wakeup_type); return 0; } @@ -1694,8 +1811,11 @@ static int _signal_module_clear(PyObject *module) { _signal_module_state *state = get_signal_state(module); +#ifdef PYHAVE_ITIMER_ERROR Py_CLEAR(state->itimer_error); +#endif Py_CLEAR(state->siginfo_type); + Py_CLEAR(state->wakeup_type); return 0; } @@ -1704,7 +1824,6 @@ _signal_module_free(void *module) { _signal_module_clear((PyObject *)module); } -#endif // PYHAVE_ITIMER_ERROR static PyModuleDef_Slot signal_slots[] = { @@ -1722,11 +1841,9 @@ static struct PyModuleDef signal_module = { .m_size = sizeof(_signal_module_state), .m_methods = signal_methods, .m_slots = signal_slots, -#ifdef PYHAVE_ITIMER_ERROR .m_traverse = _signal_module_traverse, .m_clear = _signal_module_clear, .m_free = _signal_module_free, -#endif };