Skip to content

Commit 9d3627e

Browse files
authored
bpo-33332: Add signal.valid_signals() (GH-6581)
1 parent 491bbed commit 9d3627e

File tree

14 files changed

+171
-26
lines changed

14 files changed

+171
-26
lines changed

Doc/library/signal.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ The :mod:`signal` module defines the following functions:
216216
.. versionadded:: 3.8
217217

218218

219+
.. function:: valid_signals()
220+
221+
Return the set of valid signal numbers on this platform. This can be
222+
less than ``range(1, NSIG)`` if some signals are reserved by the system
223+
for internal use.
224+
225+
.. versionadded:: 3.8
226+
227+
219228
.. function:: pause()
220229

221230
Cause the process to sleep until a signal is received; the appropriate handler
@@ -268,8 +277,8 @@ The :mod:`signal` module defines the following functions:
268277
argument.
269278

270279
*mask* is a set of signal numbers (e.g. {:const:`signal.SIGINT`,
271-
:const:`signal.SIGTERM`}). Use ``range(1, signal.NSIG)`` for a full mask
272-
including all signals.
280+
:const:`signal.SIGTERM`}). Use :func:`~signal.valid_signals` for a full
281+
mask including all signals.
273282

274283
For example, ``signal.pthread_sigmask(signal.SIG_BLOCK, [])`` reads the
275284
signal mask of the calling thread.

Lib/asyncio/unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ def _check_signal(self, sig):
167167
if not isinstance(sig, int):
168168
raise TypeError(f'sig must be an int, not {sig!r}')
169169

170-
if not (1 <= sig < signal.NSIG):
171-
raise ValueError(f'sig {sig} out of range(1, {signal.NSIG})')
170+
if sig not in signal.valid_signals():
171+
raise ValueError(f'invalid signal number {sig}')
172172

173173
def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
174174
extra=None):

Lib/multiprocessing/resource_sharer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _start(self):
136136

137137
def _serve(self):
138138
if hasattr(signal, 'pthread_sigmask'):
139-
signal.pthread_sigmask(signal.SIG_BLOCK, range(1, signal.NSIG))
139+
signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
140140
while 1:
141141
try:
142142
with self._listener.accept() as conn:

Lib/signal.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ def pthread_sigmask(how, mask):
6565
if 'sigpending' in _globals:
6666
@_wraps(_signal.sigpending)
6767
def sigpending():
68-
sigs = _signal.sigpending()
69-
return set(_int_to_enum(x, Signals) for x in sigs)
68+
return {_int_to_enum(x, Signals) for x in _signal.sigpending()}
7069

7170

7271
if 'sigwait' in _globals:
@@ -76,4 +75,11 @@ def sigwait(sigset):
7675
return _int_to_enum(retsig, Signals)
7776
sigwait.__doc__ = _signal.sigwait
7877

78+
79+
if 'valid_signals' in _globals:
80+
@_wraps(_signal.valid_signals)
81+
def valid_signals():
82+
return {_int_to_enum(x, Signals) for x in _signal.valid_signals()}
83+
84+
7985
del _globals, _wraps

Lib/test/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2810,7 +2810,7 @@ class SaveSignals:
28102810
def __init__(self):
28112811
import signal
28122812
self.signal = signal
2813-
self.signals = list(range(1, signal.NSIG))
2813+
self.signals = signal.valid_signals()
28142814
# SIGKILL and SIGSTOP signals cannot be ignored nor caught
28152815
for signame in ('SIGKILL', 'SIGSTOP'):
28162816
try:

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def test_handle_signal_cancelled_handler(self):
6969
@mock.patch('asyncio.unix_events.signal')
7070
def test_add_signal_handler_setup_error(self, m_signal):
7171
m_signal.NSIG = signal.NSIG
72+
m_signal.valid_signals = signal.valid_signals
7273
m_signal.set_wakeup_fd.side_effect = ValueError
7374

7475
self.assertRaises(
@@ -96,6 +97,7 @@ async def simple_coroutine():
9697
@mock.patch('asyncio.unix_events.signal')
9798
def test_add_signal_handler(self, m_signal):
9899
m_signal.NSIG = signal.NSIG
100+
m_signal.valid_signals = signal.valid_signals
99101

100102
cb = lambda: True
101103
self.loop.add_signal_handler(signal.SIGHUP, cb)
@@ -106,6 +108,7 @@ def test_add_signal_handler(self, m_signal):
106108
@mock.patch('asyncio.unix_events.signal')
107109
def test_add_signal_handler_install_error(self, m_signal):
108110
m_signal.NSIG = signal.NSIG
111+
m_signal.valid_signals = signal.valid_signals
109112

110113
def set_wakeup_fd(fd):
111114
if fd == -1:
@@ -125,6 +128,7 @@ class Err(OSError):
125128
@mock.patch('asyncio.base_events.logger')
126129
def test_add_signal_handler_install_error2(self, m_logging, m_signal):
127130
m_signal.NSIG = signal.NSIG
131+
m_signal.valid_signals = signal.valid_signals
128132

129133
class Err(OSError):
130134
errno = errno.EINVAL
@@ -145,6 +149,7 @@ class Err(OSError):
145149
errno = errno.EINVAL
146150
m_signal.signal.side_effect = Err
147151
m_signal.NSIG = signal.NSIG
152+
m_signal.valid_signals = signal.valid_signals
148153

149154
self.assertRaises(
150155
RuntimeError,
@@ -156,6 +161,7 @@ class Err(OSError):
156161
@mock.patch('asyncio.unix_events.signal')
157162
def test_remove_signal_handler(self, m_signal):
158163
m_signal.NSIG = signal.NSIG
164+
m_signal.valid_signals = signal.valid_signals
159165

160166
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
161167

@@ -170,6 +176,7 @@ def test_remove_signal_handler(self, m_signal):
170176
def test_remove_signal_handler_2(self, m_signal):
171177
m_signal.NSIG = signal.NSIG
172178
m_signal.SIGINT = signal.SIGINT
179+
m_signal.valid_signals = signal.valid_signals
173180

174181
self.loop.add_signal_handler(signal.SIGINT, lambda: True)
175182
self.loop._signal_handlers[signal.SIGHUP] = object()
@@ -187,6 +194,7 @@ def test_remove_signal_handler_2(self, m_signal):
187194
@mock.patch('asyncio.base_events.logger')
188195
def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
189196
m_signal.NSIG = signal.NSIG
197+
m_signal.valid_signals = signal.valid_signals
190198
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
191199

192200
m_signal.set_wakeup_fd.side_effect = ValueError
@@ -197,6 +205,7 @@ def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
197205
@mock.patch('asyncio.unix_events.signal')
198206
def test_remove_signal_handler_error(self, m_signal):
199207
m_signal.NSIG = signal.NSIG
208+
m_signal.valid_signals = signal.valid_signals
200209
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
201210

202211
m_signal.signal.side_effect = OSError
@@ -207,6 +216,7 @@ def test_remove_signal_handler_error(self, m_signal):
207216
@mock.patch('asyncio.unix_events.signal')
208217
def test_remove_signal_handler_error2(self, m_signal):
209218
m_signal.NSIG = signal.NSIG
219+
m_signal.valid_signals = signal.valid_signals
210220
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
211221

212222
class Err(OSError):
@@ -219,6 +229,7 @@ class Err(OSError):
219229
@mock.patch('asyncio.unix_events.signal')
220230
def test_close(self, m_signal):
221231
m_signal.NSIG = signal.NSIG
232+
m_signal.valid_signals = signal.valid_signals
222233

223234
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
224235
self.loop.add_signal_handler(signal.SIGCHLD, lambda: True)
@@ -236,6 +247,7 @@ def test_close(self, m_signal):
236247
@mock.patch('asyncio.unix_events.signal')
237248
def test_close_on_finalizing(self, m_signal, m_sys):
238249
m_signal.NSIG = signal.NSIG
250+
m_signal.valid_signals = signal.valid_signals
239251
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
240252

241253
self.assertEqual(len(self.loop._signal_handlers), 1)

Lib/test/test_signal.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,28 @@ def test_interprocess_signal(self):
6767
script = os.path.join(dirname, 'signalinterproctester.py')
6868
assert_python_ok(script)
6969

70+
def test_valid_signals(self):
71+
s = signal.valid_signals()
72+
self.assertIsInstance(s, set)
73+
self.assertIn(signal.Signals.SIGINT, s)
74+
self.assertIn(signal.Signals.SIGALRM, s)
75+
self.assertNotIn(0, s)
76+
self.assertNotIn(signal.NSIG, s)
77+
self.assertLess(len(s), signal.NSIG)
78+
7079

7180
@unittest.skipUnless(sys.platform == "win32", "Windows specific")
7281
class WindowsSignalTests(unittest.TestCase):
82+
83+
def test_valid_signals(self):
84+
s = signal.valid_signals()
85+
self.assertIsInstance(s, set)
86+
self.assertGreaterEqual(len(s), 6)
87+
self.assertIn(signal.Signals.SIGINT, s)
88+
self.assertNotIn(0, s)
89+
self.assertNotIn(signal.NSIG, s)
90+
self.assertLess(len(s), signal.NSIG)
91+
7392
def test_issue9324(self):
7493
# Updated for issue #10003, adding SIGBREAK
7594
handler = lambda x, y: None
@@ -922,6 +941,17 @@ def test_pthread_sigmask_arguments(self):
922941
self.assertRaises(TypeError, signal.pthread_sigmask, 1)
923942
self.assertRaises(TypeError, signal.pthread_sigmask, 1, 2, 3)
924943
self.assertRaises(OSError, signal.pthread_sigmask, 1700, [])
944+
with self.assertRaises(ValueError):
945+
signal.pthread_sigmask(signal.SIG_BLOCK, [signal.NSIG])
946+
947+
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
948+
'need signal.pthread_sigmask()')
949+
def test_pthread_sigmask_valid_signals(self):
950+
s = signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
951+
self.addCleanup(signal.pthread_sigmask, signal.SIG_SETMASK, s)
952+
# Get current blocked set
953+
s = signal.pthread_sigmask(signal.SIG_UNBLOCK, signal.valid_signals())
954+
self.assertLessEqual(s, signal.valid_signals())
925955

926956
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
927957
'need signal.pthread_sigmask()')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``signal.valid_signals()`` to expose the POSIX sigfillset()
2+
functionality.

Modules/clinic/signalmodule.c.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,31 @@ PyDoc_STRVAR(signal_sigwait__doc__,
341341

342342
#endif /* defined(HAVE_SIGWAIT) */
343343

344+
#if (defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS))
345+
346+
PyDoc_STRVAR(signal_valid_signals__doc__,
347+
"valid_signals($module, /)\n"
348+
"--\n"
349+
"\n"
350+
"Return a set of valid signal numbers on this platform.\n"
351+
"\n"
352+
"The signal numbers returned by this function can be safely passed to\n"
353+
"functions like `pthread_sigmask`.");
354+
355+
#define SIGNAL_VALID_SIGNALS_METHODDEF \
356+
{"valid_signals", (PyCFunction)signal_valid_signals, METH_NOARGS, signal_valid_signals__doc__},
357+
358+
static PyObject *
359+
signal_valid_signals_impl(PyObject *module);
360+
361+
static PyObject *
362+
signal_valid_signals(PyObject *module, PyObject *Py_UNUSED(ignored))
363+
{
364+
return signal_valid_signals_impl(module);
365+
}
366+
367+
#endif /* (defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)) */
368+
344369
#if defined(HAVE_SIGWAITINFO)
345370

346371
PyDoc_STRVAR(signal_sigwaitinfo__doc__,
@@ -459,6 +484,10 @@ signal_pthread_kill(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
459484
#define SIGNAL_SIGWAIT_METHODDEF
460485
#endif /* !defined(SIGNAL_SIGWAIT_METHODDEF) */
461486

487+
#ifndef SIGNAL_VALID_SIGNALS_METHODDEF
488+
#define SIGNAL_VALID_SIGNALS_METHODDEF
489+
#endif /* !defined(SIGNAL_VALID_SIGNALS_METHODDEF) */
490+
462491
#ifndef SIGNAL_SIGWAITINFO_METHODDEF
463492
#define SIGNAL_SIGWAITINFO_METHODDEF
464493
#endif /* !defined(SIGNAL_SIGWAITINFO_METHODDEF) */
@@ -470,4 +499,4 @@ signal_pthread_kill(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
470499
#ifndef SIGNAL_PTHREAD_KILL_METHODDEF
471500
#define SIGNAL_PTHREAD_KILL_METHODDEF
472501
#endif /* !defined(SIGNAL_PTHREAD_KILL_METHODDEF) */
473-
/*[clinic end generated code: output=7b41486acf93aa8e input=a9049054013a1b77]*/
502+
/*[clinic end generated code: output=f35d79e0cfee3f1b input=a9049054013a1b77]*/

Modules/signalmodule.c

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -841,11 +841,21 @@ iterable_to_sigset(PyObject *iterable, sigset_t *mask)
841841
if (signum == -1 && PyErr_Occurred())
842842
goto error;
843843
if (0 < signum && signum < NSIG) {
844-
/* bpo-33329: ignore sigaddset() return value as it can fail
845-
* for some reserved signals, but we want the `range(1, NSIG)`
846-
* idiom to allow selecting all valid signals.
847-
*/
848-
(void) sigaddset(mask, (int)signum);
844+
if (sigaddset(mask, (int)signum)) {
845+
if (errno != EINVAL) {
846+
/* Probably impossible */
847+
PyErr_SetFromErrno(PyExc_OSError);
848+
goto error;
849+
}
850+
/* For backwards compatibility, allow idioms such as
851+
* `range(1, NSIG)` but warn about invalid signal numbers
852+
*/
853+
const char *msg =
854+
"invalid signal number %ld, please use valid_signals()";
855+
if (PyErr_WarnFormat(PyExc_RuntimeWarning, 1, msg, signum)) {
856+
goto error;
857+
}
858+
}
849859
}
850860
else {
851861
PyErr_Format(PyExc_ValueError,
@@ -1001,6 +1011,47 @@ signal_sigwait(PyObject *module, PyObject *sigset)
10011011
#endif /* #ifdef HAVE_SIGWAIT */
10021012

10031013

1014+
#if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)
1015+
1016+
/*[clinic input]
1017+
signal.valid_signals
1018+
1019+
Return a set of valid signal numbers on this platform.
1020+
1021+
The signal numbers returned by this function can be safely passed to
1022+
functions like `pthread_sigmask`.
1023+
[clinic start generated code]*/
1024+
1025+
static PyObject *
1026+
signal_valid_signals_impl(PyObject *module)
1027+
/*[clinic end generated code: output=1609cffbcfcf1314 input=86a3717ff25288f2]*/
1028+
{
1029+
#ifdef MS_WINDOWS
1030+
#ifdef SIGBREAK
1031+
PyObject *tup = Py_BuildValue("(iiiiiii)", SIGABRT, SIGBREAK, SIGFPE,
1032+
SIGILL, SIGINT, SIGSEGV, SIGTERM);
1033+
#else
1034+
PyObject *tup = Py_BuildValue("(iiiiii)", SIGABRT, SIGFPE, SIGILL,
1035+
SIGINT, SIGSEGV, SIGTERM);
1036+
#endif
1037+
if (tup == NULL) {
1038+
return NULL;
1039+
}
1040+
PyObject *set = PySet_New(tup);
1041+
Py_DECREF(tup);
1042+
return set;
1043+
#else
1044+
sigset_t mask;
1045+
if (sigemptyset(&mask) || sigfillset(&mask)) {
1046+
return PyErr_SetFromErrno(PyExc_OSError);
1047+
}
1048+
return sigset_to_set(mask);
1049+
#endif
1050+
}
1051+
1052+
#endif /* #if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS) */
1053+
1054+
10041055
#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
10051056
static int initialized;
10061057
static PyStructSequence_Field struct_siginfo_fields[] = {
@@ -1225,6 +1276,9 @@ static PyMethodDef signal_methods[] = {
12251276
SIGNAL_SIGWAIT_METHODDEF
12261277
SIGNAL_SIGWAITINFO_METHODDEF
12271278
SIGNAL_SIGTIMEDWAIT_METHODDEF
1279+
#if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)
1280+
SIGNAL_VALID_SIGNALS_METHODDEF
1281+
#endif
12281282
{NULL, NULL} /* sentinel */
12291283
};
12301284

0 commit comments

Comments
 (0)