diff --git a/appveyor.yml b/appveyor.yml index 11fa374..104fbed 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ image: Visual Studio 2015 configuration: - - debug - release +# - debug platform: - x86 diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index 1f0e008..eabdddc 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -2,6 +2,7 @@ #include "shared-table.h" #include "garbage-collector.h" #include "channel.h" +#include "thread_runner.h" #include @@ -52,30 +53,18 @@ sol::object luaDump(sol::this_state lua, const sol::stack_object& 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) << "bad argument #1 to 'effil.thread' (function expected, got " << luaTypename(obj) << ")"; auto lua = sol::state_view(state); - const sol::function func = obj.as(); - - auto config = lua.create_table_with( - "path", lua["package"]["path"], - "cpath", lua["package"]["cpath"], - "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( - self["path"], self["cpath"], self["step"], func, args)); - }; - - config[sol::metatable_key] = meta; - return config; + return sol::make_object(lua, GC::instance().create( + lua["package"]["path"], + lua["package"]["cpath"], + 200, + obj.as() + )); } } // namespace @@ -89,6 +78,7 @@ int luaopen_effil(lua_State* L) { Thread::exportAPI(lua); SharedTable::exportAPI(lua); Channel::exportAPI(lua); + ThreadRunner::exportAPI(lua); const sol::table gcApi = GC::exportAPI(lua); const sol::object gLuaTable = sol::make_object(lua, globalTable); @@ -106,7 +96,7 @@ int luaopen_effil(lua_State* L) { }; sol::usertype type("new", sol::no_constructor, - "thread", luaThreadConfig, + "thread", createThreadRunner, "thread_id", threadId, "sleep", sleep, "yield", yield, @@ -122,7 +112,7 @@ int luaopen_effil(lua_State* L) { "next", SharedTable::globalLuaNext, "size", luaSize, "dump", luaDump, - "hardware_threads", std::thread::hardware_concurrency, + "hardware_threads", std::thread::hardware_concurrency, sol::meta_function::index, luaIndex ); diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index a44c220..fca5a1d 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -4,6 +4,7 @@ #include "shared-table.h" #include "function.h" #include "utils.h" +#include "thread_runner.h" #include #include @@ -180,6 +181,8 @@ StoredObject fromSolObject(const SolObject& luaObject, SolTableToShared& visited return std::make_unique>(luaObject); else if (luaObject.template is()) return std::make_unique(); + else if (luaObject.template is()) + return std::make_unique>(luaObject); else throw Exception() << "Unable to store userdata object"; case sol::type::function: { diff --git a/src/cpp/thread_runner.cpp b/src/cpp/thread_runner.cpp new file mode 100644 index 0000000..bf97911 --- /dev/null +++ b/src/cpp/thread_runner.cpp @@ -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( + ctx_->path_, ctx_->cpath_, ctx_->step_, ctx_->function_->unpack(lua), args)); +} + +void ThreadRunner::exportAPI(sol::state_view& lua) { + + sol::usertype 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(lua); +} + +} // namespace effil \ No newline at end of file diff --git a/src/cpp/thread_runner.h b/src/cpp/thread_runner.h new file mode 100644 index 0000000..3be75d0 --- /dev/null +++ b/src/cpp/thread_runner.h @@ -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 { + 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 \ No newline at end of file diff --git a/src/cpp/threading.cpp b/src/cpp/threading.cpp index 235966e..253d97b 100644 --- a/src/cpp/threading.cpp +++ b/src/cpp/threading.cpp @@ -45,29 +45,22 @@ std::string statusToString(Status status) { return "unknown"; } -#if LUA_VERSION_NUM > 501 - int luaErrorHandler(lua_State* state) { luaL_traceback(state, state, nullptr, 1); const auto stacktrace = sol::stack::pop(state); thisThreadHandle->result().emplace_back(createStoredObject(stacktrace)); - throw Exception() << sol::stack::pop(state); + return 1; } const lua_CFunction luaErrorHandlerPtr = luaErrorHandler; -#else - -const lua_CFunction luaErrorHandlerPtr = nullptr; - -#endif // LUA_VERSION_NUM > 501 - void luaHook(lua_State*, lua_Debug*) { assert(thisThreadHandle); switch (thisThreadHandle->command()) { case Command::Run: break; case Command::Cancel: + thisThreadHandle->changeStatus(Status::Canceled); throw LuaHookStopException(); case Command::Pause: { thisThreadHandle->changeStatus(Status::Paused); @@ -75,10 +68,12 @@ void luaHook(lua_State*, lua_Debug*) { do { cmd = thisThreadHandle->waitForCommandChange(NO_TIMEOUT); } while(cmd != Command::Run && cmd != Command::Cancel); - if (cmd == Command::Run) + if (cmd == Command::Run) { thisThreadHandle->changeStatus(Status::Running); - else + } else { + thisThreadHandle->changeStatus(Status::Canceled); throw LuaHookStopException(); + } break; } } @@ -89,7 +84,7 @@ void luaHook(lua_State*, lua_Debug*) { ThreadHandle::ThreadHandle() : status_(Status::Running) , command_(Command::Run) - , lua_(std::make_unique(luaErrorHandlerPtr)) { + , lua_(std::make_unique()) { luaL_openlibs(*lua_); } @@ -126,9 +121,26 @@ void Thread::runThread(Thread thread, arguments.clear(); thread.ctx_->destroyLua(); }); - sol::function userFuncObj = function.loadFunction(thread.ctx_->lua()); - sol::function_result results = userFuncObj(std::move(arguments)); - (void)results; // just leave all returns on the stack + sol::protected_function userFuncObj = function.loadFunction(thread.ctx_->lua()); + + #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())); for (const auto& iter : args) { StoredObject store = createStoredObject(iter.get()); @@ -143,7 +155,7 @@ void Thread::runThread(Thread thread, thread.ctx_->changeStatus(Status::Completed); } catch (const LuaHookStopException&) { thread.ctx_->changeStatus(Status::Canceled); - } catch (const sol::error& err) { + } catch (const std::exception& err) { DEBUG("thread") << "Failed with msg: " << err.what() << std::endl; auto& returns = thread.ctx_->result(); returns.insert(returns.begin(), @@ -193,7 +205,10 @@ void Thread::initialize( ctx_->lua()["package"]["path"] = path; ctx_->lua()["package"]["cpath"] = cpath; - ctx_->lua().script("require 'effil'"); + try { + luaopen_effil(ctx_->lua()); + sol::stack::pop(ctx_->lua()); + } RETHROW_WITH_PREFIX("effil.thread"); if (step != 0) lua_sethook(ctx_->lua(), luaHook, LUA_MASKCOUNT, step); diff --git a/tests/lua/gc-stress.lua b/tests/lua/gc-stress.lua index f3a5d07..2a77b7b 100644 --- a/tests/lua/gc-stress.lua +++ b/tests/lua/gc-stress.lua @@ -45,6 +45,7 @@ test.gc_stress.regress_for_concurent_thread_creation = function () end end + test.gc_stress.regress_for_concurent_function_creation = function () local a = function() end local b = function() end diff --git a/tests/lua/thread.lua b/tests/lua/thread.lua index ff92c09..53aa7be 100644 --- a/tests/lua/thread.lua +++ b/tests/lua/thread.lua @@ -8,6 +8,28 @@ test.thread.hardware_threads = function() test.is_true(effil.hardware_threads() >= 0) 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 () local thread = effil.thread(function() print 'Effil is not that tower' @@ -395,13 +417,15 @@ test.thread.traceback = function() local status, err, trace = effil.thread(foo)():wait() print("status: ", status) print("error: ", err) - print("stacktrace: ", trace) + print("stacktrace:") + print(trace) test.equal(status, "failed") -- .lua:: test.is_not_nil(string.find(err, curr_file .. ":%d+: err msg")) test.is_not_nil(string.find(trace, ( [[stack traceback: +%%s%%[C%%]: in function 'error' %%s%s:%%d+: in function 'boom' %%s%s:%%d+: in function 'bar' %%s%s:%%d+: in function <%s:%%d+>]] @@ -409,4 +433,4 @@ test.thread.traceback = function() )) end -end -- LUA_VERSION > 51 \ No newline at end of file +end -- LUA_VERSION > 51 diff --git a/tests/lua/upvalues.lua b/tests/lua/upvalues.lua index 94f0376..077a7bd 100644 --- a/tests/lua/upvalues.lua +++ b/tests/lua/upvalues.lua @@ -40,7 +40,7 @@ test.upvalues.check_single_upvalue_p(function() return effil.thread(foo)() end, test.upvalues.check_invalid_coroutine = function() local obj = coroutine.create(foo) 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 ret:wait() end