// vim: set ts=8 sts=2 sw=2 tw=99 et: // // This file is part of SourcePawn. // // SourcePawn is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // SourcePawn is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with SourcePawn. If not, see . #include "watchdog_timer.h" #include "x86/jit_x86.h" #include WatchdogTimer g_WatchdogTimer; WatchdogTimer::WatchdogTimer() : terminate_(false), mainthread_(ke::GetCurrentThreadId()), last_frame_id_(0), second_timeout_(false), timedout_(false) { } WatchdogTimer::~WatchdogTimer() { assert(!thread_); } bool WatchdogTimer::Initialize(size_t timeout_ms) { if (thread_) return false; timeout_ms_ = timeout_ms; thread_ = new ke::Thread(this, "SM Watchdog"); if (!thread_->Succeeded()) return false; return true; } void WatchdogTimer::Shutdown() { if (terminate_ || !thread_) return; { ke::AutoLock lock(&cv_); terminate_ = true; cv_.Notify(); } thread_->Join(); thread_ = NULL; } void WatchdogTimer::Run() { ke::AutoLock lock(&cv_); // Initialize the frame id, so we don't have to wait longer on startup. last_frame_id_ = g_Jit.FrameId(); while (!terminate_) { ke::WaitResult rv = cv_.Wait(timeout_ms_ / 2); if (terminate_) return; if (rv == ke::Wait_Error) return; if (rv != ke::Wait_Timeout) continue; // We reached a timeout. If the current frame is not equal to the last // frame, then we assume the server is still moving enough to process // frames. We also make sure JIT code is actually on the stack, since // our concept of frames won't move if JIT code is not running. // // Note that it's okay if these two race: it's just a heuristic, and // worst case, we'll miss something that might have timed out but // ended up resuming. uintptr_t frame_id = g_Jit.FrameId(); if (frame_id != last_frame_id_ || !g_Jit.RunningCode()) { last_frame_id_ = frame_id; second_timeout_ = false; continue; } // We woke up with the same frame. We might be timing out. Wait another // time to see if so. if (!second_timeout_) { second_timeout_ = true; continue; } { // Prevent the JIT from linking or destroying runtimes and functions. ke::AutoLock lock(g_Jit.Mutex()); // Set the timeout notification bit. If this is detected before any patched // JIT backedges are reached, the main thread will attempt to acquire the // monitor lock, and block until we call Wait(). timedout_ = true; // Patch all jumps. This can race with the main thread's execution since // all code writes are 32-bit integer instruction operands, which are // guaranteed to be atomic on x86. g_Jit.PatchAllJumpsForTimeout(); } // The JIT will be free to compile new functions while we wait, but it will // see the timeout bit set above and immediately bail out. cv_.Wait(); second_timeout_ = false; // Reset the last frame ID to something unlikely to be chosen again soon. last_frame_id_--; // It's possible that Shutdown() raced with the timeout, and if so, we // must leave now to prevent a deadlock. if (terminate_) return; } } bool WatchdogTimer::NotifyTimeoutReceived() { // We are guaranteed that the watchdog thread is waiting for our // notification, and is therefore blocked. We take the JIT lock // anyway for sanity. { ke::AutoLock lock(g_Jit.Mutex()); g_Jit.UnpatchAllJumpsFromTimeout(); } timedout_ = false; // Wake up the watchdog thread, it's okay to keep processing now. ke::AutoLock lock(&cv_); cv_.Notify(); return false; } bool WatchdogTimer::HandleInterrupt() { if (timedout_) return NotifyTimeoutReceived(); return true; }