[libc-commits] [libc] [libc] End a semaphore wait with EINTR when a signal handler runs (PR #236969)
Mike Lothian via libc-commits
libc-commits at lists.llvm.org
Fri Oct 30 08:53:58 PDT 2026
https://github.com/FireBurn updated https://github.com/llvm/llvm-project/pull/236969
>From 46f5062ed424fe0dfd0c68423bce92d95975d1fc Mon Sep 17 00:00:00 2001
From: Mike Lothian <mike at fireburn.co.uk>
Date: Sun, 11 Oct 2026 16:50:57 +0100
Subject: [PATCH] [libc] End a semaphore wait with EINTR when a signal handler
runs
The futex wait started again after every interruption, so sem_wait,
sem_timedwait and sem_clockwait never returned for a signal. POSIX has
them fail with EINTR, and Python's locks rely on it to deliver
KeyboardInterrupt while waiting.
Assisted-by: Claude
---
.../src/__support/threads/linux/futex_utils.h | 28 +++++++++++++------
libc/src/semaphore/linux/semaphore.h | 15 +++++++---
libc/test/src/semaphore/CMakeLists.txt | 4 +++
libc/test/src/semaphore/sem_wait_test.cpp | 24 ++++++++++++++++
4 files changed, 59 insertions(+), 12 deletions(-)
diff --git a/libc/src/__support/threads/linux/futex_utils.h b/libc/src/__support/threads/linux/futex_utils.h
index c8adc9aab981d2..ab1813e7c1aea2 100644
--- a/libc/src/__support/threads/linux/futex_utils.h
+++ b/libc/src/__support/threads/linux/futex_utils.h
@@ -1,10 +1,15 @@
-//===--- Futex Wrapper ------------------------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Wrapper around a futex word.
+///
+//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_FUTEX_UTILS_H
@@ -34,6 +39,9 @@ static_assert(
"with 64-bit tv_nsec.");
#endif
+// What wait() does when a signal handler interrupts the futex wait.
+enum class FutexSigAction { Restart, Exit };
+
class Futex : public cpp::Atomic<FutexWordType> {
public:
using Timeout = internal::AbsTimeout;
@@ -43,9 +51,10 @@ class Futex : public cpp::Atomic<FutexWordType> {
cpp::Atomic<FutexWordType>::store(value);
return *this;
}
- LIBC_INLINE ErrorOr<int> wait(FutexWordType expected,
- cpp::optional<Timeout> timeout = cpp::nullopt,
- bool is_shared = false) {
+ LIBC_INLINE ErrorOr<int>
+ wait(FutexWordType expected, cpp::optional<Timeout> timeout = cpp::nullopt,
+ bool is_shared = false,
+ FutexSigAction sig_action = FutexSigAction::Restart) {
// use bitset variants to enforce abs_time
uint32_t op = is_shared ? FUTEX_WAIT_BITSET : FUTEX_WAIT_BITSET_PRIVATE;
if (timeout && timeout->is_realtime()) {
@@ -89,10 +98,13 @@ class Futex : public cpp::Atomic<FutexWordType> {
/*ignored=*/nullptr,
/*bitset=*/FUTEX_BITSET_MATCH_ANY);
- // continue waiting if interrupted; otherwise return the result
- // which should normally be 0 or -ETIMEOUT.
- if (ret == -EINTR)
- continue;
+ // continue waiting if interrupted, unless the caller asks to be notified;
+ // otherwise return the result which should normally be 0 or -ETIMEOUT.
+ if (ret == -EINTR) {
+ if (sig_action == FutexSigAction::Restart)
+ continue;
+ return cpp::unexpected(EINTR);
+ }
// EAGAIN and EWOULDBLOCK will be treated as a normal finish, as the value
// has changed.
diff --git a/libc/src/semaphore/linux/semaphore.h b/libc/src/semaphore/linux/semaphore.h
index 400a913f5f4aa9..159b415b161b42 100644
--- a/libc/src/semaphore/linux/semaphore.h
+++ b/libc/src/semaphore/linux/semaphore.h
@@ -1,10 +1,15 @@
-//===-- Linux Semaphore implementation for POSIX semaphores ---------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux implementation of POSIX semaphores.
+///
+//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H
#define LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H
@@ -44,7 +49,9 @@ class Semaphore {
// absolute timeout.
LIBC_INLINE int wait_until(internal::AbsTimeout timeout) {
while (trywait() != 0) {
- auto wait_or = value.wait(/*expected=*/0, timeout, is_shared);
+ // POSIX has a signal handler end the wait with EINTR.
+ auto wait_or =
+ value.wait(/*expected=*/0, timeout, is_shared, FutexSigAction::Exit);
// returned error is not a benign race EAGAIN
if (!wait_or.has_value() && wait_or.error() != EAGAIN) {
@@ -139,8 +146,8 @@ class Semaphore {
// Futex wait re-checks the value atomically,
// so a racing post() before sleep is not lost.
// Spurious wakeups just send back to trywait().
- auto wait_or =
- value.wait(/*expected=*/0, /*timeout=*/cpp::nullopt, is_shared);
+ auto wait_or = value.wait(/*expected=*/0, /*timeout=*/cpp::nullopt,
+ is_shared, FutexSigAction::Exit);
// A successful wake or EAGAIN both just send it back to trywait().
// Any other error is unexpected.
diff --git a/libc/test/src/semaphore/CMakeLists.txt b/libc/test/src/semaphore/CMakeLists.txt
index 1812799103bd2e..708ee2e782a869 100644
--- a/libc/test/src/semaphore/CMakeLists.txt
+++ b/libc/test/src/semaphore/CMakeLists.txt
@@ -38,6 +38,8 @@ add_libc_test(
DEPENDS
libc.hdr.errno_macros
libc.hdr.limits_macros
+ libc.hdr.signal_macros
+ libc.hdr.types.struct_sigaction
libc.hdr.time_macros
libc.hdr.types.sem_t
libc.hdr.types.struct_timespec
@@ -51,6 +53,8 @@ add_libc_test(
libc.src.semaphore.sem_timedwait
libc.src.semaphore.sem_trywait
libc.src.semaphore.sem_wait
+ libc.src.signal.sigaction
+ libc.src.unistd.alarm
libc.test.UnitTest.ErrnoCheckingTest
libc.test.UnitTest.ErrnoSetterMatcher
)
diff --git a/libc/test/src/semaphore/sem_wait_test.cpp b/libc/test/src/semaphore/sem_wait_test.cpp
index b46be16a34610a..66f9fee0eb442b 100644
--- a/libc/test/src/semaphore/sem_wait_test.cpp
+++ b/libc/test/src/semaphore/sem_wait_test.cpp
@@ -13,8 +13,10 @@
#include "hdr/errno_macros.h"
#include "hdr/limits_macros.h"
+#include "hdr/signal_macros.h"
#include "hdr/time_macros.h"
#include "hdr/types/sem_t.h"
+#include "hdr/types/struct_sigaction.h"
#include "hdr/types/struct_timespec.h"
#include "src/__support/time/clock_gettime.h"
#include "src/semaphore/sem_clockwait.h"
@@ -25,6 +27,8 @@
#include "src/semaphore/sem_timedwait.h"
#include "src/semaphore/sem_trywait.h"
#include "src/semaphore/sem_wait.h"
+#include "src/signal/sigaction.h"
+#include "src/unistd/alarm.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"
@@ -146,3 +150,23 @@ TEST_F(LlvmLibcSemWaitTest, ClockWaitUnsupportedClock) {
ASSERT_THAT(LIBC_NAMESPACE::sem_destroy(&sem), Succeeds());
}
+
+TEST_F(LlvmLibcSemWaitTest, ASignalHandlerEndsTheWaitWithEINTR) {
+ struct sigaction action{};
+ action.sa_handler = [](int) {};
+ // No SA_RESTART: the call is not to be started again.
+ ASSERT_THAT(LIBC_NAMESPACE::sigaction(SIGALRM, &action, nullptr), Succeeds());
+
+ sem_t sem;
+ ASSERT_THAT(LIBC_NAMESPACE::sem_init(&sem, 0, 0), Succeeds());
+
+ LIBC_NAMESPACE::alarm(1);
+ ASSERT_THAT(LIBC_NAMESPACE::sem_wait(&sem), Fails(EINTR));
+
+ LIBC_NAMESPACE::alarm(1);
+ timespec far = deadline_in_10ms(CLOCK_REALTIME);
+ far.tv_sec += 30;
+ ASSERT_THAT(LIBC_NAMESPACE::sem_timedwait(&sem, &far), Fails(EINTR));
+
+ ASSERT_THAT(LIBC_NAMESPACE::sem_destroy(&sem), Succeeds());
+}
More information about the libc-commits
mailing list