| Index: runtime/vm/thread_interrupter.cc
|
| diff --git a/runtime/vm/thread_interrupter.cc b/runtime/vm/thread_interrupter.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7085a4397d3748d1e1b58c80ca9fdb3cf3186284
|
| --- /dev/null
|
| +++ b/runtime/vm/thread_interrupter.cc
|
| @@ -0,0 +1,387 @@
|
| +// 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
|
|
|