mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
AGS: Test for hanging script using timeout instead of while loops
From upstream c34f0c96596f1a7814c15e7a06c2c498cb95ebfc
This commit is contained in:
@@ -417,7 +417,7 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
|
||||
// NOTE: we must do this before plugin start, because some plugins may
|
||||
// require access to script API at initialization time.
|
||||
//
|
||||
ccSetScriptAliveTimer(150000);
|
||||
ccSetScriptAliveTimer(10u, 1000u);
|
||||
ccSetStringClassImpl(&_GP(myScriptStringImpl));
|
||||
setup_script_exports(base_api, compat_api);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "ags/engine/ac/dynobj/script_user_object.h"
|
||||
#include "ags/engine/ac/statobj/ags_static_object.h"
|
||||
#include "ags/engine/ac/statobj/static_array.h"
|
||||
#include "ags/engine/ac/sys_events.h"
|
||||
#include "ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h"
|
||||
#include "ags/shared/util/memory.h"
|
||||
#include "ags/shared/util/string_utils.h" // linux strnicmp definition
|
||||
@@ -206,7 +207,6 @@ struct FunctionCallStack {
|
||||
int Count;
|
||||
};
|
||||
|
||||
|
||||
ccInstance *ccInstance::GetCurrentInstance() {
|
||||
return _GP(InstThreads).size() > 0 ? _GP(InstThreads).back() : nullptr;
|
||||
}
|
||||
@@ -226,6 +226,11 @@ ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
|
||||
return cinst;
|
||||
}
|
||||
|
||||
void ccInstance::SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms) {
|
||||
_G(timeoutCheckMs) = sys_poll_ms;
|
||||
_G(timeoutAbortMs) = abort_ms;
|
||||
}
|
||||
|
||||
ccInstance::ccInstance() {
|
||||
flags = 0;
|
||||
globaldata = nullptr;
|
||||
@@ -431,7 +436,6 @@ int ccInstance::Run(int32_t curpc) {
|
||||
int32_t thisbase[MAXNEST], funcstart[MAXNEST];
|
||||
int was_just_callas = -1;
|
||||
int curnest = 0;
|
||||
int loopIterations = 0;
|
||||
int num_args_to_func = -1;
|
||||
int next_call_needs_object = 0;
|
||||
int loopIterationCheckDisabled = 0;
|
||||
@@ -441,9 +445,13 @@ int ccInstance::Run(int32_t curpc) {
|
||||
bool write_debug_dump = ccGetOption(SCOPT_DEBUGRUN) ||
|
||||
(gDebugLevel > 0 && DebugMan.isDebugChannelEnabled(::AGS::kDebugScript));
|
||||
ScriptOperation codeOp;
|
||||
|
||||
FunctionCallStack func_callstack;
|
||||
|
||||
const auto timeout = std::chrono::milliseconds(_G(timeoutCheckMs));
|
||||
const auto timeout_abort = std::chrono::milliseconds(_G(timeoutAbortMs));
|
||||
_lastAliveTs = AGS_Clock::now();
|
||||
bool timeout_warn = false;
|
||||
|
||||
while ((flags & INSTF_ABORTED) == 0) {
|
||||
if (_G(abort_engine))
|
||||
return -1;
|
||||
@@ -774,15 +782,30 @@ int ccInstance::Run(int32_t curpc) {
|
||||
case SCMD_JMP:
|
||||
pc += arg1.IValue;
|
||||
|
||||
if ((arg1.IValue < 0) && (_G(maxWhileLoops) > 0) && (loopIterationCheckDisabled == 0)) {
|
||||
// Make sure it's not stuck in a While loop
|
||||
loopIterations ++;
|
||||
// Make sure it's not stuck in a While loop
|
||||
if (arg1.IValue < 0) {
|
||||
auto now = AGS_Clock::now();
|
||||
auto test_dur = std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastAliveTs);
|
||||
if (flags & INSTF_RUNNING) {
|
||||
loopIterations = 0;
|
||||
// was notified still running, don't do anything
|
||||
flags &= ~INSTF_RUNNING;
|
||||
} else if (loopIterations > _G(maxWhileLoops)) {
|
||||
cc_error("!Script appears to be hung (a while loop ran %d times). The problem may be in a calling function; check the call stack.", loopIterations);
|
||||
return -1;
|
||||
_lastAliveTs = now;
|
||||
timeout_warn = false;
|
||||
} else if (test_dur > timeout) {
|
||||
// minimal timeout occured
|
||||
if (test_dur.count() > timeout_abort.count()) {
|
||||
// critical timeout occured
|
||||
if (loopIterationCheckDisabled == 0) {
|
||||
cc_error("!Script appears to be hung (no game update for %lld ms). The problem may be in a calling function; check the call stack.", test_dur.count());
|
||||
return -1;
|
||||
}
|
||||
if (!timeout_warn) {
|
||||
debug_script_warn("WARNING: script execution hung? (%lld ms)", test_dur.count());
|
||||
timeout_warn = true;
|
||||
}
|
||||
}
|
||||
// at least let user to manipulate the game window
|
||||
sys_evt_process_pending();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "ags/lib/std/memory.h"
|
||||
#include "ags/lib/std/map.h"
|
||||
#include "ags/engine/ac/timer.h"
|
||||
#include "ags/shared/script/cc_internal.h"
|
||||
#include "ags/shared/script/cc_script.h" // ccScript
|
||||
#include "ags/engine/script/non_blocking_script_function.h"
|
||||
@@ -144,6 +145,7 @@ public:
|
||||
// create a runnable instance of the supplied script
|
||||
static ccInstance *CreateFromScript(PScript script);
|
||||
static ccInstance *CreateEx(PScript scri, ccInstance *joined);
|
||||
static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms);
|
||||
|
||||
ccInstance();
|
||||
~ccInstance();
|
||||
@@ -176,7 +178,7 @@ public:
|
||||
// Also change CALLEXT op-codes to CALLAS when they pertain to a script instance
|
||||
bool ResolveImportFixups(const ccScript *scri);
|
||||
|
||||
protected:
|
||||
private:
|
||||
bool _Create(PScript scri, ccInstance *joined);
|
||||
// free the memory associated with the instance
|
||||
void Free();
|
||||
@@ -213,6 +215,9 @@ protected:
|
||||
// Function call stack processing
|
||||
void PushToFuncCallStack(FunctionCallStack &func_callstack, const RuntimeScriptValue &rval);
|
||||
void PopFromFuncCallStack(FunctionCallStack &func_callstack, int32_t num_entries);
|
||||
|
||||
// Last time the script was noted of being "alive"
|
||||
AGS_Clock::time_point _lastAliveTs;
|
||||
};
|
||||
|
||||
extern void script_commands_init();
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
// static const char ccRunnerCopyright[] = "ScriptExecuter32 v" SCOM_VERSIONSTR " (c) 2001 Chris Jones";
|
||||
|
||||
bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn) {
|
||||
return _GP(simp).add(name, RuntimeScriptValue().SetStaticFunction(pfn), nullptr) != UINT32_MAX;
|
||||
}
|
||||
@@ -115,9 +113,9 @@ Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name) {
|
||||
// If a while loop does this many iterations without the
|
||||
// NofityScriptAlive function getting called, the script
|
||||
// aborts. Set to 0 to disable.
|
||||
void ccSetScriptAliveTimer(int numloop) {
|
||||
_G(maxWhileLoops) = numloop;
|
||||
}
|
||||
void ccSetScriptAliveTimer(unsigned sys_poll_timeout, unsigned abort_timeout) {
|
||||
ccInstance::SetExecTimeout(sys_poll_timeout, abort_timeout);
|
||||
}
|
||||
|
||||
void ccNotifyScriptStillAlive() {
|
||||
ccInstance *cur_inst = ccInstance::GetCurrentInstance();
|
||||
|
||||
@@ -77,8 +77,10 @@ extern Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name);
|
||||
typedef void (*new_line_hook_type)(ccInstance *, int);
|
||||
extern void ccSetDebugHook(new_line_hook_type jibble);
|
||||
|
||||
// Set the number of while loop iterations that aborts the script
|
||||
extern void ccSetScriptAliveTimer(int);
|
||||
// Set the script interpreter timeout values, in milliseconds:
|
||||
// * sys_poll_timeout - defines the timeout at which the interpreter will run system events poll;
|
||||
// * abort_timeout - defines the timeout at which the interpreter will cancel with error.
|
||||
extern void ccSetScriptAliveTimer(unsigned sys_poll_timeout, unsigned abort_timeout);
|
||||
// reset the current while loop counter
|
||||
extern void ccNotifyScriptStillAlive();
|
||||
// for calling exported plugin functions old-style
|
||||
|
||||
@@ -1279,7 +1279,12 @@ public:
|
||||
*/
|
||||
|
||||
new_line_hook_type _new_line_hook = nullptr;
|
||||
int _maxWhileLoops = 0;
|
||||
// Minimal timeout: how much time may pass without any engine update
|
||||
// before we want to check on the situation and do system poll
|
||||
unsigned _timeoutCheckMs = 60u;
|
||||
// Critical timeout: how much time may pass without any engine update
|
||||
// before we abort or post a warning
|
||||
unsigned _timeoutAbortMs = 60u * 10;
|
||||
ccInstance *_loadedInstances[MAX_LOADED_INSTANCES];
|
||||
|
||||
/**@}*/
|
||||
|
||||
Reference in New Issue
Block a user