| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <gtest/gtest.h> |
| |
| #if __has_include(<threads.h>) |
| |
| #define HAVE_THREADS_H |
| #include <threads.h> |
| |
| static int g_call_once_call_count; |
| |
| static void increment_call_count() { |
| ++g_call_once_call_count; |
| } |
| |
| static int g_dtor_call_count; |
| |
| static void tss_dtor(void* ptr) { |
| ++g_dtor_call_count; |
| free(ptr); |
| } |
| |
| static int return_arg(void* arg) { |
| return static_cast<int>(reinterpret_cast<uintptr_t>(arg)); |
| } |
| |
| static int exit_arg(void* arg) { |
| thrd_exit(static_cast<int>(reinterpret_cast<uintptr_t>(arg))); |
| } |
| |
| #endif |
| |
| #include <time.h> |
| |
| #include <thread> |
| |
| #include <android-base/silent_death_test.h> |
| |
| #include "SignalUtils.h" |
| |
| TEST(threads, call_once) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| once_flag flag = ONCE_FLAG_INIT; |
| call_once(&flag, increment_call_count); |
| call_once(&flag, increment_call_count); |
| std::thread([&flag] { |
| call_once(&flag, increment_call_count); |
| }).join(); |
| ASSERT_EQ(1, g_call_once_call_count); |
| #endif |
| } |
| |
| TEST(threads, cnd_broadcast__cnd_wait) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| |
| cnd_t c; |
| ASSERT_EQ(thrd_success, cnd_init(&c)); |
| |
| std::atomic_int i = 0; |
| |
| auto waiter = [&c, &i, &m] { |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| while (i != 1) ASSERT_EQ(thrd_success, cnd_wait(&c, &m)); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| }; |
| std::thread t1(waiter); |
| std::thread t2(waiter); |
| std::thread t3(waiter); |
| |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| i = 1; |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| |
| ASSERT_EQ(thrd_success, cnd_broadcast(&c)); |
| |
| t1.join(); |
| t2.join(); |
| t3.join(); |
| |
| mtx_destroy(&m); |
| cnd_destroy(&c); |
| #endif |
| } |
| |
| TEST(threads, cnd_init__cnd_destroy) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| cnd_t c; |
| ASSERT_EQ(thrd_success, cnd_init(&c)); |
| cnd_destroy(&c); |
| #endif |
| } |
| |
| TEST(threads, cnd_signal__cnd_wait) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| cnd_t c; |
| ASSERT_EQ(thrd_success, cnd_init(&c)); |
| |
| std::atomic_int count = 0; |
| auto waiter = [&c, &m, &count] { |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| ASSERT_EQ(thrd_success, cnd_wait(&c, &m)); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| ++count; |
| }; |
| std::thread t1(waiter); |
| std::thread t2(waiter); |
| std::thread t3(waiter); |
| |
| // This is inherently racy, but attempts to distinguish between cnd_signal and |
| // cnd_broadcast. |
| usleep(100000); |
| ASSERT_EQ(thrd_success, cnd_signal(&c)); |
| while (count == 0) { |
| } |
| usleep(100000); |
| ASSERT_EQ(1, count); |
| |
| ASSERT_EQ(thrd_success, cnd_signal(&c)); |
| while (count == 1) { |
| } |
| usleep(100000); |
| ASSERT_EQ(2, count); |
| |
| ASSERT_EQ(thrd_success, cnd_signal(&c)); |
| while (count == 2) { |
| } |
| usleep(100000); |
| ASSERT_EQ(3, count); |
| |
| t1.join(); |
| t2.join(); |
| t3.join(); |
| |
| mtx_destroy(&m); |
| cnd_destroy(&c); |
| #endif |
| } |
| |
| TEST(threads, cnd_timedwait_timedout) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed)); |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| |
| cnd_t c; |
| ASSERT_EQ(thrd_success, cnd_init(&c)); |
| |
| timespec ts = {}; |
| ASSERT_EQ(thrd_timedout, cnd_timedwait(&c, &m, &ts)); |
| #endif |
| } |
| |
| TEST(threads, cnd_timedwait) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed)); |
| |
| cnd_t c; |
| ASSERT_EQ(thrd_success, cnd_init(&c)); |
| |
| std::atomic_bool done = false; |
| std::thread t([&c, &m, &done] { |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| |
| // cnd_timewait's time is *absolute*. |
| timespec ts; |
| ASSERT_EQ(TIME_UTC, timespec_get(&ts, TIME_UTC)); |
| ts.tv_sec += 666; |
| |
| ASSERT_EQ(thrd_success, cnd_timedwait(&c, &m, &ts)); |
| done = true; |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| }); |
| |
| while (!done) ASSERT_EQ(thrd_success, cnd_signal(&c)); |
| |
| t.join(); |
| #endif |
| } |
| |
| TEST(threads, mtx_init) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed)); |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain | mtx_recursive)); |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed | mtx_recursive)); |
| ASSERT_EQ(thrd_error, mtx_init(&m, 123)); |
| ASSERT_EQ(thrd_error, mtx_init(&m, mtx_recursive)); |
| #endif |
| } |
| |
| TEST(threads, mtx_destroy) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| mtx_destroy(&m); |
| #endif |
| } |
| |
| TEST(threads, mtx_lock_plain) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| ASSERT_EQ(thrd_busy, mtx_trylock(&m)); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| |
| mtx_destroy(&m); |
| #endif |
| } |
| |
| TEST(threads, mtx_lock_recursive) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain | mtx_recursive)); |
| |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| ASSERT_EQ(thrd_success, mtx_trylock(&m)); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| |
| mtx_destroy(&m); |
| #endif |
| } |
| |
| TEST(threads, mtx_timedlock) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed)); |
| |
| timespec ts = {}; |
| ASSERT_EQ(thrd_success, mtx_timedlock(&m, &ts)); |
| |
| std::thread([&m] { |
| timespec ts = { .tv_nsec = 500000 }; |
| ASSERT_EQ(thrd_timedout, mtx_timedlock(&m, &ts)); |
| }).join(); |
| |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| mtx_destroy(&m); |
| #endif |
| } |
| |
| |
| TEST(threads, mtx_unlock) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| mtx_t m; |
| ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain)); |
| ASSERT_EQ(thrd_success, mtx_lock(&m)); |
| std::thread([&m] { |
| ASSERT_EQ(thrd_busy, mtx_trylock(&m)); |
| }).join(); |
| ASSERT_EQ(thrd_success, mtx_unlock(&m)); |
| std::thread([&m] { |
| ASSERT_EQ(thrd_success, mtx_trylock(&m)); |
| }).join(); |
| #endif |
| } |
| |
| TEST(threads, thrd_current__thrd_equal) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| thrd_t t1 = thrd_current(); |
| // (As a side-effect, this demonstrates interoperability with std::thread.) |
| std::thread([&t1] { |
| thrd_t t2 = thrd_current(); |
| ASSERT_FALSE(thrd_equal(t1, t2)); |
| thrd_t t2_2 = thrd_current(); |
| ASSERT_TRUE(thrd_equal(t2, t2_2)); |
| }).join(); |
| thrd_t t1_2 = thrd_current(); |
| ASSERT_TRUE(thrd_equal(t1, t1_2)); |
| #endif |
| } |
| |
| TEST(threads, thrd_create__thrd_detach) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| thrd_t t; |
| ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(1))); |
| ASSERT_EQ(thrd_success, thrd_detach(t)); |
| #endif |
| } |
| |
| TEST(threads, thrd_create__thrd_exit) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| // Similar to the thrd_join test, but with a function that calls thrd_exit |
| // instead. |
| thrd_t t; |
| int result; |
| ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(1))); |
| ASSERT_EQ(thrd_success, thrd_join(t, &result)); |
| ASSERT_EQ(1, result); |
| |
| ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(2))); |
| ASSERT_EQ(thrd_success, thrd_join(t, &result)); |
| ASSERT_EQ(2, result); |
| |
| // The `result` argument can be null if you don't care... |
| ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(3))); |
| ASSERT_EQ(thrd_success, thrd_join(t, nullptr)); |
| #endif |
| } |
| |
| using threads_DeathTest = SilentDeathTest; |
| |
| TEST(threads_DeathTest, thrd_exit_main_thread) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| // "The program terminates normally after the last thread has been terminated. |
| // The behavior is as if the program called the exit function with the status |
| // EXIT_SUCCESS at thread termination time." (ISO/IEC 9899:2018) |
| ASSERT_EXIT(thrd_exit(12), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); |
| #endif |
| } |
| |
| TEST(threads, thrd_create__thrd_join) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| // Similar to the thrd_exit test, but with a function that calls return |
| // instead. |
| thrd_t t; |
| int result; |
| ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(1))); |
| ASSERT_EQ(thrd_success, thrd_join(t, &result)); |
| ASSERT_EQ(1, result); |
| |
| ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(2))); |
| ASSERT_EQ(thrd_success, thrd_join(t, &result)); |
| ASSERT_EQ(2, result); |
| |
| // The `result` argument can be null if you don't care... |
| ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(3))); |
| ASSERT_EQ(thrd_success, thrd_join(t, nullptr)); |
| #endif |
| } |
| |
| TEST(threads, thrd_sleep_signal) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| ScopedSignalHandler ssh{SIGALRM, [](int) {}}; |
| std::thread t([] { |
| timespec long_time = { .tv_sec = 666 }; |
| timespec remaining = {}; |
| ASSERT_EQ(-1, thrd_sleep(&long_time, &remaining)); |
| uint64_t t = remaining.tv_sec * 1000000000 + remaining.tv_nsec; |
| ASSERT_LE(t, 666ULL * 1000000000); |
| }); |
| usleep(100000); // 0.1s |
| pthread_kill(t.native_handle(), SIGALRM); |
| t.join(); |
| #endif |
| } |
| |
| TEST(threads, thrd_sleep_signal_nullptr) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| ScopedSignalHandler ssh{SIGALRM, [](int) {}}; |
| std::thread t([] { |
| timespec long_time = { .tv_sec = 666 }; |
| ASSERT_EQ(-1, thrd_sleep(&long_time, nullptr)); |
| }); |
| usleep(100000); // 0.1s |
| pthread_kill(t.native_handle(), SIGALRM); |
| t.join(); |
| #endif |
| } |
| |
| TEST(threads, thrd_sleep_error) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| timespec invalid = { .tv_sec = -1 }; |
| ASSERT_EQ(-2, thrd_sleep(&invalid, nullptr)); |
| #endif |
| } |
| |
| TEST(threads, thrd_yield) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| thrd_yield(); |
| #endif |
| } |
| |
| TEST(threads, TSS_DTOR_ITERATIONS_macro) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| ASSERT_EQ(PTHREAD_DESTRUCTOR_ITERATIONS, TSS_DTOR_ITERATIONS); |
| #endif |
| } |
| |
| TEST(threads, tss_create) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| tss_t key; |
| ASSERT_EQ(thrd_success, tss_create(&key, nullptr)); |
| tss_delete(key); |
| #endif |
| } |
| |
| TEST(threads, tss_create_dtor) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| tss_dtor_t dtor = tss_dtor; |
| tss_t key; |
| ASSERT_EQ(thrd_success, tss_create(&key, dtor)); |
| |
| ASSERT_EQ(thrd_success, tss_set(key, strdup("hello"))); |
| std::thread([&key] { |
| ASSERT_EQ(thrd_success, tss_set(key, strdup("world"))); |
| }).join(); |
| // Thread exit calls the destructor... |
| ASSERT_EQ(1, g_dtor_call_count); |
| |
| // "[A call to tss_set] will not invoke the destructor associated with the |
| // key on the value being replaced" (ISO/IEC 9899:2018). |
| g_dtor_call_count = 0; |
| ASSERT_EQ(thrd_success, tss_set(key, strdup("hello"))); |
| ASSERT_EQ(0, g_dtor_call_count); |
| |
| // "Calling tss_delete will not result in the invocation of any |
| // destructors" (ISO/IEC 9899:2018). |
| // The destructor for "hello" won't be called until *this* thread exits. |
| g_dtor_call_count = 0; |
| tss_delete(key); |
| ASSERT_EQ(0, g_dtor_call_count); |
| #endif |
| } |
| |
| TEST(threads, tss_get__tss_set) { |
| #if !defined(HAVE_THREADS_H) |
| GTEST_SKIP() << "<threads.h> unavailable"; |
| #else |
| tss_t key; |
| ASSERT_EQ(thrd_success, tss_create(&key, nullptr)); |
| |
| ASSERT_EQ(thrd_success, tss_set(key, const_cast<char*>("hello"))); |
| ASSERT_STREQ("hello", reinterpret_cast<char*>(tss_get(key))); |
| std::thread([&key] { |
| ASSERT_EQ(nullptr, tss_get(key)); |
| ASSERT_EQ(thrd_success, tss_set(key, const_cast<char*>("world"))); |
| ASSERT_STREQ("world", reinterpret_cast<char*>(tss_get(key))); |
| }).join(); |
| ASSERT_STREQ("hello", reinterpret_cast<char*>(tss_get(key))); |
| |
| tss_delete(key); |
| #endif |
| } |