| //===-- Integration test for CndVar with C11 threads ----------------------===// |
| // |
| // 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 "hdr/time_macros.h" |
| #include "src/__support/CPP/expected.h" |
| #include "src/__support/threads/CndVar.h" |
| #include "src/__support/threads/mutex.h" |
| #include "src/__support/threads/mutex_common.h" |
| #include "src/__support/threads/sleep.h" |
| #include "src/__support/time/clock_gettime.h" |
| #include "src/threads/thrd_create.h" |
| #include "src/threads/thrd_join.h" |
| #include "test/IntegrationTest/test.h" |
| |
| namespace { |
| |
| constexpr int THREAD_COUNT = 16; |
| constexpr int NUM_ITERATIONS = 250; |
| |
| struct QueueState { |
| LIBC_NAMESPACE::CndVar cnd{false}; |
| LIBC_NAMESPACE::Mutex m{false, false, false, false}; |
| size_t consumed = 0; |
| size_t produced = 0; |
| LIBC_NAMESPACE::cpp::Atomic<size_t> exited_consumers = 0; |
| bool use_broadcast; |
| bool allow_timeout; |
| }; |
| |
| void stress_test(bool use_broadcast, bool allow_timeout) { |
| for (int iter = 0; iter < NUM_ITERATIONS; ++iter) { |
| QueueState state{}; |
| state.use_broadcast = use_broadcast; |
| state.allow_timeout = allow_timeout; |
| constexpr size_t PRODUCER = THREAD_COUNT / 2; |
| constexpr size_t CONSUMER = THREAD_COUNT - PRODUCER; |
| constexpr size_t ITEMS_PER_PRODUCER = 200; |
| thrd_t producer_threads[PRODUCER]; |
| thrd_t consumer_threads[CONSUMER]; |
| using TimeoutOpt = |
| LIBC_NAMESPACE::cpp::optional<LIBC_NAMESPACE::CndVar::Timeout>; |
| for (size_t i = 0; i < CONSUMER; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::thrd_create( |
| &consumer_threads[i], |
| [](void *arg) { |
| auto *state = static_cast<QueueState *>(arg); |
| state->m.lock(); |
| while (state->consumed != PRODUCER * ITEMS_PER_PRODUCER) { |
| TimeoutOpt timeout = LIBC_NAMESPACE::cpp::nullopt; |
| if (state->allow_timeout) { |
| timespec now{}; |
| LIBC_NAMESPACE::internal::clock_gettime( |
| CLOCK_MONOTONIC, &now); |
| size_t sleep_ns = 1000; |
| now.tv_nsec += sleep_ns; |
| if (now.tv_nsec >= 1'000'000'000) { |
| now.tv_sec++; |
| now.tv_nsec -= 1'000'000'000; |
| } |
| timeout = TimeoutOpt( |
| LIBC_NAMESPACE::CndVar::Timeout::from_timespec( |
| now, |
| /*realtime=*/false) |
| .value()); |
| } |
| ASSERT_NE(state->cnd.wait(&state->m, timeout), |
| LIBC_NAMESPACE::CndVarResult::MutexError); |
| if (state->produced == 0) |
| continue; |
| state->produced--; |
| state->consumed++; |
| } |
| state->m.unlock(); |
| state->exited_consumers.fetch_add(1); |
| return 0; |
| }, |
| &state), |
| int(thrd_success)); |
| for (size_t i = 0; i < PRODUCER; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::thrd_create( |
| &producer_threads[i], |
| [](void *arg) { |
| auto *state = static_cast<QueueState *>(arg); |
| for (size_t j = 0; j < ITEMS_PER_PRODUCER; ++j) { |
| state->m.lock(); |
| state->produced++; |
| if (state->use_broadcast) |
| state->cnd.broadcast(); |
| else |
| state->cnd.notify_one(); |
| state->m.unlock(); |
| } |
| return 0; |
| }, |
| &state), |
| int(thrd_success)); |
| |
| // join producers |
| for (size_t i = 0; i < PRODUCER; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::thrd_join(producer_threads[i], nullptr), |
| int(thrd_success)); |
| // keep signalling until all consumers have consumed all items |
| while (state.exited_consumers != CONSUMER) { |
| if (state.use_broadcast) |
| state.cnd.broadcast(); |
| else |
| state.cnd.notify_one(); |
| } |
| // join consumers |
| for (size_t i = 0; i < CONSUMER; ++i) |
| ASSERT_EQ(LIBC_NAMESPACE::thrd_join(consumer_threads[i], nullptr), |
| int(thrd_success)); |
| } |
| } |
| } // namespace |
| |
| TEST_MAIN() { |
| stress_test(false, false); |
| stress_test(true, false); |
| stress_test(false, true); |
| stress_test(true, true); |
| return 0; |
| } |