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.
This commit is contained in:
Victor Stinner 2015-03-28 03:52:05 +01:00
parent e245231fab
commit f5faad2bf0
3 changed files with 76 additions and 219 deletions

View File

@ -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);

View File

@ -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;
}
r = acquire_timed(self->lock_lock, microseconds);
static PyObject *
lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds)
{
_PyTime_t timeout;
PyLockStatus r;
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 */

View File

@ -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)
{
@ -842,10 +726,6 @@ _PyTime_Init(void)
if (_PyTime_GetSystemClockWithInfo(&t, NULL) < 0)
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;