| //===----------------------------------------------------------------------===// |
| // |
| // 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; |
| } |