| // 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/thread_interrupter.h" | |
| #include "vm/flags.h" | |
| #include "vm/lockers.h" | |
| #include "vm/os.h" | |
| #include "vm/simulator.h" | |
| namespace dart { | |
| #if defined(DART_INCLUDE_PROFILER) | |
| // Notes: | |
| // | |
| // The ThreadInterrupter interrupts all threads actively running isolates once | |
| // per interrupt period (default is 1 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 Linux | |
| // and Android, is SIGPROF. The second, used on Windows, Fuchsia, Mac and iOS, | |
| // is explicit suspend and resume thread system calls. (Although Mac supports | |
| // SIGPROF, when the process is attached to lldb, it becomes unusably slow, and | |
| // signal masking is unreliable across fork-exec.) 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 is used | |
| // to synchronize startup, shutdown, and waking up from a deep sleep. | |
| // | |
| DEFINE_FLAG(bool, trace_thread_interrupter, false, "Trace thread interrupter"); | |
| bool ThreadInterrupter::initialized_ = false; | |
| bool ThreadInterrupter::shutdown_ = false; | |
| bool ThreadInterrupter::thread_running_ = false; | |
| bool ThreadInterrupter::woken_up_ = false; | |
| ThreadJoinId ThreadInterrupter::interrupter_thread_id_ = | |
| OSThread::kInvalidThreadJoinId; | |
| Monitor* ThreadInterrupter::monitor_ = nullptr; | |
| intptr_t ThreadInterrupter::interrupt_period_ = 1000; | |
| intptr_t ThreadInterrupter::current_wait_time_ = Monitor::kNoTimeout; | |
| void ThreadInterrupter::Init() { | |
| ASSERT(!initialized_); | |
| monitor_ = new Monitor(); | |
| initialized_ = true; | |
| shutdown_ = true; | |
| } | |
| void ThreadInterrupter::Cleanup() { | |
| Shutdown(); | |
| delete monitor_; | |
| monitor_ = nullptr; | |
| initialized_ = false; | |
| } | |
| void ThreadInterrupter::Startup() { | |
| ASSERT(initialized_); | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter starting up.\n"); | |
| } | |
| ASSERT(interrupter_thread_id_ == OSThread::kInvalidThreadJoinId); | |
| { | |
| MonitorLocker startup_ml(monitor_); | |
| shutdown_ = false; | |
| OSThread::Start("Dart Profiler ThreadInterrupter", ThreadMain, 0); | |
| while (!thread_running_) { | |
| startup_ml.Wait(); | |
| } | |
| } | |
| ASSERT(interrupter_thread_id_ != OSThread::kInvalidThreadJoinId); | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter running.\n"); | |
| } | |
| } | |
| void ThreadInterrupter::Shutdown() { | |
| { | |
| MonitorLocker shutdown_ml(monitor_); | |
| if (shutdown_) { | |
| // Already shutdown. | |
| return; | |
| } | |
| shutdown_ = true; | |
| // Notify. | |
| shutdown_ml.Notify(); | |
| ASSERT(initialized_); | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter shutting down.\n"); | |
| } | |
| } | |
| // Join the thread. | |
| ASSERT(interrupter_thread_id_ != OSThread::kInvalidThreadJoinId); | |
| OSThread::Join(interrupter_thread_id_); | |
| interrupter_thread_id_ = OSThread::kInvalidThreadJoinId; | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter shut down.\n"); | |
| } | |
| } | |
| // Delay between interrupts. | |
| void ThreadInterrupter::SetInterruptPeriod(intptr_t period) { | |
| ASSERT(period > 0); | |
| if (!initialized_) { | |
| // Profiler may not be enabled. | |
| return; | |
| } | |
| MonitorLocker ml(monitor_); | |
| interrupt_period_ = period; | |
| } | |
| void ThreadInterrupter::WakeUp() { | |
| if (monitor_ == nullptr) { | |
| // Early call. | |
| return; | |
| } | |
| { | |
| MonitorLocker ml(monitor_); | |
| if (shutdown_) { | |
| // Late call | |
| return; | |
| } | |
| if (!initialized_) { | |
| // Early call. | |
| return; | |
| } | |
| woken_up_ = true; | |
| if (!InDeepSleep()) { | |
| // No need to notify, regularly waking up. | |
| return; | |
| } | |
| // Notify the interrupter to wake it from its deep sleep. | |
| ml.Notify(); | |
| } | |
| } | |
| void ThreadInterrupter::ThreadMain(uword parameters) { | |
| ASSERT(initialized_); | |
| InstallSignalHandler(); | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter thread running.\n"); | |
| } | |
| { | |
| // Signal to main thread we are ready. | |
| MonitorLocker startup_ml(monitor_); | |
| OSThread* os_thread = OSThread::Current(); | |
| ASSERT(os_thread != nullptr); | |
| interrupter_thread_id_ = OSThread::GetCurrentThreadJoinId(os_thread); | |
| thread_running_ = true; | |
| startup_ml.Notify(); | |
| } | |
| { | |
| intptr_t interrupted_thread_count = 0; | |
| MonitorLocker wait_ml(monitor_); | |
| current_wait_time_ = interrupt_period_; | |
| while (!shutdown_) { | |
| intptr_t r = wait_ml.WaitMicros(current_wait_time_); | |
| if (shutdown_) { | |
| break; | |
| } | |
| if ((r == Monitor::kNotified) && InDeepSleep()) { | |
| // Woken up from deep sleep. | |
| ASSERT(interrupted_thread_count == 0); | |
| // Return to regular interrupts. | |
| current_wait_time_ = interrupt_period_; | |
| } else if (current_wait_time_ != interrupt_period_) { | |
| // The interrupt period may have been updated via the service protocol. | |
| current_wait_time_ = interrupt_period_; | |
| } | |
| // Reset count before interrupting any threads. | |
| interrupted_thread_count = 0; | |
| // Temporarily drop the monitor while we interrupt threads. | |
| wait_ml.Exit(); | |
| { | |
| OSThreadIterator it; | |
| while (it.HasNext()) { | |
| OSThread* thread = it.Next(); | |
| if (thread->ThreadInterruptsEnabled()) { | |
| interrupted_thread_count++; | |
| InterruptThread(thread); | |
| } | |
| } | |
| } | |
| // Take the monitor lock again. | |
| wait_ml.Enter(); | |
| // Now that we have the lock, check if we were signaled to wake up while | |
| // interrupting threads. | |
| if (!woken_up_ && (interrupted_thread_count == 0)) { | |
| // No threads were interrupted and we were not signaled to interrupt | |
| // new threads. In order to reduce unnecessary CPU load, we will wait | |
| // until we are notified before attempting to interrupt again. | |
| current_wait_time_ = Monitor::kNoTimeout; | |
| continue; | |
| } | |
| woken_up_ = false; | |
| ASSERT(current_wait_time_ != Monitor::kNoTimeout); | |
| } | |
| } | |
| RemoveSignalHandler(); | |
| if (FLAG_trace_thread_interrupter) { | |
| OS::PrintErr("ThreadInterrupter thread exiting.\n"); | |
| } | |
| { | |
| // Signal to main thread we are exiting. | |
| MonitorLocker shutdown_ml(monitor_); | |
| thread_running_ = false; | |
| shutdown_ml.Notify(); | |
| } | |
| } | |
| #endif // defined(DART_INCLUDE_PROFILER) | |
| } // namespace dart |