165 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// 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 <http://www.gnu.org/licenses/>.
 | 
						|
#include "watchdog_timer.h"
 | 
						|
#include "x86/jit_x86.h"
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
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;
 | 
						|
}
 |