blob: 2d44208739fd38bdf776c603fa8100bd170e51dc [file] [log] [blame]
// Copyright (c) 2013, 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/simulator.h"
#include "vm/thread_interrupter.h"
namespace dart {
// Notes:
//
// The ThreadInterrupter interrupts all registered threads once per
// interrupt period (default is every millisecond). While the thread is
// interrupted, the thread's interrupt callback is invoked. Callbacks cannot
// rely on being executed on the interrupted thread.
//
// There are two mechanisms used to interrupt a thread. The first, used on OSs
// with pthreads (Android, Linux, and Mac), is thread specific signal delivery.
// The second, used on Windows, is explicit suspend and resume thread system
// calls. Signal delivery forbids taking locks and allocating memory (which
// takes a lock). Explicit suspend and resume means that the interrupt callback
// will not be executing on the interrupted thread, making it meaningless to
// access TLS from within the thread interrupt callback. Combining these
// limitations, thread interrupt callbacks are forbidden from:
//
// * Accessing TLS.
// * Allocating memory.
// * Taking a lock.
//
// The ThreadInterrupter has a single monitor (monitor_). This monitor guards
// access to the list of threads registered to receive interrupts (threads_).
//
// A thread can only register and unregister itself. Each thread has a heap
// allocated ThreadState. A thread's ThreadState is lazily allocated the first
// time the thread is registered. A pointer to a thread's ThreadState is stored
// in the list of threads registered to receive interrupts (threads_) and in
// thread local storage. When a thread's ThreadState is being modified, the
// thread local storage pointer is temporarily set to NULL while the
// modification is occurring. After the ThreadState has been updated, the
// thread local storage pointer is set again. This has an important side
// effect: if the thread is interrupted by a signal handler during a ThreadState
// update the signal handler will immediately return.
DEFINE_FLAG(bool, trace_thread_interrupter, false,
"Trace thread interrupter");
bool ThreadInterrupter::initialized_ = false;
bool ThreadInterrupter::shutdown_ = false;
bool ThreadInterrupter::thread_running_ = false;
ThreadId ThreadInterrupter::interrupter_thread_id_ = Thread::kInvalidThreadId;
Monitor* ThreadInterrupter::monitor_ = NULL;
intptr_t ThreadInterrupter::interrupt_period_ = 1000;
ThreadLocalKey ThreadInterrupter::thread_state_key_ =
Thread::kUnsetThreadLocalKey;
ThreadInterrupter::ThreadState** ThreadInterrupter::threads_ = NULL;
intptr_t ThreadInterrupter::threads_capacity_ = 0;
intptr_t ThreadInterrupter::threads_size_ = 0;
void ThreadInterrupter::InitOnce() {
ASSERT(!initialized_);
initialized_ = true;
ASSERT(thread_state_key_ == Thread::kUnsetThreadLocalKey);
thread_state_key_ = Thread::CreateThreadLocal();
ASSERT(thread_state_key_ != Thread::kUnsetThreadLocalKey);
monitor_ = new Monitor();
ResizeThreads(16);
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter starting up.\n");
}
ASSERT(interrupter_thread_id_ == Thread::kInvalidThreadId);
{
MonitorLocker startup_ml(monitor_);
Thread::Start(ThreadMain, 0);
while (!thread_running_) {
startup_ml.Wait();
}
}
ASSERT(interrupter_thread_id_ != Thread::kInvalidThreadId);
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter running.\n");
}
}
void ThreadInterrupter::Shutdown() {
if (shutdown_) {
// Already shutdown.
return;
}
ASSERT(initialized_);
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter shutting down.\n");
}
intptr_t size_at_shutdown = 0;
{
MonitorLocker ml(monitor_);
shutdown_ = true;
size_at_shutdown = threads_size_;
threads_size_ = 0;
threads_capacity_ = 0;
free(threads_);
threads_ = NULL;
}
{
MonitorLocker shutdown_ml(monitor_);
while (thread_running_) {
shutdown_ml.Wait();
}
}
interrupter_thread_id_ = Thread::kInvalidThreadId;
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter shut down (%" Pd ").\n", size_at_shutdown);
}
}
// Delay between interrupts.
void ThreadInterrupter::SetInterruptPeriod(intptr_t period) {
if (shutdown_) {
return;
}
ASSERT(initialized_);
ASSERT(period > 0);
{
MonitorLocker ml(monitor_);
interrupt_period_ = period;
}
}
// Register the currently running thread for interrupts. If the current thread
// is already registered, callback and data will be updated.
void ThreadInterrupter::Register(ThreadInterruptCallback callback, void* data) {
if (shutdown_) {
return;
}
ASSERT(initialized_);
{
MonitorLocker ml(monitor_);
_EnsureThreadStateCreated();
// Set callback and data.
UpdateStateObject(callback, data);
_Enable();
}
}
// Unregister the currently running thread for interrupts.
void ThreadInterrupter::Unregister() {
if (shutdown_) {
return;
}
ASSERT(initialized_);
{
MonitorLocker ml(monitor_);
_EnsureThreadStateCreated();
// Clear callback and data.
UpdateStateObject(NULL, NULL);
_Disable();
}
}
void ThreadInterrupter::Enable() {
if (shutdown_) {
return;
}
ASSERT(initialized_);
{
MonitorLocker ml(monitor_);
_EnsureThreadStateCreated();
_Enable();
}
}
void ThreadInterrupter::Disable() {
if (shutdown_) {
return;
}
ASSERT(initialized_);
{
MonitorLocker ml(monitor_);
_EnsureThreadStateCreated();
_Disable();
}
}
void ThreadInterrupter::_EnsureThreadStateCreated() {
ThreadState* state = CurrentThreadState();
if (state == NULL) {
// Create thread state object lazily.
ThreadId current_thread = Thread::GetCurrentThreadId();
if (FLAG_trace_thread_interrupter) {
intptr_t tid = Thread::ThreadIdToIntPtr(current_thread);
OS::Print("ThreadInterrupter Tracking %p\n",
reinterpret_cast<void*>(tid));
}
state = new ThreadState();
state->callback = NULL;
state->data = NULL;
state->id = current_thread;
SetCurrentThreadState(state);
}
}
void ThreadInterrupter::_Enable() {
// Must be called with monitor_ locked.
ThreadId current_thread = Thread::GetCurrentThreadId();
if (Thread::Compare(current_thread, interrupter_thread_id_)) {
return;
}
intptr_t i = FindThreadIndex(current_thread);
if (i >= 0) {
return;
}
AddThread(current_thread);
if (FLAG_trace_thread_interrupter) {
intptr_t tid = Thread::ThreadIdToIntPtr(current_thread);
OS::Print("ThreadInterrupter Added %p\n", reinterpret_cast<void*>(tid));
}
}
void ThreadInterrupter::_Disable() {
// Must be called with monitor_ locked.
ThreadId current_thread = Thread::GetCurrentThreadId();
if (Thread::Compare(current_thread, interrupter_thread_id_)) {
return;
}
intptr_t index = FindThreadIndex(current_thread);
if (index < 0) {
// Not registered.
return;
}
ThreadState* state = RemoveThread(index);
ASSERT(state != NULL);
ASSERT(state == ThreadInterrupter::CurrentThreadState());
if (FLAG_trace_thread_interrupter) {
intptr_t tid = Thread::ThreadIdToIntPtr(current_thread);
OS::Print("ThreadInterrupter Removed %p\n", reinterpret_cast<void*>(tid));
}
}
void ThreadInterrupter::UpdateStateObject(ThreadInterruptCallback callback,
void* data) {
// Must be called with monitor_ locked.
ThreadState* state = CurrentThreadState();
ThreadId current_thread = Thread::GetCurrentThreadId();
ASSERT(state != NULL);
ASSERT(Thread::Compare(state->id, Thread::GetCurrentThreadId()));
SetCurrentThreadState(NULL);
// It is now safe to modify the state object. If an interrupt occurs,
// the current thread state will be NULL.
state->callback = callback;
state->data = data;
SetCurrentThreadState(state);
if (FLAG_trace_thread_interrupter) {
intptr_t tid = Thread::ThreadIdToIntPtr(current_thread);
if (callback == NULL) {
OS::Print("ThreadInterrupter Cleared %p\n", reinterpret_cast<void*>(tid));
} else {
OS::Print("ThreadInterrupter Updated %p\n", reinterpret_cast<void*>(tid));
}
}
}
ThreadInterrupter::ThreadState* ThreadInterrupter::CurrentThreadState() {
ThreadState* state = reinterpret_cast<ThreadState*>(
Thread::GetThreadLocal(thread_state_key_));
return state;
}
void ThreadInterrupter::SetCurrentThreadState(ThreadState* state) {
Thread::SetThreadLocal(thread_state_key_, reinterpret_cast<uword>(state));
}
void ThreadInterrupter::ResizeThreads(intptr_t new_capacity) {
// Must be called with monitor_ locked.
ASSERT(new_capacity < kMaxThreads);
ASSERT(new_capacity > threads_capacity_);
ThreadState* state = NULL;
threads_ = reinterpret_cast<ThreadState**>(
realloc(threads_, sizeof(state) * new_capacity));
for (intptr_t i = threads_capacity_; i < new_capacity; i++) {
threads_[i] = NULL;
}
threads_capacity_ = new_capacity;
}
void ThreadInterrupter::AddThread(ThreadId id) {
// Must be called with monitor_ locked.
if (threads_ == NULL) {
// We are shutting down.
return;
}
ThreadState* state = CurrentThreadState();
if (state->callback == NULL) {
// No callback.
return;
}
if (threads_size_ == threads_capacity_) {
ResizeThreads(threads_capacity_ == 0 ? 16 : threads_capacity_ * 2);
}
threads_[threads_size_] = state;
threads_size_++;
}
intptr_t ThreadInterrupter::FindThreadIndex(ThreadId id) {
// Must be called with monitor_ locked.
if (threads_ == NULL) {
// We are shutting down.
return -1;
}
for (intptr_t i = 0; i < threads_size_; i++) {
if (threads_[i]->id == id) {
return i;
}
}
return -1;
}
ThreadInterrupter::ThreadState* ThreadInterrupter::RemoveThread(intptr_t i) {
// Must be called with monitor_ locked.
if (threads_ == NULL) {
// We are shutting down.
return NULL;
}
ASSERT(i < threads_size_);
ThreadState* state = threads_[i];
ASSERT(state != NULL);
intptr_t last = threads_size_ - 1;
if (i != last) {
threads_[i] = threads_[last];
}
// Mark last as NULL.
threads_[last] = NULL;
// Pop.
threads_size_--;
return state;
}
void ThreadInterruptNoOp(const InterruptedThreadState& state, void* data) {
// NoOp.
}
void ThreadInterrupter::ThreadMain(uword parameters) {
ASSERT(initialized_);
InstallSignalHandler();
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter thread running.\n");
}
{
// Signal to main thread we are ready.
MonitorLocker startup_ml(monitor_);
thread_running_ = true;
interrupter_thread_id_ = Thread::GetCurrentThreadId();
startup_ml.Notify();
}
{
MonitorLocker ml(monitor_);
while (!shutdown_) {
int64_t current_time = OS::GetCurrentTimeMicros();
InterruptThreads(current_time);
ml.WaitMicros(interrupt_period_);
}
}
if (FLAG_trace_thread_interrupter) {
OS::Print("ThreadInterrupter thread exiting.\n");
}
{
// Signal to main thread we are exiting.
MonitorLocker shutdown_ml(monitor_);
thread_running_ = false;
shutdown_ml.Notify();
}
}
} // namespace dart