Skip to content

Commit f5faad2

Browse files
committed
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.
1 parent e245231 commit f5faad2

File tree

3 files changed

+76
-219
lines changed

3 files changed

+76
-219
lines changed

Include/pytime.h

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,6 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
7474
long *nsec,
7575
_PyTime_round_t);
7676

77-
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
78-
The clock is not affected by system clock updates. The reference point of
79-
the returned value is undefined, so that only the difference between the
80-
results of consecutive calls is valid.
81-
82-
The function never fails. _PyTime_Init() ensures that a monotonic clock
83-
is available and works. */
84-
PyAPI_FUNC(void) _PyTime_monotonic(
85-
_PyTime_timeval *tp);
86-
87-
/* Similar to _PyTime_monotonic(), fill also info (if set) with information of
88-
the function used to get the time.
89-
90-
Return 0 on success, raise an exception and return -1 on error. */
91-
PyAPI_FUNC(int) _PyTime_monotonic_info(
92-
_PyTime_timeval *tp,
93-
_Py_clock_info_t *info);
94-
9577
/* Add interval seconds to tv */
9678
PyAPI_FUNC(void)
9779
_PyTime_AddDouble(_PyTime_timeval *tv, double interval,
@@ -105,6 +87,8 @@ PyAPI_FUNC(int) _PyTime_Init(void);
10587

10688
#ifdef PY_INT64_T
10789
typedef PY_INT64_T _PyTime_t;
90+
#define _PyTime_MIN PY_LLONG_MIN
91+
#define _PyTime_MAX PY_LLONG_MAX
10892
#else
10993
# error "_PyTime_t need signed 64-bit integer type"
11094
#endif
@@ -125,6 +109,10 @@ PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t);
125109
PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
126110
_PyTime_round_t round);
127111

112+
/* Convert timestamp to a number of microseconds (10^-6 seconds). */
113+
PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
114+
_PyTime_round_t round);
115+
128116
/* Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int
129117
object. */
130118
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);

Modules/_threadmodule.c

Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,18 @@ lock_dealloc(lockobject *self)
4949
* timeout.
5050
*/
5151
static PyLockStatus
52-
acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds)
52+
acquire_timed(PyThread_type_lock lock, _PyTime_t timeout)
5353
{
5454
PyLockStatus r;
55-
_PyTime_timeval curtime;
56-
_PyTime_timeval endtime;
57-
58-
59-
if (microseconds > 0) {
60-
_PyTime_monotonic(&endtime);
61-
endtime.tv_sec += microseconds / (1000 * 1000);
62-
endtime.tv_usec += microseconds % (1000 * 1000);
63-
}
55+
_PyTime_t endtime = 0;
56+
_PyTime_t microseconds;
6457

58+
if (timeout > 0)
59+
endtime = _PyTime_GetMonotonicClock() + timeout;
6560

6661
do {
62+
microseconds = _PyTime_AsMicroseconds(timeout, _PyTime_ROUND_UP);
63+
6764
/* first a simple non-blocking try without releasing the GIL */
6865
r = PyThread_acquire_lock_timed(lock, 0, 0);
6966
if (r == PY_LOCK_FAILURE && microseconds != 0) {
@@ -82,14 +79,12 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds)
8279

8380
/* If we're using a timeout, recompute the timeout after processing
8481
* signals, since those can take time. */
85-
if (microseconds > 0) {
86-
_PyTime_monotonic(&curtime);
87-
microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 +
88-
(endtime.tv_usec - curtime.tv_usec));
82+
if (timeout > 0) {
83+
timeout = endtime - _PyTime_GetMonotonicClock();
8984

9085
/* Check for negative values, since those mean block forever.
9186
*/
92-
if (microseconds <= 0) {
87+
if (timeout <= 0) {
9388
r = PY_LOCK_FAILURE;
9489
}
9590
}
@@ -99,44 +94,60 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds)
9994
return r;
10095
}
10196

102-
static PyObject *
103-
lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds)
97+
static int
98+
lock_acquire_parse_args(PyObject *args, PyObject *kwds,
99+
_PyTime_t *timeout)
104100
{
105101
char *kwlist[] = {"blocking", "timeout", NULL};
106102
int blocking = 1;
107-
double timeout = -1;
108-
PY_TIMEOUT_T microseconds;
109-
PyLockStatus r;
103+
PyObject *timeout_obj = NULL;
104+
const _PyTime_t unset_timeout = _PyTime_FromNanoseconds(-1000000000);
110105

111-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist,
112-
&blocking, &timeout))
113-
return NULL;
106+
*timeout = unset_timeout ;
114107

115-
if (!blocking && timeout != -1) {
116-
PyErr_SetString(PyExc_ValueError, "can't specify a timeout "
117-
"for a non-blocking call");
118-
return NULL;
108+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iO:acquire", kwlist,
109+
&blocking, &timeout_obj))
110+
return -1;
111+
112+
if (timeout_obj
113+
&& _PyTime_FromSecondsObject(timeout, timeout_obj, _PyTime_ROUND_UP) < 0)
114+
return -1;
115+
116+
if (!blocking && *timeout != unset_timeout ) {
117+
PyErr_SetString(PyExc_ValueError,
118+
"can't specify a timeout for a non-blocking call");
119+
return -1;
119120
}
120-
if (timeout < 0 && timeout != -1) {
121-
PyErr_SetString(PyExc_ValueError, "timeout value must be "
122-
"strictly positive");
123-
return NULL;
121+
if (*timeout < 0 && *timeout != unset_timeout) {
122+
PyErr_SetString(PyExc_ValueError,
123+
"timeout value must be positive");
124+
return -1;
124125
}
125126
if (!blocking)
126-
microseconds = 0;
127-
else if (timeout == -1)
128-
microseconds = -1;
129-
else {
130-
timeout *= 1e6;
131-
if (timeout >= (double) PY_TIMEOUT_MAX) {
127+
*timeout = 0;
128+
else if (*timeout != unset_timeout) {
129+
_PyTime_t microseconds;
130+
131+
microseconds = _PyTime_AsMicroseconds(*timeout, _PyTime_ROUND_UP);
132+
if (microseconds >= PY_TIMEOUT_MAX) {
132133
PyErr_SetString(PyExc_OverflowError,
133134
"timeout value is too large");
134-
return NULL;
135+
return -1;
135136
}
136-
microseconds = (PY_TIMEOUT_T) timeout;
137137
}
138+
return 0;
139+
}
140+
141+
static PyObject *
142+
lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds)
143+
{
144+
_PyTime_t timeout;
145+
PyLockStatus r;
138146

139-
r = acquire_timed(self->lock_lock, microseconds);
147+
if (lock_acquire_parse_args(args, kwds, &timeout) < 0)
148+
return NULL;
149+
150+
r = acquire_timed(self->lock_lock, timeout);
140151
if (r == PY_LOCK_INTR) {
141152
return NULL;
142153
}
@@ -281,41 +292,13 @@ rlock_dealloc(rlockobject *self)
281292
static PyObject *
282293
rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds)
283294
{
284-
char *kwlist[] = {"blocking", "timeout", NULL};
285-
int blocking = 1;
286-
double timeout = -1;
287-
PY_TIMEOUT_T microseconds;
295+
_PyTime_t timeout;
288296
long tid;
289297
PyLockStatus r = PY_LOCK_ACQUIRED;
290298

291-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist,
292-
&blocking, &timeout))
299+
if (lock_acquire_parse_args(args, kwds, &timeout) < 0)
293300
return NULL;
294301

295-
if (!blocking && timeout != -1) {
296-
PyErr_SetString(PyExc_ValueError, "can't specify a timeout "
297-
"for a non-blocking call");
298-
return NULL;
299-
}
300-
if (timeout < 0 && timeout != -1) {
301-
PyErr_SetString(PyExc_ValueError, "timeout value must be "
302-
"strictly positive");
303-
return NULL;
304-
}
305-
if (!blocking)
306-
microseconds = 0;
307-
else if (timeout == -1)
308-
microseconds = -1;
309-
else {
310-
timeout *= 1e6;
311-
if (timeout >= (double) PY_TIMEOUT_MAX) {
312-
PyErr_SetString(PyExc_OverflowError,
313-
"timeout value is too large");
314-
return NULL;
315-
}
316-
microseconds = (PY_TIMEOUT_T) timeout;
317-
}
318-
319302
tid = PyThread_get_thread_ident();
320303
if (self->rlock_count > 0 && tid == self->rlock_owner) {
321304
unsigned long count = self->rlock_count + 1;
@@ -327,7 +310,7 @@ rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds)
327310
self->rlock_count = count;
328311
Py_RETURN_TRUE;
329312
}
330-
r = acquire_timed(self->rlock_lock, microseconds);
313+
r = acquire_timed(self->rlock_lock, timeout);
331314
if (r == PY_LOCK_ACQUIRED) {
332315
assert(self->rlock_count == 0);
333316
self->rlock_owner = tid;
@@ -1362,7 +1345,9 @@ static struct PyModuleDef threadmodule = {
13621345
PyMODINIT_FUNC
13631346
PyInit__thread(void)
13641347
{
1365-
PyObject *m, *d, *timeout_max;
1348+
PyObject *m, *d, *v;
1349+
double time_max;
1350+
double timeout_max;
13661351

13671352
/* Initialize types: */
13681353
if (PyType_Ready(&localdummytype) < 0)
@@ -1379,10 +1364,14 @@ PyInit__thread(void)
13791364
if (m == NULL)
13801365
return NULL;
13811366

1382-
timeout_max = PyFloat_FromDouble(PY_TIMEOUT_MAX / 1000000);
1383-
if (!timeout_max)
1367+
timeout_max = PY_TIMEOUT_MAX / 1000000;
1368+
time_max = floor(_PyTime_AsSecondsDouble(_PyTime_MAX));
1369+
timeout_max = Py_MIN(timeout_max, time_max);
1370+
1371+
v = PyFloat_FromDouble(timeout_max);
1372+
if (!v)
13841373
return NULL;
1385-
if (PyModule_AddObject(m, "TIMEOUT_MAX", timeout_max) < 0)
1374+
if (PyModule_AddObject(m, "TIMEOUT_MAX", v) < 0)
13861375
return NULL;
13871376

13881377
/* Add a symbolic constant */

0 commit comments

Comments
 (0)