blob: 0e110ed11436543568408bc858c6ad04857ac1b3 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/task/common/operations_controller.h"
#include <atomic>
#include <cstdint>
#include <utility>
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace internal {
namespace {
class ScopedShutdown {
public:
ScopedShutdown(OperationsController* controller) : controller_(*controller) {}
~ScopedShutdown() { controller_.ShutdownAndWaitForZeroOperations(); }
private:
OperationsController& controller_;
};
TEST(OperationsControllerTest, CanBeDestroyedWithoutWaiting) {
OperationsController controller;
}
TEST(OperationsControllerTest, CanShutdownIfNotStarted) {
OperationsController controller;
controller.ShutdownAndWaitForZeroOperations();
}
TEST(OperationsControllerTest, FailsToBeginWhenNotStarted) {
OperationsController controller;
auto operation_token = controller.TryBeginOperation();
EXPECT_FALSE(operation_token);
}
TEST(OperationsControllerTest, CanShutdownAfterTryCallsIfNotStarted) {
OperationsController controller;
auto operation_token = controller.TryBeginOperation();
ASSERT_FALSE(operation_token);
controller.ShutdownAndWaitForZeroOperations();
}
TEST(OperationsControllerTest,
StartAcceptingOperationsReturnsFalseIfNoRejectedBeginAttempts) {
OperationsController controller;
ScopedShutdown cleanup(&controller);
EXPECT_FALSE(controller.StartAcceptingOperations());
}
TEST(OperationsControllerTest,
StartAcceptingOperationsReturnsTrueIfFailedBeginAttempts) {
OperationsController controller;
ScopedShutdown cleanup(&controller);
auto operation_token = controller.TryBeginOperation();
ASSERT_FALSE(operation_token);
EXPECT_TRUE(controller.StartAcceptingOperations());
}
TEST(OperationsControllerTest, SuccesfulBeginReturnsValidScopedObject) {
OperationsController controller;
ScopedShutdown cleanup(&controller);
controller.StartAcceptingOperations();
auto operation_token = controller.TryBeginOperation();
EXPECT_TRUE(operation_token);
}
TEST(OperationsControllerTest, BeginFailsAfterShutdown) {
OperationsController controller;
controller.StartAcceptingOperations();
controller.ShutdownAndWaitForZeroOperations();
auto operation_token = controller.TryBeginOperation();
EXPECT_FALSE(operation_token);
}
TEST(OperationsControllerTest, ScopedOperationsControllerIsMoveConstructible) {
OperationsController controller;
ScopedShutdown cleanup(&controller);
controller.StartAcceptingOperations();
auto operation_token_1 = controller.TryBeginOperation();
auto operation_token_2 = std::move(operation_token_1);
EXPECT_FALSE(operation_token_1);
EXPECT_TRUE(operation_token_2);
}
// Dummy SimpleThread implementation that periodically begins and ends
// operations until one of them fails.
class TestThread : public SimpleThread {
public:
explicit TestThread(OperationsController* ref_controller,
std::atomic<bool>* started,
std::atomic<int32_t>* thread_counter)
: SimpleThread("TestThread"),
controller_(*ref_controller),
started_(*started),
thread_counter_(*thread_counter) {}
void Run() override {
thread_counter_.fetch_add(1, std::memory_order_relaxed);
while (true) {
PlatformThread::YieldCurrentThread();
bool was_started = started_.load(std::memory_order_relaxed);
std::vector<OperationsController::OperationToken> tokens;
for (int i = 0; i < 100; ++i) {
tokens.push_back(controller_.TryBeginOperation());
}
if (!was_started)
continue;
if (std::any_of(tokens.begin(), tokens.end(),
[](const auto& token) { return !token; })) {
break;
}
}
}
private:
OperationsController& controller_;
std::atomic<bool>& started_;
std::atomic<int32_t>& thread_counter_;
};
TEST(OperationsControllerTest, BeginsFromMultipleThreads) {
constexpr int32_t kNumThreads = 10;
for (int32_t i = 0; i < 10; ++i) {
OperationsController ref_controller;
std::atomic<bool> started(false);
std::atomic<int32_t> running_threads(0);
std::vector<std::unique_ptr<TestThread>> threads;
for (int j = 0; j < kNumThreads; ++j) {
threads.push_back(std::make_unique<TestThread>(&ref_controller, &started,
&running_threads));
threads.back()->Start();
}
// Make sure all threads are running.
while (running_threads.load(std::memory_order_relaxed) != kNumThreads) {
PlatformThread::YieldCurrentThread();
}
// Wait a bit before starting to try to introduce races.
constexpr TimeDelta kRaceInducingTimeout = TimeDelta::FromMicroseconds(50);
PlatformThread::Sleep(kRaceInducingTimeout);
ref_controller.StartAcceptingOperations();
// Signal threads to terminate on TryBeginOperation() failures
started.store(true, std::memory_order_relaxed);
// Let the test run for a while before shuting down.
PlatformThread::Sleep(TimeDelta::FromMilliseconds(5));
ref_controller.ShutdownAndWaitForZeroOperations();
for (const auto& t : threads) {
t->Join();
}
}
}
} // namespace
} // namespace internal
} // namespace base