From 643cd68ea4b8d33a6d0163ef693ef6518f76b88f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Mar 2012 22:54:03 +0100 Subject: [PATCH] Issue #13964: signal.sigtimedwait() timeout is now a float instead of a tuple Add a private API to convert an int or float to a C timespec structure. --- Doc/library/signal.rst | 9 ++++---- Include/pytime.h | 11 ++++++++++ Lib/test/test_signal.py | 10 ++++----- Lib/test/test_time.py | 21 +++++++++++++++++- Modules/_testcapimodule.c | 19 +++++++++++++++++ Modules/signalmodule.c | 11 +++------- Python/pytime.c | 45 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 20 deletions(-) diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index 4fc3fd6405c..04afd9e8a4e 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -369,12 +369,11 @@ The :mod:`signal` module defines the following functions: .. versionadded:: 3.3 -.. function:: sigtimedwait(sigset, (timeout_sec, timeout_nsec)) +.. function:: sigtimedwait(sigset, timeout) - 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. + Like :func:`sigwaitinfo`, but takes an additional *timeout* argument + specifying a timeout. If *timeout* is 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). diff --git a/Include/pytime.h b/Include/pytime.h index d707bdb9a87..2ea64c9bc4a 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -3,6 +3,7 @@ #define Py_PYTIME_H #include "pyconfig.h" /* include for defines */ +#include "object.h" /************************************************************************** Symbols and macros to supply platform-independent interfaces to time related @@ -37,6 +38,16 @@ do { \ ((tv_end.tv_sec - tv_start.tv_sec) + \ (tv_end.tv_usec - tv_start.tv_usec) * 0.000001) +#ifndef Py_LIMITED_API +/* Convert a number of seconds, int or float, to a timespec structure. + nsec is always in the range [0; 999999999]. For example, -1.2 is converted + to (-2, 800000000). */ +PyAPI_FUNC(int) _PyTime_ObjectToTimespec( + PyObject *obj, + time_t *sec, + long *nsec); +#endif + /* Dummy to force linking. */ PyAPI_FUNC(void) _PyTime_Init(void); diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index fdeb4c2261f..6be259bdb93 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -662,7 +662,7 @@ class PendingSignalsTests(unittest.TestCase): self.wait_helper(signal.SIGALRM, ''' def test(signum): signal.alarm(1) - info = signal.sigtimedwait([signum], (10, 1000)) + info = signal.sigtimedwait([signum], 10.1000) if info.si_signo != signum: raise Exception('info.si_signo != %s' % signum) ''') @@ -675,7 +675,7 @@ class PendingSignalsTests(unittest.TestCase): def test(signum): import os os.kill(os.getpid(), signum) - info = signal.sigtimedwait([signum], (0, 0)) + info = signal.sigtimedwait([signum], 0) if info.si_signo != signum: raise Exception('info.si_signo != %s' % signum) ''') @@ -685,7 +685,7 @@ class PendingSignalsTests(unittest.TestCase): def test_sigtimedwait_timeout(self): self.wait_helper(signal.SIGALRM, ''' def test(signum): - received = signal.sigtimedwait([signum], (1, 0)) + received = signal.sigtimedwait([signum], 1.0) if received is not None: raise Exception("received=%r" % (received,)) ''') @@ -694,9 +694,7 @@ class PendingSignalsTests(unittest.TestCase): 'need signal.sigtimedwait()') def test_sigtimedwait_negative_timeout(self): signum = signal.SIGALRM - self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, -1)) - self.assertRaises(ValueError, signal.sigtimedwait, [signum], (0, -1)) - self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, 0)) + self.assertRaises(ValueError, signal.sigtimedwait, [signum], -1.0) @unittest.skipUnless(hasattr(signal, 'sigwaitinfo'), 'need signal.sigwaitinfo()') diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index a89c511c691..26492c1c6c5 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -497,12 +497,31 @@ class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear): pass +class TestPytime(unittest.TestCase): + def test_timespec(self): + from _testcapi import pytime_object_to_timespec + for obj, timespec in ( + (0, (0, 0)), + (-1, (-1, 0)), + (-1.0, (-1, 0)), + (-1e-9, (-1, 999999999)), + (-1.2, (-2, 800000000)), + (1.123456789, (1, 123456789)), + ): + self.assertEqual(pytime_object_to_timespec(obj), timespec) + + for invalid in (-(2 ** 100), -(2.0 ** 100.0), 2 ** 100, 2.0 ** 100.0): + self.assertRaises(OverflowError, pytime_object_to_timespec, invalid) + + + def test_main(): support.run_unittest( TimeTestCase, TestLocale, TestAsctime4dyear, - TestStrftime4dyear) + TestStrftime4dyear, + TestPytime) if __name__ == "__main__": test_main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 23a4d5ac9b7..9294df3e63b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2323,6 +2323,24 @@ run_in_subinterp(PyObject *self, PyObject *args) return PyLong_FromLong(r); } +static PyObject * +test_pytime_object_to_timespec(PyObject *self, PyObject *args) +{ + PyObject *obj; + time_t sec; + long nsec; + if (!PyArg_ParseTuple(args, "O:pytime_object_to_timespec", &obj)) + return NULL; + if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1) + return NULL; +#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG + return Py_BuildValue("Ll", (PY_LONG_LONG)sec, nsec); +#else + assert(sizeof(time_t) <= sizeof(long)); + return Py_BuildValue("ll", (long)sec, nsec); +#endif +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, @@ -2412,6 +2430,7 @@ static PyMethodDef TestMethods[] = { METH_NOARGS}, {"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS}, {"run_in_subinterp", run_in_subinterp, METH_VARARGS}, + {"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index e46f8cf1dcf..2eb7f297ebe 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -783,16 +783,11 @@ signal_sigtimedwait(PyObject *self, PyObject *args) siginfo_t si; int err; - if (!PyArg_ParseTuple(args, "OO:sigtimedwait", &signals, &timeout)) + 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))) + if (_PyTime_ObjectToTimespec(timeout, &buf.tv_sec, &buf.tv_nsec) == -1) return NULL; if (buf.tv_sec < 0 || buf.tv_nsec < 0) { diff --git a/Python/pytime.c b/Python/pytime.c index bec1c713e60..d23ce75b43f 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -70,6 +70,51 @@ _PyTime_gettimeofday(_PyTime_timeval *tp) #endif /* MS_WINDOWS */ } +int +_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec) +{ + if (PyFloat_Check(obj)) { + double d, intpart, floatpart, err; + + d = PyFloat_AsDouble(obj); + floatpart = modf(d, &intpart); + if (floatpart < 0) { + floatpart = 1.0 + floatpart; + intpart -= 1.0; + } + + *sec = (time_t)intpart; + err = intpart - (double)*sec; + if (err <= -1.0 || err >= 1.0) + goto overflow; + + floatpart *= 1e9; + *nsec = (long)floatpart; + return 0; + } + else { +#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG + *sec = PyLong_AsLongLong(obj); +#else + assert(sizeof(time_t) <= sizeof(long)); + *sec = PyLong_AsLong(obj); +#endif + if (*sec == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) + goto overflow; + else + return -1; + } + *nsec = 0; + return 0; + } + +overflow: + PyErr_SetString(PyExc_OverflowError, + "timestamp out of range for platform time_t"); + return -1; +} + void _PyTime_Init() {