(Necessary to make Lua usable on public servers.)
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1605 c06c8d41-db1a-0410-9941-cceddc491573
YE7M665QKDGI7Y5WMERCWJNDZ4FUZ6GRUCK4E6GZH4SWCUM6RWLAC
if (clua.error.length())
{
mpr( ("Lua error: " + clua.error).c_str() );
}
if (!clua.error.empty())
mprf(MSGCH_WARN, "Lua error: %s\n", clua.error.c_str());
// If managed_vm is set, we have to take steps to control badly-behaved
// scripts.
bool managed_vm;
int throttle_unit_lines;
int throttle_sleep_ms;
int throttle_sleep_start, throttle_sleep_end;
int n_throttle_sleeps;
int mixed_call_depth;
int lua_call_depth;
int max_mixed_call_depth;
int max_lua_call_depth;
static const int MAX_THROTTLE_SLEEPS = 100;
CLua::CLua() : _state(NULL), sourced_files(), uniqindex(0L)
CLua clua(true);
const int CLua::MAX_THROTTLE_SLEEPS;
CLua::CLua(bool managed)
: error(), managed_vm(managed), throttle_unit_lines(10000),
throttle_sleep_ms(0), throttle_sleep_start(2),
throttle_sleep_end(800), n_throttle_sleeps(0), mixed_call_depth(0),
lua_call_depth(0), max_mixed_call_depth(8),
max_lua_call_depth(100), _state(NULL), sourced_files(), uniqindex(0L)
void CLua::init_throttle()
{
if (!managed_vm)
return;
if (throttle_unit_lines <= 0)
throttle_unit_lines = 500;
if (throttle_sleep_start < 1)
throttle_sleep_start = 1;
if (throttle_sleep_end < throttle_sleep_start)
throttle_sleep_end = throttle_sleep_start;
if (!mixed_call_depth)
{
lua_sethook(_state, clua_throttle_hook,
LUA_MASKCOUNT, throttle_unit_lines);
throttle_sleep_ms = 0;
n_throttle_sleeps = 0;
}
}
}
//////////////////////////////////////////////////////////////////////////
lua_call_throttle::lua_clua_map lua_call_throttle::lua_map;
static void clua_throttle_hook(lua_State *ls, lua_Debug *dbg)
{
CLua *lua = lua_call_throttle::find_clua(ls);
// Co-routines can create a new Lua state; in such cases, we must
// fudge it.
if (!lua)
lua = &clua;
if (lua)
{
if (!lua->throttle_sleep_ms)
lua->throttle_sleep_ms = lua->throttle_sleep_start;
else if (lua->throttle_sleep_ms < lua->throttle_sleep_end)
lua->throttle_sleep_ms *= 2;
++lua->n_throttle_sleeps;
delay(lua->throttle_sleep_ms);
// Try to kill the annoying script.
if (lua->n_throttle_sleeps > CLua::MAX_THROTTLE_SLEEPS)
{
lua->n_throttle_sleeps = CLua::MAX_THROTTLE_SLEEPS;
luaL_error(ls, BUGGY_SCRIPT_ERROR);
}
}
// This function is a replacement for Lua's in-built pcall function. It behaves
// like pcall in all respects (as documented in the Lua 5.1 reference manual),
// but does not allow the Lua chunk/script to catch errors thrown by the
// Lua-throttling code. This is necessary so that we can interrupt scripts that
// are hogging CPU.
//
// If we did not intercept pcall, the script could do the equivalent
// of this:
//
// while true do
// pcall(function () while true do end end)
// end
//
// And there's a good chance we wouldn't be able to interrupt the
// deadloop because our errors would get caught by the pcall (more
// levels of nesting would just increase the chance of the script
// beating our throttling).
//
static int clua_guarded_pcall(lua_State *ls)
{
const int nargs = lua_gettop(ls);
const int err = lua_pcall(ls, nargs - 1, LUA_MULTRET, 0);
if (err)
{
const char *errs = lua_tostring(ls, 1);
if (!errs || strstr(errs, BUGGY_SCRIPT_ERROR))
luaL_error(ls, errs? errs : BUGGY_PCALL_ERROR);
}
lua_pushboolean(ls, !err);
lua_insert(ls, 1);
return (lua_gettop(ls));
}
lua_call_throttle::lua_call_throttle(CLua *_lua)
: lua(_lua)
{
lua->init_throttle();
if (!lua->mixed_call_depth++)
lua_map[lua->state()] = lua;
}
lua_call_throttle::~lua_call_throttle()
{
if (!--lua->mixed_call_depth)
lua_map.erase(lua->state());
}
CLua *lua_call_throttle::find_clua(lua_State *ls)
{
lua_clua_map::iterator i = lua_map.find(ls);
return (i != lua_map.end()? i->second : NULL);
}