diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -39,12 +39,13 @@ * Some care must be taken if both signals and threads are used in the same program. The fundamental thing to remember in using signals and threads simultaneously is: always perform :func:`signal` operations in the main thread - of execution. Any thread can perform an :func:`alarm`, :func:`getsignal`, or - :func:`pause`; only the main thread can set a new signal handler, and the main - thread will be the only one to receive signals (this is enforced by the Python - :mod:`signal` module, even if the underlying thread implementation supports - sending signals to individual threads). This means that signals can't be used - as a means of inter-thread communication. Use locks instead. + of execution. Any thread can perform an :func:`alarm`, :func:`getsignal`, + :func:`pause`, :func:`setitimer` or :func:`getitimer`; only the main thread + can set a new signal handler, and the main thread will be the only one to + receive signals (this is enforced by the Python :mod:`signal` module, even + if the underlying thread implementation supports sending signals to + individual threads). This means that signals can't be used as a means of + inter-thread communication. Use locks instead. The variables defined in the :mod:`signal` module are: @@ -78,6 +79,36 @@ One more than the number of the highest signal number. + +.. data:: ITIMER_REAL + + Decrements interval timer in real time, and delivers SIGALRM upon expiration. + + +.. data:: ITIMER_VIRTUAL + + Decrements interval timer only when the process is executing, and delivers + SIGVTALRM upon expiration. + + +.. data:: ITIMER_PROF + + Decrements interval timer both when the process executes and when the + system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, + this timer is usually used to profile the time spent by the application + in user and kernel space. SIGPROF is delivered upon expiration. + + +The :mod:`signal` module defines one exception: + +.. exception:: ItimerError + + Raised to signal an error from the underlying :func:`setitimer` or + :func:`getitimer` implementation. Expect this error if an invalid + interval timer or a negative time is passed to :func:`setitimer`. + This error is a subtype of :exc:`IOError`. + + The :mod:`signal` module defines the following functions: @@ -108,6 +139,29 @@ Cause the process to sleep until a signal is received; the appropriate handler will then be called. Returns nothing. Not on Windows. (See the Unix man page :manpage:`signal(2)`.) + + +.. function:: setitimer(which, seconds[, interval]) + + Sets given itimer (one of :const:`signal.ITIMER_REAL`, + :const:`signal.ITIMER_VIRTUAL` or :const:`signal.ITIMER_PROF`) especified + by *which* to fire after *seconds* (float is accepted, different from + :func:`alarm`) and after that every *interval* seconds. The interval + timer specified by *which* can be cleared by setting seconds to zero. + + The old values are returned as a tuple: (delay, interval). + + Attempting to pass an invalid interval timer will cause a + :exc:`ItimerError`. + + .. versionadded:: 2.6 + + +.. function:: getitimer(which) + + Returns current value of a given itimer especified by *which*. + + .. versionadded:: 2.6 .. function:: set_wakeup_fd(fd) @@ -122,7 +176,6 @@ When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a :exc:`ValueError` exception to be raised. - .. function:: siginterrupt(signalnum, flag) 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 @@ -260,9 +260,93 @@ i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 0)) self.assertEquals(i, False) +class ItimerTest(unittest.TestCase): + def setUp(self): + self.hndl_called = False + self.hndl_count = 0 + self.itimer = None + + def tearDown(self): + if self.itimer is not None: # test_itimer_exc doesn't change this attr + # just ensure that itimer is stopped + signal.setitimer(self.itimer, 0) + + def sig_alrm(self, *args): + self.hndl_called = True + if test_support.verbose: + print("SIGALRM handler invoked", args) + + def sig_vtalrm(self, *args): + self.hndl_called = True + + if self.hndl_count > 3: + # it shouldn't be here, because it should have been disabled. + raise signal.ItimerError("setitimer didn't disable ITIMER_VIRTUAL " + "timer.") + elif self.hndl_count == 3: + # disable ITIMER_VIRTUAL, this function shouldn't be called anymore + signal.setitimer(signal.ITIMER_VIRTUAL, 0) + if test_support.verbose: + print("last SIGVTALRM handler call") + + self.hndl_count += 1 + + if test_support.verbose: + print("SIGVTALRM handler invoked", args) + + def sig_prof(self, *args): + self.hndl_called = True + signal.setitimer(signal.ITIMER_PROF, 0) + + if test_support.verbose: + print("SIGPROF handler invoked", args) + + def test_itimer_exc(self): + # XXX I'm assuming -1 is an invalid itimer, but maybe some platform + # defines it ? + self.assertRaises(signal.ItimerError, signal.setitimer, -1, 0) + # negative time + self.assertRaises(signal.ItimerError, signal.setitimer, + signal.ITIMER_REAL, -1) + + def test_itimer_real(self): + self.itimer = signal.ITIMER_REAL + signal.signal(signal.SIGALRM, self.sig_alrm) + signal.setitimer(self.itimer, 1.0) + if test_support.verbose: + print("\ncall pause()...") + signal.pause() + + self.assertEqual(self.hndl_called, True) + + def test_itimer_virtual(self): + self.itimer = signal.ITIMER_VIRTUAL + signal.signal(signal.SIGVTALRM, self.sig_vtalrm) + signal.setitimer(self.itimer, 0.3, 0.2) + + for i in xrange(100000000): + if signal.getitimer(self.itimer) == (0.0, 0.0): + break # sig_vtalrm handler stopped this itimer + + # virtual itimer should be (0.0, 0.0) now + self.assertEquals(signal.getitimer(self.itimer), (0.0, 0.0)) + # and the handler should have been called + self.assertEquals(self.hndl_called, True) + + def test_itimer_prof(self): + self.itimer = signal.ITIMER_PROF + signal.signal(signal.SIGPROF, self.sig_prof) + signal.setitimer(self.itimer, 0.2) + + for i in xrange(100000000): + if signal.getitimer(self.itimer) == (0.0, 0.0): + break # sig_prof handler stopped this itimer + + self.assertEqual(self.hndl_called, True) + def test_main(): test_support.run_unittest(BasicSignalTests, InterProcessSignalTests, - WakeupSignalTests, SiginterruptTest) + WakeupSignalTests, SiginterruptTest, ItimerTest) if __name__ == "__main__": diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -13,6 +13,7 @@ #include #include +#include #ifndef SIG_ERR #define SIG_ERR ((PyOS_sighandler_t)(-1)) @@ -92,6 +93,48 @@ (void (*)(int))0. */ static PyOS_sighandler_t old_siginthandler = SIG_DFL; + +static PyObject *ItimerError; + +/* auxiliary functions for setitimer/getitimer */ +static void +timeval_from_double(double d, struct timeval *tv) +{ + tv->tv_sec = floor(d); + tv->tv_usec = fmod(d, 1.0) * 1000000.0; +} + +static inline double +double_from_timeval(struct timeval *tv) +{ + return tv->tv_sec + (double)(tv->tv_usec / 1000000.0); +} + +static PyObject * +itimer_retval(struct itimerval *iv) +{ + PyObject *r, *v; + + r = PyTuple_New(2); + if (r == NULL) + return NULL; + + if(!(v = PyFloat_FromDouble(double_from_timeval(&iv->it_value)))) { + Py_DECREF(r); + return NULL; + } + + PyTuple_SET_ITEM(r, 0, v); + + if(!(v = PyFloat_FromDouble(double_from_timeval(&iv->it_interval)))) { + Py_DECREF(r); + return NULL; + } + + PyTuple_SET_ITEM(r, 1, v); + + return r; +} static PyObject * @@ -347,10 +390,76 @@ } +#ifdef HAVE_SETITIMER +static PyObject * +signal_setitimer(PyObject *self, PyObject *args) +{ + double first; + double interval = 0; + int which; + struct itimerval new, old; + + if(!PyArg_ParseTuple(args, "id|d:setitimer", &which, &first, &interval)) + return NULL; + + /* Let OS do checking on which */ + timeval_from_double(first, &new.it_value); + timeval_from_double(interval, &new.it_interval); + if (setitimer(which, &new, &old) != 0) { + PyErr_SetFromErrno(ItimerError); + return NULL; + } + + return itimer_retval(&old); +} + +PyDoc_STRVAR(setitimer_doc, +"setitimer(which, seconds[, interval])\n\ +\n\ +Sets given itimer (one of ITIMER_REAL, ITIMER_VIRTUAL\n\ +or ITIMER_PROF) to fire after value seconds and after\n\ +that every interval seconds.\n\ +The itimer can be cleared by setting seconds to zero.\n\ +\n\ +Returns old values as a tuple: (delay, interval)."); +#endif + + +#ifdef HAVE_GETITIMER +static PyObject * +signal_getitimer(PyObject *self, PyObject *args) +{ + int which; + struct itimerval old; + + if (!PyArg_ParseTuple(args, "i:getitimer", &which)) + return NULL; + + if (getitimer(which, &old) != 0) { + PyErr_SetFromErrno(ItimerError); + return NULL; + } + + return itimer_retval(&old); +} + +PyDoc_STRVAR(getitimer_doc, +"getitimer(which)\n\ +\n\ +Returns current value of given itimer."); +#endif + + /* List of functions defined in the module */ static PyMethodDef signal_methods[] = { #ifdef HAVE_ALARM {"alarm", signal_alarm, METH_VARARGS, alarm_doc}, +#endif +#ifdef HAVE_SETITIMER + {"setitimer", signal_setitimer, METH_VARARGS, setitimer_doc}, +#endif +#ifdef HAVE_GETITIMER + {"getitimer", signal_getitimer, METH_VARARGS, getitimer_doc}, #endif {"signal", signal_signal, METH_VARARGS, signal_doc}, {"getsignal", signal_getsignal, METH_VARARGS, getsignal_doc}, @@ -374,19 +483,32 @@ Functions:\n\ \n\ alarm() -- cause SIGALRM after a specified time [Unix only]\n\ +setitimer() -- cause a signal (described below) after a specified\n\ + float time and the timer may restart then [Unix only]\n\ +getitimer() -- get current value of timer [Unix only]\n\ signal() -- set the action for a given signal\n\ 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\ -Constants:\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\ -\n\ SIGINT, SIGTERM, etc. -- signal numbers\n\ \n\ +itimer constants:\n\ +ITIMER_REAL -- decrements in real time, and delivers SIGALRM upon\n\ + expiration\n\ +ITIMER_VIRTUAL -- decrements only when the process is executing,\n\ + and delivers SIGVTALRM upon expiration\n\ +ITIMER_PROF -- decrements both when the process is executing and\n\ + when the system is executing on behalf of the process.\n\ + Coupled with ITIMER_VIRTUAL, this timer is usually\n\ + used to profile the time spent by the application\n\ + in user and kernel space. SIGPROF is delivered upon\n\ + expiration.\n\ +\n\n\ *** IMPORTANT NOTICE ***\n\ A signal handler function is called with two arguments:\n\ the first is the signal number, the second is the interrupted stack frame."); @@ -639,6 +761,29 @@ PyDict_SetItemString(d, "SIGINFO", x); Py_XDECREF(x); #endif + +#ifdef ITIMER_REAL + x = PyLong_FromLong(ITIMER_REAL); + PyDict_SetItemString(d, "ITIMER_REAL", x); + Py_DECREF(x); +#endif +#ifdef ITIMER_VIRTUAL + x = PyLong_FromLong(ITIMER_VIRTUAL); + PyDict_SetItemString(d, "ITIMER_VIRTUAL", x); + Py_DECREF(x); +#endif +#ifdef ITIMER_PROF + x = PyLong_FromLong(ITIMER_PROF); + PyDict_SetItemString(d, "ITIMER_PROF", x); + Py_DECREF(x); +#endif + +#if defined (HAVE_SETITIMER) || defined (HAVE_GETITIMER) + ItimerError = PyErr_NewException("signal.ItimerError", + PyExc_IOError, NULL); + PyDict_SetItemString(d, "ItimerError", ItimerError); +#endif + if (!PyErr_Occurred()) return; diff --git a/configure b/configure --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 60765 . +# From configure.in Revision: 61234 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for python 2.6. # @@ -15503,8 +15503,10 @@ -for ac_func in alarm bind_textdomain_codeset chown clock confstr \ - ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ + + +for ac_func in alarm setitimer getitimer bind_textdomain_codeset chown \ + clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ getpriority getpwent getspnam getspent getsid getwd \ kill killpg lchmod lchown lstat mkfifo mknod mktime \ diff --git a/configure.in b/configure.in --- a/configure.in +++ b/configure.in @@ -2302,8 +2302,8 @@ AC_MSG_RESULT(MACHDEP_OBJS) # checks for library functions -AC_CHECK_FUNCS(alarm bind_textdomain_codeset chown clock confstr \ - ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ +AC_CHECK_FUNCS(alarm setitimer getitimer bind_textdomain_codeset chown \ + clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ getpriority getpwent getspnam getspent getsid getwd \ kill killpg lchmod lchown lstat mkfifo mknod mktime \ diff --git a/pyconfig.h.in b/pyconfig.h.in --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -239,6 +239,9 @@ /* Define this if you have the 6-arg version of gethostbyname_r(). */ #undef HAVE_GETHOSTBYNAME_R_6_ARG + +/* Define to 1 if you have the `getitimer' function. */ +#undef HAVE_GETITIMER /* Define to 1 if you have the `getloadavg' function. */ #undef HAVE_GETLOADAVG @@ -494,6 +497,9 @@ /* Define if you have the 'setgroups' function. */ #undef HAVE_SETGROUPS + +/* Define to 1 if you have the `setitimer' function. */ +#undef HAVE_SETITIMER /* Define to 1 if you have the `setlocale' function. */ #undef HAVE_SETLOCALE