From f5faad2bf017db9e99845de29420476914f1ef1d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 28 Mar 2015 03:52:05 +0100 Subject: Issue #22117: The thread module uses the new _PyTime_t timestamp API Add also a new _PyTime_AsMicroseconds() function. threading.TIMEOUT_MAX is now be smaller: only 292 years instead of 292,271 years on 64-bit system for example. Sorry, your threads will hang a *little bit* shorter. Call me if you want to ensure that your locks wait longer, I can share some tricks with you. --- Include/pytime.h | 24 +++------ Modules/_threadmodule.c | 139 ++++++++++++++++++++++-------------------------- Python/pytime.c | 132 +++------------------------------------------ 3 files changed, 76 insertions(+), 219 deletions(-) diff --git a/Include/pytime.h b/Include/pytime.h index 3004289..3078d25 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -74,24 +74,6 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec( long *nsec, _PyTime_round_t); -/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards. - The clock is not affected by system clock updates. The reference point of - the returned value is undefined, so that only the difference between the - results of consecutive calls is valid. - - The function never fails. _PyTime_Init() ensures that a monotonic clock - is available and works. */ -PyAPI_FUNC(void) _PyTime_monotonic( - _PyTime_timeval *tp); - -/* Similar to _PyTime_monotonic(), fill also info (if set) with information of - the function used to get the time. - - Return 0 on success, raise an exception and return -1 on error. */ -PyAPI_FUNC(int) _PyTime_monotonic_info( - _PyTime_timeval *tp, - _Py_clock_info_t *info); - /* Add interval seconds to tv */ PyAPI_FUNC(void) _PyTime_AddDouble(_PyTime_timeval *tv, double interval, @@ -105,6 +87,8 @@ PyAPI_FUNC(int) _PyTime_Init(void); #ifdef PY_INT64_T typedef PY_INT64_T _PyTime_t; +#define _PyTime_MIN PY_LLONG_MIN +#define _PyTime_MAX PY_LLONG_MAX #else # error "_PyTime_t need signed 64-bit integer type" #endif @@ -125,6 +109,10 @@ PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t); PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round); +/* Convert timestamp to a number of microseconds (10^-6 seconds). */ +PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t, + _PyTime_round_t round); + /* Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int object. */ PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t); diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 8f59e03..07b01f0 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -49,21 +49,18 @@ lock_dealloc(lockobject *self) * timeout. */ static PyLockStatus -acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) +acquire_timed(PyThread_type_lock lock, _PyTime_t timeout) { PyLockStatus r; - _PyTime_timeval curtime; - _PyTime_timeval endtime; - - - if (microseconds > 0) { - _PyTime_monotonic(&endtime); - endtime.tv_sec += microseconds / (1000 * 1000); - endtime.tv_usec += microseconds % (1000 * 1000); - } + _PyTime_t endtime = 0; + _PyTime_t microseconds; + if (timeout > 0) + endtime = _PyTime_GetMonotonicClock() + timeout; do { + microseconds = _PyTime_AsMicroseconds(timeout, _PyTime_ROUND_UP); + /* first a simple non-blocking try without releasing the GIL */ r = PyThread_acquire_lock_timed(lock, 0, 0); if (r == PY_LOCK_FAILURE && microseconds != 0) { @@ -82,14 +79,12 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) /* If we're using a timeout, recompute the timeout after processing * signals, since those can take time. */ - if (microseconds > 0) { - _PyTime_monotonic(&curtime); - microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 + - (endtime.tv_usec - curtime.tv_usec)); + if (timeout > 0) { + timeout = endtime - _PyTime_GetMonotonicClock(); /* Check for negative values, since those mean block forever. */ - if (microseconds <= 0) { + if (timeout <= 0) { r = PY_LOCK_FAILURE; } } @@ -99,44 +94,60 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) return r; } -static PyObject * -lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds) +static int +lock_acquire_parse_args(PyObject *args, PyObject *kwds, + _PyTime_t *timeout) { char *kwlist[] = {"blocking", "timeout", NULL}; int blocking = 1; - double timeout = -1; - PY_TIMEOUT_T microseconds; - PyLockStatus r; + PyObject *timeout_obj = NULL; + const _PyTime_t unset_timeout = _PyTime_FromNanoseconds(-1000000000); - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist, - &blocking, &timeout)) - return NULL; + *timeout = unset_timeout ; - if (!blocking && timeout != -1) { - PyErr_SetString(PyExc_ValueError, "can't specify a timeout " - "for a non-blocking call"); - return NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iO:acquire", kwlist, + &blocking, &timeout_obj)) + return -1; + + if (timeout_obj + && _PyTime_FromSecondsObject(timeout, timeout_obj, _PyTime_ROUND_UP) < 0) + return -1; + + if (!blocking && *timeout != unset_timeout ) { + PyErr_SetString(PyExc_ValueError, + "can't specify a timeout for a non-blocking call"); + return -1; } - if (timeout < 0 && timeout != -1) { - PyErr_SetString(PyExc_ValueError, "timeout value must be " - "strictly positive"); - return NULL; + if (*timeout < 0 && *timeout != unset_timeout) { + PyErr_SetString(PyExc_ValueError, + "timeout value must be positive"); + return -1; } if (!blocking) - microseconds = 0; - else if (timeout == -1) - microseconds = -1; - else { - timeout *= 1e6; - if (timeout >= (double) PY_TIMEOUT_MAX) { + *timeout = 0; + else if (*timeout != unset_timeout) { + _PyTime_t microseconds; + + microseconds = _PyTime_AsMicroseconds(*timeout, _PyTime_ROUND_UP); + if (microseconds >= PY_TIMEOUT_MAX) { PyErr_SetString(PyExc_OverflowError, "timeout value is too large"); - return NULL; + return -1; } - microseconds = (PY_TIMEOUT_T) timeout; } + return 0; +} + +static PyObject * +lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds) +{ + _PyTime_t timeout; + PyLockStatus r; - r = acquire_timed(self->lock_lock, microseconds); + if (lock_acquire_parse_args(args, kwds, &timeout) < 0) + return NULL; + + r = acquire_timed(self->lock_lock, timeout); if (r == PY_LOCK_INTR) { return NULL; } @@ -281,41 +292,13 @@ rlock_dealloc(rlockobject *self) static PyObject * rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) { - char *kwlist[] = {"blocking", "timeout", NULL}; - int blocking = 1; - double timeout = -1; - PY_TIMEOUT_T microseconds; + _PyTime_t timeout; long tid; PyLockStatus r = PY_LOCK_ACQUIRED; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist, - &blocking, &timeout)) + if (lock_acquire_parse_args(args, kwds, &timeout) < 0) return NULL; - if (!blocking && timeout != -1) { - PyErr_SetString(PyExc_ValueError, "can't specify a timeout " - "for a non-blocking call"); - return NULL; - } - if (timeout < 0 && timeout != -1) { - PyErr_SetString(PyExc_ValueError, "timeout value must be " - "strictly positive"); - return NULL; - } - if (!blocking) - microseconds = 0; - else if (timeout == -1) - microseconds = -1; - else { - timeout *= 1e6; - if (timeout >= (double) PY_TIMEOUT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "timeout value is too large"); - return NULL; - } - microseconds = (PY_TIMEOUT_T) timeout; - } - tid = PyThread_get_thread_ident(); if (self->rlock_count > 0 && tid == self->rlock_owner) { unsigned long count = self->rlock_count + 1; @@ -327,7 +310,7 @@ rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) self->rlock_count = count; Py_RETURN_TRUE; } - r = acquire_timed(self->rlock_lock, microseconds); + r = acquire_timed(self->rlock_lock, timeout); if (r == PY_LOCK_ACQUIRED) { assert(self->rlock_count == 0); self->rlock_owner = tid; @@ -1362,7 +1345,9 @@ static struct PyModuleDef threadmodule = { PyMODINIT_FUNC PyInit__thread(void) { - PyObject *m, *d, *timeout_max; + PyObject *m, *d, *v; + double time_max; + double timeout_max; /* Initialize types: */ if (PyType_Ready(&localdummytype) < 0) @@ -1379,10 +1364,14 @@ PyInit__thread(void) if (m == NULL) return NULL; - timeout_max = PyFloat_FromDouble(PY_TIMEOUT_MAX / 1000000); - if (!timeout_max) + timeout_max = PY_TIMEOUT_MAX / 1000000; + time_max = floor(_PyTime_AsSecondsDouble(_PyTime_MAX)); + timeout_max = Py_MIN(timeout_max, time_max); + + v = PyFloat_FromDouble(timeout_max); + if (!v) return NULL; - if (PyModule_AddObject(m, "TIMEOUT_MAX", timeout_max) < 0) + if (PyModule_AddObject(m, "TIMEOUT_MAX", v) < 0) return NULL; /* Add a symbolic constant */ diff --git a/Python/pytime.c b/Python/pytime.c index 9893116..aa64977 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -119,128 +119,6 @@ _PyTime_gettimeofday(_PyTime_timeval *tp) } } -static int -pymonotonic(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise) -{ -#ifdef Py_DEBUG - static _PyTime_timeval last = {0, -1}; -#endif -#if defined(MS_WINDOWS) - ULONGLONG result; - - assert(info == NULL || raise); - - result = GetTickCount64(); - - tp->tv_sec = result / SEC_TO_MS; - tp->tv_usec = (result % SEC_TO_MS) * MS_TO_US; - - if (info) { - DWORD timeAdjustment, timeIncrement; - BOOL isTimeAdjustmentDisabled, ok; - info->implementation = "GetTickCount64()"; - info->monotonic = 1; - ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, - &isTimeAdjustmentDisabled); - if (!ok) { - PyErr_SetFromWindowsErr(0); - return -1; - } - info->resolution = timeIncrement * 1e-7; - info->adjustable = 0; - } - -#elif defined(__APPLE__) - static mach_timebase_info_data_t timebase; - uint64_t time; - - if (timebase.denom == 0) { - /* According to the Technical Q&A QA1398, mach_timebase_info() cannot - fail: https://developer.apple.com/library/mac/#qa/qa1398/ */ - (void)mach_timebase_info(&timebase); - } - - time = mach_absolute_time(); - - /* nanoseconds => microseconds */ - time /= US_TO_NS; - /* apply timebase factor */ - time *= timebase.numer; - time /= timebase.denom; - tp->tv_sec = time / SEC_TO_US; - tp->tv_usec = time % SEC_TO_US; - - if (info) { - info->implementation = "mach_absolute_time()"; - info->resolution = (double)timebase.numer / timebase.denom * 1e-9; - info->monotonic = 1; - info->adjustable = 0; - } - -#else - struct timespec ts; -#ifdef CLOCK_HIGHRES - const clockid_t clk_id = CLOCK_HIGHRES; - const char *implementation = "clock_gettime(CLOCK_HIGHRES)"; -#else - const clockid_t clk_id = CLOCK_MONOTONIC; - const char *implementation = "clock_gettime(CLOCK_MONOTONIC)"; -#endif - - assert(info == NULL || raise); - - if (clock_gettime(clk_id, &ts) != 0) { - if (raise) { - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } - tp->tv_sec = 0; - tp->tv_usec = 0; - return -1; - } - - if (info) { - struct timespec res; - info->monotonic = 1; - info->implementation = implementation; - info->adjustable = 0; - if (clock_getres(clk_id, &res) != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } - info->resolution = res.tv_sec + res.tv_nsec * 1e-9; - } - tp->tv_sec = ts.tv_sec; - tp->tv_usec = ts.tv_nsec / US_TO_NS; -#endif - assert(0 <= tp->tv_usec && tp->tv_usec < SEC_TO_US); -#ifdef Py_DEBUG - /* monotonic clock cannot go backward */ - assert(last.tv_usec == -1 - || tp->tv_sec > last.tv_sec - || (tp->tv_sec == last.tv_sec && tp->tv_usec >= last.tv_usec)); - last = *tp; -#endif - return 0; -} - -void -_PyTime_monotonic(_PyTime_timeval *tp) -{ - if (pymonotonic(tp, NULL, 0) < 0) { - /* cannot happen, _PyTime_Init() checks that pymonotonic() works */ - assert(0); - tp->tv_sec = 0; - tp->tv_usec = 0; - } -} - -int -_PyTime_monotonic_info(_PyTime_timeval *tp, _Py_clock_info_t *info) -{ - return pymonotonic(tp, info, 1); -} - static void error_time_t_overflow(void) { @@ -536,6 +414,12 @@ _PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round) return _PyTime_Multiply(t, 1000, round); } +_PyTime_t +_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round) +{ + return _PyTime_Multiply(t, 1000 * 1000, round); +} + int _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) { @@ -843,10 +727,6 @@ _PyTime_Init(void) return -1; /* ensure that the operating system provides a monotonic clock */ - if (_PyTime_monotonic_info(&tv, NULL) < 0) - return -1; - - /* ensure that the operating system provides a monotonic clock */ if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) < 0) return -1; return 0; -- cgit v0.12