diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -179,7 +179,8 @@ will then be called. Returns nothing. Not on Windows. (See the Unix man page :manpage:`signal(2)`.) - See also :func:`sigwait` and :func:`sigpending`. + See also :func:`sigwait`, :func:`sigwaitinfo`, :func:`sigtimedwait` and + :func:`sigpending`. .. function:: pthread_kill(thread_id, signum) @@ -334,7 +335,43 @@ Availability: Unix (see the man page :manpage:`sigwait(3)` for further information). - See also :func:`pause`, :func:`pthread_sigmask` and :func:`sigpending`. + See also :func:`pause`, :func:`pthread_sigmask`, :func:`sigpending`, + :func:`sigwaitinfo` and :func:`sigtimedwait`. + + .. versionadded:: 3.3 + + +.. function:: sigwaitinfo(sigset) + + Suspend execution of the calling thread until the delivery of one of the + signals specified in the signal set *sigset*. The function accepts the + signal and removes it from the pending list of signals. If one of the + signals in *sigset* is already pending for the calling thread, the function + will return immediately with information about that signal. The signal + handler is not called for the delivered signal. The return value + is an object representing the data contained in the :c:type:`siginfo_t` + structure, namely: :attr:`si_signo`, :attr:`si_code`, :attr:`si_errno`, + :attr:`si_pid`, :attr:`si_uid`, :attr:`si_status`. + + Availability: Unix (see the man page :manpage:`sigwaitinfo(2)` for further + information). + + See also :func:`pause`, :func:`sigwait` and :func:`sigtimedwait`. + + .. versionadded:: 3.3 + + +.. function:: sigtimedwait(sigset, (timeout_sec, timeout_nsec)) + + Like :func:`sigtimedwait`, but takes a tuple of ``(seconds, nanoseconds)`` + as an additional argument specifying a timeout. If both *timeout_sec* and + *timeout_nsec* are specified as :const:`0`, a poll is performed. Returns + :const:`None` if a timeout occurs. + + Availability: Unix (see the man page :manpage:`sigtimedwait(2)` for further + information). + + See also :func:`pause`, :func:`sigwait` and :func:`sigwaitinfo`. .. versionadded:: 3.3 diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -495,6 +495,7 @@ functions. """ def setUp(self): + self.hndl_called = False self.has_pthread_kill = hasattr(signal, 'pthread_kill') def handler(self, signum, frame): @@ -582,39 +583,28 @@ with self.assertRaises(ZeroDivisionError): signal.pthread_kill(current, signum) - @unittest.skipUnless(hasattr(signal, 'sigwait'), - 'need signal.sigwait()') @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), 'need signal.pthread_sigmask()') @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork()') - def test_sigwait(self): - def test(signum): - signal.alarm(1) - received = signal.sigwait([signum]) - if received != signum: - print("sigwait() received %s, not %s" - % (received, signum), - file=sys.stderr) - os._exit(1) - + def _wait_helper(self, test, handler, blocked=signal.SIGALRM): signum = signal.SIGALRM - # sigwait must be called with the signal blocked: since the current + # sig*wait* must be called with the signal blocked: since the current # process might have several threads running, we fork() a child process # to have a single thread. pid = os.fork() if pid == 0: # child: block and wait the signal try: - signal.signal(signum, self.handler) - signal.pthread_sigmask(signal.SIG_BLOCK, [signum]) + signal.signal(signum, handler) + signal.pthread_sigmask(signal.SIG_BLOCK, [blocked]) # Do the tests test(signum) # The handler must not be called on unblock try: - signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum]) + signal.pthread_sigmask(signal.SIG_UNBLOCK, [blocked]) except ZeroDivisionError: print("the signal handler has been called", file=sys.stderr) @@ -630,6 +620,88 @@ @unittest.skipUnless(hasattr(signal, 'sigwait'), 'need signal.sigwait()') + def test_sigwait(self): + def test(signum): + signal.alarm(1) + received = signal.sigwait([signum]) + if received != signum: + print("sigwait() received %s, not %s" + % (received, signum), + file=sys.stderr) + os._exit(1) + + self._wait_helper(test, self.handler) + + @unittest.skipUnless(hasattr(signal, 'sigwaitinfo'), + 'need signal.sigwaitinfo()') + def test_sigwaitinfo(self): + def test(signum): + signal.alarm(1) + info = signal.sigwaitinfo([signum]) + self.assertEqual(signum, info.si_signo) + + self._wait_helper(test, self.handler) + + @unittest.skipUnless(hasattr(signal, 'sigtimedwait'), + 'need signal.sigtimedwait()') + def test_sigtimedwait(self): + def test(signum): + signal.alarm(1) + info = signal.sigtimedwait([signum], (10, 1000)) + self.assertEqual(signum, info.si_signo) + + self._wait_helper(test, self.handler) + + # check that polling with sigtimedwait works + @unittest.skipUnless(hasattr(signal, 'sigtimedwait'), + 'need signal.sigtimedwait()') + def test_sigtimedwait_poll(self): + def test(signum): + self.kill(signum) + info = signal.sigtimedwait([signum], (0, 0)) + self.assertEqual(signum, info.si_signo) + + self._wait_helper(test, self.handler) + + @unittest.skipUnless(hasattr(signal, 'sigtimedwait'), + 'need signal.sigtimedwait()') + def test_sigtimedwait_timeout(self): + def test(signum): + self.assertEqual(None, signal.sigtimedwait([signum], (1, 35500))) + + self._wait_helper(test, self.handler) + + @unittest.skipUnless(hasattr(signal, 'sigtimedwait'), + 'need signal.sigtimedwait()') + def test_sigtimedwait_negative_timeout(self): + def test(signum): + self.assertRaises(ValueError, + signal.sigtimedwait, [signum], (-1, -1)) + + self._wait_helper(test, self.handler) + + def alarm_handler(self, signum, frame): + self.hndl_called = True + + @unittest.skipUnless(hasattr(signal, 'sigwaitinfo'), + 'need signal.sigwaitinfo()') + def test_sigwaitinfo_interrupted(self): + def test(signum): + signal.alarm(1) + try: + signal.sigwaitinfo([signal.SIGUSR1]) + except OSError as e: + if e.errno == errno.EINTR: + self.assertTrue(self.hndl_called) + else: + self.fail("Expected EINTR to be raised by sigwaitinfo") + else: + self.fail("Expected EINTR to be raised by sigwaitinfo") + + self._wait_helper(test, self.alarm_handler, signal.SIGUSR1) + + @unittest.skipUnless(hasattr(signal, 'sigwait'), + 'need signal.sigwait()') @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), 'need signal.pthread_sigmask()') @unittest.skipIf(threading is None, "test needs threading module") diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -507,7 +507,8 @@ Returns current value of given itimer."); #endif -#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT) +#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT) || \ + defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) /* Convert an iterable to a sigset. Return 0 on success, return -1 and raise an exception on error. */ @@ -679,6 +680,138 @@ Wait a signal."); #endif /* #ifdef HAVE_SIGPENDING */ +#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) +static int initialized; +static PyStructSequence_Field struct_siginfo_fields[] = { + {"si_signo", "signal number"}, + {"si_code", "signal code"}, + {"si_errno", "errno associated with this signal"}, + {"si_pid", "sending process ID"}, + {"si_uid", "real user ID of sending process"}, + {"si_status", "exit value or signal"}, + {0} +}; + +PyDoc_STRVAR(struct_siginfo__doc__, +"struct_siginfo: Result from sigwaitinfo or sigtimedwait.\n\n\ +This object may be accessed either as a tuple of\n\ +(si_signo, si_code, si_errno, si_pid, si_uid, si_status),\n\ +or via the attributes si_signo, si_code, and so on."); + +static PyStructSequence_Desc struct_siginfo_desc = { + "signal.struct_siginfo", /* name */ + struct_siginfo__doc__, /* doc */ + struct_siginfo_fields, /* fields */ + 6 /* n_in_sequence */ +}; + +static PyTypeObject SiginfoType; + +static PyObject * +_fill_siginfo(siginfo_t *si) +{ + PyObject *result = PyStructSequence_New(&SiginfoType); + if (!result) + return NULL; + + PyStructSequence_SET_ITEM(result, 0, PyLong_FromLong((long)(si->si_signo))); + PyStructSequence_SET_ITEM(result, 1, PyLong_FromLong((long)(si->si_code))); + PyStructSequence_SET_ITEM(result, 2, PyLong_FromLong((long)(si->si_errno))); + PyStructSequence_SET_ITEM(result, 3, PyLong_FromPid(si->si_pid)); + PyStructSequence_SET_ITEM(result, 4, PyLong_FromPid(si->si_uid)); + PyStructSequence_SET_ITEM(result, 5, + PyLong_FromLong((long)(si->si_status))); + if (PyErr_Occurred()) { + Py_DECREF(result); + return NULL; + } + + return result; +} +#endif + +#ifdef HAVE_SIGWAITINFO +static PyObject * +signal_sigwaitinfo(PyObject *self, PyObject *args) +{ + PyObject *signals; + sigset_t set; + siginfo_t si; + int err; + + if (!PyArg_ParseTuple(args, "O:sigwaitinfo", &signals)) + return NULL; + + if (iterable_to_sigset(signals, &set)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + err = sigwaitinfo(&set, &si); + Py_END_ALLOW_THREADS + if (err == -1) + return PyErr_SetFromErrno(PyExc_OSError); + + return _fill_siginfo(&si); +} + +PyDoc_STRVAR(signal_sigwaitinfo_doc, +"sigwaitinfo(sigset) -> struct_siginfo\n\ +\n\ +Wait synchronously for a signal until one of the signals in *sigset* is\n\ +delivered.\n\ +Returns a struct_siginfo containing information about the signal."); +#endif /* #ifdef HAVE_SIGWAITINFO */ + +#ifdef HAVE_SIGTIMEDWAIT +static PyObject * +signal_sigtimedwait(PyObject *self, PyObject *args) +{ + PyObject *signals, *timeout; + struct timespec buf; + sigset_t set; + siginfo_t si; + int err; + + if (!PyArg_ParseTuple(args, "OO:sigtimedwait", &signals, &timeout)) + return NULL; + + if (!PyTuple_Check(timeout) || PyTuple_Size(timeout) != 2) { + PyErr_SetString(PyExc_TypeError, + "sigtimedwait() arg 2 must be a tuple " + "(timeout_sec, timeout_nsec)"); + return NULL; + } else if (!PyArg_ParseTuple(timeout, "ll:sigtimedwait", + &(buf.tv_sec), &(buf.tv_nsec))) + return NULL; + + if (buf.tv_sec < 0 || buf.tv_nsec < 0) { + PyErr_SetString(PyExc_ValueError, "timeout must be non-negative"); + return NULL; + } + + if (iterable_to_sigset(signals, &set)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + err = sigtimedwait(&set, &si, &buf); + Py_END_ALLOW_THREADS + if (err == -1) { + if (errno == EAGAIN) + Py_RETURN_NONE; + else + return PyErr_SetFromErrno(PyExc_OSError); + } + + return _fill_siginfo(&si); +} + +PyDoc_STRVAR(signal_sigtimedwait_doc, +"sigtimedwait(sigset, (timeout_sec, timeout_nsec)) -> struct_siginfo\n\ +\n\ +Like sigwaitinfo(), but with a timeout specified as a tuple of (seconds,\n\ +nanoseconds)."); +#endif /* #ifdef HAVE_SIGTIMEDWAIT */ + #if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD) static PyObject * @@ -752,6 +885,14 @@ {"sigwait", (PyCFunction)signal_sigwait, METH_VARARGS, signal_sigwait_doc}, #endif +#ifdef HAVE_SIGWAITINFO + {"sigwaitinfo", (PyCFunction)signal_sigwaitinfo, + METH_VARARGS, signal_sigwaitinfo_doc}, +#endif +#ifdef HAVE_SIGTIMEDWAIT + {"sigtimedwait", (PyCFunction)signal_sigtimedwait, + METH_VARARGS, signal_sigtimedwait_doc}, +#endif {NULL, NULL} /* sentinel */ }; @@ -820,6 +961,15 @@ if (m == NULL) return NULL; +#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) + if (!initialized) + PyStructSequence_InitType(&SiginfoType, &struct_siginfo_desc); + + Py_INCREF((PyObject*) &SiginfoType); + PyModule_AddObject(m, "struct_siginfo", (PyObject*) &SiginfoType); + initialized = 1; +#endif + /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); diff --git a/configure.in b/configure.in --- a/configure.in +++ b/configure.in @@ -2552,8 +2552,8 @@ select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid sethostname \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ - sigaction sigaltstack siginterrupt sigpending \ - sigrelse sigwait snprintf strftime strlcpy symlinkat sync \ + sigaction sigaltstack siginterrupt sigpending sigrelse \ + sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm writev _getpty)