| // 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<TestTask>(&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<TestTask>(&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<SleepTask>(&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<TestTask>(&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<SpawnTask>(pool_, sync_, todo_ - child_todo, total_, done_); | |
| } | |
| if (todo_ > 1) { | |
| pool_->Run<SpawnTask>(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<SpawnTask>(&thread_pool, &sync, kTotalTasks, kTotalTasks, | |
| &done); | |
| { | |
| MonitorLocker ml(&sync); | |
| while (done < kTotalTasks) { | |
| ml.Wait(); | |
| } | |
| } | |
| EXPECT_EQ(kTotalTasks, done); | |
| } | |
| } // namespace dart |