blob: 205e8b879ba75fa9bd13ac08445fb0ba352b961d [file]
//===----------------------------------------------------------------------===//
//
// 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;
}