diff --git a/Include/pytime.h b/Include/pytime.h index 1648d039116..17d5ea17c4f 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -145,6 +145,12 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round); +#ifdef HAVE_CLOCK_GETTIME +/* Convert a timestamp to a timespec structure (nanosecond resolution). + Raise an exception and return -1 on error, return 0 on success. */ +PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts); +#endif + /* Get the current time from the system clock. * Fill clock information if info is not NULL. * Raise an exception and return -1 on error, return 0 on success. diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 817da8a01d4..cfec329f014 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -10,6 +10,11 @@ try: import threading except ImportError: threading = None +try: + import _testcapi +except ImportError: + _testcapi = None + # Max year is only limited by the size of C int. SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 @@ -768,7 +773,8 @@ class TestPytime(unittest.TestCase): self.assertIs(lt.tm_zone, None) -@support.cpython_only +@unittest.skipUnless(_testcapi is not None, + 'need the _testcapi module') class TestPyTime_t(unittest.TestCase): def test_FromSecondsObject(self): from _testcapi import PyTime_FromSecondsObject @@ -896,6 +902,27 @@ class TestPyTime_t(unittest.TestCase): self.assertEqual(PyTime_AsSecondsDouble(nanoseconds), seconds) + @unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'), + 'need _testcapi.PyTime_AsTimespec') + def test_timespec(self): + from _testcapi import PyTime_AsTimespec + for ns, ts in ( + # nanoseconds + (0, (0, 0)), + (1, (0, 1)), + (-1, (-1, 999999999)), + + # seconds + (2 * SEC_TO_NS, (2, 0)), + (-3 * SEC_TO_NS, (-3, 0)), + + # seconds + nanoseconds + (1234567890, (1, 234567890)), + (-1234567890, (-2, 765432110)), + ): + with self.subTest(nanoseconds=ns, timespec=ts): + self.assertEqual(PyTime_AsTimespec(ns), ts) + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b3820811583..5029105a882 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3408,6 +3408,23 @@ test_pytime_assecondsdouble(PyObject *self, PyObject *args) return PyFloat_FromDouble(d); } +#ifdef HAVE_CLOCK_GETTIME +static PyObject * +test_PyTime_AsTimespec(PyObject *self, PyObject *args) +{ + PY_LONG_LONG ns; + _PyTime_t t; + struct timespec ts; + + if (!PyArg_ParseTuple(args, "L", &ns)) + return NULL; + t = _PyTime_FromNanoseconds(ns); + if (_PyTime_AsTimespec(t, &ts) == -1) + return NULL; + return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec); +} +#endif + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, @@ -3573,6 +3590,9 @@ static PyMethodDef TestMethods[] = { return_result_with_error, METH_NOARGS}, {"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS}, {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, +#ifdef HAVE_CLOCK_GETTIME + {"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS}, +#endif {NULL, NULL} /* sentinel */ }; diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 5dba8b1fd32..f3b2e298678 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -966,16 +966,18 @@ Returns a struct_siginfo containing information about the signal."); static PyObject * signal_sigtimedwait(PyObject *self, PyObject *args) { - PyObject *signals; - double timeout, frac; + PyObject *signals, *timeout_obj; struct timespec ts; sigset_t set; siginfo_t si; int res; - _PyTime_timeval deadline, monotonic; + _PyTime_t timeout, deadline, monotonic; - if (!PyArg_ParseTuple(args, "Od:sigtimedwait", - &signals, &timeout)) + if (!PyArg_ParseTuple(args, "OO:sigtimedwait", + &signals, &timeout_obj)) + return NULL; + + if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_UP) < 0) return NULL; if (timeout < 0) { @@ -986,14 +988,11 @@ signal_sigtimedwait(PyObject *self, PyObject *args) if (iterable_to_sigset(signals, &set)) return NULL; - _PyTime_monotonic(&deadline); - _PyTime_AddDouble(&deadline, timeout, _PyTime_ROUND_UP); + deadline = _PyTime_GetMonotonicClock() + timeout; do { - frac = fmod(timeout, 1.0); - timeout = floor(timeout); - ts.tv_sec = (long)timeout; - ts.tv_nsec = (long)(frac*1e9); + if (_PyTime_AsTimespec(timeout, &ts) < 0) + return NULL; Py_BEGIN_ALLOW_THREADS res = sigtimedwait(&set, &si, &ts); @@ -1013,9 +1012,9 @@ signal_sigtimedwait(PyObject *self, PyObject *args) if (PyErr_CheckSignals()) return NULL; - _PyTime_monotonic(&monotonic); - timeout = _PyTime_INTERVAL(monotonic, deadline); - if (timeout <= 0.0) + monotonic = _PyTime_GetMonotonicClock(); + timeout = deadline - monotonic; + if (timeout <= 0) break; } while (1); diff --git a/Python/pytime.c b/Python/pytime.c index 0f30f428d8e..bd94787241d 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -572,6 +572,27 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) return 0; } +#ifdef HAVE_CLOCK_GETTIME +int +_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) +{ + _PyTime_t sec, nsec; + sec = t / SEC_TO_NS; + nsec = t % SEC_TO_NS; + if (nsec < 0) { + nsec += SEC_TO_NS; + sec -= 1; + } + ts->tv_sec = (time_t)sec; + if ((_PyTime_t)ts->tv_sec != sec) { + _PyTime_overflow(); + return -1; + } + ts->tv_nsec = nsec; + return 0; +} +#endif + static int pygettimeofday_new(_PyTime_t *tp, _Py_clock_info_t *info, int raise) {