Closed anthonyclark-as closed 2 years ago
Following up. Is this a smart way to do this?
Summary:
sol::state
in a class. That class has an operator() that runs in a thread. Essential 1:1 thread to statethis
in each state so we can access in a LUA hook. Could have used a global std::map<> or something...LUA_MASKCOUNT
hook against each statethis
from the state to check a fieldluaL_error
but I'm not sure if this is safe to do. The result of this is that the sol::protected_function
"run" has a proper exit path. I'm hoping this is safe to do while the function is being "yielded" by a LUA internal convention ; hopefully this means it's "safe" to inject an error call in the middle.#include <cassert>
#include <csignal>
#include <atomic>
#include <vector>
#include <map>
#include <string>
#include <thread>
//#include "asi/lua/lua.hh"
#define SOL_ALL_SAFETIES_ON 1
#include "sol/sol.hpp"
std::atomic_bool gStop{false};
static void yield_hook(lua_State* L, lua_Debug *ar);
struct LuaTask
{
std::unique_ptr<sol::state> m_lua;
std::atomic_bool & m_stop;
LuaTask(std::atomic_bool & stop) :
m_stop{stop}
{
m_lua = std::make_unique<sol::state>();
m_lua->open_libraries(sol::lib::base);
// store this in the lua state so the yield hook can
// get it out. Maybe there is a better way
(*m_lua)["__tcb"] = this;
::lua_sethook(m_lua->lua_state(),
&yield_hook, LUA_MASKCOUNT, 50 /* bigger? */);
// example
m_lua->script(R"(
x = 0
function run()
while true do
x = x + 1
end
end
)");
}
void operator()()
{
auto result = sol::protected_function{(*m_lua)["run"]}();
if (! result.valid()) {
// Call failed
sol::error err = result;
std::string what = err.what();
std::cout << what << std::endl;
}
// just get some global state to show that run() was
// working as intented. TODO I did print() but it was
// hogging stdio to a point that was confusing.
auto x = (*m_lua)["x"].get<int>();
std::cout << "Run is done: X? " << x << std::endl;
}
auto & signal() const {
return m_stop;
}
};
static void yield_hook(lua_State* L, lua_Debug *ar)
{
sol::state_view state{L};
// get the object out, safety?
LuaTask * that = state["__tcb"];
// is calling luaL_error OK?
if (that->signal()) {
::luaL_error(L, "Aborting via signal");
}
std::this_thread::yield();
}
int main()
{
std::signal(SIGINT, [](int) { gStop = true; });
LuaTask task{gStop};
// call operator()
std::thread task_thread{std::ref(task)};
// simulate main thread work
while (! gStop)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "MAIN THREAD POLL\n";
}
task_thread.join();
}
Edit: some reference output
$ ./a.out
MAIN THREAD POLL
MAIN THREAD POLL
MAIN THREAD POLL
MAIN THREAD POLL
^CAborting via signal
stack traceback:
[string "..."]:5: in function 'base.run'
Run is done: X? 42231649
MAIN THREAD POLL
ThePhD,
Thanks so much for sol2. Is it possible to terminate a function call initiated from the C++ interface? Imagine I invoke a function
lua["run"]();
but want to eventually cancel or abort. Of course I can detach an OS thread then terminate it, but I'm curious if there exists a sol2-idiomatic way to do this.I've previously done this with the LUA C API and a debug hook for counting instructions and checking some external global stop variables. I tried to get that to integrate with sol2 but it seems it'll take some work.
Thanks!