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 <string.h>
 | |
| #include "environment.h"
 | |
| //#include "x86/jit_x86.h"
 | |
| 
 | |
| WatchdogTimer::WatchdogTimer(Environment *env)
 | |
|  : env_(env),
 | |
|    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_ = env_->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 = env_->FrameId();
 | |
|     if (frame_id != last_frame_id_ || !env_->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(env_->lock());
 | |
| 
 | |
|       // 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.
 | |
|       env_->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(env_->lock());
 | |
|     env_->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;
 | |
| }
 |