From 34f20eac987b1ca006ebdfe5531a6931243294fb Mon Sep 17 00:00:00 2001 From: "Michael W. Hudson" Date: Mon, 27 May 2002 15:08:24 +0000 Subject: [PATCH] This is patch [ 559250 ] more POSIX signal stuff Adds support (and docs and tests and autoconfery) for posix signal mask handling -- sigpending, sigprocmask and sigsuspend. --- Doc/lib/libsignal.tex | 54 +++++++++++- Doc/whatsnew/whatsnew23.tex | 6 ++ Lib/test/test_signal.py | 68 +++++++++++++- Misc/NEWS | 3 + Modules/signalmodule.c | 171 ++++++++++++++++++++++++++++++++++++ configure | 7 +- configure.in | 4 +- pyconfig.h.in | 3 + 8 files changed, 304 insertions(+), 12 deletions(-) diff --git a/Doc/lib/libsignal.tex b/Doc/lib/libsignal.tex index e3aa1717757..30293858524 100644 --- a/Doc/lib/libsignal.tex +++ b/Doc/lib/libsignal.tex @@ -17,10 +17,6 @@ regardless of the underlying implementation), with the exception of the handler for \constant{SIGCHLD}, which follows the underlying implementation. -\item -There is no way to ``block'' signals temporarily from critical -sections (since this is not supported by all \UNIX{} flavors). - \item Although Python signal handlers are called asynchronously as far as the Python user is concerned, they can only occur between the @@ -92,6 +88,16 @@ The variables defined in the \module{signal} module are: One more than the number of the highest signal number. \end{datadesc} +\begin{datadesc}{SIG_BLOCK} +\end{datadesc} +\begin{datadesc}{SIG_UNBLOCK} +\end{datadesc} +\begin{datadesc}{SIG_SETMASK} + These constants are for use as the first parameter of the + \function{sigprocmask} function described below. +\end{datadesc} + + The \module{signal} module defines the following functions: \begin{funcdesc}{alarm}{time} @@ -144,6 +150,46 @@ The \module{signal} module defines the following functions: \obindex{frame} \end{funcdesc} +The following functions are supported if your platform does. Most +modern \UNIX-alikes now do. + +\begin{funcdesc}{sigpending}{} + Return the set of pending signals, i.e. a list containing the + numbers of those signals that have been raised while blocked. + \versionadded{2.3} +\end{funcdesc} + +\begin{funcdesc}{sigprocmask}{how, sigset} + Change the list of currently blocked signals. The parameter + \var{how} should be one of \constant{SIG_BLOCK}, + \constant{SIG_UNBLOCK} or \constant{SIG_SETMASK} and \var{sigset} + should be a sequence of signal numbers. The behaviour of the call + depends on the value of \var{how}: + + \begin{tableii}{l|l}{textrm}{Value of \var{how}}{Behaviour of call} + \lineii{\constant{SIG_BLOCK}} + {The set of blocked signals is the union of the current set + and \var{sigset}.} + \lineii{\constant{SIG_UNBLOCK}} + {The signals in \var{sigset} are removed from the current + set of blocked signals. It is legal to attempt to unblock + a signal which is not blocked.} + \lineii{\constant{SIG_SETMASK}} + {The set of blocked signals is set to the \var{sigset}.} + \end{tableii} + + A list contating the numbers of the previously blocked signals is + returned. + \versionadded{2.3} +\end{funcdesc} + +\begin{funcdesc}{sigsuspend}{sigset} + Temporarily replace the signal mask with \var{sigset} (which should + be a sequnce of signal numbers) and suspend the process until a + signal is received. + \versionadded{2.3} +\end{funcdesc} + \subsection{Example} \nodename{Signal Example} diff --git a/Doc/whatsnew/whatsnew23.tex b/Doc/whatsnew/whatsnew23.tex index cb5d6fd24f0..489452d72ab 100644 --- a/Doc/whatsnew/whatsnew23.tex +++ b/Doc/whatsnew/whatsnew23.tex @@ -532,6 +532,12 @@ contents, and the \code{*=} assignment operator to repeat an array. functions: \function{get_history_item()}, \function{get_current_history_length()}, and \function{redisplay()}. +\item Support for more advanced POSIX signal handling -- specifically +the functions \function{sigpending}, \function{sigprocmask} and +\function{sigsupend}, and depending on platform support -- was added +to the \module{signal} module. These functions make some previously +unavoidable race conditions avoidable. + \end{itemize} diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 05bdcab0f48..78b90b70173 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1,8 +1,7 @@ # Test the signal module -from test_support import verbose, TestSkipped +from test_support import verbose, TestSkipped, TestFailed import signal -import os -import sys +import os, sys, time if sys.platform[:3] in ('win', 'os2') or sys.platform=='riscos': raise TestSkipped, "Can't test signal on %s" % sys.platform @@ -64,3 +63,66 @@ try: except KeyboardInterrupt: if verbose: print "KeyboardInterrupt (assume the alarm() went off)" + + +if hasattr(signal, "sigprocmask"): + class HupDelivered(Exception): + pass + def hup(signum, frame): + raise HupDelivered + def hup2(signum, frame): + signal.signal(signal.SIGHUP, hup) + return + signal.signal(signal.SIGHUP, hup) + + if verbose: + print "blocking SIGHUP" + + defaultmask = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGHUP]) + + if verbose: + print "sending SIGHUP" + + try: + os.kill(pid, signal.SIGHUP) + except HupDelivered: + raise TestFailed, "HUP not blocked" + + if signal.SIGHUP not in signal.sigpending(): + raise TestFailed, "HUP not pending" + + if verbose: + print "unblocking SIGHUP" + + try: + signal.sigprocmask(signal.SIG_UNBLOCK, [signal.SIGHUP]) + except HupDelivered: + pass + else: + raise TestFailed, "HUP not delivered" + + if verbose: + print "testing sigsuspend" + + signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGHUP]) + signal.signal(signal.SIGHUP, hup2) + + if not os.fork(): + time.sleep(2) + os.kill(pid, signal.SIGHUP) + time.sleep(2) + os.kill(pid, signal.SIGHUP) + os._exit(0) + else: + try: + signal.sigsuspend(defaultmask) + except: + raise TestFailed, "sigsuspend erroneously raised" + + try: + signal.sigsuspend(defaultmask) + except HupDelivered: + pass + else: + raise TestFailed, "sigsupsend didn't raise" + diff --git a/Misc/NEWS b/Misc/NEWS index f544af42cf2..85da0d36963 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -106,6 +106,9 @@ Extension modules of sizeof(int)!=sizeof(long)!=sizeof(void*) is delayed until dl.open is called. +- signal.sigpending, signal.sigprocmask and signal.sigsuspend have + been added where available. + Library - added degree/radian conversion functions to the math module. diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index f6027ab1f1f..3cea2dd77de 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -270,6 +270,153 @@ None -- if an unknown handler is in effect\n\ anything else -- the callable Python object used as a handler\n\ "; +#ifdef HAVE_SIGPROCMASK /* we assume that having SIGPROCMASK is enough + to guarantee full POSIX signal handling */ +/* returns 0 for success, <0 for failure (with exception set) */ +static int +_signal_list_to_sigset(PyObject* seq, sigset_t* set, char* mesg) +{ + int i, len, val; + + seq = PySequence_Fast(seq, mesg); + if (!seq) + return -1; + + len = PySequence_Fast_GET_SIZE(seq); + + sigemptyset(set); + + for (i = 0; i < len; i++) { + val = PyInt_AsLong(PySequence_Fast_GET_ITEM(seq, i)); + if (val == -1 && PyErr_Occurred()) { + Py_DECREF(seq); + return -1; + } + if (sigaddset(set, val) < 0) { + Py_DECREF(seq); + PyErr_SetFromErrno(PyExc_ValueError); + return -1; + } + } + + Py_DECREF(seq); + return 0; +} + +static PyObject* +_signal_sigset_to_list(sigset_t* set) +{ + PyObject* ret; + PyObject* ob; + int i; + + ret = PyList_New(0); + if (!ret) + return NULL; + + for (i = 1; i < NSIG; i++) { + if (sigismember(set, i)) { + ob = PyInt_FromLong(i); + if (!ob) { + Py_DECREF(ret); + return NULL; + } + PyList_Append(ret, ob); + Py_DECREF(ob); + } + } + + return ret; +} + +static PyObject* +signal_sigprocmask(PyObject* self, PyObject* args) +{ + int how; + sigset_t newset, oldset; + PyObject* seq; + + if (!PyArg_ParseTuple(args, "iO", &how, &seq)) + return NULL; + + if (_signal_list_to_sigset(seq, &newset, + "sigprocmask requires a sequence") < 0) + return NULL; + + if (sigprocmask(how, &newset, &oldset) < 0) { + return PyErr_SetFromErrno(PyExc_ValueError); + } + + if (PyErr_CheckSignals()) + return NULL; + + return _signal_sigset_to_list(&oldset); +} + +static char sigprocmask_doc[] = +"sigprocmask(how, sigset) -> sigset\n\ +\n\ +Change the list of currently blocked signals. The parameter how should be\n\ +one of SIG_BLOCK, SIG_UNBLOCK or SIG_SETMASK and sigset should be a\n\ +sequence of signal numbers. The behaviour of the call depends on the value\n\ +of how:\n\ +\n\ + SIG_BLOCK\n\ + The set of blocked signals is the union of the current set and the\n\ + sigset argument.\n\ + SIG_UNBLOCK\n\ + The signals in sigset are removed from the current set of blocked\n\ + signals. It is legal to attempt to unblock a signal which is not\n\ + blocked.\n\ + SIG_SETMASK\n\ + The set of blocked signals is set to the argument set.\n\ +\n\ +A list contating the numbers of the previously blocked signals is returned."; + +static PyObject* +signal_sigpending(PyObject* self) +{ + sigset_t set; + + if (sigpending(&set) < 0) { + return PyErr_SetFromErrno(PyExc_ValueError); + } + + return _signal_sigset_to_list(&set); +} + +static char sigpending_doc[] = +"sigpending() -> sigset\n\ +\n\ +Return the set of pending signals, i.e. a list containing the numbers of\n\ +those signals that have been raised while blocked."; + +static PyObject* +signal_sigsuspend(PyObject* self, PyObject* arg) +{ + sigset_t set; + + if (_signal_list_to_sigset(arg, &set, + "sigsuspend requires a sequence") < 0) + return NULL; + + Py_BEGIN_ALLOW_THREADS + sigsuspend(&set); + Py_END_ALLOW_THREADS + + if (PyErr_CheckSignals()) + return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +static char sigsuspend_doc[] = +"sigsuspend(sigset) -> None\n\ +\n\ +Temporarily replace the signal mask with sigset (which should be a sequence\n\ +of signal numbers) and suspend the process until a signal is received."; +#endif /* List of functions defined in the module */ static PyMethodDef signal_methods[] = { @@ -284,6 +431,14 @@ static PyMethodDef signal_methods[] = { #endif {"default_int_handler", signal_default_int_handler, METH_VARARGS, default_int_handler_doc}, +#ifdef HAVE_SIGPROCMASK + {"sigprocmask", (PyCFunction)signal_sigprocmask, + METH_VARARGS, sigprocmask_doc}, + {"sigpending", (PyCFunction)signal_sigpending, + METH_NOARGS, sigpending_doc}, + {"sigsuspend", (PyCFunction)signal_sigsuspend, + METH_O, sigsuspend_doc}, +#endif {NULL, NULL} /* sentinel */ }; @@ -299,6 +454,10 @@ getsignal() -- get the signal action for a given signal\n\ pause() -- wait until a signal arrives [Unix only]\n\ default_int_handler() -- default SIGINT handler\n\ \n\ +sigpending() |\n\ +sigprocmask() |-- posix signal mask handling [Unix only]\n\ +sigsuspend() |\n\ +\n\ Constants:\n\ \n\ SIG_DFL -- used to refer to the system default handler\n\ @@ -547,6 +706,18 @@ initsignal(void) PyDict_SetItemString(d, "SIGINFO", x); Py_XDECREF(x); #endif +#ifdef HAVE_SIGPROCMASK + x = PyInt_FromLong(SIG_BLOCK); + PyDict_SetItemString(d, "SIG_BLOCK", x); + Py_XDECREF(x); + x = PyInt_FromLong(SIG_UNBLOCK); + PyDict_SetItemString(d, "SIG_UNBLOCK", x); + Py_XDECREF(x); + x = PyInt_FromLong(SIG_SETMASK); + PyDict_SetItemString(d, "SIG_SETMASK", x); + Py_XDECREF(x); +#endif + if (!PyErr_Occurred()) return; diff --git a/configure b/configure index 21ca48b7885..aca04101775 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 1.316 . +# From configure.in Revision: 1.317 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.53. # @@ -11276,6 +11276,7 @@ echo "${ECHO_T}MACHDEP_OBJS" >&6 + for ac_func in alarm chown chroot clock confstr ctermid ctermid_r execv \ @@ -11286,8 +11287,8 @@ for ac_func in alarm chown chroot clock confstr ctermid ctermid_r execv \ putenv readlink \ select setegid seteuid setgid setgroups \ setlocale setregid setreuid setsid setpgid setuid setvbuf snprintf \ - sigaction siginterrupt sigrelse strftime strptime symlink sysconf \ - tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ + sigaction siginterrupt sigprocmask sigrelse strftime strptime symlink \ + sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unsetenv waitpid _getpty getpriority do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` diff --git a/configure.in b/configure.in index dcae3d8eef4..37c6f636c46 100644 --- a/configure.in +++ b/configure.in @@ -1593,8 +1593,8 @@ AC_CHECK_FUNCS(alarm chown chroot clock confstr ctermid ctermid_r execv \ putenv readlink \ select setegid seteuid setgid setgroups \ setlocale setregid setreuid setsid setpgid setuid setvbuf snprintf \ - sigaction siginterrupt sigrelse strftime strptime symlink sysconf \ - tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ + sigaction siginterrupt sigprocmask sigrelse strftime strptime symlink \ + sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unsetenv waitpid _getpty getpriority) # check for openpty and forkpty diff --git a/pyconfig.h.in b/pyconfig.h.in index 8e96db2c08b..c8fa5afd676 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -383,6 +383,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SIGNAL_H +/* Define to 1 if you have the `sigprocmask' function. */ +#undef HAVE_SIGPROCMASK + /* Define to 1 if you have the `sigrelse' function. */ #undef HAVE_SIGRELSE