blob: 09eb24a1fab63e80062ede8b9c348c954a3f58da [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2016 Dmitry Vyukov <dvyukov@google.com>
// Copyright (C) 2016 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
#define EIGEN_USE_THREADS
#include "main.h"
#include <Eigen/ThreadPool>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <memory>
#include <thread>
#include <vector>
// Visual studio doesn't implement a rand_r() function since its
// implementation of rand() is already thread safe
int rand_reentrant(unsigned int* s) {
#if EIGEN_COMP_MSVC_STRICT
EIGEN_UNUSED_VARIABLE(s);
return rand();
#else
return rand_r(s);
#endif
}
static void test_basic_eventcount() {
MaxSizeVector<EventCount::Waiter> waiters(1);
waiters.resize(1);
EventCount ec(waiters);
EventCount::Waiter& w = waiters[0];
ec.Notify(false);
ec.Prewait();
ec.Notify(true);
ec.CommitWait(&w);
ec.Prewait();
ec.CancelWait();
}
static void test_cancel_wait_signal_forwarding() {
MaxSizeVector<EventCount::Waiter> waiters(2);
waiters.resize(2);
EventCount ec(waiters);
std::atomic<bool> t0_started(false);
std::atomic<bool> t0_done(false);
std::unique_ptr<std::thread> t0(new std::thread([&]() {
ec.Prewait();
t0_started.store(true);
ec.CommitWait(&waiters[0]);
t0_done.store(true);
}));
while (!t0_started.load()) {
std::this_thread::yield();
}
// Brief delay to allow t0 to enter CommitWait and park on stack.
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// Thread 1 calls Prewait, so waiters count becomes 1 (with t0 on the waiter stack).
ec.Prewait();
// Main thread signals a waiter.
// Because waiters count is 1 and signals count is 0 (signals < waiters),
// Notify(false) increments signals count rather than unparking t0 from the stack.
ec.Notify(false);
// Thread 1 cancels its wait.
// Without the fix, CancelWait decrements signals count to maintain signals <= waiters,
// discarding the notification and leaving t0 parked on the stack forever.
// With the fix, CancelWait calls Notify(false) to forward the discarded signal to t0.
ec.CancelWait();
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!t0_done.load() && std::chrono::steady_clock::now() < deadline) {
std::this_thread::yield();
}
const bool done = t0_done.load();
if (!done) {
// Unblock t0 if test failed so the process doesn't hang.
ec.Notify(false);
}
t0->join();
VERIFY(done);
}
// Fake bounded counter-based queue.
struct TestQueue {
std::atomic<int> val_;
static const int kQueueSize = 10;
TestQueue() : val_() {}
~TestQueue() { VERIFY_IS_EQUAL(val_.load(), 0); }
bool Push() {
int val = val_.load(std::memory_order_relaxed);
for (;;) {
VERIFY_GE(val, 0);
VERIFY_LE(val, kQueueSize);
if (val == kQueueSize) return false;
if (val_.compare_exchange_weak(val, val + 1, std::memory_order_relaxed)) return true;
}
}
bool Pop() {
int val = val_.load(std::memory_order_relaxed);
for (;;) {
VERIFY_GE(val, 0);
VERIFY_LE(val, kQueueSize);
if (val == 0) return false;
if (val_.compare_exchange_weak(val, val - 1, std::memory_order_relaxed)) return true;
}
}
bool Empty() { return val_.load(std::memory_order_relaxed) == 0; }
};
const int TestQueue::kQueueSize;
// A number of producers send messages to a set of consumers using a set of
// fake queues. Ensure that it does not crash, consumers don't deadlock and
// number of blocked and unblocked threads match.
static void test_stress_eventcount() {
const int kThreads = (std::min)(static_cast<int>(std::thread::hardware_concurrency()), 16);
static const int kEvents = 1 << 16;
static const int kQueues = 10;
MaxSizeVector<EventCount::Waiter> waiters(kThreads);
waiters.resize(kThreads);
EventCount ec(waiters);
TestQueue queues[kQueues];
std::vector<std::unique_ptr<std::thread>> producers;
for (int i = 0; i < kThreads; i++) {
producers.emplace_back(new std::thread([&ec, &queues]() {
unsigned int rnd = static_cast<unsigned int>(std::hash<std::thread::id>()(std::this_thread::get_id()));
for (int j = 0; j < kEvents; j++) {
unsigned idx = rand_reentrant(&rnd) % kQueues;
if (queues[idx].Push()) {
ec.Notify(false);
continue;
}
EIGEN_THREAD_YIELD();
j--;
}
}));
}
std::vector<std::unique_ptr<std::thread>> consumers;
for (int i = 0; i < kThreads; i++) {
consumers.emplace_back(new std::thread([&ec, &queues, &waiters, i]() {
EventCount::Waiter& w = waiters[i];
unsigned int rnd = static_cast<unsigned int>(std::hash<std::thread::id>()(std::this_thread::get_id()));
for (int j = 0; j < kEvents; j++) {
unsigned idx = rand_reentrant(&rnd) % kQueues;
if (queues[idx].Pop()) continue;
j--;
ec.Prewait();
bool empty = true;
for (int q = 0; q < kQueues; q++) {
if (!queues[q].Empty()) {
empty = false;
break;
}
}
if (!empty) {
ec.CancelWait();
continue;
}
ec.CommitWait(&w);
}
}));
}
for (int i = 0; i < kThreads; i++) {
producers[i]->join();
consumers[i]->join();
}
}
EIGEN_DECLARE_TEST(threads_eventcount) {
CALL_SUBTEST(test_basic_eventcount());
CALL_SUBTEST(test_cancel_wait_signal_forwarding());
CALL_SUBTEST(test_stress_eventcount());
}