You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
517 lines
13 KiB
517 lines
13 KiB
/*
|
|
* 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
|
|
}
|