Chromium Code Reviews| 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..96057d5a316fca2afef8db0ff7838496cf34437b |
| --- /dev/null |
| +++ b/runtime/vm/thread_interrupter.cc |
| @@ -0,0 +1,395 @@ |
| +// 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, thread_interrupter, true, "Enable thread interrupter"); |
| +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; |
| +Monitor* ThreadInterrupter::start_stop_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() { |
| +#if defined(USING_SIMULATOR) |
| + FLAG_thread_interrupter = false; |
| +#endif |
| + if (!FLAG_thread_interrupter) { |
|
siva
2013/12/13 21:29:14
If FLAG_profiler is false will FLAG_thread_interru
Cutch
2013/12/13 22:40:18
Eventually.
|
| + return; |
| + } |
| + ASSERT(!initialized_); |
| + initialized_ = true; |
| + ASSERT(thread_state_key_ == Thread::kUnsetThreadLocalKey); |
| + thread_state_key_ = Thread::CreateThreadLocal(); |
| + ASSERT(thread_state_key_ != Thread::kUnsetThreadLocalKey); |
| + monitor_ = new Monitor(); |
| + start_stop_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(start_stop_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 (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + 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; |
| + ml.Notify(); |
| + } |
| + { |
| + MonitorLocker shutdown_ml(start_stop_monitor_); |
| + while (thread_running_) { |
| + shutdown_ml.Wait(); |
| + } |
| + } |
| + 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 (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + 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 (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + { |
| + MonitorLocker ml(monitor_); |
| + _EnsureThreadStateCreated(); |
| + // Set callback and data. |
| + UpdateStateObject(callback, data); |
| + _Enable(); |
| + ml.Notify(); |
| + } |
| +} |
| + |
| + |
| +// Unregister the currently running thread for interrupts. |
| +void ThreadInterrupter::Unregister() { |
| + if (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + { |
| + MonitorLocker ml(monitor_); |
| + _EnsureThreadStateCreated(); |
| + // Clear callback and data. |
| + UpdateStateObject(NULL, NULL); |
| + _Disable(); |
| + ml.Notify(); |
| + } |
| +} |
| + |
| + |
| +void ThreadInterrupter::Enable() { |
| + if (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + { |
| + MonitorLocker ml(monitor_); |
| + _EnsureThreadStateCreated(); |
| + _Enable(); |
| + ml.Notify(); |
|
siva
2013/12/13 21:29:14
Is this notify necessary? This thread will get tic
Cutch
2013/12/13 22:40:18
Done.
|
| + } |
| +} |
| + |
| + |
| +void ThreadInterrupter::Disable() { |
| + if (!FLAG_thread_interrupter) { |
| + return; |
| + } |
| + { |
| + MonitorLocker ml(monitor_); |
| + _EnsureThreadStateCreated(); |
| + _Disable(); |
| + ml.Notify(); |
|
siva
2013/12/13 21:29:14
Ditto question.
Cutch
2013/12/13 22:40:18
Done.
|
| + } |
| +} |
| + |
| + |
| +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(); |
| + ASSERT(!Thread::Compare(current_thread, interrupter_thread_id_)); |
| + 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(); |
| + ASSERT(!Thread::Compare(current_thread, interrupter_thread_id_)); |
| + 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(FLAG_thread_interrupter); |
| + ASSERT(initialized_); |
| + InstallSignalHandler(); |
| + if (FLAG_trace_thread_interrupter) { |
| + OS::Print("ThreadInterrupter thread running.\n"); |
| + } |
| + { |
| + // Signal to main thread we are ready. |
| + MonitorLocker startup_ml(start_stop_monitor_); |
| + thread_running_ = true; |
| + interrupter_thread_id_ = Thread::GetCurrentThreadId(); |
| + startup_ml.Notify(); |
| + } |
| + monitor_->Enter(); |
| + while (!shutdown_) { |
| + int64_t current_time = OS::GetCurrentTimeMicros(); |
| + InterruptThreads(current_time); |
| + monitor_->Exit(); |
| + OS::SleepMicros(interrupt_period_); |
| + monitor_->Enter(); |
| + } |
|
siva
2013/12/13 21:29:14
Could this be:
MonitorLocker loop(monitor_);
whil
Cutch
2013/12/13 22:40:18
Done.
|
| + if (FLAG_trace_thread_interrupter) { |
| + OS::Print("ThreadInterrupter thread exiting.\n"); |
| + } |
| + { |
| + // Signal to main thread we are exiting. |
| + MonitorLocker shutdown_ml(start_stop_monitor_); |
| + thread_running_ = false; |
| + shutdown_ml.Notify(); |
| + } |
| +} |
| + |
| +} // namespace dart |