[libc-commits] [libc] [libc] Implement pthread_kill (PR #222625)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Thu Sep 10 05:21:50 PDT 2026


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/222625

>From 9b5e7a2dbcb1aa8578d9829d2c77293f9233660f Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Wed, 9 Sep 2026 11:15:54 +0000
Subject: [PATCH] [libc] Implement pthread_kill

The implementation delegates to Thread::kill, which uses tgkill to
target the thread ID.

The main complication is handling zombie threads. POSIX.1-2024 requires
that pthread_kill on a terminated (zombie) thread does not return ESRCH
because the pthread_t handle is still valid. The Linux kernel reaps
threads immediately on exit, so calling tgkill on an exited thread would
return ESRCH (or worse, target a recycled TID). To handle this:
- we check the thread's detach_state first. If it's already EXITING, we
  simply return success without calling tgkill (a zombie thread cannot
  handle signals anyway).
- if the thread exits concurrently and tgkill returns -ESRCH, we assume
  it transitioned to a zombie and treat it as success.

I've also added a tgkill syscall wrapper and replaced one raw usage of
SYS_tgkill.

I've added an integration test covering signal delivery to self and
other threads, signal 0 checks, invalid signals, zombie threads, and
signal mask interactions.
---
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/riscv/entrypoints.txt       |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/include/CMakeLists.txt                   |   1 +
 libc/include/signal.yaml                      |  10 ++
 .../linux/syscall_wrappers/CMakeLists.txt     |  15 ++
 .../OSUtil/linux/syscall_wrappers/raise.h     |   9 +-
 .../OSUtil/linux/syscall_wrappers/tgkill.h    |  34 ++++
 .../__support/threads/linux/CMakeLists.txt    |   3 +
 libc/src/__support/threads/linux/thread.cpp   |  40 +++++
 libc/src/__support/threads/thread.h           |   3 +
 libc/src/signal/CMakeLists.txt                |   7 +
 libc/src/signal/linux/CMakeLists.txt          |  16 ++
 libc/src/signal/linux/pthread_kill.cpp        |  42 +++++
 libc/src/signal/pthread_kill.h                |  31 ++++
 .../integration/src/pthread/CMakeLists.txt    |  26 +++
 .../src/pthread/pthread_kill_test.cpp         | 170 ++++++++++++++++++
 17 files changed, 407 insertions(+), 3 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/tgkill.h
 create mode 100644 libc/src/signal/linux/pthread_kill.cpp
 create mode 100644 libc/src/signal/pthread_kill.h
 create mode 100644 libc/test/integration/src/pthread/pthread_kill_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 6c9015d43941e..d76621a44ff32 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1282,6 +1282,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
     # signal.h entrypoints
     libc.src.signal.kill
+    libc.src.signal.pthread_kill
     libc.src.signal.pthread_sigmask
     libc.src.signal.raise
     libc.src.signal.sigaction
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index b9078023d25d5..d8db54701575e 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1474,6 +1474,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
     # signal.h entrypoints
     libc.src.signal.kill
+    libc.src.signal.pthread_kill
     libc.src.signal.pthread_sigmask
     libc.src.signal.raise
     libc.src.signal.sigaction
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 342527a1c767f..e46e05fc3aa98 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1487,6 +1487,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
     # signal.h entrypoints
     libc.src.signal.kill
+    libc.src.signal.pthread_kill
     libc.src.signal.pthread_sigmask
     libc.src.signal.raise
     libc.src.signal.sigaction
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 87b52bb756d11..4ab70e43740f2 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -454,6 +454,7 @@ add_header_macro(
   DEPENDS
     .llvm-libc-macros.signal_macros
     .llvm-libc-types.pid_t
+    .llvm-libc-types.pthread_t
     .llvm-libc-types.sig_atomic_t
     .llvm-libc-types.sig_t
     .llvm-libc-types.sighandler_t
diff --git a/libc/include/signal.yaml b/libc/include/signal.yaml
index 88c931edd51f9..f5f110915cfc8 100644
--- a/libc/include/signal.yaml
+++ b/libc/include/signal.yaml
@@ -43,6 +43,9 @@ types:
   - type_name: struct_sigaction
   - type_name: struct_sigevent
   - type_name: union_sigval
+  - type_name: pthread_t
+    standards:
+      - posix
 enums: []
 objects: []
 functions:
@@ -128,3 +131,10 @@ functions:
       - type: int
       - type: const sigset_t *__restrict
       - type: sigset_t *__restrict
+  - name: pthread_kill
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: pthread_t
+      - type: int
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 9512528e9e5e2..7400f7fccafa9 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -410,10 +410,12 @@ add_header_library(
   DEPENDS
     libc.src.__support.OSUtil.osutil
     libc.src.__support.OSUtil.linux.syscall_wrappers.rt_sigprocmask
+    libc.src.__support.OSUtil.linux.syscall_wrappers.tgkill
     libc.src.__support.common
     libc.src.__support.error_or
     libc.src.__support.macros.config
     libc.hdr.signal_macros
+    libc.hdr.types.pid_t
     libc.hdr.types.sigset_t
     libc.include.sys_syscall
 )
@@ -1251,3 +1253,16 @@ add_header_library(
     libc.src.__support.macros.config
     libc.include.sys_syscall
 )
+
+add_header_library(
+  tgkill
+  HDRS
+    tgkill.h
+  DEPENDS
+    libc.hdr.types.pid_t
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.include.sys_syscall
+)
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
index 2b61e4e624c89..adbe2ab3a6308 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
@@ -10,9 +10,11 @@
 #define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_RAISE_H
 
 #include "hdr/signal_macros.h"
+#include "hdr/types/pid_t.h"
 #include "hdr/types/sigset_t.h"
 #include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
 #include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/tgkill.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
@@ -54,9 +56,10 @@ LIBC_INLINE ErrorOr<int> raise(int sig) {
     if (tid < 0)
       return Error(-static_cast<int>(tid));
 
-    int result = syscall_impl<int>(SYS_tgkill, pid, tid, sig);
-    if (result < 0)
-      return Error(-result);
+    auto result = linux_syscalls::tgkill(static_cast<pid_t>(pid),
+                                         static_cast<pid_t>(tid), sig);
+    if (!result.has_value())
+      return Error(result.error());
   }
   return status;
 }
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/tgkill.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/tgkill.h
new file mode 100644
index 0000000000000..094c724631ef4
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/tgkill.h
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Syscall wrapper for tgkill.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_TGKILL_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_TGKILL_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_checked
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> tgkill(pid_t tgid, pid_t tid, int sig) {
+  return syscall_checked<int>(SYS_tgkill, tgid, tid, sig);
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_TGKILL_H
diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt
index 51e9d4005ad0d..e7f5f5cbfe3c7 100644
--- a/libc/src/__support/threads/linux/CMakeLists.txt
+++ b/libc/src/__support/threads/linux/CMakeLists.txt
@@ -51,6 +51,7 @@ add_object_library(
     libc.hdr.fcntl_macros
     libc.hdr.errno_macros
     libc.hdr.sched_macros
+    libc.hdr.signal_macros
     libc.hdr.sys_mman_macros
     libc.src.errno.errno
     libc.src.__support.CPP.atomic
@@ -59,6 +60,7 @@ add_object_library(
     libc.src.__support.common
     libc.src.__support.error_or
     libc.src.__support.OSUtil.linux.syscall_wrappers.close
+    libc.src.__support.OSUtil.linux.syscall_wrappers.getpid
     libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
     libc.src.__support.OSUtil.linux.syscall_wrappers.mprotect
     libc.src.__support.OSUtil.linux.syscall_wrappers.munmap
@@ -67,6 +69,7 @@ add_object_library(
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getparam
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getscheduler
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_setscheduler
+    libc.src.__support.OSUtil.linux.syscall_wrappers.tgkill
     libc.src.__support.OSUtil.linux.syscall_wrappers.write
     libc.src.__support.threads.thread_common
   COMPILE_OPTIONS
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 20b3e5b88fa8d..136471edf39c7 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -12,6 +12,7 @@
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/CPP/stringstream.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/mprotect.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
@@ -20,6 +21,7 @@
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getparam.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getscheduler.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_setscheduler.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/tgkill.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
 #include "src/__support/OSUtil/syscall.h" // For syscall functions.
 #include "src/__support/common.h"
@@ -27,6 +29,7 @@
 #include "src/__support/libc_errno.h" // For error macros
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
+#include "src/__support/threads/thread_attributes.h"
 
 #ifdef LIBC_TARGET_ARCH_IS_AARCH64
 #include <arm_acle.h>
@@ -35,6 +38,7 @@
 #include "hdr/errno_macros.h"
 #include "hdr/fcntl_macros.h"
 #include "hdr/sched_macros.h" // For CLONE_* flags.
+#include "hdr/signal_macros.h"
 #include "hdr/stdint_proxy.h"
 #include "hdr/sys_mman_macros.h" // For PROT_* and MAP_* definitions.
 #include <linux/param.h> // For EXEC_PAGESIZE.
@@ -519,6 +523,42 @@ ErrorOr<SchedParameters> Thread::getschedparam() const {
   return SchedParameters{pol_result.value(), param};
 }
 
+ErrorOr<void> Thread::kill(int sig) {
+  auto state = static_cast<DetachState>(
+      attrib->detach_state.load(cpp::MemoryOrder::RELAXED));
+  switch (state) {
+  case DetachState::EXITING:
+    // The thread is exiting, or has already exited. POSIX.1-2024 requires that
+    // pthread_kill does not return ESRCH because the pthread_t (unlike the OS
+    // TID) is still valid. Calling tgkill would return ESRCH (or target a
+    // recycled TID), so we return success directly. We only need to "request
+    // that a signal be delivered", and not actually make sure it has been
+    // handled. A zombie thread cannot handle signals.
+    return {};
+  case DetachState::JOINABLE:
+  case DetachState::DETACHED:
+    // A thread in these states can handle a signal. Note that a JOINABLE thread
+    // can transition to the EXITING state at any moment (and a DETACHED thread
+    // can disappear), but we're not allowed to take any locks to prevent that
+    // from happening (this function needs to be async-signal-safe).
+    break;
+  }
+
+  pid_t pid = linux_syscalls::getpid();
+  auto result = linux_syscalls::tgkill(pid, attrib->tid, sig);
+
+  if (!result.has_value()) {
+    if (result.error() == ESRCH) {
+      // Either the thread has exited since we've checked its state, or this
+      // object is corrupted. The latter is UB, so we're going to assume the
+      // former.
+      return {};
+    }
+    return Error(result.error());
+  }
+  return {};
+}
+
 void thread_exit(ThreadReturnValue retval, ThreadStyle style) {
   auto attrib = current_thread().attrib;
 
diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h
index c42f87a1a3c57..fcff23a98bb6c 100644
--- a/libc/src/__support/threads/thread.h
+++ b/libc/src/__support/threads/thread.h
@@ -151,6 +151,9 @@ struct Thread {
   // Get the scheduling policy and parameters of the thread.
   // Return SchedParameters on success, or an error number on failure.
   ErrorOr<SchedParameters> getschedparam() const;
+
+  // Send a signal to the thread.
+  ErrorOr<void> kill(int sig);
 };
 
 // Platforms should implement this function.
diff --git a/libc/src/signal/CMakeLists.txt b/libc/src/signal/CMakeLists.txt
index a55b55e54ed1c..5611a5da853b9 100644
--- a/libc/src/signal/CMakeLists.txt
+++ b/libc/src/signal/CMakeLists.txt
@@ -44,6 +44,13 @@ add_entrypoint_object(
     .${LIBC_TARGET_OS}.pthread_sigmask
 )
 
+add_entrypoint_object(
+  pthread_kill
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.pthread_kill
+)
+
 add_entrypoint_object(
   sigemptyset
   ALIAS
diff --git a/libc/src/signal/linux/CMakeLists.txt b/libc/src/signal/linux/CMakeLists.txt
index 5d7c8c25dccb6..e585aa1a01fce 100644
--- a/libc/src/signal/linux/CMakeLists.txt
+++ b/libc/src/signal/linux/CMakeLists.txt
@@ -107,6 +107,22 @@ add_entrypoint_object(
     libc.src.__support.macros.config
 )
 
+add_entrypoint_object(
+  pthread_kill
+  SRCS
+    pthread_kill.cpp
+  HDRS
+    ../pthread_kill.h
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.signal_macros
+    libc.hdr.types.pthread_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.__support.threads.thread
+)
+
 add_entrypoint_object(
   sigemptyset
   SRCS
diff --git a/libc/src/signal/linux/pthread_kill.cpp b/libc/src/signal/linux/pthread_kill.cpp
new file mode 100644
index 0000000000000..2a897068a18c5
--- /dev/null
+++ b/libc/src/signal/linux/pthread_kill.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 pthread_kill.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/signal/pthread_kill.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/types/pthread_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/threads/thread.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+static_assert(sizeof(pthread_t) == sizeof(LIBC_NAMESPACE::Thread),
+              "Mismatch between pthread_t and internal Thread.");
+
+LLVM_LIBC_FUNCTION(int, pthread_kill, (pthread_t th, int sig)) {
+  auto *thread = reinterpret_cast<Thread *>(&th);
+  LIBC_CRASH_ON_NULLPTR(thread->attrib);
+
+  // We can't delegate this check to the kernel since some of the code paths
+  // don't go through the syscall.
+  if (sig < 0 || sig >= NSIG)
+    return EINVAL;
+
+  auto res = thread->kill(sig);
+  return res ? 0 : res.error();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/signal/pthread_kill.h b/libc/src/signal/pthread_kill.h
new file mode 100644
index 0000000000000..dca33e8a02b92
--- /dev/null
+++ b/libc/src/signal/pthread_kill.h
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Implementation header for pthread_kill.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SIGNAL_PTHREAD_KILL_H
+#define LLVM_LIBC_SRC_SIGNAL_PTHREAD_KILL_H
+
+#include "hdr/types/pthread_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Request that a signal be delivered to a thread.
+///
+/// \param thread The thread to receive the signal.
+/// \param sig The signal to deliver.
+/// \return 0 on success, or an error number on failure.
+int pthread_kill(pthread_t thread, int sig);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SIGNAL_PTHREAD_KILL_H
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index 75c9fed78a713..6ec434cc51681 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -365,3 +365,29 @@ add_integration_test(
     libc.src.pthread.pthread_self
     libc.src.pthread.pthread_setschedparam
 )
+
+add_integration_test(
+  pthread_kill_test
+  SUITE
+    libc-pthread-integration-tests
+  SRCS
+    pthread_kill_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.signal_macros
+    libc.hdr.stdint_proxy
+    libc.hdr.types.pthread_t
+    libc.hdr.types.sigset_t
+    libc.hdr.types.struct_sigaction
+    libc.src.__support.CPP.atomic
+    libc.src.__support.threads.futex_utils
+    libc.src.__support.threads.thread
+    libc.src.pthread.pthread_create
+    libc.src.pthread.pthread_join
+    libc.src.pthread.pthread_self
+    libc.src.signal.pthread_kill
+    libc.src.signal.pthread_sigmask
+    libc.src.signal.sigaction
+    libc.src.signal.sigaddset
+    libc.src.signal.sigemptyset
+)
diff --git a/libc/test/integration/src/pthread/pthread_kill_test.cpp b/libc/test/integration/src/pthread/pthread_kill_test.cpp
new file mode 100644
index 0000000000000..205e8b879ba75
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_kill_test.cpp
@@ -0,0 +1,170 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Integration tests for pthread_kill.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/pthread_t.h"
+#include "hdr/types/sigset_t.h"
+#include "hdr/types/struct_sigaction.h"
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/threads/futex_utils.h"
+#include "src/__support/threads/thread.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_self.h"
+#include "src/signal/pthread_kill.h"
+#include "src/signal/pthread_sigmask.h"
+#include "src/signal/sigaction.h"
+#include "src/signal/sigaddset.h"
+#include "src/signal/sigemptyset.h"
+#include "test/IntegrationTest/test.h"
+
+static LIBC_NAMESPACE::Futex usr1_count(0);
+static LIBC_NAMESPACE::Futex usr2_count(0);
+
+static void sigusr1_handler(int) {
+  usr1_count.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+}
+
+static void sigusr2_handler(int) {
+  usr2_count.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+}
+
+static void setup_signal_handlers() {
+  struct sigaction sa = {};
+  sa.sa_handler = sigusr1_handler;
+  sa.sa_flags = 0;
+  LIBC_NAMESPACE::sigemptyset(&sa.sa_mask);
+  ASSERT_EQ(LIBC_NAMESPACE::sigaction(SIGUSR1, &sa, nullptr), 0);
+
+  sa.sa_handler = sigusr2_handler;
+  ASSERT_EQ(LIBC_NAMESPACE::sigaction(SIGUSR2, &sa, nullptr), 0);
+}
+
+static void test_invalid_signal() {
+  pthread_t self = LIBC_NAMESPACE::pthread_self();
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(self, -1), EINVAL);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(self, 1000), EINVAL);
+}
+
+static void test_self_signal_zero() {
+  pthread_t self = LIBC_NAMESPACE::pthread_self();
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(self, 0), 0);
+}
+
+static void test_self_signal_delivery() {
+  pthread_t self = LIBC_NAMESPACE::pthread_self();
+  uint32_t initial = usr1_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(self, SIGUSR1), 0);
+  ASSERT_EQ(usr1_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED),
+            initial + 1);
+}
+
+static void *cross_thread_worker(void *initial_opaque) {
+  uint32_t initial =
+      static_cast<uint32_t>(reinterpret_cast<uintptr_t>(initial_opaque));
+  usr1_count.wait(initial);
+  return nullptr;
+}
+
+static void test_cross_thread_signal() {
+  pthread_t th;
+  uint32_t initial = usr1_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, cross_thread_worker,
+                                           reinterpret_cast<void *>(initial)),
+            0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(th, 0), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(th, SIGUSR1), 0);
+
+  void *retval;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+}
+
+static void *zombie_worker(void *) { return nullptr; }
+
+static void test_zombie_thread() {
+  pthread_t th;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, nullptr, zombie_worker, nullptr), 0);
+
+  // Wait until thread has exited.
+  auto *thread_internal = reinterpret_cast<LIBC_NAMESPACE::Thread *>(&th);
+  thread_internal->wait();
+
+  // POSIX.1-2024 requires that a zombie thread ID does not return ESRCH.
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(th, 0), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(th, -1), EINVAL);
+
+  void *retval;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+}
+
+static LIBC_NAMESPACE::Futex mask_ready(0);
+static LIBC_NAMESPACE::Futex unblock_signal(0);
+
+static void *mask_worker(void *) {
+  sigset_t set;
+  LIBC_NAMESPACE::sigemptyset(&set);
+  LIBC_NAMESPACE::sigaddset(&set, SIGUSR2);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_sigmask(SIG_BLOCK, &set, nullptr), 0);
+
+  mask_ready.store(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELEASE);
+  mask_ready.notify_one();
+
+  unblock_signal.wait(0);
+
+  // Signal was sent while blocked. It should not have been delivered yet.
+  ASSERT_EQ(usr2_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 0);
+
+  // Unblocking the signal should cause immediate delivery.
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_sigmask(SIG_UNBLOCK, &set, nullptr), 0);
+
+  ASSERT_EQ(usr2_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 1);
+  return nullptr;
+}
+
+static void test_signal_mask_interaction() {
+  mask_ready.store(0, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+  unblock_signal.store(0, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+  usr2_count.store(0, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+
+  pthread_t th;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, mask_worker, nullptr),
+            0);
+
+  mask_ready.wait(0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_kill(th, SIGUSR2), 0);
+
+  unblock_signal.store(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELEASE);
+  unblock_signal.notify_one();
+
+  void *retval;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+  ASSERT_EQ(usr2_count.load(LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED), 1);
+}
+
+TEST_MAIN() {
+  setup_signal_handlers();
+
+  test_invalid_signal();
+  test_self_signal_zero();
+  test_self_signal_delivery();
+  test_cross_thread_signal();
+  test_zombie_thread();
+  test_signal_mask_interaction();
+
+  return 0;
+}



More information about the libc-commits mailing list