[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