Issue #160: make thread runner shared table (#168)

Issue #160: make thread runner shared table
This commit is contained in:
mihacooper 2022-02-22 23:37:55 +03:00 committed by GitHub
parent 92a7bb50d2
commit 2333863a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 42 deletions

View File

@ -1,8 +1,8 @@
image: Visual Studio 2015 image: Visual Studio 2015
configuration: configuration:
- debug
- release - release
# - debug
platform: platform:
- x86 - x86

View File

@ -2,6 +2,7 @@
#include "shared-table.h" #include "shared-table.h"
#include "garbage-collector.h" #include "garbage-collector.h"
#include "channel.h" #include "channel.h"
#include "thread_runner.h"
#include <lua.hpp> #include <lua.hpp>
@ -52,30 +53,18 @@ sol::object luaDump(sol::this_state lua, const sol::stack_object& obj) {
<< luaTypename(obj) << ")"; << luaTypename(obj) << ")";
} }
sol::table luaThreadConfig(sol::this_state state, const sol::stack_object& obj) { sol::table createThreadRunner(sol::this_state state, const sol::stack_object& obj) {
REQUIRE(obj.valid() && obj.get_type() == sol::type::function) REQUIRE(obj.valid() && obj.get_type() == sol::type::function)
<< "bad argument #1 to 'effil.thread' (function expected, got " << "bad argument #1 to 'effil.thread' (function expected, got "
<< luaTypename(obj) << ")"; << luaTypename(obj) << ")";
auto lua = sol::state_view(state); auto lua = sol::state_view(state);
const sol::function func = obj.as<sol::function>(); return sol::make_object(lua, GC::instance().create<ThreadRunner>(
lua["package"]["path"],
auto config = lua.create_table_with( lua["package"]["cpath"],
"path", lua["package"]["path"], 200,
"cpath", lua["package"]["cpath"], obj.as<sol::function>()
"step", 200 ));
);
auto meta = lua.create_table_with();
meta[sol::meta_function::call] = [func](sol::this_state lua,
const sol::stack_table& self, const sol::variadic_args& args)
{
return sol::make_object(lua, GC::instance().create<Thread>(
self["path"], self["cpath"], self["step"], func, args));
};
config[sol::metatable_key] = meta;
return config;
} }
} // namespace } // namespace
@ -89,6 +78,7 @@ int luaopen_effil(lua_State* L) {
Thread::exportAPI(lua); Thread::exportAPI(lua);
SharedTable::exportAPI(lua); SharedTable::exportAPI(lua);
Channel::exportAPI(lua); Channel::exportAPI(lua);
ThreadRunner::exportAPI(lua);
const sol::table gcApi = GC::exportAPI(lua); const sol::table gcApi = GC::exportAPI(lua);
const sol::object gLuaTable = sol::make_object(lua, globalTable); const sol::object gLuaTable = sol::make_object(lua, globalTable);
@ -106,7 +96,7 @@ int luaopen_effil(lua_State* L) {
}; };
sol::usertype<EffilApiMarker> type("new", sol::no_constructor, sol::usertype<EffilApiMarker> type("new", sol::no_constructor,
"thread", luaThreadConfig, "thread", createThreadRunner,
"thread_id", threadId, "thread_id", threadId,
"sleep", sleep, "sleep", sleep,
"yield", yield, "yield", yield,
@ -122,7 +112,7 @@ int luaopen_effil(lua_State* L) {
"next", SharedTable::globalLuaNext, "next", SharedTable::globalLuaNext,
"size", luaSize, "size", luaSize,
"dump", luaDump, "dump", luaDump,
"hardware_threads", std::thread::hardware_concurrency, "hardware_threads", std::thread::hardware_concurrency,
sol::meta_function::index, luaIndex sol::meta_function::index, luaIndex
); );

View File

@ -4,6 +4,7 @@
#include "shared-table.h" #include "shared-table.h"
#include "function.h" #include "function.h"
#include "utils.h" #include "utils.h"
#include "thread_runner.h"
#include <map> #include <map>
#include <vector> #include <vector>
@ -180,6 +181,8 @@ StoredObject fromSolObject(const SolObject& luaObject, SolTableToShared& visited
return std::make_unique<GCObjectHolder<Thread>>(luaObject); return std::make_unique<GCObjectHolder<Thread>>(luaObject);
else if (luaObject.template is<EffilApiMarker>()) else if (luaObject.template is<EffilApiMarker>())
return std::make_unique<ApiReferenceHolder>(); return std::make_unique<ApiReferenceHolder>();
else if (luaObject.template is<ThreadRunner>())
return std::make_unique<GCObjectHolder<ThreadRunner>>(luaObject);
else else
throw Exception() << "Unable to store userdata object"; throw Exception() << "Unable to store userdata object";
case sol::type::function: { case sol::type::function: {

39
src/cpp/thread_runner.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "thread_runner.h"
namespace effil {
void ThreadRunner::initialize(
const std::string& path,
const std::string& cpath,
lua_Number step,
const sol::function& func)
{
ctx_->path_ = path;
ctx_->cpath_ = cpath;
ctx_->step_ = step;
try {
ctx_->function_ = createStoredObject(func);
} RETHROW_WITH_PREFIX("effil.thread");
ctx_->addReference(ctx_->function_->gcHandle());
ctx_->function_->releaseStrongReference();
}
sol::object ThreadRunner::call(sol::this_state lua, const sol::variadic_args& args) {
return sol::make_object(lua, GC::instance().create<Thread>(
ctx_->path_, ctx_->cpath_, ctx_->step_, ctx_->function_->unpack(lua), args));
}
void ThreadRunner::exportAPI(sol::state_view& lua) {
sol::usertype<ThreadRunner> type("new", sol::no_constructor,
sol::meta_function::call, &ThreadRunner::call,
"path", sol::property(&ThreadRunner::getPath, &ThreadRunner::setPath),
"cpath", sol::property(&ThreadRunner::getCPath, &ThreadRunner::setCPath),
"step", sol::property(&ThreadRunner::getStep, &ThreadRunner::setStep)
);
sol::stack::push(lua, type);
sol::stack::pop<sol::object>(lua);
}
} // namespace effil

39
src/cpp/thread_runner.h Normal file
View File

@ -0,0 +1,39 @@
#include "threading.h"
#include "gc-data.h"
#include "gc-object.h"
namespace effil {
class ThreadRunnerData : public GCData {
public:
std::string path_;
std::string cpath_;
lua_Number step_;
StoredObject function_;
};
struct ThreadRunner: public GCObject<ThreadRunnerData> {
static void exportAPI(sol::state_view& lua);
std::string getPath() const { return ctx_->path_; }
void setPath(const std::string& p) { ctx_->path_ = p; }
std::string getCPath() const { return ctx_->cpath_; }
void setCPath(const std::string& p) { ctx_->cpath_ = p; }
lua_Number getStep() const { return ctx_->step_; }
void setStep(lua_Number s) { ctx_->step_ = s; }
private:
ThreadRunner() = default;
void initialize(
const std::string& path,
const std::string& cpath,
lua_Number step,
const sol::function& func);
sol::object call(sol::this_state lua, const sol::variadic_args& args);
friend class GC;
};
} // namespace effil

View File

@ -45,29 +45,22 @@ std::string statusToString(Status status) {
return "unknown"; return "unknown";
} }
#if LUA_VERSION_NUM > 501
int luaErrorHandler(lua_State* state) { int luaErrorHandler(lua_State* state) {
luaL_traceback(state, state, nullptr, 1); luaL_traceback(state, state, nullptr, 1);
const auto stacktrace = sol::stack::pop<std::string>(state); const auto stacktrace = sol::stack::pop<std::string>(state);
thisThreadHandle->result().emplace_back(createStoredObject(stacktrace)); thisThreadHandle->result().emplace_back(createStoredObject(stacktrace));
throw Exception() << sol::stack::pop<std::string>(state); return 1;
} }
const lua_CFunction luaErrorHandlerPtr = luaErrorHandler; const lua_CFunction luaErrorHandlerPtr = luaErrorHandler;
#else
const lua_CFunction luaErrorHandlerPtr = nullptr;
#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()) {
case Command::Run: case Command::Run:
break; break;
case Command::Cancel: case Command::Cancel:
thisThreadHandle->changeStatus(Status::Canceled);
throw LuaHookStopException(); throw LuaHookStopException();
case Command::Pause: { case Command::Pause: {
thisThreadHandle->changeStatus(Status::Paused); thisThreadHandle->changeStatus(Status::Paused);
@ -75,10 +68,12 @@ void luaHook(lua_State*, lua_Debug*) {
do { do {
cmd = thisThreadHandle->waitForCommandChange(NO_TIMEOUT); cmd = thisThreadHandle->waitForCommandChange(NO_TIMEOUT);
} while(cmd != Command::Run && cmd != Command::Cancel); } while(cmd != Command::Run && cmd != Command::Cancel);
if (cmd == Command::Run) if (cmd == Command::Run) {
thisThreadHandle->changeStatus(Status::Running); thisThreadHandle->changeStatus(Status::Running);
else } else {
thisThreadHandle->changeStatus(Status::Canceled);
throw LuaHookStopException(); throw LuaHookStopException();
}
break; break;
} }
} }
@ -89,7 +84,7 @@ void luaHook(lua_State*, lua_Debug*) {
ThreadHandle::ThreadHandle() ThreadHandle::ThreadHandle()
: status_(Status::Running) : status_(Status::Running)
, command_(Command::Run) , command_(Command::Run)
, lua_(std::make_unique<sol::state>(luaErrorHandlerPtr)) { , lua_(std::make_unique<sol::state>()) {
luaL_openlibs(*lua_); luaL_openlibs(*lua_);
} }
@ -126,9 +121,26 @@ void Thread::runThread(Thread thread,
arguments.clear(); arguments.clear();
thread.ctx_->destroyLua(); thread.ctx_->destroyLua();
}); });
sol::function userFuncObj = function.loadFunction(thread.ctx_->lua()); sol::protected_function userFuncObj = function.loadFunction(thread.ctx_->lua());
sol::function_result results = userFuncObj(std::move(arguments));
(void)results; // just leave all returns on the stack #if LUA_VERSION_NUM > 501
sol::stack::push(thread.ctx_->lua(), luaErrorHandlerPtr);
userFuncObj.error_handler = sol::reference(thread.ctx_->lua());
sol::stack::pop_n(thread.ctx_->lua(), 1);
#endif // LUA_VERSION NUM > 501
sol::protected_function_result result = userFuncObj(std::move(arguments));
if (!result.valid()) {
if (thread.ctx_->status() == Status::Canceled)
return;
sol::error err = result;
std::string what = err.what();
throw std::runtime_error(what);
}
sol::variadic_args args(thread.ctx_->lua(), -lua_gettop(thread.ctx_->lua())); sol::variadic_args args(thread.ctx_->lua(), -lua_gettop(thread.ctx_->lua()));
for (const auto& iter : args) { for (const auto& iter : args) {
StoredObject store = createStoredObject(iter.get<sol::object>()); StoredObject store = createStoredObject(iter.get<sol::object>());
@ -143,7 +155,7 @@ void Thread::runThread(Thread thread,
thread.ctx_->changeStatus(Status::Completed); thread.ctx_->changeStatus(Status::Completed);
} catch (const LuaHookStopException&) { } catch (const LuaHookStopException&) {
thread.ctx_->changeStatus(Status::Canceled); thread.ctx_->changeStatus(Status::Canceled);
} catch (const sol::error& err) { } catch (const std::exception& err) {
DEBUG("thread") << "Failed with msg: " << err.what() << std::endl; DEBUG("thread") << "Failed with msg: " << err.what() << std::endl;
auto& returns = thread.ctx_->result(); auto& returns = thread.ctx_->result();
returns.insert(returns.begin(), returns.insert(returns.begin(),
@ -193,7 +205,10 @@ void Thread::initialize(
ctx_->lua()["package"]["path"] = path; ctx_->lua()["package"]["path"] = path;
ctx_->lua()["package"]["cpath"] = cpath; ctx_->lua()["package"]["cpath"] = cpath;
ctx_->lua().script("require 'effil'"); try {
luaopen_effil(ctx_->lua());
sol::stack::pop<sol::object>(ctx_->lua());
} RETHROW_WITH_PREFIX("effil.thread");
if (step != 0) if (step != 0)
lua_sethook(ctx_->lua(), luaHook, LUA_MASKCOUNT, step); lua_sethook(ctx_->lua(), luaHook, LUA_MASKCOUNT, step);

View File

@ -45,6 +45,7 @@ test.gc_stress.regress_for_concurent_thread_creation = function ()
end end
end end
test.gc_stress.regress_for_concurent_function_creation = function () test.gc_stress.regress_for_concurent_function_creation = function ()
local a = function() end local a = function() end
local b = function() end local b = function() end

View File

@ -8,6 +8,28 @@ test.thread.hardware_threads = function()
test.is_true(effil.hardware_threads() >= 0) test.is_true(effil.hardware_threads() >= 0)
end end
test.thread.runner_is_serializible = function ()
local table = effil.table()
local runner = effil.thread(function(n) return n * 2 end)
table["runner"] = runner
test.equal(table["runner"](123):get(), 246)
end
test.thread.runner_path_check_p = function (config_key, pkg)
local table = effil.table()
local runner = effil.thread(function()
require(pkg)
end)
test.equal(runner():wait(), "completed")
runner[config_key] = ""
test.equal(runner():wait(), "failed")
end
test.thread.runner_path_check_p("path", "size") -- some testing Lua file to import
test.thread.runner_path_check_p("cpath", "effil")
test.thread.wait = function () test.thread.wait = function ()
local thread = effil.thread(function() local thread = effil.thread(function()
print 'Effil is not that tower' print 'Effil is not that tower'
@ -395,13 +417,15 @@ test.thread.traceback = function()
local status, err, trace = effil.thread(foo)():wait() local status, err, trace = effil.thread(foo)():wait()
print("status: ", status) print("status: ", status)
print("error: ", err) print("error: ", err)
print("stacktrace: ", trace) print("stacktrace:")
print(trace)
test.equal(status, "failed") test.equal(status, "failed")
-- <souce file>.lua:<string number>: <error message> -- <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(err, curr_file .. ":%d+: err msg"))
test.is_not_nil(string.find(trace, ( test.is_not_nil(string.find(trace, (
[[stack traceback: [[stack traceback:
%%s%%[C%%]: in function 'error'
%%s%s:%%d+: in function 'boom' %%s%s:%%d+: in function 'boom'
%%s%s:%%d+: in function 'bar' %%s%s:%%d+: in function 'bar'
%%s%s:%%d+: in function <%s:%%d+>]] %%s%s:%%d+: in function <%s:%%d+>]]

View File

@ -40,7 +40,7 @@ test.upvalues.check_single_upvalue_p(function() return effil.thread(foo)() end,
test.upvalues.check_invalid_coroutine = function() test.upvalues.check_invalid_coroutine = function()
local obj = coroutine.create(foo) local obj = coroutine.create(foo)
local thread_worker = function() return tostring(obj) end local thread_worker = function() return tostring(obj) end
local ret, err = pcall(effil.thread(thread_worker)) local ret, err = pcall(effil.thread, thread_worker)
if ret then if ret then
ret:wait() ret:wait()
end end