Skip to content

Commit 0e1aeab

Browse files
bpo-41710: PyThread_acquire_lock_timed() uses sem_clockwait() (GH-28671) (GH-28683)
On Unix, if the sem_clockwait() function is available in the C library (glibc 2.30 and newer), the threading.Lock.acquire() method now uses the monotonic clock (time.CLOCK_MONOTONIC) for the timeout, rather than using the system clock (time.CLOCK_REALTIME), to not be affected by system clock changes. configure now checks if the sem_clockwait() function is available. (cherry picked from commit 6df8c32) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 8822526 commit 0e1aeab

File tree

5 files changed

+51
-14
lines changed

5 files changed

+51
-14
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
On Unix, if the ``sem_clockwait()`` function is available in the C library
2+
(glibc 2.30 and newer), the :meth:`threading.Lock.acquire` method now uses the
3+
monotonic clock (:data:`time.CLOCK_MONOTONIC`) for the timeout, rather than
4+
using the system clock (:data:`time.CLOCK_REALTIME`), to not be affected by
5+
system clock changes. Patch by Victor Stinner.

Python/thread_pthread.h

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,17 @@
8989
* mutexes and condition variables:
9090
*/
9191
#if (defined(_POSIX_SEMAPHORES) && !defined(HAVE_BROKEN_POSIX_SEMAPHORES) && \
92-
defined(HAVE_SEM_TIMEDWAIT))
92+
(defined(HAVE_SEM_TIMEDWAIT) || defined(HAVE_SEM_CLOCKWAIT)))
9393
# define USE_SEMAPHORES
9494
#else
9595
# undef USE_SEMAPHORES
9696
#endif
9797

98+
#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
99+
// monotonic is supported statically. It doesn't mean it works on runtime.
100+
#define CONDATTR_MONOTONIC
101+
#endif
102+
98103

99104
/* On platforms that don't use standard POSIX threads pthread_sigmask()
100105
* isn't present. DEC threads uses sigprocmask() instead as do most
@@ -120,16 +125,23 @@ do { \
120125
ts.tv_nsec = tv.tv_usec * 1000; \
121126
} while(0)
122127

128+
#if defined(CONDATTR_MONOTONIC) || defined(HAVE_SEM_CLOCKWAIT)
129+
static void
130+
monotonic_abs_timeout(long long us, struct timespec *abs)
131+
{
132+
clock_gettime(CLOCK_MONOTONIC, abs);
133+
abs->tv_sec += us / 1000000;
134+
abs->tv_nsec += (us % 1000000) * 1000;
135+
abs->tv_sec += abs->tv_nsec / 1000000000;
136+
abs->tv_nsec %= 1000000000;
137+
}
138+
#endif
139+
123140

124141
/*
125142
* pthread_cond support
126143
*/
127144

128-
#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
129-
// monotonic is supported statically. It doesn't mean it works on runtime.
130-
#define CONDATTR_MONOTONIC
131-
#endif
132-
133145
// NULL when pthread_condattr_setclock(CLOCK_MONOTONIC) is not supported.
134146
static pthread_condattr_t *condattr_monotonic = NULL;
135147

@@ -151,16 +163,13 @@ _PyThread_cond_init(PyCOND_T *cond)
151163
return pthread_cond_init(cond, condattr_monotonic);
152164
}
153165

166+
154167
void
155168
_PyThread_cond_after(long long us, struct timespec *abs)
156169
{
157170
#ifdef CONDATTR_MONOTONIC
158171
if (condattr_monotonic) {
159-
clock_gettime(CLOCK_MONOTONIC, abs);
160-
abs->tv_sec += us / 1000000;
161-
abs->tv_nsec += (us % 1000000) * 1000;
162-
abs->tv_sec += abs->tv_nsec / 1000000000;
163-
abs->tv_nsec %= 1000000000;
172+
monotonic_abs_timeout(us, abs);
164173
return;
165174
}
166175
#endif
@@ -431,7 +440,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
431440
sem_t *thelock = (sem_t *)lock;
432441
int status, error = 0;
433442
struct timespec ts;
443+
#ifndef HAVE_SEM_CLOCKWAIT
434444
_PyTime_t deadline = 0;
445+
#endif
435446

436447
(void) error; /* silence unused-but-set-variable warning */
437448
dprintf(("PyThread_acquire_lock_timed(%p, %lld, %d) called\n",
@@ -442,6 +453,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
442453
}
443454

444455
if (microseconds > 0) {
456+
#ifdef HAVE_SEM_CLOCKWAIT
457+
monotonic_abs_timeout(microseconds, &ts);
458+
#else
445459
MICROSECONDS_TO_TIMESPEC(microseconds, ts);
446460

447461
if (!intr_flag) {
@@ -450,11 +464,17 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
450464
_PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000);
451465
deadline = _PyTime_GetMonotonicClock() + timeout;
452466
}
467+
#endif
453468
}
454469

455470
while (1) {
456471
if (microseconds > 0) {
472+
#ifdef HAVE_SEM_CLOCKWAIT
473+
status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC,
474+
&ts));
475+
#else
457476
status = fix_status(sem_timedwait(thelock, &ts));
477+
#endif
458478
}
459479
else if (microseconds == 0) {
460480
status = fix_status(sem_trywait(thelock));
@@ -469,6 +489,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
469489
break;
470490
}
471491

492+
// sem_clockwait() uses an absolute timeout, there is no need
493+
// to recompute the relative timeout.
494+
#ifndef HAVE_SEM_CLOCKWAIT
472495
if (microseconds > 0) {
473496
/* wait interrupted by a signal (EINTR): recompute the timeout */
474497
_PyTime_t dt = deadline - _PyTime_GetMonotonicClock();
@@ -490,13 +513,19 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
490513
microseconds = 0;
491514
}
492515
}
516+
#endif
493517
}
494518

495519
/* Don't check the status if we're stopping because of an interrupt. */
496520
if (!(intr_flag && status == EINTR)) {
497521
if (microseconds > 0) {
498-
if (status != ETIMEDOUT)
522+
if (status != ETIMEDOUT) {
523+
#ifdef HAVE_SEM_CLOCKWAIT
524+
CHECK_STATUS("sem_clockwait");
525+
#else
499526
CHECK_STATUS("sem_timedwait");
527+
#endif
528+
}
500529
}
501530
else if (microseconds == 0) {
502531
if (status != EAGAIN)

configure

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11723,7 +11723,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
1172311723
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
1172411724
pthread_condattr_setclock pthread_init pthread_kill pwrite pwritev pwritev2 \
1172511725
readlink readlinkat readv realpath renameat \
11726-
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
11726+
sem_open sem_timedwait sem_clockwait sem_getvalue sem_unlink sendfile setegid seteuid \
1172711727
setgid sethostname \
1172811728
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
1172911729
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3707,7 +3707,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
37073707
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
37083708
pthread_condattr_setclock pthread_init pthread_kill pwrite pwritev pwritev2 \
37093709
readlink readlinkat readv realpath renameat \
3710-
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
3710+
sem_open sem_timedwait sem_clockwait sem_getvalue sem_unlink sendfile setegid seteuid \
37113711
setgid sethostname \
37123712
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
37133713
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,9 @@
887887
/* Define to 1 if you have the `sched_setscheduler' function. */
888888
#undef HAVE_SCHED_SETSCHEDULER
889889

890+
/* Define to 1 if you have the `sem_clockwait' function. */
891+
#undef HAVE_SEM_CLOCKWAIT
892+
890893
/* Define to 1 if you have the `sem_getvalue' function. */
891894
#undef HAVE_SEM_GETVALUE
892895

0 commit comments

Comments
 (0)