Implement stacktrace collecting (#95)

This commit is contained in:
mihacooper 2017-10-22 14:08:24 +03:00 committed by Ilia
parent e84fbb32f4
commit 1df5e73f49
5 changed files with 77 additions and 17 deletions

View File

@ -11,14 +11,14 @@ std::string luaError(int errCode) {
case LUA_ERRRUN: return "Execution error (LUA_ERRRUN)"; case LUA_ERRRUN: return "Execution error (LUA_ERRRUN)";
case LUA_ERRGCMM: return "Error in __gc method (LUA_ERRGCMM)"; case LUA_ERRGCMM: return "Error in __gc method (LUA_ERRGCMM)";
case LUA_ERRERR: return "Recursive error (LUA_ERRERR)"; case LUA_ERRERR: return "Recursive error (LUA_ERRERR)";
default: return "Unknown"; default: return "Unknown (" + std::to_string(errCode) + ")";
} }
} }
int dumpMemoryWriter(lua_State*, const void* batch, size_t batchSize, void* storage) { int dumpMemoryWriter(lua_State*, const void* batch, size_t batchSize, void* storage) {
if (storage == nullptr || batch == nullptr) if (storage == nullptr)
return 1; return 1;
if (batchSize) { if (batchSize && batch) {
std::string& buff = *reinterpret_cast<std::string*>(storage); std::string& buff = *reinterpret_cast<std::string*>(storage);
const char* newData = reinterpret_cast<const char*>(batch); const char* newData = reinterpret_cast<const char*>(batch);
buff.insert(buff.end(), newData, newData + batchSize); buff.insert(buff.end(), newData, newData + batchSize);

View File

@ -164,7 +164,7 @@ StoredObject fromSolObject(const SolObject& luaObject) {
else if (luaObject.template is<Thread>()) else if (luaObject.template is<Thread>())
return std::make_unique<GCObjectHolder<Thread>>(luaObject); return std::make_unique<GCObjectHolder<Thread>>(luaObject);
else else
throw Exception() << "Unable to store userdata object\n"; throw Exception() << "Unable to store userdata object";
case sol::type::function: { case sol::type::function: {
FunctionObject func = GC::instance().create<FunctionObject>(luaObject); FunctionObject func = GC::instance().create<FunctionObject>(luaObject);
return std::make_unique<FunctionHolder>(func.handle()); return std::make_unique<FunctionHolder>(func.handle());

View File

@ -51,14 +51,26 @@ enum class Command {
Pause Pause
}; };
#if LUA_VERSION_NUM > 501
int luaErrorHandler(lua_State* state);
const lua_CFunction luaErrorHandlerPtr = luaErrorHandler;
#else
const lua_CFunction luaErrorHandlerPtr = nullptr;
#endif // LUA_VERSION_NUM > 501
} // namespace } // namespace
class ThreadHandle { class ThreadHandle {
public: public:
ThreadHandle() ThreadHandle()
: status_(Status::Running) : status_(Status::Running)
, command_(Command::Run) , command_(Command::Run)
, lua_(std::make_unique<sol::state>()) { , lua_(std::make_unique<sol::state>(luaErrorHandlerPtr)) {
luaL_openlibs(*lua_); luaL_openlibs(*lua_);
} }
@ -141,6 +153,17 @@ const sol::optional<std::chrono::milliseconds> NO_TIMEOUT;
static thread_local ThreadHandle* thisThreadHandle = nullptr; static thread_local ThreadHandle* thisThreadHandle = nullptr;
#if LUA_VERSION_NUM > 501
int luaErrorHandler(lua_State* state) {
luaL_traceback(state, state, nullptr, 1);
const auto stacktrace = sol::stack::pop<std::string>(state);
thisThreadHandle->result().emplace_back(createStoredObject(stacktrace));
throw Exception() << sol::stack::pop<std::string>(state);
}
#endif // LUA_VERSION_NUM > 501
void luaHook(lua_State*, lua_Debug*) { void luaHook(lua_State*, lua_Debug*) {
assert(thisThreadHandle); assert(thisThreadHandle);
switch (thisThreadHandle->command()) { switch (thisThreadHandle->command()) {
@ -198,7 +221,10 @@ void Thread::runThread(Thread thread,
thread.handle_->changeStatus(Status::Canceled); thread.handle_->changeStatus(Status::Canceled);
} catch (const sol::error& err) { } catch (const sol::error& err) {
DEBUG << "Failed with msg: " << err.what() << std::endl; DEBUG << "Failed with msg: " << err.what() << std::endl;
thread.handle_->result().emplace_back(createStoredObject(err.what())); auto& returns = thread.handle_->result();
returns.insert(returns.begin(),
{ createStoredObject("failed"),
createStoredObject(err.what()) });
thread.handle_->changeStatus(Status::Failed); thread.handle_->changeStatus(Status::Failed);
} }
} }
@ -279,14 +305,14 @@ void Thread::exportAPI(sol::state_view& lua) {
sol::stack::pop<sol::object>(lua); sol::stack::pop<sol::object>(lua);
} }
std::pair<sol::object, sol::object> Thread::status(const sol::this_state& lua) { StoredArray Thread::status(const sol::this_state& lua) {
sol::object luaStatus = sol::make_object(lua, statusToString(handle_->status())); const auto stat = handle_->status();
if (stat == Status::Failed) {
if (handle_->status() == Status::Failed) {
assert(!handle_->result().empty()); assert(!handle_->result().empty());
return std::make_pair(luaStatus, handle_->result()[0]->unpack(lua)); return handle_->result();
} else { } else {
return std::make_pair(luaStatus, sol::nil); const sol::object luaStatus = sol::make_object(lua, statusToString(stat));
return StoredArray({createStoredObject(luaStatus)});
} }
} }
@ -298,7 +324,7 @@ sol::optional<std::chrono::milliseconds> toOptionalTime(const sol::optional<int>
return sol::optional<std::chrono::milliseconds>(); return sol::optional<std::chrono::milliseconds>();
} }
std::pair<sol::object, sol::object> Thread::wait(const sol::this_state& lua, StoredArray Thread::wait(const sol::this_state& lua,
const sol::optional<int>& duration, const sol::optional<int>& duration,
const sol::optional<std::string>& period) { const sol::optional<std::string>& period) {
handle_->waitForCompletion(toOptionalTime(duration, period)); handle_->waitForCompletion(toOptionalTime(duration, period));

View File

@ -23,8 +23,8 @@ public:
static void exportAPI(sol::state_view& lua); static void exportAPI(sol::state_view& lua);
std::pair<sol::object, sol::object> status(const sol::this_state& state); StoredArray status(const sol::this_state& state);
std::pair<sol::object, sol::object> wait(const sol::this_state& state, StoredArray wait(const sol::this_state& state,
const sol::optional<int>& duration, const sol::optional<int>& duration,
const sol::optional<std::string>& period); const sol::optional<std::string>& period);
StoredArray get(const sol::optional<int>& duration, StoredArray get(const sol::optional<int>& duration,

View File

@ -370,3 +370,37 @@ test.this_thread.pause_on_finished_thread = function ()
test.is_true(worker_thread:get(2, "s")) test.is_true(worker_thread:get(2, "s"))
test.is_true(effil.thread(call_pause)(worker_thread):get(5, "s")) test.is_true(effil.thread(call_pause)(worker_thread):get(5, "s"))
end end
if LUA_VERSION > 51 then
test.thread.traceback = function()
local curr_file = debug.getinfo(1,'S').short_src
local function foo()
function boom()
error("err msg")
end
function bar()
boom()
end
bar()
end
local status, err, trace = effil.thread(foo)():wait()
print("status: ", status)
print("error: ", err)
print("stacktrace: ", trace)
test.equal(status, "failed")
-- <souce file>.lua:<string number>: <error message>
test.is_not_nil(string.find(err, curr_file .. ":%d+: err msg"))
test.is_not_nil(string.find(trace, (
[[stack traceback:
%%s%s:%%d+: in function 'boom'
%%s%s:%%d+: in function 'bar'
%%s%s:%%d+: in function <%s:%%d+>]]
):format(curr_file, curr_file, curr_file, curr_file)
))
end
end -- LUA_VERSION > 51