// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/thread_pool.h" #include "vm/lockers.h" #include "vm/os.h" #include "vm/unit_test.h" namespace dart { DECLARE_FLAG(int, worker_timeout_millis); // Some of these tests change VM flags, so they should run without a full VM // startup to prevent races on the flag changes. None of the tests require full // VM startup, so we do this for all of them. #define THREAD_POOL_UNIT_TEST_CASE(name) \ static void name##helper(); \ UNIT_TEST_CASE(name) { \ OSThread::Init(); \ name##helper(); \ /* Delete the current thread's TLS and set it's TLS to null. */ \ /* If it is the last thread then the destructor would call */ \ /* OSThread::Cleanup. */ \ OSThread* os_thread = OSThread::Current(); \ OSThread::SetCurrent(nullptr); \ delete os_thread; \ } \ void name##helper() THREAD_POOL_UNIT_TEST_CASE(ThreadPool_Create) { ThreadPool thread_pool; } class TestTask : public ThreadPool::Task { public: TestTask(Monitor* sync, bool* done) : sync_(sync), done_(done) {} // Before running the task, *done_ should be true. This lets the caller // ASSERT things knowing that the thread is still around. To unblock the // thread, the caller should take the lock, set *done_ to false, and Notify() // the monitor. virtual void Run() { { MonitorLocker ml(sync_); while (*done_) { ml.Wait(); } } MonitorLocker ml(sync_); *done_ = true; ml.Notify(); } private: Monitor* sync_; bool* done_; }; THREAD_POOL_UNIT_TEST_CASE(ThreadPool_RunOne) { ThreadPool thread_pool; Monitor sync; bool done = true; thread_pool.Run(&sync, &done); { MonitorLocker ml(&sync); done = false; ml.Notify(); while (!done) { ml.Wait(); } } EXPECT(done); // Do a sanity test on the worker stats. EXPECT_EQ(1U, thread_pool.workers_started()); EXPECT(!thread_pool.has_pending_dead_worker()); } THREAD_POOL_UNIT_TEST_CASE(ThreadPool_RunMany) { const int kTaskCount = 100; ThreadPool thread_pool; Monitor sync[kTaskCount]; bool done[kTaskCount]; for (int i = 0; i < kTaskCount; i++) { done[i] = true; thread_pool.Run(&sync[i], &done[i]); } for (int i = 0; i < kTaskCount; i++) { MonitorLocker ml(&sync[i]); done[i] = false; ml.Notify(); while (!done[i]) { ml.Wait(); } EXPECT(done[i]); } } class SleepTask : public ThreadPool::Task { public: SleepTask(Monitor* sync, int* started_count, int* slept_count, int millis) : sync_(sync), started_count_(started_count), slept_count_(slept_count), millis_(millis) {} virtual void Run() { { MonitorLocker ml(sync_); *started_count_ = *started_count_ + 1; ml.Notify(); } // Sleep so we can be sure the ThreadPool destructor blocks until we're // done. OS::Sleep(millis_); { MonitorLocker ml(sync_); *slept_count_ = *slept_count_ + 1; // No notification here. The main thread is blocked in ThreadPool // shutdown waiting for this thread to finish. } } private: Monitor* sync_; int* started_count_; int* slept_count_; int millis_; }; THREAD_POOL_UNIT_TEST_CASE(ThreadPool_WorkerShutdown) { const int kTaskCount = 10; Monitor sync; int slept_count = 0; int started_count = 0; // Set up the ThreadPool so that workers notify before they exit. ThreadPool* thread_pool = new ThreadPool(); // Run a single task. for (int i = 0; i < kTaskCount; i++) { thread_pool->Run(&sync, &started_count, &slept_count, 2); } { // Wait for everybody to start. MonitorLocker ml(&sync); while (started_count < kTaskCount) { ml.Wait(); } } // Kill the thread pool while the workers are sleeping. delete thread_pool; thread_pool = nullptr; int final_count = 0; { MonitorLocker ml(&sync); final_count = slept_count; } // We should have waited for all the workers to finish, so they all should // have had a chance to increment slept_count. EXPECT_EQ(kTaskCount, final_count); } THREAD_POOL_UNIT_TEST_CASE(ThreadPool_WorkerTimeout) { // Adjust the worker timeout so that we timeout quickly. int saved_timeout = FLAG_worker_timeout_millis; FLAG_worker_timeout_millis = 1; { ThreadPool thread_pool; EXPECT_EQ(0U, thread_pool.workers_started()); EXPECT(!thread_pool.has_pending_dead_worker()); // Run a worker. Monitor sync; bool done = true; thread_pool.Run(&sync, &done); EXPECT_EQ(1U, thread_pool.workers_started()); EXPECT(!thread_pool.has_pending_dead_worker()); { MonitorLocker ml(&sync); done = false; ml.Notify(); while (!done) { ml.Wait(); } } EXPECT(done); // Wait up to 5 seconds to see if a worker times out. const int kMaxWait = 5000; int waited = 0; while (!thread_pool.has_pending_dead_worker() && waited < kMaxWait) { OS::Sleep(1); waited += 1; } EXPECT(thread_pool.has_pending_dead_worker()); } FLAG_worker_timeout_millis = saved_timeout; } class SpawnTask : public ThreadPool::Task { public: SpawnTask(ThreadPool* pool, Monitor* sync, int todo, int total, int* done) : pool_(pool), sync_(sync), todo_(todo), total_(total), done_(done) {} virtual void Run() { todo_--; // Subtract one for current task. int child_todo = todo_ / 2; // Spawn 0-2 children. if (todo_ > 0) { pool_->Run(pool_, sync_, todo_ - child_todo, total_, done_); } if (todo_ > 1) { pool_->Run(pool_, sync_, child_todo, total_, done_); } { MonitorLocker ml(sync_); (*done_)++; if (*done_ >= total_) { ml.Notify(); } } } private: ThreadPool* pool_; Monitor* sync_; int todo_; int total_; int* done_; }; THREAD_POOL_UNIT_TEST_CASE(ThreadPool_RecursiveSpawn) { ThreadPool thread_pool; Monitor sync; const int kTotalTasks = 500; int done = 0; thread_pool.Run(&thread_pool, &sync, kTotalTasks, kTotalTasks, &done); { MonitorLocker ml(&sync); while (done < kTotalTasks) { ml.Wait(); } } EXPECT_EQ(kTotalTasks, done); } } // namespace dart