Implement a watchdog timer for scripts that take too long to execute (bug 5837, r=fyren).

--HG--
extra : rebase_source : ffacb38457eca581660ce8f15c444ad828b7fedd
This commit is contained in:
David Anderson
2013-08-14 23:54:25 -07:00
parent 74f1b800c2
commit 3ac43497b9
25 changed files with 1250 additions and 99 deletions
+5 -5
View File
@@ -64,6 +64,11 @@ class Assembler
return pos_ - buffer_;
}
// Current offset into the code stream.
uint32_t pc() const {
return uint32_t(pos_ - buffer_);
}
protected:
void writeByte(uint8_t byte) {
write<uint8_t>(byte);
@@ -125,11 +130,6 @@ class Assembler
return int32_t(pos_ - buffer_);
}
// pc is the unsigned version of position().
uint32_t pc() const {
return uint32_t(pos_ - buffer_);
}
protected:
void assertCanWrite(size_t bytes) {
assert(pos_ + bytes <= end_);
+8
View File
@@ -640,6 +640,10 @@ class AssemblerX86 : public Assembler
emit2(0xdc, 0xc0 + src.code);
}
void jmp32(Label *dest) {
emit1(0xe9);
emitJumpTarget(dest);
}
void jmp(Label *dest) {
int8_t d8;
if (canEmitSmallJump(dest, &d8)) {
@@ -655,6 +659,10 @@ class AssemblerX86 : public Assembler
void jmp(const Operand &target) {
emit1(0xff, 4, target);
}
void j32(ConditionCode cc, Label *dest) {
emit2(0x0f, 0x80 + uint8_t(cc));
emitJumpTarget(dest);
}
void j(ConditionCode cc, Label *dest) {
int8_t d8;
if (canEmitSmallJump(dest, &d8)) {
+137
View File
@@ -0,0 +1,137 @@
/**
* vim: set sts=2 ts=8 sw=2 tw=99 noet :
* =============================================================================
* SourcePawn
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program 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
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*/
#ifndef _include_sourcepawn_inline_list_h_
#define _include_sourcepawn_inline_list_h_
#include <stddef.h>
template <typename T> class InlineList;
template <typename T>
class InlineListNode
{
friend class InlineList<T>;
public:
InlineListNode()
: next_(NULL),
prev_(NULL)
{
}
InlineListNode(InlineListNode *next, InlineListNode *prev)
: next_(next),
prev_(prev)
{
}
protected:
InlineListNode *next_;
InlineListNode *prev_;
};
template <typename T>
class InlineList
{
typedef InlineListNode<T> Node;
Node head_;
public:
InlineList()
: head_(&head_, &head_)
{
}
public:
class iterator
{
friend class InlineList;
Node *iter_;
public:
iterator(Node *iter)
: iter_(iter)
{
}
iterator & operator ++() {
iter_ = iter_->next;
return *iter_;
}
iterator operator ++(int) {
iterator old(*this);
iter_ = iter_->next_;
return old;
}
T * operator *() {
return static_cast<T *>(iter_);
}
T * operator ->() {
return static_cast<T *>(iter_);
}
bool operator !=(const iterator &where) const {
return iter_ != where.iter_;
}
bool operator ==(const iterator &where) const {
return iter_ == where.iter_;
}
iterator prev() const {
iterator p(iter_->prev_);
return p;
}
iterator next() const {
iterator p(iter_->next_);
return p;
}
};
iterator begin() {
return iterator(head_.next_);
}
iterator end() {
return iterator(&head_);
}
void erase(Node *t) {
t->prev_->next_ = t->next_;
t->next_->prev_ = t->prev_;
}
void insert(Node *t) {
t->prev_ = head_.prev_;
t->next_ = &head_;
head_.prev_->next_ = t;
head_.prev_ = t;
}
};
#endif // _include_sourcepawn_inline_list_h_
+188
View File
@@ -0,0 +1,188 @@
// 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/>.
#ifndef _include_sourcepawn_thread_posix_h_
#define _include_sourcepawn_thread_posix_h_
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#if defined(__linux__)
# include <sys/prctl.h>
#endif
namespace ke {
class Mutex : public Lockable
{
public:
Mutex() {
#if !defined(NDEBUG)
int rv =
#endif
pthread_mutex_init(&mutex_, NULL);
assert(rv == 0);
}
~Mutex() {
pthread_mutex_destroy(&mutex_);
}
bool DoTryLock() {
return pthread_mutex_trylock(&mutex_) == 0;
}
void DoLock() {
pthread_mutex_lock(&mutex_);
}
void DoUnlock() {
pthread_mutex_unlock(&mutex_);
}
pthread_mutex_t *raw() {
return &mutex_;
}
private:
pthread_mutex_t mutex_;
};
// Currently, this class only supports single-listener CVs.
class ConditionVariable : public Lockable
{
public:
ConditionVariable() {
#if !defined(NDEBUG)
int rv =
#endif
pthread_cond_init(&cv_, NULL);
assert(rv == 0);
}
~ConditionVariable() {
pthread_cond_destroy(&cv_);
}
bool DoTryLock() {
return mutex_.DoTryLock();
}
void DoLock() {
mutex_.DoLock();
}
void DoUnlock() {
mutex_.DoUnlock();
}
void Notify() {
AssertCurrentThreadOwns();
pthread_cond_signal(&cv_);
}
WaitResult Wait(size_t timeout_ms) {
AssertCurrentThreadOwns();
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
return Wait_Error;
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
DebugSetUnlocked();
int rv = pthread_cond_timedwait(&cv_, mutex_.raw(), &ts);
DebugSetLocked();
if (rv == ETIMEDOUT)
return Wait_Timeout;
if (rv == 0)
return Wait_Signaled;
return Wait_Error;
}
WaitResult Wait() {
AssertCurrentThreadOwns();
DebugSetUnlocked();
int rv = pthread_cond_wait(&cv_, mutex_.raw());
DebugSetLocked();
if (rv == 0)
return Wait_Signaled;
return Wait_Error;
}
private:
Mutex mutex_;
pthread_cond_t cv_;
};
class Thread
{
struct ThreadData {
IRunnable *run;
char name[17];
};
public:
Thread(IRunnable *run, const char *name = NULL) {
ThreadData *data = new ThreadData;
data->run = run;
snprintf(data->name, sizeof(data->name), "%s", name ? name : "");
initialized_ = (pthread_create(&thread_, NULL, Main, data) == 0);
if (!initialized_)
delete data;
}
bool Succeeded() const {
return initialized_;
}
void Join() {
if (!Succeeded())
return;
pthread_join(thread_, NULL);
}
private:
static void *Main(void *arg) {
AutoPtr<ThreadData> data((ThreadData *)arg);
if (data->name[0]) {
#if defined(__linux__)
prctl(PR_SET_NAME, (unsigned long)data->name);
#elif defined(__APPLE__)
int (*fn)(const char *) = (int (*)(const char *))dlsym(RTLD_DEFAULT, "pthread_setname_np");
if (fn)
fn(data->name);
#endif
}
data->run->Run();
return NULL;
}
private:
bool initialized_;
pthread_t thread_;
};
} // namespace ke
#endif // _include_sourcepawn_thread_posix_h_
+241
View File
@@ -0,0 +1,241 @@
// 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/>.
#ifndef _include_sourcepawn_threads_
#define _include_sourcepawn_threads_
#include <assert.h>
#if defined(_MSC_VER)
# include <windows.h>
#else
# include <pthread.h>
#endif
#include <ke_utility.h>
// Thread primitives for SourcePawn.
//
// -- Mutexes --
//
// A Lockable is a mutual exclusion primitive. It can be owned by at most one
// thread at a time, and ownership blocks any other thread from taking taking
// ownership. Ownership must be acquired and released on the same thread.
// Lockables are not re-entrant.
//
// While a few classes support the Lockable interface, the simplest Lockable
// object that can be instantiated is a Mutex.
//
// -- Condition Variables --
//
// A ConditionVariable provides mutually exclusive access based on a
// condition ocurring. CVs provide two capabilities: Wait(), which will block
// until the condition is triggered, and Notify(), which signals any blocking
// thread that the condition has occurred.
//
// Condition variables have an underlying mutex lock. This lock must be
// acquired before calling Wait() or Notify(). It is automatically released
// once Wait begins blocking. This operation is atomic with respect to other
// threads and the mutex. For example, it is not possible for the lock to be
// acquired by another thread in between unlocking and blocking. Since Notify
// also requires the lock to be acquired, there is no risk of an event
// accidentally dissipating into thin air because it was sent before the other
// thread began blocking.
//
// When Wait() returns, the lock is automatically re-acquired. This operation
// is NOT atomic. In between waking up and re-acquiring the lock, another
// thread may steal the lock and issue another event. Applications must
// account for this. For example, a message pump should check that there are no
// messages left to process before blocking again.
//
// -- Threads --
//
// A Thread object, when created, spawns a new thread with the given callback
// (the callbacks must implement IRunnable). Threads have one method of
// interest, Join(), which will block until the thread's execution finishes.
// Deleting a thread object will free any operating system resources associated
// with that thread, if the thread has finished executing.
//
// Threads can fail to spawn; make sure to check Succeeded().
//
namespace ke {
// Abstraction for accessing the current thread.
#if defined(_MSC_VER)
typedef HANDLE ThreadId;
static inline ThreadId GetCurrentThreadId()
{
return GetCurrentThread();
}
#else
typedef pthread_t ThreadId;
static inline ThreadId GetCurrentThreadId()
{
return pthread_self();
}
#endif
// Classes which use non-reentrant, same-thread lock/unlock semantics should
// inherit from this and implement DoLock/DoUnlock.
class Lockable
{
public:
Lockable()
{
#if !defined(NDEBUG)
owner_ = 0;
#endif
}
virtual ~Lockable() {
}
bool TryLock() {
if (DoTryLock()) {
DebugSetLocked();
return true;
}
return false;
}
void Lock() {
assert(Owner() != GetCurrentThreadId());
DoLock();
DebugSetLocked();
}
void Unlock() {
assert(Owner() == GetCurrentThreadId());
DebugSetUnlocked();
DoUnlock();
}
void AssertCurrentThreadOwns() const {
assert(Owner() == GetCurrentThreadId());
}
#if !defined(NDEBUG)
bool Locked() const {
return owner_ != 0;
}
ThreadId Owner() const {
return owner_;
}
#endif
virtual bool DoTryLock() = 0;
virtual void DoLock() = 0;
virtual void DoUnlock() = 0;
protected:
void DebugSetUnlocked() {
#if !defined(NDEBUG)
owner_ = 0;
#endif
}
void DebugSetLocked() {
#if !defined(NDEBUG)
owner_ = GetCurrentThreadId();
#endif
}
protected:
#if !defined(NDEBUG)
ThreadId owner_;
#endif
};
// RAII for automatically locking and unlocking an object.
class AutoLock
{
public:
AutoLock(Lockable *lock)
: lock_(lock)
{
lock_->Lock();
}
~AutoLock() {
lock_->Unlock();
}
private:
Lockable *lock_;
};
class AutoTryLock
{
public:
AutoTryLock(Lockable *lock)
{
lock_ = lock->TryLock() ? lock : NULL;
}
~AutoTryLock() {
if (lock_)
lock_->Unlock();
}
private:
Lockable *lock_;
};
// RAII for automatically unlocking and relocking an object.
class AutoUnlock
{
public:
AutoUnlock(Lockable *lock)
: lock_(lock)
{
lock_->Unlock();
}
~AutoUnlock() {
lock_->Lock();
}
private:
Lockable *lock_;
};
enum WaitResult {
// Woke up because something happened.
Wait_Signaled,
// Woke up because nothing happened and a timeout was specified.
Wait_Timeout,
// Woke up, but because of an error.
Wait_Error
};
// This must be implemented in order to spawn a new thread.
class IRunnable
{
public:
virtual ~IRunnable() {
}
virtual void Run() = 0;
};
} // namespace ke
// Include the actual thread implementations.
#if defined(_MSC_VER)
# include "ke_thread_windows.h"
#else
# include "ke_thread_posix.h"
#endif
#endif // _include_sourcepawn_threads_
+144
View File
@@ -0,0 +1,144 @@
// 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/>.
#ifndef _include_sourcepawn_thread_windows_h_
#define _include_sourcepawn_thread_windows_h_
#include <windows.h>
namespace ke {
class CriticalSection : public Lockable
{
public:
CriticalSection() {
InitializeCriticalSection(&cs_);
}
~CriticalSection() {
DeleteCriticalSection(&cs_);
}
bool DoTryLock() {
return !!TryEnterCriticalSection(&cs_);
}
void DoLock() {
EnterCriticalSection(&cs_);
}
void DoUnlock() {
LeaveCriticalSection(&cs_);
}
private:
CRITICAL_SECTION cs_;
};
typedef CriticalSection Mutex;
// Currently, this class only supports single-listener CVs.
class ConditionVariable : public Lockable
{
public:
ConditionVariable() {
event_ = CreateEvent(NULL, FALSE, FALSE, NULL);
}
~ConditionVariable() {
CloseHandle(event_);
}
bool DoTryLock() {
return cs_.DoTryLock();
}
void DoLock() {
cs_.DoLock();
}
void DoUnlock() {
cs_.DoUnlock();
}
void Notify() {
AssertCurrentThreadOwns();
SetEvent(event_);
}
WaitResult Wait(size_t timeout_ms) {
// This will assert if the lock has not been acquired. We don't need to be
// atomic here, like pthread_cond_wait, because the event bit will stick
// until reset by a wait function.
Unlock();
DWORD rv = WaitForSingleObject(event_, timeout_ms);
Lock();
if (rv == WAIT_TIMEOUT)
return Wait_Timeout;
if (rv == WAIT_FAILED)
return Wait_Error;
return Wait_Signaled;
}
WaitResult Wait() {
return Wait(INFINITE);
}
private:
CriticalSection cs_;
HANDLE event_;
};
class Thread
{
public:
Thread(IRunnable *run, const char *name = NULL) {
thread_ = CreateThread(NULL, 0, Main, run, 0, NULL);
}
~Thread() {
if (!thread_)
return;
CloseHandle(thread_);
}
bool Succeeded() const {
return !!thread_;
}
void Join() {
if (!Succeeded())
return;
WaitForSingleObject(thread_, INFINITE);
}
private:
static DWORD WINAPI Main(LPVOID arg) {
((IRunnable *)arg)->Run();
return 0;
}
#pragma pack(push, 8)
struct ThreadNameInfo {
DWORD dwType;
LPCSTR szName;
DWORD dwThreadID;
DWORD dwFlags;
};
#pragma pack(pop)
private:
HANDLE thread_;
};
} // namespace ke
#endif // _include_sourcepawn_thread_windows_h_
+21 -18
View File
@@ -48,34 +48,45 @@ static const size_t kKB = 1024;
static const size_t kMB = 1024 * kKB;
static const size_t kGB = 1024 * kMB;
template <typename T> T
ReturnAndVoid(T &t)
{
T saved = t;
t = T();
return saved;
}
template <typename T>
class AutoFree
class AutoPtr
{
T *t_;
public:
AutoFree()
AutoPtr()
: t_(NULL)
{
}
AutoFree(T *t)
explicit AutoPtr(T *t)
: t_(t)
{
}
~AutoFree() {
free(t_);
~AutoPtr() {
delete t_;
}
T *take() {
T *t = t_;
t_ = NULL;
return t;
return ReturnAndVoid(t_);
}
T *operator *() const {
return t_;
}
T *operator ->() const {
return t_;
}
operator T *() const {
return t_;
}
void operator =(T *t) {
if (t_)
free(t_);
delete t_;
t_ = t;
}
};
@@ -289,14 +300,6 @@ Max(const T &t1, const T &t2)
return t1 > t2 ? t1 : t2;
}
template <typename T> T
ReturnAndVoid(T &t)
{
T saved = t;
t = T();
return saved;
}
#define OFFSETOF(Class, Member) reinterpret_cast<size_t>(&((Class *)NULL)->Member)
#if defined(_MSC_VER)
+10 -1
View File
@@ -40,7 +40,7 @@
/** SourcePawn Engine API Version */
#define SOURCEPAWN_ENGINE_API_VERSION 4
#define SOURCEPAWN_ENGINE2_API_VERSION 4
#define SOURCEPAWN_ENGINE2_API_VERSION 5
#if !defined SOURCEMOD_BUILD
#define SOURCEMOD_BUILD
@@ -1286,6 +1286,15 @@ namespace SourcePawn
* @return New runtime, or NULL if not enough memory.
*/
virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
/**
* @brief Initiates the watchdog timer with the specified timeout
* length. This cannot be called more than once.
*
* @param timeout Timeout, in ms.
* @return True on success, false on failure.
*/
virtual bool InstallWatchdogTimer(size_t timeout_ms) =0;
};
};
+1
View File
@@ -87,6 +87,7 @@ typedef uint32_t funcid_t; /**< Function index code */
#define SP_ERROR_CODE_TOO_NEW 27 /**< Code is too new for this VM */
#define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */
#define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
#define SP_ERROR_TIMEOUT 30 /**< Timeout */
//Hey you! Update the string table if you add to the end of me! */
/**********************************************