From c317166ecb0cffbe0750e42abbab9f274ad66181 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sun, 22 Feb 2026 23:02:10 +0200 Subject: [PATCH 1/3] Fix sem_clockwait detection for cross-compiled builds When cross-compiling against old glibc headers (e.g., Debian Stretch's glibc 2.24), configure cannot detect sem_clockwait (added in glibc 2.30). This causes CPython to fall back to sem_timedwait with CLOCK_REALTIME, making threading.Event.wait() hang indefinitely when the system clock jumps backward (e.g., NTP sync). Add patches that declare sem_clockwait as a weak symbol on Linux when configure didn't detect it. At runtime, the weak symbol resolves to the real function on glibc 2.30+ or to NULL on older glibc. The patched code checks this at runtime and falls back to sem_timedwait gracefully. Version-specific patches for CPython 3.10, 3.11, 3.12, and 3.13+ account for differences in the threading internals across versions. --- cpython-unix/build-cpython.sh | 17 +++ .../patch-sem-clockwait-weak-3.10.patch | 113 ++++++++++++++++++ .../patch-sem-clockwait-weak-3.11.patch | 103 ++++++++++++++++ .../patch-sem-clockwait-weak-3.13.patch | 113 ++++++++++++++++++ cpython-unix/patch-sem-clockwait-weak.patch | 104 ++++++++++++++++ cpython-unix/test-sem-clockwait.py | 82 +++++++++++++ 6 files changed, 532 insertions(+) create mode 100644 cpython-unix/patch-sem-clockwait-weak-3.10.patch create mode 100644 cpython-unix/patch-sem-clockwait-weak-3.11.patch create mode 100644 cpython-unix/patch-sem-clockwait-weak-3.13.patch create mode 100644 cpython-unix/patch-sem-clockwait-weak.patch create mode 100644 cpython-unix/test-sem-clockwait.py diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 68686f4c..0fc9e8d8 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -658,6 +658,23 @@ if [ -n "${CROSS_COMPILING}" ]; then # TODO: There are probably more of these, see #599. fi +# Apply weak sem_clockwait patch for runtime detection on old glibc. +# When cross-compiling against old glibc headers (< 2.30), configure cannot detect +# sem_clockwait, causing threading.Event.wait() to use CLOCK_REALTIME instead of +# CLOCK_MONOTONIC. This makes waits hang when the system clock jumps backward. +# The patch declares sem_clockwait as a weak symbol and checks at runtime. +if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then + if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.13.patch + elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_12}" ]; then + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak.patch + elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_11}" ]; then + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.11.patch + else + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.10.patch + fi +fi + # Adjust the Python startup logic (getpath.py) to properly locate the installation, even when # invoked through a symlink or through an incorrect argv[0]. Because this Python is relocatable, we # don't get to rely on the fallback to the compiled-in installation prefix. diff --git a/cpython-unix/patch-sem-clockwait-weak-3.10.patch b/cpython-unix/patch-sem-clockwait-weak-3.10.patch new file mode 100644 index 00000000..12115f37 --- /dev/null +++ b/cpython-unix/patch-sem-clockwait-weak-3.10.patch @@ -0,0 +1,113 @@ +--- a/Python/thread_pthread.h ++++ b/Python/thread_pthread.h +@@ -87,6 +87,18 @@ + #endif + #endif + ++/* When cross-compiling against old glibc headers (e.g., glibc < 2.30), ++ * configure cannot detect sem_clockwait. Declare it as a weak symbol so it ++ * resolves to NULL on old glibc and to the real function on glibc 2.30+. ++ * This enables monotonic clock waits at runtime when available, preventing ++ * hangs when the system clock jumps backward (e.g., NTP sync). */ ++#if defined(__linux__) && !defined(HAVE_SEM_CLOCKWAIT) ++#include ++__attribute__((weak)) extern int sem_clockwait(sem_t *, clockid_t, ++ const struct timespec *); ++#define HAVE_SEM_CLOCKWAIT 1 ++#define _Py_SEM_CLOCKWAIT_WEAK 1 ++#endif + + /* Whether or not to use semaphores directly rather than emulating them with + * mutexes and condition variables: +@@ -443,9 +455,7 @@ + sem_t *thelock = (sem_t *)lock; + int status, error = 0; + struct timespec ts; +-#ifndef HAVE_SEM_CLOCKWAIT + _PyTime_t deadline = 0; +-#endif + + (void) error; /* silence unused-but-set-variable warning */ + dprintf(("PyThread_acquire_lock_timed(%p, %lld, %d) called\n", +@@ -455,29 +465,35 @@ + Py_FatalError("Timeout larger than PY_TIMEOUT_MAX"); + } + +- if (microseconds > 0) { +-#ifdef HAVE_SEM_CLOCKWAIT +- monotonic_abs_timeout(microseconds, &ts); ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ int use_clockwait = (sem_clockwait != NULL); + #else +- MICROSECONDS_TO_TIMESPEC(microseconds, ts); ++ int use_clockwait = 1; ++#endif + +- if (!intr_flag) { +- /* cannot overflow thanks to (microseconds > PY_TIMEOUT_MAX) +- check done above */ +- _PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000); +- deadline = _PyTime_GetMonotonicClock() + timeout; ++ if (microseconds > 0) { ++ if (use_clockwait) { ++ monotonic_abs_timeout(microseconds, &ts); ++ } else { ++ MICROSECONDS_TO_TIMESPEC(microseconds, ts); ++ ++ if (!intr_flag) { ++ /* cannot overflow thanks to (microseconds > PY_TIMEOUT_MAX) ++ check done above */ ++ _PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000); ++ deadline = _PyTime_GetMonotonicClock() + timeout; ++ } + } +-#endif + } + + while (1) { + if (microseconds > 0) { +-#ifdef HAVE_SEM_CLOCKWAIT +- status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, +- &ts)); +-#else +- status = fix_status(sem_timedwait(thelock, &ts)); +-#endif ++ if (use_clockwait) { ++ status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, ++ &ts)); ++ } else { ++ status = fix_status(sem_timedwait(thelock, &ts)); ++ } + } + else if (microseconds == 0) { + status = fix_status(sem_trywait(thelock)); +@@ -494,8 +510,7 @@ + + // sem_clockwait() uses an absolute timeout, there is no need + // to recompute the relative timeout. +-#ifndef HAVE_SEM_CLOCKWAIT +- if (microseconds > 0) { ++ if (!use_clockwait && microseconds > 0) { + /* wait interrupted by a signal (EINTR): recompute the timeout */ + _PyTime_t dt = deadline - _PyTime_GetMonotonicClock(); + if (dt < 0) { +@@ -516,18 +531,13 @@ + microseconds = 0; + } + } +-#endif + } + + /* Don't check the status if we're stopping because of an interrupt. */ + if (!(intr_flag && status == EINTR)) { + if (microseconds > 0) { + if (status != ETIMEDOUT) { +-#ifdef HAVE_SEM_CLOCKWAIT +- CHECK_STATUS("sem_clockwait"); +-#else +- CHECK_STATUS("sem_timedwait"); +-#endif ++ CHECK_STATUS(use_clockwait ? "sem_clockwait" : "sem_timedwait"); + } + } + else if (microseconds == 0) { diff --git a/cpython-unix/patch-sem-clockwait-weak-3.11.patch b/cpython-unix/patch-sem-clockwait-weak-3.11.patch new file mode 100644 index 00000000..93b9e7ad --- /dev/null +++ b/cpython-unix/patch-sem-clockwait-weak-3.11.patch @@ -0,0 +1,103 @@ +--- a/Python/thread_pthread.h ++++ b/Python/thread_pthread.h +@@ -89,6 +89,18 @@ + #endif + #endif + ++/* When cross-compiling against old glibc headers (e.g., glibc < 2.30), ++ * configure cannot detect sem_clockwait. Declare it as a weak symbol so it ++ * resolves to NULL on old glibc and to the real function on glibc 2.30+. ++ * This enables monotonic clock waits at runtime when available, preventing ++ * hangs when the system clock jumps backward (e.g., NTP sync). */ ++#if defined(__linux__) && !defined(HAVE_SEM_CLOCKWAIT) ++#include ++__attribute__((weak)) extern int sem_clockwait(sem_t *, clockid_t, ++ const struct timespec *); ++#define HAVE_SEM_CLOCKWAIT 1 ++#define _Py_SEM_CLOCKWAIT_WEAK 1 ++#endif + + /* Whether or not to use semaphores directly rather than emulating them with + * mutexes and condition variables: +@@ -463,32 +475,32 @@ + timeout = _PyTime_FromNanoseconds(-1); + } + +-#ifdef HAVE_SEM_CLOCKWAIT +- struct timespec abs_timeout; +- // Local scope for deadline +- { +- _PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); +- _PyTime_AsTimespec_clamp(deadline, &abs_timeout); +- } ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ int use_clockwait = (sem_clockwait != NULL); + #else ++ int use_clockwait = 1; ++#endif ++ struct timespec abs_timeout; + _PyTime_t deadline = 0; +- if (timeout > 0 && !intr_flag) { ++ if (use_clockwait) { ++ _PyTime_t dl = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); ++ _PyTime_AsTimespec_clamp(dl, &abs_timeout); ++ } else if (timeout > 0 && !intr_flag) { + deadline = _PyDeadline_Init(timeout); + } +-#endif + + while (1) { + if (timeout > 0) { +-#ifdef HAVE_SEM_CLOCKWAIT +- status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, +- &abs_timeout)); +-#else +- _PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), +- timeout); +- struct timespec ts; +- _PyTime_AsTimespec_clamp(abs_time, &ts); +- status = fix_status(sem_timedwait(thelock, &ts)); +-#endif ++ if (use_clockwait) { ++ status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, ++ &abs_timeout)); ++ } else { ++ _PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), ++ timeout); ++ struct timespec ts; ++ _PyTime_AsTimespec_clamp(abs_time, &ts); ++ status = fix_status(sem_timedwait(thelock, &ts)); ++ } + } + else if (timeout == 0) { + status = fix_status(sem_trywait(thelock)); +@@ -505,8 +517,7 @@ + + // sem_clockwait() uses an absolute timeout, there is no need + // to recompute the relative timeout. +-#ifndef HAVE_SEM_CLOCKWAIT +- if (timeout > 0) { ++ if (!use_clockwait && timeout > 0) { + /* wait interrupted by a signal (EINTR): recompute the timeout */ + timeout = _PyDeadline_Get(deadline); + if (timeout < 0) { +@@ -514,18 +525,13 @@ + break; + } + } +-#endif + } + + /* Don't check the status if we're stopping because of an interrupt. */ + if (!(intr_flag && status == EINTR)) { + if (timeout > 0) { + if (status != ETIMEDOUT) { +-#ifdef HAVE_SEM_CLOCKWAIT +- CHECK_STATUS("sem_clockwait"); +-#else +- CHECK_STATUS("sem_timedwait"); +-#endif ++ CHECK_STATUS(use_clockwait ? "sem_clockwait" : "sem_timedwait"); + } + } + else if (timeout == 0) { diff --git a/cpython-unix/patch-sem-clockwait-weak-3.13.patch b/cpython-unix/patch-sem-clockwait-weak-3.13.patch new file mode 100644 index 00000000..dda7ecc6 --- /dev/null +++ b/cpython-unix/patch-sem-clockwait-weak-3.13.patch @@ -0,0 +1,113 @@ +--- a/Python/thread_pthread.h ++++ b/Python/thread_pthread.h +@@ -100,6 +100,19 @@ + #undef HAVE_SEM_CLOCKWAIT + #endif + ++/* When cross-compiling against old glibc headers (e.g., glibc < 2.30), ++ * configure cannot detect sem_clockwait. Declare it as a weak symbol so it ++ * resolves to NULL on old glibc and to the real function on glibc 2.30+. ++ * This enables monotonic clock waits at runtime when available, preventing ++ * hangs when the system clock jumps backward (e.g., NTP sync). */ ++#if defined(__linux__) && !defined(HAVE_SEM_CLOCKWAIT) ++#include ++__attribute__((weak)) extern int sem_clockwait(sem_t *, clockid_t, ++ const struct timespec *); ++#define HAVE_SEM_CLOCKWAIT 1 ++#define _Py_SEM_CLOCKWAIT_WEAK 1 ++#endif ++ + /* Whether or not to use semaphores directly rather than emulating them with + * mutexes and condition variables: + */ +@@ -516,38 +529,38 @@ + timeout = -1; + } + +-#ifdef HAVE_SEM_CLOCKWAIT ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ int use_clockwait = (sem_clockwait != NULL); ++#else ++ int use_clockwait = 1; ++#endif + struct timespec abs_timeout; +- // Local scope for deadline +- { ++ PyTime_t deadline = 0; ++ if (use_clockwait) { + PyTime_t now; + // silently ignore error: cannot report error to the caller + (void)PyTime_MonotonicRaw(&now); +- PyTime_t deadline = _PyTime_Add(now, timeout); +- _PyTime_AsTimespec_clamp(deadline, &abs_timeout); +- } +-#else +- PyTime_t deadline = 0; +- if (timeout > 0 && !intr_flag) { ++ PyTime_t dl = _PyTime_Add(now, timeout); ++ _PyTime_AsTimespec_clamp(dl, &abs_timeout); ++ } else if (timeout > 0 && !intr_flag) { + deadline = _PyDeadline_Init(timeout); + } +-#endif + + while (1) { + if (timeout > 0) { +-#ifdef HAVE_SEM_CLOCKWAIT +- status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, +- &abs_timeout)); +-#else +- PyTime_t now; +- // silently ignore error: cannot report error to the caller +- (void)PyTime_TimeRaw(&now); +- PyTime_t abs_time = _PyTime_Add(now, timeout); ++ if (use_clockwait) { ++ status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, ++ &abs_timeout)); ++ } else { ++ PyTime_t now; ++ // silently ignore error: cannot report error to the caller ++ (void)PyTime_TimeRaw(&now); ++ PyTime_t abs_time = _PyTime_Add(now, timeout); + +- struct timespec ts; +- _PyTime_AsTimespec_clamp(abs_time, &ts); +- status = fix_status(sem_timedwait(thelock, &ts)); +-#endif ++ struct timespec ts; ++ _PyTime_AsTimespec_clamp(abs_time, &ts); ++ status = fix_status(sem_timedwait(thelock, &ts)); ++ } + } + else if (timeout == 0) { + status = fix_status(sem_trywait(thelock)); +@@ -564,8 +577,7 @@ + + // sem_clockwait() uses an absolute timeout, there is no need + // to recompute the relative timeout. +-#ifndef HAVE_SEM_CLOCKWAIT +- if (timeout > 0) { ++ if (!use_clockwait && timeout > 0) { + /* wait interrupted by a signal (EINTR): recompute the timeout */ + timeout = _PyDeadline_Get(deadline); + if (timeout < 0) { +@@ -573,18 +585,13 @@ + break; + } + } +-#endif + } + + /* Don't check the status if we're stopping because of an interrupt. */ + if (!(intr_flag && status == EINTR)) { + if (timeout > 0) { + if (status != ETIMEDOUT) { +-#ifdef HAVE_SEM_CLOCKWAIT +- CHECK_STATUS("sem_clockwait"); +-#else +- CHECK_STATUS("sem_timedwait"); +-#endif ++ CHECK_STATUS(use_clockwait ? "sem_clockwait" : "sem_timedwait"); + } + } + else if (timeout == 0) { diff --git a/cpython-unix/patch-sem-clockwait-weak.patch b/cpython-unix/patch-sem-clockwait-weak.patch new file mode 100644 index 00000000..79ab0caa --- /dev/null +++ b/cpython-unix/patch-sem-clockwait-weak.patch @@ -0,0 +1,104 @@ +--- a/Python/thread_pthread.h ++++ b/Python/thread_pthread.h +@@ -96,6 +96,19 @@ + #undef HAVE_SEM_CLOCKWAIT + #endif + ++/* When cross-compiling against old glibc headers (e.g., glibc < 2.30), ++ * configure cannot detect sem_clockwait. Declare it as a weak symbol so it ++ * resolves to NULL on old glibc and to the real function on glibc 2.30+. ++ * This enables monotonic clock waits at runtime when available, preventing ++ * hangs when the system clock jumps backward (e.g., NTP sync). */ ++#if defined(__linux__) && !defined(HAVE_SEM_CLOCKWAIT) ++#include ++__attribute__((weak)) extern int sem_clockwait(sem_t *, clockid_t, ++ const struct timespec *); ++#define HAVE_SEM_CLOCKWAIT 1 ++#define _Py_SEM_CLOCKWAIT_WEAK 1 ++#endif ++ + /* Whether or not to use semaphores directly rather than emulating them with + * mutexes and condition variables: + */ +@@ -456,32 +469,32 @@ + timeout = _PyTime_FromNanoseconds(-1); + } + +-#ifdef HAVE_SEM_CLOCKWAIT +- struct timespec abs_timeout; +- // Local scope for deadline +- { +- _PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); +- _PyTime_AsTimespec_clamp(deadline, &abs_timeout); +- } ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ int use_clockwait = (sem_clockwait != NULL); + #else ++ int use_clockwait = 1; ++#endif ++ struct timespec abs_timeout; + _PyTime_t deadline = 0; +- if (timeout > 0 && !intr_flag) { ++ if (use_clockwait) { ++ _PyTime_t dl = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); ++ _PyTime_AsTimespec_clamp(dl, &abs_timeout); ++ } else if (timeout > 0 && !intr_flag) { + deadline = _PyDeadline_Init(timeout); + } +-#endif + + while (1) { + if (timeout > 0) { +-#ifdef HAVE_SEM_CLOCKWAIT +- status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, +- &abs_timeout)); +-#else +- _PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), +- timeout); +- struct timespec ts; +- _PyTime_AsTimespec_clamp(abs_time, &ts); +- status = fix_status(sem_timedwait(thelock, &ts)); +-#endif ++ if (use_clockwait) { ++ status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC, ++ &abs_timeout)); ++ } else { ++ _PyTime_t abs_time = _PyTime_Add(_PyTime_GetSystemClock(), ++ timeout); ++ struct timespec ts; ++ _PyTime_AsTimespec_clamp(abs_time, &ts); ++ status = fix_status(sem_timedwait(thelock, &ts)); ++ } + } + else if (timeout == 0) { + status = fix_status(sem_trywait(thelock)); +@@ -498,8 +511,7 @@ + + // sem_clockwait() uses an absolute timeout, there is no need + // to recompute the relative timeout. +-#ifndef HAVE_SEM_CLOCKWAIT +- if (timeout > 0) { ++ if (!use_clockwait && timeout > 0) { + /* wait interrupted by a signal (EINTR): recompute the timeout */ + timeout = _PyDeadline_Get(deadline); + if (timeout < 0) { +@@ -507,18 +519,13 @@ + break; + } + } +-#endif + } + + /* Don't check the status if we're stopping because of an interrupt. */ + if (!(intr_flag && status == EINTR)) { + if (timeout > 0) { + if (status != ETIMEDOUT) { +-#ifdef HAVE_SEM_CLOCKWAIT +- CHECK_STATUS("sem_clockwait"); +-#else +- CHECK_STATUS("sem_timedwait"); +-#endif ++ CHECK_STATUS(use_clockwait ? "sem_clockwait" : "sem_timedwait"); + } + } + else if (timeout == 0) { diff --git a/cpython-unix/test-sem-clockwait.py b/cpython-unix/test-sem-clockwait.py new file mode 100644 index 00000000..78f261ba --- /dev/null +++ b/cpython-unix/test-sem-clockwait.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Test that sem_clockwait is available and threading waits use monotonic clock. + +Run against a python-build-standalone distribution to verify the weak symbol +patch is working: + + ./install/bin/python3 test-sem-clockwait.py + +On Linux with glibc >= 2.30, sem_clockwait should be detected (either by +configure or via the weak symbol runtime check). On older glibc, the weak +symbol resolves to NULL and the fallback to sem_timedwait is used. +""" + +import platform +import sys +import sysconfig +import threading +import time + + +def check_have_sem_clockwait(): + """Check if HAVE_SEM_CLOCKWAIT is set in the build configuration.""" + config_vars = sysconfig.get_config_vars() + have = config_vars.get("HAVE_SEM_CLOCKWAIT") + print(f"HAVE_SEM_CLOCKWAIT = {have!r}") + if platform.system() == "Linux": + if not have: + print("FAIL: HAVE_SEM_CLOCKWAIT not set on Linux") + return False + print("OK: HAVE_SEM_CLOCKWAIT is set") + else: + print(f"SKIP: not Linux ({platform.system()})") + return True + + +def check_event_wait_timing(): + """Sanity check that Event.wait returns in approximately the right time.""" + event = threading.Event() + wait_sec = 0.1 + + start = time.monotonic() + result = event.wait(timeout=wait_sec) + elapsed = time.monotonic() - start + + print(f"Event.wait({wait_sec}) returned {result} in {elapsed:.3f}s") + + if result: + print("FAIL: Event.wait returned True (event was unexpectedly set)") + return False + + # Allow generous bounds: 50ms to 2s + if elapsed < 0.05: + print(f"FAIL: returned too quickly ({elapsed:.3f}s < 0.05s)") + return False + if elapsed > 2.0: + print(f"FAIL: took too long ({elapsed:.3f}s > 2.0s)") + return False + + print("OK: timing is reasonable") + return True + + +def main(): + print(f"Python {sys.version}") + print(f"Platform: {platform.system()} {platform.machine()}") + print() + + ok = True + ok = check_have_sem_clockwait() and ok + print() + ok = check_event_wait_timing() and ok + print() + + if ok: + print("All checks passed.") + else: + print("Some checks failed.") + sys.exit(1) + + +if __name__ == "__main__": + main() From d0523daee70cd8882a7297d4338423f7febfb1e4 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sun, 22 Feb 2026 23:20:18 +0200 Subject: [PATCH 2/3] Rename base patch to patch-sem-clockwait-weak-3.12.patch, drop test from repo --- cpython-unix/build-cpython.sh | 2 +- ...ch => patch-sem-clockwait-weak-3.12.patch} | 0 cpython-unix/test-sem-clockwait.py | 82 ------------------- 3 files changed, 1 insertion(+), 83 deletions(-) rename cpython-unix/{patch-sem-clockwait-weak.patch => patch-sem-clockwait-weak-3.12.patch} (100%) delete mode 100644 cpython-unix/test-sem-clockwait.py diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 0fc9e8d8..3c789e42 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -667,7 +667,7 @@ if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.13.patch elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_12}" ]; then - patch -p1 -i ${ROOT}/patch-sem-clockwait-weak.patch + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.12.patch elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_11}" ]; then patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.11.patch else diff --git a/cpython-unix/patch-sem-clockwait-weak.patch b/cpython-unix/patch-sem-clockwait-weak-3.12.patch similarity index 100% rename from cpython-unix/patch-sem-clockwait-weak.patch rename to cpython-unix/patch-sem-clockwait-weak-3.12.patch diff --git a/cpython-unix/test-sem-clockwait.py b/cpython-unix/test-sem-clockwait.py deleted file mode 100644 index 78f261ba..00000000 --- a/cpython-unix/test-sem-clockwait.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -"""Test that sem_clockwait is available and threading waits use monotonic clock. - -Run against a python-build-standalone distribution to verify the weak symbol -patch is working: - - ./install/bin/python3 test-sem-clockwait.py - -On Linux with glibc >= 2.30, sem_clockwait should be detected (either by -configure or via the weak symbol runtime check). On older glibc, the weak -symbol resolves to NULL and the fallback to sem_timedwait is used. -""" - -import platform -import sys -import sysconfig -import threading -import time - - -def check_have_sem_clockwait(): - """Check if HAVE_SEM_CLOCKWAIT is set in the build configuration.""" - config_vars = sysconfig.get_config_vars() - have = config_vars.get("HAVE_SEM_CLOCKWAIT") - print(f"HAVE_SEM_CLOCKWAIT = {have!r}") - if platform.system() == "Linux": - if not have: - print("FAIL: HAVE_SEM_CLOCKWAIT not set on Linux") - return False - print("OK: HAVE_SEM_CLOCKWAIT is set") - else: - print(f"SKIP: not Linux ({platform.system()})") - return True - - -def check_event_wait_timing(): - """Sanity check that Event.wait returns in approximately the right time.""" - event = threading.Event() - wait_sec = 0.1 - - start = time.monotonic() - result = event.wait(timeout=wait_sec) - elapsed = time.monotonic() - start - - print(f"Event.wait({wait_sec}) returned {result} in {elapsed:.3f}s") - - if result: - print("FAIL: Event.wait returned True (event was unexpectedly set)") - return False - - # Allow generous bounds: 50ms to 2s - if elapsed < 0.05: - print(f"FAIL: returned too quickly ({elapsed:.3f}s < 0.05s)") - return False - if elapsed > 2.0: - print(f"FAIL: took too long ({elapsed:.3f}s > 2.0s)") - return False - - print("OK: timing is reasonable") - return True - - -def main(): - print(f"Python {sys.version}") - print(f"Platform: {platform.system()} {platform.machine()}") - print() - - ok = True - ok = check_have_sem_clockwait() and ok - print() - ok = check_event_wait_timing() and ok - print() - - if ok: - print("All checks passed.") - else: - print("Some checks failed.") - sys.exit(1) - - -if __name__ == "__main__": - main() From 4f545d7136a2452590647a274f1789236b60de55 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Mon, 23 Feb 2026 19:57:41 +0200 Subject: [PATCH 3/3] Add sem_clockwait weak symbol patch for CPython 3.15 In 3.15, the semaphore lock code moved from thread_pthread.h to parking_lot.c. --- cpython-unix/build-cpython.sh | 4 +- .../patch-sem-clockwait-weak-3.15.patch | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 cpython-unix/patch-sem-clockwait-weak-3.15.patch diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 3c789e42..e8b42ca8 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -664,7 +664,9 @@ fi # CLOCK_MONOTONIC. This makes waits hang when the system clock jumps backward. # The patch declares sem_clockwait as a weak symbol and checks at runtime. if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then - if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then + if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then + patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.15.patch + elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.13.patch elif [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_12}" ]; then patch -p1 -i ${ROOT}/patch-sem-clockwait-weak-3.12.patch diff --git a/cpython-unix/patch-sem-clockwait-weak-3.15.patch b/cpython-unix/patch-sem-clockwait-weak-3.15.patch new file mode 100644 index 00000000..4fd48861 --- /dev/null +++ b/cpython-unix/patch-sem-clockwait-weak-3.15.patch @@ -0,0 +1,51 @@ +--- a/Python/parking_lot.c ++++ b/Python/parking_lot.c +@@ -10,7 +10,21 @@ + + #include + ++/* When cross-compiling against old glibc headers (e.g., glibc < 2.30), ++ * configure cannot detect sem_clockwait. Declare it as a weak symbol so it ++ * resolves to NULL on old glibc and to the real function on glibc 2.30+. ++ * This enables monotonic clock waits at runtime when available, preventing ++ * hangs when the system clock jumps backward (e.g., NTP sync). */ ++#if defined(__linux__) && defined(_Py_USE_SEMAPHORES) && \ ++ !defined(HAVE_SEM_CLOCKWAIT) && !defined(_Py_THREAD_SANITIZER) ++#include ++__attribute__((weak)) extern int sem_clockwait(sem_t *, clockid_t, ++ const struct timespec *); ++#define HAVE_SEM_CLOCKWAIT 1 ++#define _Py_SEM_CLOCKWAIT_WEAK 1 ++#endif + ++ + typedef struct { + // The mutex protects the waiter queue and the num_waiters counter. + _PyRawMutex mutex; +@@ -150,6 +164,9 @@ + struct timespec ts; + + #if defined(CLOCK_MONOTONIC) && defined(HAVE_SEM_CLOCKWAIT) && !defined(_Py_THREAD_SANITIZER) ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ if (sem_clockwait != NULL) { ++#endif + PyTime_t now; + // silently ignore error: cannot report error to the caller + (void)PyTime_MonotonicRaw(&now); +@@ -157,6 +174,16 @@ + _PyTime_AsTimespec_clamp(deadline, &ts); + + err = sem_clockwait(&sema->platform_sem, CLOCK_MONOTONIC, &ts); ++#ifdef _Py_SEM_CLOCKWAIT_WEAK ++ } else { ++ PyTime_t now; ++ (void)PyTime_TimeRaw(&now); ++ PyTime_t deadline = _PyTime_Add(now, timeout); ++ _PyTime_AsTimespec_clamp(deadline, &ts); ++ ++ err = sem_timedwait(&sema->platform_sem, &ts); ++ } ++#endif + #else + PyTime_t now; + // silently ignore error: cannot report error to the caller