blob: 9fd43201d8cf2ddd88275f83eed7fc1c79de634c [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
/// Tests for pthread_cleanup_push and pthread_cleanup_pop.
///
//===----------------------------------------------------------------------===//
#include "src/pthread/__pthread_cleanup_pop.h"
#include "src/pthread/__pthread_cleanup_push.h"
#include "src/pthread/pthread_create.h"
#include "src/pthread/pthread_exit.h"
#include "src/pthread/pthread_getspecific.h"
#include "src/pthread/pthread_join.h"
#include "src/pthread/pthread_key_create.h"
#include "src/pthread/pthread_key_delete.h"
#include "src/pthread/pthread_mutex_destroy.h"
#include "src/pthread/pthread_mutex_init.h"
#include "src/pthread/pthread_mutex_lock.h"
#include "src/pthread/pthread_mutex_trylock.h"
#include "src/pthread/pthread_mutex_unlock.h"
#include "src/pthread/pthread_setspecific.h"
#include "test/IntegrationTest/test.h"
#include "hdr/pthread_macros.h"
// To make pthread_cleanup_push/pop work outside of LIBC_NAMESPACE
#define __pthread_cleanup_push LIBC_NAMESPACE::__pthread_cleanup_push
#define __pthread_cleanup_pop LIBC_NAMESPACE::__pthread_cleanup_pop
static int call_order = 0;
static void record_routine(void *arg) {
*reinterpret_cast<int *>(arg) = ++call_order;
}
// 1. Test direct pop with execute = 0 and execute = 1 on the main thread.
static void test_direct_pop() {
int val = 0;
call_order = 0;
pthread_cleanup_push(record_routine, &val);
pthread_cleanup_pop(0);
ASSERT_EQ(val, 0);
pthread_cleanup_push(record_routine, &val);
pthread_cleanup_pop(1);
ASSERT_EQ(val, 1);
}
// 2. Test nested push / pop blocks in LIFO order on the main thread.
static void test_nested_pop() {
int order[2] = {0, 0};
call_order = 0;
pthread_cleanup_push(record_routine, &order[1]);
pthread_cleanup_push(record_routine, &order[0]);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
ASSERT_EQ(order[0], 1);
ASSERT_EQ(order[1], 2);
}
// 3. Test sequential push / pop blocks in the same scope.
static void test_sequential_pop() {
int first = 0;
int second = 0;
call_order = 0;
pthread_cleanup_push(record_routine, &first);
pthread_cleanup_pop(1);
pthread_cleanup_push(record_routine, &second);
pthread_cleanup_pop(1);
ASSERT_EQ(first, 1);
ASSERT_EQ(second, 2);
}
// 4. Test pthread_exit running handlers in LIFO order.
static int exit_order[3] = {0, 0, 0};
static void *thread_exit_lifo_func(void *) {
pthread_cleanup_push(record_routine, &exit_order[2]);
pthread_cleanup_push(record_routine, &exit_order[1]);
pthread_cleanup_push(record_routine, &exit_order[0]);
LIBC_NAMESPACE::pthread_exit(nullptr);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return nullptr;
}
static void test_exit_lifo() {
pthread_t th;
void *retval = nullptr;
call_order = 0;
ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_exit_lifo_func,
nullptr),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
ASSERT_EQ(exit_order[0], 1);
ASSERT_EQ(exit_order[1], 2);
ASSERT_EQ(exit_order[2], 3);
}
// 5. Test that handlers popped prior to pthread_exit are not executed.
static int exit_called1 = 0;
static int exit_called2 = 0;
static void *thread_popped_func(void *) {
pthread_cleanup_push(record_routine, &exit_called1);
pthread_cleanup_push(record_routine, &exit_called2);
pthread_cleanup_pop(0); // Pop handler2 without executing
LIBC_NAMESPACE::pthread_exit(nullptr);
pthread_cleanup_pop(0);
return nullptr;
}
static void test_exit_popped_not_called() {
pthread_t th;
void *retval = nullptr;
call_order = 0;
ASSERT_EQ(
LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_popped_func, nullptr),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
ASSERT_EQ(exit_called1, 1);
ASSERT_EQ(exit_called2, 0);
}
// 6. Test that cleanup handlers run before TSS destructors.
static pthread_key_t tss_key;
static int global_sequence = 0;
static int cleanup_sequence = 0;
static int tss_sequence = 0;
static int dummy_tss_val = 99;
static void tss_destructor(void *) { tss_sequence = ++global_sequence; }
static void tss_cleanup_handler(void *) {
ASSERT_EQ(LIBC_NAMESPACE::pthread_getspecific(tss_key), &dummy_tss_val);
cleanup_sequence = ++global_sequence;
}
static void *thread_tss_order_func(void *) {
LIBC_NAMESPACE::pthread_setspecific(tss_key, &dummy_tss_val);
pthread_cleanup_push(tss_cleanup_handler, nullptr);
LIBC_NAMESPACE::pthread_exit(nullptr);
pthread_cleanup_pop(0);
return nullptr;
}
static void test_exit_order_before_tss() {
ASSERT_EQ(LIBC_NAMESPACE::pthread_key_create(&tss_key, tss_destructor), 0);
pthread_t th;
void *retval = nullptr;
ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_tss_order_func,
nullptr),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
ASSERT_EQ(cleanup_sequence, 1);
ASSERT_EQ(tss_sequence, 2);
ASSERT_EQ(LIBC_NAMESPACE::pthread_key_delete(tss_key), 0);
}
// 7. Test re-entrancy: a cleanup handler pushes and pops an inner handler.
static bool outer_called = false;
static bool inner_called = false;
static void inner_handler(void *) { inner_called = true; }
static void outer_handler(void *) {
outer_called = true;
pthread_cleanup_push(inner_handler, nullptr);
pthread_cleanup_pop(1);
}
static void *thread_reentrancy_func(void *) {
pthread_cleanup_push(outer_handler, nullptr);
LIBC_NAMESPACE::pthread_exit(nullptr);
pthread_cleanup_pop(0);
return nullptr;
}
static void test_reentrancy() {
pthread_t th;
void *retval = nullptr;
ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_reentrancy_func,
nullptr),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
ASSERT_TRUE(outer_called);
ASSERT_TRUE(inner_called);
}
// 8. Test releasing a mutex in a cleanup function.
static pthread_mutex_t test_mutex;
static void mutex_cleanup_routine(void *arg) {
auto *mutex = reinterpret_cast<pthread_mutex_t *>(arg);
LIBC_NAMESPACE::pthread_mutex_unlock(mutex);
}
static void *thread_mutex_cleanup_func(void *) {
LIBC_NAMESPACE::pthread_mutex_lock(&test_mutex);
pthread_cleanup_push(mutex_cleanup_routine, &test_mutex);
// Thread exits while holding the mutex. The cleanup handler should unlock it.
LIBC_NAMESPACE::pthread_exit(nullptr);
pthread_cleanup_pop(0);
return nullptr;
}
static void test_mutex_cleanup() {
ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_init(&test_mutex, nullptr), 0);
pthread_t th;
void *retval = nullptr;
ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr,
thread_mutex_cleanup_func, nullptr),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
// Since the cleanup handler released the mutex, we should now be able to
// lock it without blocking.
ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_trylock(&test_mutex), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_unlock(&test_mutex), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_destroy(&test_mutex), 0);
}
TEST_MAIN() {
test_direct_pop();
test_nested_pop();
test_sequential_pop();
test_exit_lifo();
test_exit_popped_not_called();
test_exit_order_before_tss();
test_reentrancy();
test_mutex_cleanup();
return 0;
}