| // Copyright 2019 The Fuchsia 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 "src/developer/debug/shared/worker_pool.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <iostream> | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| using namespace std::chrono_literals; | |
| namespace debug_ipc { | |
| namespace { | |
| class TestWorkerPoolObserver : public WorkerPool::Observer { | |
| public: | |
| TestWorkerPoolObserver(std::mutex* mutex, std::condition_variable* cv) | |
| : mutex_(mutex), task_done_cv_(cv) {} | |
| void OnWorkerCreation() override; | |
| void OnWorkerExiting() override; | |
| void OnExecutingTask() override; | |
| void OnShutdown() override; | |
| void Reset(); | |
| int workers_created() const { return workers_created_; } | |
| int workers_exited() const { return workers_exited_; } | |
| int tasks_executed() const { return tasks_executed_; } | |
| bool shutdown_called() const { return shutdown_called_; } | |
| bool all_tasks_done() const { return all_tasks_done_; } | |
| std::mutex& mutex() { return *mutex_; } | |
| std::condition_variable& task_done_cv() { return *task_done_cv_; } | |
| private: | |
| std::atomic<int> workers_created_ = 0; | |
| std::atomic<int> workers_exited_ = 0; | |
| std::atomic<int> tasks_executed_ = 0; | |
| std::atomic<bool> all_tasks_done_ = false; | |
| std::atomic<bool> shutdown_called_ = false; | |
| std::mutex* mutex_; | |
| std::condition_variable* task_done_cv_; | |
| }; | |
| constexpr int kWorkerCount = 5; | |
| void WaitForTasksToBeDone(TestWorkerPoolObserver* observer) { | |
| auto timeout = 1s; | |
| auto threshold = std::chrono::system_clock::now() + timeout; | |
| std::unique_lock<std::mutex> lock(observer->mutex()); | |
| DEBUG_LOG(Test) << std::this_thread::get_id() << " Waiting for tasks."; | |
| observer->task_done_cv().wait_for(lock, timeout, [&observer, threshold] { | |
| // Either the tasks are done or we wait till the timeout. | |
| if (observer->all_tasks_done()) | |
| return true; | |
| return threshold < std::chrono::system_clock::now(); | |
| }); | |
| } | |
| TEST(WorkerPool, PostTasks) { | |
| // Enable for debugging the test. | |
| // debug_ipc::SetDebugMode(true); | |
| std::mutex mutex; | |
| std::condition_variable task_done_cv; | |
| TestWorkerPoolObserver observer(&mutex, &task_done_cv); | |
| WorkerPool task_queue(kWorkerCount, &observer); | |
| std::vector<bool> tasks_called; | |
| for (int i = 0; i < kWorkerCount; i++) { | |
| std::lock_guard<std::mutex> lock(observer.mutex()); | |
| tasks_called.push_back(false); | |
| bool posted = task_queue.PostTask([i, &tasks_called, &observer]() { | |
| std::lock_guard<std::mutex> guard(observer.mutex()); | |
| tasks_called[i] = true; | |
| DEBUG_LOG(Test) << std::this_thread::get_id() << " First round: Task " << i << " executed."; | |
| }); | |
| ASSERT_TRUE(posted) << "First round: while posting task " << i; | |
| } | |
| // Since we're not running, no tasks should've been run. | |
| ASSERT_EQ(observer.workers_created(), 0); | |
| // Will run until all the tasks have been completed or timeout. | |
| task_queue.Run(); | |
| WaitForTasksToBeDone(&observer); | |
| ASSERT_TRUE(observer.all_tasks_done()); | |
| ASSERT_EQ(observer.tasks_executed(), kWorkerCount); | |
| // Should've created workers and not exited any. | |
| ASSERT_TRUE(observer.workers_created() > 0); | |
| ASSERT_EQ(observer.workers_exited(), 0); | |
| // Should've run all the tasks. | |
| ASSERT_EQ(observer.tasks_executed(), kWorkerCount); | |
| for (int i = 0; i < kWorkerCount; i++) { | |
| EXPECT_TRUE(tasks_called[i]) << "First Round: Task " << i << " not called."; | |
| } | |
| // We append some more tasks. | |
| observer.Reset(); | |
| tasks_called.clear(); | |
| for (int i = 0; i < kWorkerCount; i++) { | |
| std::lock_guard<std::mutex> lock(observer.mutex()); | |
| tasks_called.push_back(false); | |
| bool posted = task_queue.PostTask([i, &tasks_called, &observer]() { | |
| std::lock_guard<std::mutex> lock(observer.mutex()); | |
| tasks_called[i] = true; | |
| DEBUG_LOG(Test) << std::this_thread::get_id() << " Second round: Task " << i << " executed."; | |
| }); | |
| ASSERT_TRUE(posted) << "Second round: while posting task " << i; | |
| } | |
| WaitForTasksToBeDone(&observer); | |
| ASSERT_TRUE(observer.all_tasks_done()); | |
| ASSERT_EQ(observer.tasks_executed(), kWorkerCount); | |
| // Should've run all the tasks. | |
| for (int i = 0; i < kWorkerCount; i++) { | |
| EXPECT_TRUE(tasks_called[i]) << "Second Round: Task " << i << " not called."; | |
| } | |
| // No workers should've exited. | |
| ASSERT_EQ(observer.workers_exited(), 0); | |
| task_queue.Shutdown(); | |
| // Should've join all the workers. | |
| EXPECT_TRUE(observer.shutdown_called()); | |
| EXPECT_EQ(observer.workers_exited(), observer.workers_created()); | |
| // Shouldn't be able to create tasks when shutdown. | |
| EXPECT_FALSE(task_queue.PostTask({})); | |
| } | |
| // TestWorkerPoolObserver | |
| // ------------------------------------------------------- | |
| void TestWorkerPoolObserver::OnWorkerCreation() { workers_created_++; } | |
| void TestWorkerPoolObserver::OnWorkerExiting() { workers_exited_++; } | |
| void TestWorkerPoolObserver::OnExecutingTask() { | |
| std::lock_guard<std::mutex> lock(*mutex_); | |
| tasks_executed_++; | |
| DEBUG_LOG(Test) << std::this_thread::get_id() << " executed task count: " << tasks_executed_; | |
| if (tasks_executed_ == kWorkerCount) { | |
| DEBUG_LOG(Test) << "All tasks are done!"; | |
| all_tasks_done_ = true; | |
| task_done_cv_->notify_one(); | |
| } | |
| } | |
| void TestWorkerPoolObserver::OnShutdown() { shutdown_called_ = true; }; | |
| void TestWorkerPoolObserver::Reset() { | |
| std::lock_guard<std::mutex> lock(*mutex_); | |
| tasks_executed_ = 0; | |
| all_tasks_done_ = false; | |
| } | |
| } // namespace | |
| } // namespace debug_ipc |