Skip to content

Commit 001fee1

Browse files
authored
bpo-12822: use monotonic clock for condvar if possible (GH-11723)
1 parent 46a9792 commit 001fee1

File tree

8 files changed

+87
-51
lines changed

8 files changed

+87
-51
lines changed

‎Makefile.pre.in‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,8 @@ regen-opcode-targets:
939939
$(srcdir)/Python/opcode_targets.h.new
940940
$(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new
941941

942-
Python/ceval.o: $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/ceval_gil.h
942+
Python/ceval.o: $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/ceval_gil.h \
943+
$(srcdir)/Python/condvar.h
943944

944945
Python/frozen.o: $(srcdir)/Python/importlib.h $(srcdir)/Python/importlib_external.h \
945946
$(srcdir)/Python/importlib_zipimport.h
@@ -1838,7 +1839,7 @@ patchcheck: @DEF_MAKE_RULE@
18381839

18391840
# Dependencies
18401841

1841-
Python/thread.o: @THREADHEADERS@
1842+
Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h
18421843

18431844
# Declare targets that aren't real files
18441845
.PHONY: all build_all sharedmods check-clean-src oldsharedmods test quicktest
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Use monotonic clock for ``pthread_cond_timedwait`` when
2+
``pthread_condattr_setclock`` and ``CLOCK_MONOTONIC`` are available.

‎Python/ceval.c‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ PyEval_InitThreads(void)
171171
{
172172
if (gil_created())
173173
return;
174+
PyThread_init_thread();
174175
create_gil();
175176
take_gil(_PyThreadState_GET());
176177
_PyRuntime.ceval.pending.main_thread = PyThread_get_thread_ident();

‎Python/condvar.h‎

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,17 @@
4848
* POSIX support
4949
*/
5050

51-
#define PyCOND_ADD_MICROSECONDS(tv, interval) \
52-
do { /* TODO: add overflow and truncation checks */ \
53-
tv.tv_usec += (long) interval; \
54-
tv.tv_sec += tv.tv_usec / 1000000; \
55-
tv.tv_usec %= 1000000; \
56-
} while (0)
57-
58-
/* We assume all modern POSIX systems have gettimeofday() */
59-
#ifdef GETTIMEOFDAY_NO_TZ
60-
#define PyCOND_GETTIMEOFDAY(ptv) gettimeofday(ptv)
61-
#else
62-
#define PyCOND_GETTIMEOFDAY(ptv) gettimeofday(ptv, (struct timezone *)NULL)
63-
#endif
51+
/* These private functions are implemented in Python/thread_pthread.h */
52+
int _PyThread_cond_init(PyCOND_T *cond);
53+
void _PyThread_cond_after(long long us, struct timespec *abs);
6454

6555
/* The following functions return 0 on success, nonzero on error */
6656
#define PyMUTEX_INIT(mut) pthread_mutex_init((mut), NULL)
6757
#define PyMUTEX_FINI(mut) pthread_mutex_destroy(mut)
6858
#define PyMUTEX_LOCK(mut) pthread_mutex_lock(mut)
6959
#define PyMUTEX_UNLOCK(mut) pthread_mutex_unlock(mut)
7060

71-
#define PyCOND_INIT(cond) pthread_cond_init((cond), NULL)
61+
#define PyCOND_INIT(cond) _PyThread_cond_init(cond)
7262
#define PyCOND_FINI(cond) pthread_cond_destroy(cond)
7363
#define PyCOND_SIGNAL(cond) pthread_cond_signal(cond)
7464
#define PyCOND_BROADCAST(cond) pthread_cond_broadcast(cond)
@@ -78,22 +68,16 @@ do { /* TODO: add overflow and truncation checks */ \
7868
Py_LOCAL_INLINE(int)
7969
PyCOND_TIMEDWAIT(PyCOND_T *cond, PyMUTEX_T *mut, long long us)
8070
{
81-
int r;
82-
struct timespec ts;
83-
struct timeval deadline;
84-
85-
PyCOND_GETTIMEOFDAY(&deadline);
86-
PyCOND_ADD_MICROSECONDS(deadline, us);
87-
ts.tv_sec = deadline.tv_sec;
88-
ts.tv_nsec = deadline.tv_usec * 1000;
89-
90-
r = pthread_cond_timedwait((cond), (mut), &ts);
91-
if (r == ETIMEDOUT)
71+
struct timespec abs;
72+
_PyThread_cond_after(us, &abs);
73+
int ret = pthread_cond_timedwait(cond, mut, &abs);
74+
if (ret == ETIMEDOUT) {
9275
return 1;
93-
else if (r)
76+
}
77+
if (ret) {
9478
return -1;
95-
else
96-
return 0;
79+
}
80+
return 0;
9781
}
9882

9983
#elif defined(NT_THREADS)

‎Python/thread_pthread.h‎

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@
5656
#endif
5757
#endif
5858

59-
#if !defined(pthread_attr_default)
60-
# define pthread_attr_default ((pthread_attr_t *)NULL)
61-
#endif
62-
#if !defined(pthread_mutexattr_default)
63-
# define pthread_mutexattr_default ((pthread_mutexattr_t *)NULL)
64-
#endif
65-
#if !defined(pthread_condattr_default)
66-
# define pthread_condattr_default ((pthread_condattr_t *)NULL)
67-
#endif
68-
6959

7060
/* Whether or not to use semaphores directly rather than emulating them with
7161
* mutexes and condition variables:
@@ -110,6 +100,56 @@ do { \
110100
} while(0)
111101

112102

103+
/*
104+
* pthread_cond support
105+
*/
106+
107+
#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
108+
// monotonic is supported statically. It doesn't mean it works on runtime.
109+
#define CONDATTR_MONOTONIC
110+
#endif
111+
112+
// NULL when pthread_condattr_setclock(CLOCK_MONOTONIC) is not supported.
113+
static pthread_condattr_t *condattr_monotonic = NULL;
114+
115+
static void
116+
init_condattr()
117+
{
118+
#ifdef CONDATTR_MONOTONIC
119+
static pthread_condattr_t ca;
120+
pthread_condattr_init(&ca);
121+
if (pthread_condattr_setclock(&ca, CLOCK_MONOTONIC) == 0) {
122+
condattr_monotonic = &ca; // Use monotonic clock
123+
}
124+
#endif
125+
}
126+
127+
int
128+
_PyThread_cond_init(PyCOND_T *cond)
129+
{
130+
return pthread_cond_init(cond, condattr_monotonic);
131+
}
132+
133+
void
134+
_PyThread_cond_after(long long us, struct timespec *abs)
135+
{
136+
#ifdef CONDATTR_MONOTONIC
137+
if (condattr_monotonic) {
138+
clock_gettime(CLOCK_MONOTONIC, abs);
139+
abs->tv_sec += us / 1000000;
140+
abs->tv_nsec += (us % 1000000) * 1000;
141+
abs->tv_sec += abs->tv_nsec / 1000000000;
142+
abs->tv_nsec %= 1000000000;
143+
return;
144+
}
145+
#endif
146+
147+
struct timespec ts;
148+
MICROSECONDS_TO_TIMESPEC(us, ts);
149+
*abs = ts;
150+
}
151+
152+
113153
/* A pthread mutex isn't sufficient to model the Python lock type
114154
* because, according to Draft 5 of the docs (P1003.4a/D5), both of the
115155
* following are undefined:
@@ -146,6 +186,7 @@ PyThread__init_thread(void)
146186
extern void pthread_init(void);
147187
pthread_init();
148188
#endif
189+
init_condattr();
149190
}
150191

151192
/*
@@ -462,8 +503,7 @@ PyThread_allocate_lock(void)
462503
memset((void *)lock, '\0', sizeof(pthread_lock));
463504
lock->locked = 0;
464505

465-
status = pthread_mutex_init(&lock->mut,
466-
pthread_mutexattr_default);
506+
status = pthread_mutex_init(&lock->mut, NULL);
467507
CHECK_STATUS_PTHREAD("pthread_mutex_init");
468508
/* Mark the pthread mutex underlying a Python mutex as
469509
pure happens-before. We can't simply mark the
@@ -472,8 +512,7 @@ PyThread_allocate_lock(void)
472512
will cause errors. */
473513
_Py_ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(&lock->mut);
474514

475-
status = pthread_cond_init(&lock->lock_released,
476-
pthread_condattr_default);
515+
status = _PyThread_cond_init(&lock->lock_released);
477516
CHECK_STATUS_PTHREAD("pthread_cond_init");
478517

479518
if (error) {
@@ -532,9 +571,10 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
532571
success = PY_LOCK_ACQUIRED;
533572
}
534573
else if (microseconds != 0) {
535-
struct timespec ts;
536-
if (microseconds > 0)
537-
MICROSECONDS_TO_TIMESPEC(microseconds, ts);
574+
struct timespec abs;
575+
if (microseconds > 0) {
576+
_PyThread_cond_after(microseconds, &abs);
577+
}
538578
/* continue trying until we get the lock */
539579

540580
/* mut must be locked by me -- part of the condition
@@ -543,10 +583,13 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
543583
if (microseconds > 0) {
544584
status = pthread_cond_timedwait(
545585
&thelock->lock_released,
546-
&thelock->mut, &ts);
586+
&thelock->mut, &abs);
587+
if (status == 1) {
588+
break;
589+
}
547590
if (status == ETIMEDOUT)
548591
break;
549-
CHECK_STATUS_PTHREAD("pthread_cond_timed_wait");
592+
CHECK_STATUS_PTHREAD("pthread_cond_timedwait");
550593
}
551594
else {
552595
status = pthread_cond_wait(

‎configure‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11448,7 +11448,8 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
1144811448
memrchr mbrtowc mkdirat mkfifo \
1144911449
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
1145011450
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
11451-
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
11451+
pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \
11452+
readlink readlinkat readv realpath renameat \
1145211453
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
1145311454
setgid sethostname \
1145411455
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \

‎configure.ac‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3506,7 +3506,8 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
35063506
memrchr mbrtowc mkdirat mkfifo \
35073507
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
35083508
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
3509-
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
3509+
pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \
3510+
readlink readlinkat readv realpath renameat \
35103511
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
35113512
setgid sethostname \
35123513
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \

‎pyconfig.h.in‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,9 @@
765765
/* Define if your compiler supports function prototype */
766766
#undef HAVE_PROTOTYPES
767767

768+
/* Define to 1 if you have the `pthread_condattr_setclock' function. */
769+
#undef HAVE_PTHREAD_CONDATTR_SETCLOCK
770+
768771
/* Defined for Solaris 2.6 bug in pthread header. */
769772
#undef HAVE_PTHREAD_DESTRUCTOR
770773

0 commit comments

Comments
 (0)