| //===-- Tests for pthread_join-- ------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/pthread/pthread_create.h" |
| #include "src/pthread/pthread_join.h" |
| #include "src/pthread/pthread_self.h" |
| |
| #include "src/__support/CPP/atomic.h" |
| #include "src/__support/threads/thread.h" |
| #include "test/IntegrationTest/test.h" |
| |
| #include <errno.h> |
| #include <pthread.h> |
| |
| static void *simple_func(void *) { return nullptr; } |
| |
| static void null_join_test() { |
| pthread_t tid; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&tid, nullptr, simple_func, nullptr), |
| 0); |
| ASSERT_ERRNO_SUCCESS(); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(tid, nullptr), 0); |
| ASSERT_ERRNO_SUCCESS(); |
| } |
| |
| static void self_join_test() { |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_join(LIBC_NAMESPACE::pthread_self(), nullptr), |
| EDEADLK); |
| } |
| |
| struct MutualJoinArgs { |
| pthread_t *peer; |
| LIBC_NAMESPACE::cpp::Atomic<int> *ready_count; |
| LIBC_NAMESPACE::cpp::Atomic<int> *start; |
| LIBC_NAMESPACE::cpp::Atomic<int> *result; |
| int start_value; |
| }; |
| |
| static void *mutual_join_func(void *arg) { |
| auto *args = reinterpret_cast<MutualJoinArgs *>(arg); |
| args->ready_count->fetch_add(1); |
| while (args->start->load() < args->start_value) |
| ; // Spin until this thread is released to join its peer. |
| |
| args->result->store(LIBC_NAMESPACE::pthread_join(*args->peer, nullptr)); |
| return nullptr; |
| } |
| |
| static bool is_joining(pthread_t joiner, pthread_t target) { |
| auto *joiner_thread = reinterpret_cast<LIBC_NAMESPACE::Thread *>(&joiner); |
| auto *target_thread = reinterpret_cast<LIBC_NAMESPACE::Thread *>(&target); |
| return target_thread->attrib->joiner.load() == joiner_thread->attrib; |
| } |
| |
| static void mutual_join_test() { |
| pthread_t thread1; |
| pthread_t thread2; |
| LIBC_NAMESPACE::cpp::Atomic<int> ready_count(0); |
| LIBC_NAMESPACE::cpp::Atomic<int> start(0); |
| LIBC_NAMESPACE::cpp::Atomic<int> result1(-1); |
| LIBC_NAMESPACE::cpp::Atomic<int> result2(-1); |
| |
| MutualJoinArgs args1{&thread2, &ready_count, &start, &result1, 1}; |
| MutualJoinArgs args2{&thread1, &ready_count, &start, &result2, 2}; |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&thread1, nullptr, mutual_join_func, |
| &args1), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&thread2, nullptr, mutual_join_func, |
| &args2), |
| 0); |
| |
| while (ready_count.load() != 2) |
| ; // Spin until both threads are ready to join each other. |
| start.store(1); |
| while (!is_joining(thread1, thread2)) |
| ; // Spin until thread1 has started joining thread2. |
| start.store(2); |
| |
| while (result1.load() == -1 || result2.load() == -1) |
| ; // Spin until the successful joiner and deadlock loser have both exited. |
| |
| // A thread is recovered to joinable state if its joining requester gets |
| // EDEADLK. |
| bool thread1_joinable = result2.load() == EDEADLK; |
| bool thread2_joinable = result1.load() == EDEADLK; |
| |
| ASSERT_TRUE(thread1_joinable || thread2_joinable); |
| |
| if (thread1_joinable) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(thread1, nullptr), 0); |
| if (thread2_joinable) |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(thread2, nullptr), 0); |
| } |
| |
| TEST_MAIN() { |
| errno = 0; |
| null_join_test(); |
| self_join_test(); |
| mutual_join_test(); |
| return 0; |
| } |