Commits

Victor Stinner committed 67d9595

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.

Comments (0)

Files changed (7)

Doc/library/signal.rst

    .. 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).
 #define Py_PYTIME_H
 
 #include "pyconfig.h" /* include for defines */
+#include "object.h"
 
 /**************************************************************************
 Symbols and macros to supply platform-independent interfaces to time related
     ((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);
 

Lib/test/test_signal.py

         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)
         ''')
         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)
         ''')
     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,))
         ''')
                          '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()')

Lib/test/test_time.py

     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()

Modules/_testcapimodule.c

     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},
      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 */
 };
 

Modules/signalmodule.c

     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) {
 #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()
 {