diff --git a/Lib/signal.py b/Lib/signal.py index d4a6d6fe2ada8b..4c0f908ab4cf65 100644 --- a/Lib/signal.py +++ b/Lib/signal.py @@ -12,10 +12,6 @@ and (name.startswith('SIG') and not name.startswith('SIG_')) or name.startswith('CTRL_')) -_IntEnum._convert_( - 'Handlers', __name__, - lambda name: name in ('SIG_DFL', 'SIG_IGN')) - if 'pthread_sigmask' in _globals: _IntEnum._convert_( 'Sigmasks', __name__, @@ -32,28 +28,6 @@ def _int_to_enum(value, enum_klass): return value -def _enum_to_int(value): - """Convert an IntEnum member to a numeric value. - If it's not an IntEnum member return the value itself. - """ - try: - return int(value) - except (ValueError, TypeError): - return value - - -@_wraps(_signal.signal) -def signal(signalnum, handler): - handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) - return _int_to_enum(handler, Handlers) - - -@_wraps(_signal.getsignal) -def getsignal(signalnum): - handler = _signal.getsignal(signalnum) - return _int_to_enum(handler, Handlers) - - if 'pthread_sigmask' in _globals: @_wraps(_signal.pthread_sigmask) def pthread_sigmask(how, mask): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 45553a6a42de79..7432910d5570cf 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1,5 +1,7 @@ +import copy import errno import os +import pickle import random import signal import socket @@ -21,9 +23,7 @@ class GenericTests(unittest.TestCase): def test_enums(self): for name in dir(signal): sig = getattr(signal, name) - if name in {'SIG_DFL', 'SIG_IGN'}: - self.assertIsInstance(sig, signal.Handlers) - elif name in {'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'}: + if name in {'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'}: self.assertIsInstance(sig, signal.Sigmasks) elif name.startswith('SIG') and not name.startswith('SIG_'): self.assertIsInstance(sig, signal.Signals) @@ -31,6 +31,25 @@ def test_enums(self): self.assertIsInstance(sig, signal.Signals) self.assertEqual(sys.platform, "win32") + def test_standard_handlers(self): + for name in 'SIG_DFL', 'SIG_IGN': + with self.subTest(name): + handler = getattr(signal, name) + self.assertIn(name, repr(handler)) + self.assertNotEqual(handler.__doc__, type(handler).__doc__) + + self.assertIs(copy.copy(handler), handler) + self.assertIs(copy.deepcopy(handler), handler) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + p = pickle.dumps(handler, proto) + self.assertIs(pickle.loads(p), handler) + + old_handler = signal.signal(signal.SIGINT, handler) + try: + self.assertIs(signal.getsignal(signal.SIGINT), handler) + finally: + signal.signal(signal.SIGINT, old_handler) + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class PosixTests(unittest.TestCase): @@ -51,7 +70,6 @@ def test_setting_signal_handler_to_none_raises_error(self): def test_getsignal(self): hup = signal.signal(signal.SIGHUP, self.trivial_signal_handler) - self.assertIsInstance(hup, signal.Handlers) self.assertEqual(signal.getsignal(signal.SIGHUP), self.trivial_signal_handler) signal.signal(signal.SIGHUP, hup) diff --git a/Lib/unittest/signals.py b/Lib/unittest/signals.py index e6a5fc52439712..29180dfd936133 100644 --- a/Lib/unittest/signals.py +++ b/Lib/unittest/signals.py @@ -10,19 +10,9 @@ class _InterruptHandler(object): def __init__(self, default_handler): self.called = False self.original_handler = default_handler - if isinstance(default_handler, int): - if default_handler == signal.SIG_DFL: - # Pretend it's signal.default_int_handler instead. - default_handler = signal.default_int_handler - elif default_handler == signal.SIG_IGN: - # Not quite the same thing as SIG_IGN, but the closest we - # can make it: do nothing. - def default_handler(unused_signum, unused_frame): - pass - else: - raise TypeError("expected SIGINT signal handler to be " - "signal.SIG_IGN, signal.SIG_DFL, or a " - "callable object") + if default_handler == signal.SIG_DFL: + # Pretend it's signal.default_int_handler instead. + default_handler = signal.default_int_handler self.default_handler = default_handler def __call__(self, signum, frame): diff --git a/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst b/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst new file mode 100644 index 00000000000000..0eae5d7dfe4ff6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst @@ -0,0 +1,2 @@ +Singletons :data:`~signal.SIG_DFL` and :data:`~signal.SIG_IGN` are no longer +integers. They are now copyable and pickleable and have docstrings. diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h index 33a278e488f943..8cea548c199be9 100644 --- a/Modules/clinic/signalmodule.c.h +++ b/Modules/clinic/signalmodule.c.h @@ -556,6 +556,72 @@ signal_pthread_kill(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* defined(HAVE_PTHREAD_KILL) */ +PyDoc_STRVAR(signal_SIG_DFL__doc__, +"SIG_DFL($module, signalnum, frame, /)\n" +"--\n" +"\n" +"Standard signal handler used to refer to the system default handler."); + +#define SIGNAL_SIG_DFL_METHODDEF \ + {"SIG_DFL", (PyCFunction)(void(*)(void))signal_SIG_DFL, METH_FASTCALL, signal_SIG_DFL__doc__}, + +static PyObject * +signal_SIG_DFL_impl(PyObject *module, int signalnum, PyObject *frame); + +static PyObject * +signal_SIG_DFL(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int signalnum; + PyObject *frame; + + if (!_PyArg_CheckPositional("SIG_DFL", nargs, 2, 2)) { + goto exit; + } + signalnum = _PyLong_AsInt(args[0]); + if (signalnum == -1 && PyErr_Occurred()) { + goto exit; + } + frame = args[1]; + return_value = signal_SIG_DFL_impl(module, signalnum, frame); + +exit: + return return_value; +} + +PyDoc_STRVAR(signal_SIG_IGN__doc__, +"SIG_IGN($module, signalnum, frame, /)\n" +"--\n" +"\n" +"Standard signal handler used to ignore the given signal."); + +#define SIGNAL_SIG_IGN_METHODDEF \ + {"SIG_IGN", (PyCFunction)(void(*)(void))signal_SIG_IGN, METH_FASTCALL, signal_SIG_IGN__doc__}, + +static PyObject * +signal_SIG_IGN_impl(PyObject *module, int signalnum, PyObject *frame); + +static PyObject * +signal_SIG_IGN(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int signalnum; + PyObject *frame; + + if (!_PyArg_CheckPositional("SIG_IGN", nargs, 2, 2)) { + goto exit; + } + signalnum = _PyLong_AsInt(args[0]); + if (signalnum == -1 && PyErr_Occurred()) { + goto exit; + } + frame = args[1]; + return_value = signal_SIG_IGN_impl(module, signalnum, frame); + +exit: + return return_value; +} + #if (defined(__linux__) && defined(__NR_pidfd_send_signal)) PyDoc_STRVAR(signal_pidfd_send_signal__doc__, @@ -662,4 +728,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=dff93c869101f043 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=66c16d84d35950bc input=a9049054013a1b77]*/ diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index ef3536a210b04c..1279de4d654370 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -1261,6 +1261,42 @@ signal_pthread_kill_impl(PyObject *module, unsigned long thread_id, #endif /* #if defined(HAVE_PTHREAD_KILL) */ +/*[clinic input] +signal.SIG_DFL + signalnum: int + frame: object + / + +Standard signal handler used to refer to the system default handler. +[clinic start generated code]*/ + +static PyObject * +signal_SIG_DFL_impl(PyObject *module, int signalnum, PyObject *frame) +/*[clinic end generated code: output=7293f25d5f7a3415 input=9df97af30cd58214]*/ +{ + PyErr_SetString(PyExc_TypeError, + "system default handler can't be called directly"); + return NULL; +} + + +/*[clinic input] +signal.SIG_IGN + signalnum: int + frame: object + / + +Standard signal handler used to ignore the given signal. +[clinic start generated code]*/ + +static PyObject * +signal_SIG_IGN_impl(PyObject *module, int signalnum, PyObject *frame) +/*[clinic end generated code: output=8b52aad79325460a input=1b49e704cb42377a]*/ +{ + Py_RETURN_NONE; +} + + #if defined(__linux__) && defined(__NR_pidfd_send_signal) /*[clinic input] signal.pidfd_send_signal @@ -1318,6 +1354,8 @@ static PyMethodDef signal_methods[] = { #if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS) SIGNAL_VALID_SIGNALS_METHODDEF #endif + SIGNAL_SIG_DFL_METHODDEF + SIGNAL_SIG_IGN_METHODDEF {NULL, NULL} /* sentinel */ }; @@ -1337,8 +1375,6 @@ pause() -- wait until a signal arrives [Unix only]\n\ default_int_handler() -- default SIGINT handler\n\ \n\ signal constants:\n\ -SIG_DFL -- used to refer to the system default handler\n\ -SIG_IGN -- used to ignore the signal\n\ NSIG -- number of defined signals\n\ SIGINT, SIGTERM, etc. -- signal numbers\n\ \n\ @@ -1394,17 +1430,15 @@ PyInit__signal(void) /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); - DefaultHandler = PyLong_FromVoidPtr((void *)SIG_DFL); - if (!DefaultHandler || - PyDict_SetItemString(d, "SIG_DFL", DefaultHandler) < 0) { + DefaultHandler = PyDict_GetItemString(d, "SIG_DFL"); + if (!DefaultHandler) goto finally; - } + Py_INCREF(DefaultHandler); - IgnoreHandler = PyLong_FromVoidPtr((void *)SIG_IGN); - if (!IgnoreHandler || - PyDict_SetItemString(d, "SIG_IGN", IgnoreHandler) < 0) { + IgnoreHandler = PyDict_GetItemString(d, "SIG_IGN"); + if (!IgnoreHandler) goto finally; - } + Py_INCREF(IgnoreHandler); if (PyModule_AddIntMacro(m, NSIG)) goto finally;