mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:10:22 +00:00
d01d0ea8e6
Split up the StackOverflow runtime entry. Bug: https://github.com/dart-lang/sdk/issues/31578 Change-Id: Ibe26101db1651d6e776c3da5fde6b44fd27cd00e Reviewed-on: https://dart-review.googlesource.com/27414 Reviewed-by: Zach Anderson <zra@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
486 lines
12 KiB
C++
486 lines
12 KiB
C++
// 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/dart.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/lockers.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(int,
|
|
worker_timeout_millis,
|
|
5000,
|
|
"Free workers when they have been idle for this amount of time.");
|
|
|
|
ThreadPool::ThreadPool()
|
|
: shutting_down_(false),
|
|
all_workers_(NULL),
|
|
idle_workers_(NULL),
|
|
count_started_(0),
|
|
count_stopped_(0),
|
|
count_running_(0),
|
|
count_idle_(0),
|
|
shutting_down_workers_(NULL),
|
|
join_list_(NULL) {}
|
|
|
|
ThreadPool::~ThreadPool() {
|
|
Shutdown();
|
|
}
|
|
|
|
bool ThreadPool::Run(Task* task) {
|
|
Worker* worker = NULL;
|
|
bool new_worker = false;
|
|
{
|
|
// We need ThreadPool::mutex_ to access worker lists and other
|
|
// ThreadPool state.
|
|
MutexLocker ml(&mutex_);
|
|
if (shutting_down_) {
|
|
return false;
|
|
}
|
|
if (idle_workers_ == NULL) {
|
|
worker = new Worker(this);
|
|
ASSERT(worker != NULL);
|
|
new_worker = true;
|
|
count_started_++;
|
|
|
|
// Add worker to the all_workers_ list.
|
|
worker->all_next_ = all_workers_;
|
|
all_workers_ = worker;
|
|
worker->owned_ = true;
|
|
count_running_++;
|
|
} else {
|
|
// Get the first worker from the idle worker list.
|
|
worker = idle_workers_;
|
|
idle_workers_ = worker->idle_next_;
|
|
worker->idle_next_ = NULL;
|
|
count_idle_--;
|
|
count_running_++;
|
|
}
|
|
}
|
|
|
|
// Release ThreadPool::mutex_ before calling Worker functions.
|
|
ASSERT(worker != NULL);
|
|
worker->SetTask(task);
|
|
if (new_worker) {
|
|
// Call StartThread after we've assigned the first task.
|
|
worker->StartThread();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ThreadPool::Shutdown() {
|
|
Worker* saved = NULL;
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
shutting_down_ = true;
|
|
saved = all_workers_;
|
|
all_workers_ = NULL;
|
|
idle_workers_ = NULL;
|
|
|
|
Worker* current = saved;
|
|
while (current != NULL) {
|
|
Worker* next = current->all_next_;
|
|
current->idle_next_ = NULL;
|
|
current->owned_ = false;
|
|
current = next;
|
|
count_stopped_++;
|
|
}
|
|
|
|
count_idle_ = 0;
|
|
count_running_ = 0;
|
|
ASSERT(count_started_ == count_stopped_);
|
|
}
|
|
// Release ThreadPool::mutex_ before calling Worker functions.
|
|
|
|
{
|
|
MonitorLocker eml(&exit_monitor_);
|
|
|
|
// First tell all the workers to shut down.
|
|
Worker* current = saved;
|
|
OSThread* os_thread = OSThread::Current();
|
|
ASSERT(os_thread != NULL);
|
|
ThreadId id = os_thread->id();
|
|
while (current != NULL) {
|
|
Worker* next = current->all_next_;
|
|
ThreadId currentId = current->id();
|
|
if (currentId != id) {
|
|
AddWorkerToShutdownList(current);
|
|
}
|
|
current->Shutdown();
|
|
current = next;
|
|
}
|
|
saved = NULL;
|
|
|
|
// Wait until all workers will exit.
|
|
while (shutting_down_workers_ != NULL) {
|
|
// Here, we are waiting for workers to exit. When a worker exits we will
|
|
// be notified.
|
|
eml.Wait();
|
|
}
|
|
}
|
|
|
|
// Extract the join list, and join on the threads.
|
|
JoinList* list = NULL;
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
list = join_list_;
|
|
join_list_ = NULL;
|
|
}
|
|
|
|
// Join non-idle threads.
|
|
JoinList::Join(&list);
|
|
|
|
#if defined(DEBUG)
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
ASSERT(join_list_ == NULL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool ThreadPool::IsIdle(Worker* worker) {
|
|
ASSERT(worker != NULL && worker->owned_);
|
|
for (Worker* current = idle_workers_; current != NULL;
|
|
current = current->idle_next_) {
|
|
if (current == worker) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ThreadPool::RemoveWorkerFromIdleList(Worker* worker) {
|
|
ASSERT(worker != NULL && worker->owned_);
|
|
if (idle_workers_ == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Special case head of list.
|
|
if (idle_workers_ == worker) {
|
|
idle_workers_ = worker->idle_next_;
|
|
worker->idle_next_ = NULL;
|
|
return true;
|
|
}
|
|
|
|
for (Worker* current = idle_workers_; current->idle_next_ != NULL;
|
|
current = current->idle_next_) {
|
|
if (current->idle_next_ == worker) {
|
|
current->idle_next_ = worker->idle_next_;
|
|
worker->idle_next_ = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ThreadPool::RemoveWorkerFromAllList(Worker* worker) {
|
|
ASSERT(worker != NULL && worker->owned_);
|
|
if (all_workers_ == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Special case head of list.
|
|
if (all_workers_ == worker) {
|
|
all_workers_ = worker->all_next_;
|
|
worker->all_next_ = NULL;
|
|
worker->owned_ = false;
|
|
worker->done_ = true;
|
|
return true;
|
|
}
|
|
|
|
for (Worker* current = all_workers_; current->all_next_ != NULL;
|
|
current = current->all_next_) {
|
|
if (current->all_next_ == worker) {
|
|
current->all_next_ = worker->all_next_;
|
|
worker->all_next_ = NULL;
|
|
worker->owned_ = false;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ThreadPool::SetIdleLocked(Worker* worker) {
|
|
ASSERT(mutex_.IsOwnedByCurrentThread());
|
|
ASSERT(worker->owned_ && !IsIdle(worker));
|
|
worker->idle_next_ = idle_workers_;
|
|
idle_workers_ = worker;
|
|
count_idle_++;
|
|
count_running_--;
|
|
}
|
|
|
|
void ThreadPool::SetIdleAndReapExited(Worker* worker) {
|
|
JoinList* list = NULL;
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
if (shutting_down_) {
|
|
return;
|
|
}
|
|
if (join_list_ == NULL) {
|
|
// Nothing to join, add to the idle list and return.
|
|
SetIdleLocked(worker);
|
|
return;
|
|
}
|
|
// There is something to join. Grab the join list, drop the lock, do the
|
|
// join, then grab the lock again and add to the idle list.
|
|
list = join_list_;
|
|
join_list_ = NULL;
|
|
}
|
|
JoinList::Join(&list);
|
|
|
|
{
|
|
MutexLocker ml(&mutex_);
|
|
if (shutting_down_) {
|
|
return;
|
|
}
|
|
SetIdleLocked(worker);
|
|
}
|
|
}
|
|
|
|
bool ThreadPool::ReleaseIdleWorker(Worker* worker) {
|
|
MutexLocker ml(&mutex_);
|
|
if (shutting_down_) {
|
|
return false;
|
|
}
|
|
// Remove from idle list.
|
|
if (!RemoveWorkerFromIdleList(worker)) {
|
|
return false;
|
|
}
|
|
// Remove from all list.
|
|
bool found = RemoveWorkerFromAllList(worker);
|
|
ASSERT(found);
|
|
|
|
// The thread for worker will exit. Add its ThreadId to the join_list_
|
|
// so that we can join on it at the next opportunity.
|
|
OSThread* os_thread = OSThread::Current();
|
|
ASSERT(os_thread != NULL);
|
|
ThreadJoinId join_id = OSThread::GetCurrentThreadJoinId(os_thread);
|
|
JoinList::AddLocked(join_id, &join_list_);
|
|
count_stopped_++;
|
|
count_idle_--;
|
|
return true;
|
|
}
|
|
|
|
// Only call while holding the exit_monitor_
|
|
void ThreadPool::AddWorkerToShutdownList(Worker* worker) {
|
|
ASSERT(exit_monitor_.IsOwnedByCurrentThread());
|
|
worker->shutdown_next_ = shutting_down_workers_;
|
|
shutting_down_workers_ = worker;
|
|
}
|
|
|
|
// Only call while holding the exit_monitor_
|
|
bool ThreadPool::RemoveWorkerFromShutdownList(Worker* worker) {
|
|
ASSERT(worker != NULL);
|
|
ASSERT(shutting_down_workers_ != NULL);
|
|
ASSERT(exit_monitor_.IsOwnedByCurrentThread());
|
|
|
|
// Special case head of list.
|
|
if (shutting_down_workers_ == worker) {
|
|
shutting_down_workers_ = worker->shutdown_next_;
|
|
worker->shutdown_next_ = NULL;
|
|
return true;
|
|
}
|
|
|
|
for (Worker* current = shutting_down_workers_;
|
|
current->shutdown_next_ != NULL; current = current->shutdown_next_) {
|
|
if (current->shutdown_next_ == worker) {
|
|
current->shutdown_next_ = worker->shutdown_next_;
|
|
worker->shutdown_next_ = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ThreadPool::JoinList::AddLocked(ThreadJoinId id, JoinList** list) {
|
|
*list = new JoinList(id, *list);
|
|
}
|
|
|
|
void ThreadPool::JoinList::Join(JoinList** list) {
|
|
while (*list != NULL) {
|
|
JoinList* current = *list;
|
|
*list = current->next();
|
|
OSThread::Join(current->id());
|
|
delete current;
|
|
}
|
|
}
|
|
|
|
ThreadPool::Task::Task() {}
|
|
|
|
ThreadPool::Task::~Task() {}
|
|
|
|
ThreadPool::Worker::Worker(ThreadPool* pool)
|
|
: pool_(pool),
|
|
task_(NULL),
|
|
id_(OSThread::kInvalidThreadId),
|
|
done_(false),
|
|
owned_(false),
|
|
all_next_(NULL),
|
|
idle_next_(NULL),
|
|
shutdown_next_(NULL) {}
|
|
|
|
ThreadId ThreadPool::Worker::id() {
|
|
MonitorLocker ml(&monitor_);
|
|
return id_;
|
|
}
|
|
|
|
void ThreadPool::Worker::StartThread() {
|
|
#if defined(DEBUG)
|
|
// Must call SetTask before StartThread.
|
|
{ // NOLINT
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(task_ != NULL);
|
|
}
|
|
#endif
|
|
int result = OSThread::Start("Dart ThreadPool Worker", &Worker::Main,
|
|
reinterpret_cast<uword>(this));
|
|
if (result != 0) {
|
|
FATAL1("Could not start worker thread: result = %d.", result);
|
|
}
|
|
}
|
|
|
|
void ThreadPool::Worker::SetTask(Task* task) {
|
|
MonitorLocker ml(&monitor_);
|
|
ASSERT(task_ == NULL);
|
|
task_ = task;
|
|
ml.Notify();
|
|
}
|
|
|
|
static int64_t ComputeTimeout(int64_t idle_start) {
|
|
int64_t worker_timeout_micros =
|
|
FLAG_worker_timeout_millis * kMicrosecondsPerMillisecond;
|
|
if (worker_timeout_micros <= 0) {
|
|
// No timeout.
|
|
return 0;
|
|
} else {
|
|
int64_t waited = OS::GetCurrentMonotonicMicros() - idle_start;
|
|
if (waited >= worker_timeout_micros) {
|
|
// We must have gotten a spurious wakeup just before we timed
|
|
// out. Give the worker one last desperate chance to live. We
|
|
// are merciful.
|
|
return 1;
|
|
} else {
|
|
return worker_timeout_micros - waited;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ThreadPool::Worker::Loop() {
|
|
MonitorLocker ml(&monitor_);
|
|
int64_t idle_start;
|
|
while (true) {
|
|
ASSERT(task_ != NULL);
|
|
Task* task = task_;
|
|
task_ = NULL;
|
|
|
|
// Release monitor while handling the task.
|
|
ml.Exit();
|
|
task->Run();
|
|
ASSERT(Isolate::Current() == NULL);
|
|
delete task;
|
|
ml.Enter();
|
|
|
|
ASSERT(task_ == NULL);
|
|
if (IsDone()) {
|
|
return false;
|
|
}
|
|
ASSERT(!done_);
|
|
pool_->SetIdleAndReapExited(this);
|
|
idle_start = OS::GetCurrentMonotonicMicros();
|
|
while (true) {
|
|
Monitor::WaitResult result = ml.WaitMicros(ComputeTimeout(idle_start));
|
|
if (task_ != NULL) {
|
|
// We've found a task. Process it, regardless of whether the
|
|
// worker is done_.
|
|
break;
|
|
}
|
|
if (IsDone()) {
|
|
return false;
|
|
}
|
|
if ((result == Monitor::kTimedOut) && pool_->ReleaseIdleWorker(this)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
|
|
void ThreadPool::Worker::Shutdown() {
|
|
MonitorLocker ml(&monitor_);
|
|
done_ = true;
|
|
ml.Notify();
|
|
}
|
|
|
|
// static
|
|
void ThreadPool::Worker::Main(uword args) {
|
|
Worker* worker = reinterpret_cast<Worker*>(args);
|
|
OSThread* os_thread = OSThread::Current();
|
|
ASSERT(os_thread != NULL);
|
|
ThreadId id = os_thread->id();
|
|
ThreadPool* pool;
|
|
|
|
// Set the thread's stack_base based on the current stack pointer.
|
|
os_thread->RefineStackBoundsFromSP(OSThread::GetCurrentStackPointer());
|
|
|
|
{
|
|
MonitorLocker ml(&worker->monitor_);
|
|
ASSERT(worker->task_);
|
|
worker->id_ = id;
|
|
pool = worker->pool_;
|
|
}
|
|
|
|
bool released = worker->Loop();
|
|
|
|
// It should be okay to access these unlocked here in this assert.
|
|
// worker->all_next_ is retained by the pool for shutdown monitoring.
|
|
ASSERT(!worker->owned_ && (worker->idle_next_ == NULL));
|
|
|
|
if (!released) {
|
|
// This worker is exiting because the thread pool is being shut down.
|
|
// Inform the thread pool that we are exiting. We remove this worker from
|
|
// shutting_down_workers_ list because there will be no need for the
|
|
// ThreadPool to take action for this worker.
|
|
ThreadJoinId join_id = OSThread::GetCurrentThreadJoinId(os_thread);
|
|
{
|
|
MutexLocker ml(&pool->mutex_);
|
|
JoinList::AddLocked(join_id, &pool->join_list_);
|
|
}
|
|
|
|
// worker->id_ should never be read again, so set to invalid in debug mode
|
|
// for asserts.
|
|
#if defined(DEBUG)
|
|
{
|
|
MonitorLocker ml(&worker->monitor_);
|
|
worker->id_ = OSThread::kInvalidThreadId;
|
|
}
|
|
#endif
|
|
|
|
// Remove from the shutdown list, delete, and notify the thread pool.
|
|
{
|
|
MonitorLocker eml(&pool->exit_monitor_);
|
|
pool->RemoveWorkerFromShutdownList(worker);
|
|
delete worker;
|
|
eml.Notify();
|
|
}
|
|
} else {
|
|
// This worker is going down because it was idle for too long. This case
|
|
// is not due to a ThreadPool Shutdown. Thus, we simply delete the worker.
|
|
// The worker's id is added to the thread pool's join list by
|
|
// ReleaseIdleWorker, so in the case that the thread pool begins shutting
|
|
// down immediately after returning from worker->Loop() above, we still
|
|
// wait for the thread to exit by joining on it in Shutdown().
|
|
delete worker;
|
|
}
|
|
|
|
// Call the thread exit hook here to notify the embedder that the
|
|
// thread pool thread is exiting.
|
|
if (Dart::thread_exit_callback() != NULL) {
|
|
(*Dart::thread_exit_callback())();
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|