Skip to content

Commit 6df8c32

Browse files
authored
bpo-41710: PyThread_acquire_lock_timed() uses sem_clockwait() (GH-28671)
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.
1 parent 282992b commit 6df8c32

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
@@ -92,12 +92,17 @@
9292
* mutexes and condition variables:
9393
*/
9494
#if (defined(_POSIX_SEMAPHORES) && !defined(HAVE_BROKEN_POSIX_SEMAPHORES) && \
95-
defined(HAVE_SEM_TIMEDWAIT))
95+
(defined(HAVE_SEM_TIMEDWAIT) || defined(HAVE_SEM_CLOCKWAIT)))
9696
# define USE_SEMAPHORES
9797
#else
9898
# undef USE_SEMAPHORES
9999
#endif
100100

101+
#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
102+
// monotonic is supported statically. It doesn't mean it works on runtime.
103+
#define CONDATTR_MONOTONIC
104+
#endif
105+
101106

102107
/* On platforms that don't use standard POSIX threads pthread_sigmask()
103108
* isn't present. DEC threads uses sigprocmask() instead as do most
@@ -123,16 +128,23 @@ do { \
123128
ts.tv_nsec = tv.tv_usec * 1000; \
124129
} while(0)
125130

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

127144
/*
128145
* pthread_cond support
129146
*/
130147

131-
#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
132-
// monotonic is supported statically. It doesn't mean it works on runtime.
133-
#define CONDATTR_MONOTONIC
134-
#endif
135-
136148
// NULL when pthread_condattr_setclock(CLOCK_MONOTONIC) is not supported.
137149
static pthread_condattr_t *condattr_monotonic = NULL;
138150

@@ -154,16 +166,13 @@ _PyThread_cond_init(PyCOND_T *cond)
154166
return pthread_cond_init(cond, condattr_monotonic);
155167
}
156168

169+
157170
void
158171
_PyThread_cond_after(long long us, struct timespec *abs)
159172
{
160173
#ifdef CONDATTR_MONOTONIC
161174
if (condattr_monotonic) {
162-
clock_gettime(CLOCK_MONOTONIC, abs);
163-
abs->tv_sec += us / 1000000;
164-
abs->tv_nsec += (us % 1000000) * 1000;
165-
abs->tv_sec += abs->tv_nsec / 1000000000;
166-
abs->tv_nsec %= 1000000000;
175+
monotonic_abs_timeout(us, abs);
167176
return;
168177
}
169178
#endif
@@ -434,7 +443,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
434443
sem_t *thelock = (sem_t *)lock;
435444
int status, error = 0;
436445
struct timespec ts;
446+
#ifndef HAVE_SEM_CLOCKWAIT
437447
_PyTime_t deadline = 0;
448+
#endif
438449

439450
(void) error; /* silence unused-but-set-variable warning */
440451
dprintf(("PyThread_acquire_lock_timed(%p, %lld, %d) called\n",
@@ -445,6 +456,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
445456
}
446457

447458
if (microseconds > 0) {
459+
#ifdef HAVE_SEM_CLOCKWAIT
460+
monotonic_abs_timeout(microseconds, &ts);
461+
#else
448462
MICROSECONDS_TO_TIMESPEC(microseconds, ts);
449463

450464
if (!intr_flag) {
@@ -453,11 +467,17 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
453467
_PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000);
454468
deadline = _PyTime_GetMonotonicClock() + timeout;
455469
}
470+
#endif
456471
}
457472

458473
while (1) {
459474
if (microseconds > 0) {
475+
#ifdef HAVE_SEM_CLOCKWAIT
476+
status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC,
477+
&ts));
478+
#else
460479
status = fix_status(sem_timedwait(thelock, &ts));
480+
#endif
461481
}
462482
else if (microseconds == 0) {
463483
status = fix_status(sem_trywait(thelock));
@@ -472,6 +492,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
472492
break;
473493
}
474494

495+
// sem_clockwait() uses an absolute timeout, there is no need
496+
// to recompute the relative timeout.
497+
#ifndef HAVE_SEM_CLOCKWAIT
475498
if (microseconds > 0) {
476499
/* wait interrupted by a signal (EINTR): recompute the timeout */
477500
_PyTime_t dt = deadline - _PyTime_GetMonotonicClock();
@@ -493,13 +516,19 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
493516
microseconds = 0;
494517
}
495518
}
519+
#endif
496520
}
497521

498522
/* Don't check the status if we're stopping because of an interrupt. */
499523
if (!(intr_flag && status == EINTR)) {
500524
if (microseconds > 0) {
501-
if (status != ETIMEDOUT)
525+
if (status != ETIMEDOUT) {
526+
#ifdef HAVE_SEM_CLOCKWAIT
527+
CHECK_STATUS("sem_clockwait");
528+
#else
502529
CHECK_STATUS("sem_timedwait");
530+
#endif
531+
}
503532
}
504533
else if (microseconds == 0) {
505534
if (status != EAGAIN)

configure

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11738,7 +11738,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
1173811738
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
1173911739
pthread_condattr_setclock pthread_init pthread_kill pwrite pwritev pwritev2 \
1174011740
readlink readlinkat readv realpath renameat \
11741-
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
11741+
sem_open sem_timedwait sem_clockwait sem_getvalue sem_unlink sendfile setegid seteuid \
1174211742
setgid sethostname \
1174311743
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
1174411744
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
@@ -902,6 +902,9 @@
902902
/* Define to 1 if you have the `sched_setscheduler' function. */
903903
#undef HAVE_SCHED_SETSCHEDULER
904904

905+
/* Define to 1 if you have the `sem_clockwait' function. */
906+
#undef HAVE_SEM_CLOCKWAIT
907+
905908
/* Define to 1 if you have the `sem_getvalue' function. */
906909
#undef HAVE_SEM_GETVALUE
907910

0 commit comments

Comments
 (0)