blob: 1dcf3dfde9848e8444ed4381969d39d48619a9bf [file] [log] [blame]
// 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_EQ(0U, thread_pool.workers_stopped());
}
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 = NULL;
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_EQ(0U, thread_pool.workers_stopped());
// Run a worker.
Monitor sync;
bool done = true;
thread_pool.Run<TestTask>(&sync, &done);
EXPECT_EQ(1U, thread_pool.workers_started());
EXPECT_EQ(0U, thread_pool.workers_stopped());
{
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.workers_stopped() == 0 && waited < kMaxWait) {
OS::Sleep(1);
waited += 1;
}
EXPECT_EQ(1U, thread_pool.workers_stopped());
}
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