[libc-commits] [libc] [libc] Implement pthread_kill (PR #222625)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Thu Sep 10 05:17:33 PDT 2026
https://github.com/labath created https://github.com/llvm/llvm-project/pull/222625
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.
>From 1ac7c30006fc92c68dc135b2e3b51c8e88754c90 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 | 172 ++++++++++++++++++
17 files changed, 409 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..1233a1e40da7b
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_kill_test.cpp
@@ -0,0 +1,172 @@
+//===----------------------------------------------------------------------===//
+//
+// 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