04827466b0
This removes one the last remnants of the SourceMod 1.0 VM implementation. The new parser introduces a number of design changes in the VM. First, the VM now takes greater responsibility for validating and sanity checking the structure of the SMX container format. Previously, malformed SMX files could easily crash SourcePawn. The loader now rejects files that have out-of-bounds offsets or incomplete sections. Complex sections, like debug info or the code stream, are verified lazily. Internally, the sp_plugin_t structure has been removed. It has been replaced by a new LegacyImage class, designed to be independent from the SPVM API. This potentially lets us load code streams from non-.smx containers. More importantly, it removes a lot of bookkeeping and pre-computed state from PluginRuntime. The LegacyImage class is now responsible for handling debug info as well. PluginRuntime is now intended to hold only cached or immutable data, and PluginContext holds all VM state. As such PluginContext is now responsible for allocating a plugin's runtime memory, not PluginRuntime. Finally, some aspects of the loading process have been cleaned up. The decompression and image handoff logic should now be easier to understand.
166 lines
4.2 KiB
C++
166 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"
|
|
|
|
using namespace sp;
|
|
|
|
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;
|
|
}
|