(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);}