blob: 30e97f2745eb89dc3a7ab77e2ee3ed757c9bcb2c [file]
//===-- 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;
}