Merge pull request #14 from loud-hound/extended_thread_api
Extend threading API
This commit is contained in:
commit
43dc94df8a
@ -31,7 +31,7 @@ set_target_properties(effil PROPERTIES COMPILE_FLAGS "${ENABLE_WARNINGS} ${GENER
|
|||||||
#----------
|
#----------
|
||||||
|
|
||||||
FILE(GLOB TEST_SOURCES tests/cpp/*.cpp tests/cpp/*.h)
|
FILE(GLOB TEST_SOURCES tests/cpp/*.cpp tests/cpp/*.h)
|
||||||
FILE(GLOB LUA_TEST_SOURCES tests/lua/*.lua)
|
FILE(GLOB LUA_TEST_SOURCES tests/lua/run_tests.lua)
|
||||||
set(GTEST_DIR libs/gtest/googletest)
|
set(GTEST_DIR libs/gtest/googletest)
|
||||||
|
|
||||||
include_directories(${GTEST_DIR}/include ${GTEST_DIR})
|
include_directories(${GTEST_DIR}/include ${GTEST_DIR})
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static sol::object createThread(sol::this_state lua, sol::function func, const sol::variadic_args &args) noexcept {
|
sol::object createThreadFactory(sol::this_state lua, const sol::function& func) {
|
||||||
return sol::make_object(lua, std::make_unique<effil::LuaThread>(func, args));
|
return sol::make_object(lua, std::make_unique<effil::ThreadFactory>(func));
|
||||||
}
|
}
|
||||||
|
|
||||||
static sol::object createShare(sol::this_state lua) noexcept {
|
sol::object createShare(sol::this_state lua) {
|
||||||
return sol::make_object(lua, std::make_unique<effil::SharedTable>());
|
return sol::make_object(lua, std::make_unique<effil::SharedTable>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,8 +19,12 @@ extern "C" int luaopen_libeffil(lua_State *L) {
|
|||||||
sol::state_view lua(L);
|
sol::state_view lua(L);
|
||||||
effil::LuaThread::getUserType(lua);
|
effil::LuaThread::getUserType(lua);
|
||||||
effil::SharedTable::getUserType(lua);
|
effil::SharedTable::getUserType(lua);
|
||||||
|
effil::ThreadFactory::getUserType(lua);
|
||||||
sol::table public_api = lua.create_table_with(
|
sol::table public_api = lua.create_table_with(
|
||||||
"thread", createThread,
|
"thread", createThreadFactory,
|
||||||
|
"thread_id", effil::threadId,
|
||||||
|
"sleep", effil::sleep,
|
||||||
|
"yield", effil::yield,
|
||||||
"share", createShare
|
"share", createShare
|
||||||
);
|
);
|
||||||
sol::stack::push(lua, public_api);
|
sol::stack::push(lua, public_api);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace effil {
|
|||||||
|
|
||||||
sol::object SharedTable::getUserType(sol::state_view &lua) noexcept {
|
sol::object SharedTable::getUserType(sol::state_view &lua) noexcept {
|
||||||
static sol::usertype<SharedTable> type(
|
static sol::usertype<SharedTable> type(
|
||||||
sol::call_construction(), sol::default_constructor,
|
"new", sol::no_constructor,
|
||||||
sol::meta_function::new_index, &SharedTable::luaSet,
|
sol::meta_function::new_index, &SharedTable::luaSet,
|
||||||
sol::meta_function::index, &SharedTable::luaGet,
|
sol::meta_function::index, &SharedTable::luaGet,
|
||||||
sol::meta_function::length, &SharedTable::size
|
sol::meta_function::length, &SharedTable::size
|
||||||
@ -34,8 +34,8 @@ void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_objec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_state& state) const noexcept {
|
sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_state& state) const {
|
||||||
assert(key.valid());
|
ASSERT(key.valid());
|
||||||
|
|
||||||
StoredObject cppKey(key);
|
StoredObject cppKey(key);
|
||||||
std::lock_guard<SpinMutex> g(lock_);
|
std::lock_guard<SpinMutex> g(lock_);
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public:
|
|||||||
|
|
||||||
public: // lua bindings
|
public: // lua bindings
|
||||||
void luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue);
|
void luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue);
|
||||||
sol::object luaGet(const sol::stack_object& key, const sol::this_state& state) const noexcept;
|
sol::object luaGet(const sol::stack_object& key, const sol::this_state& state) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable SpinMutex lock_;
|
mutable SpinMutex lock_;
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public:
|
|||||||
return std::hash<StoredType>()(data_);
|
return std::hash<StoredType>()(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object unpack(sol::this_state state) const noexcept final {
|
sol::object unpack(sol::this_state state) const final {
|
||||||
return sol::make_object(state, data_);
|
return sol::make_object(state, data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ public:
|
|||||||
return std::hash<std::string>()(function_);
|
return std::hash<std::string>()(function_);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object unpack(sol::this_state state) const noexcept final {
|
sol::object unpack(sol::this_state state) const final {
|
||||||
sol::state_view lua((lua_State*)state);
|
sol::state_view lua((lua_State*)state);
|
||||||
sol::function loader = lua["loadstring"];
|
sol::function loader = lua["loadstring"];
|
||||||
ASSERT(loader.valid());
|
ASSERT(loader.valid());
|
||||||
@ -77,9 +77,9 @@ private:
|
|||||||
// TODO: Trick is - sol::object has only operator==:/
|
// TODO: Trick is - sol::object has only operator==:/
|
||||||
typedef std::vector<std::pair<sol::object, SharedTable*>> SolTableToShared;
|
typedef std::vector<std::pair<sol::object, SharedTable*>> SolTableToShared;
|
||||||
|
|
||||||
void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept;
|
void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited);
|
||||||
|
|
||||||
StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) noexcept {
|
StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) {
|
||||||
if (luaObject.get_type() == sol::type::table) {
|
if (luaObject.get_type() == sol::type::table) {
|
||||||
sol::table luaTable = luaObject;
|
sol::table luaTable = luaObject;
|
||||||
auto comparator = [&luaTable](const std::pair<sol::table, SharedTable*>& element){
|
auto comparator = [&luaTable](const std::pair<sol::table, SharedTable*>& element){
|
||||||
@ -100,7 +100,7 @@ StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept {
|
void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) {
|
||||||
for(auto& row : luaTable) {
|
for(auto& row : luaTable) {
|
||||||
target->set(makeStoredObject(row.first, visited), makeStoredObject(row.second, visited));
|
target->set(makeStoredObject(row.first, visited), makeStoredObject(row.second, visited));
|
||||||
}
|
}
|
||||||
@ -150,11 +150,11 @@ StoredObject::StoredObject(SharedTable* table) noexcept
|
|||||||
: data_(new PrimitiveHolder<SharedTable*>(table)) {
|
: data_(new PrimitiveHolder<SharedTable*>(table)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredObject::StoredObject(const sol::object& object) noexcept
|
StoredObject::StoredObject(const sol::object& object)
|
||||||
: data_(fromSolObject(object)) {
|
: data_(fromSolObject(object)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredObject::StoredObject(const sol::stack_object& object) noexcept
|
StoredObject::StoredObject(const sol::stack_object& object)
|
||||||
: data_(fromSolObject(object)) {
|
: data_(fromSolObject(object)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ std::size_t StoredObject::hash() const noexcept {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object StoredObject::unpack(sol::this_state state) const noexcept {
|
sol::object StoredObject::unpack(sol::this_state state) const {
|
||||||
if (data_)
|
if (data_)
|
||||||
return data_->unpack(state);
|
return data_->unpack(state);
|
||||||
else
|
else
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual std::size_t hash() const noexcept = 0;
|
virtual std::size_t hash() const noexcept = 0;
|
||||||
virtual sol::object unpack(sol::this_state state) const noexcept = 0;
|
virtual sol::object unpack(sol::this_state state) const = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BaseHolder(const BaseHolder&) = delete;
|
BaseHolder(const BaseHolder&) = delete;
|
||||||
@ -31,12 +31,12 @@ public:
|
|||||||
StoredObject() = default;
|
StoredObject() = default;
|
||||||
StoredObject(StoredObject&& init) noexcept;
|
StoredObject(StoredObject&& init) noexcept;
|
||||||
StoredObject(SharedTable*) noexcept;
|
StoredObject(SharedTable*) noexcept;
|
||||||
StoredObject(const sol::object&) noexcept;
|
StoredObject(const sol::object&);
|
||||||
StoredObject(const sol::stack_object&) noexcept;
|
StoredObject(const sol::stack_object&);
|
||||||
|
|
||||||
operator bool() const noexcept;
|
operator bool() const noexcept;
|
||||||
std::size_t hash() const noexcept;
|
std::size_t hash() const noexcept;
|
||||||
sol::object unpack(sol::this_state state) const noexcept;
|
sol::object unpack(sol::this_state state) const;
|
||||||
StoredObject& operator=(StoredObject&& o) noexcept;
|
StoredObject& operator=(StoredObject&& o) noexcept;
|
||||||
bool operator==(const StoredObject& o) const noexcept;
|
bool operator==(const StoredObject& o) const noexcept;
|
||||||
|
|
||||||
|
|||||||
@ -1,76 +1,258 @@
|
|||||||
#include "threading.h"
|
#include "threading.h"
|
||||||
|
#include "stored-object.h"
|
||||||
|
|
||||||
namespace effil {
|
namespace effil {
|
||||||
|
|
||||||
LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept {
|
class LuaHookStopException : public std::exception {};
|
||||||
// 1. Dump function to string
|
|
||||||
sol::state_view lua(function.lua_state());
|
|
||||||
str_function_ = lua["string"]["dump"](function);
|
|
||||||
|
|
||||||
// 2. Create new state
|
std::string threadId() noexcept
|
||||||
p_state_.reset(new sol::state);
|
{
|
||||||
ASSERT(p_state_.get() != NULL);
|
|
||||||
p_state_->open_libraries(
|
|
||||||
sol::lib::base, sol::lib::string,
|
|
||||||
sol::lib::package, sol::lib::io, sol::lib::os
|
|
||||||
);
|
|
||||||
getUserType(*p_state_);
|
|
||||||
effil::SharedTable::getUserType(*p_state_);
|
|
||||||
|
|
||||||
// 3. Save parameters
|
|
||||||
storeArgs(args);
|
|
||||||
|
|
||||||
// 4. Run thread
|
|
||||||
p_thread_.reset(new std::thread(&LuaThread::work, this));
|
|
||||||
ASSERT(p_thread_.get() != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LuaThread::storeArgs(const sol::variadic_args &args) noexcept {
|
|
||||||
p_arguments_ = std::make_shared<std::vector<sol::object>>();
|
|
||||||
for(auto iter = args.begin(); iter != args.end(); iter++) {
|
|
||||||
effil::StoredObject store(iter->get<sol::object>());
|
|
||||||
p_arguments_->push_back(store.unpack(sol::this_state{p_state_->lua_state()}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LuaThread::join() noexcept {
|
|
||||||
if (p_thread_.get()) {
|
|
||||||
p_thread_->join();
|
|
||||||
p_thread_.reset();
|
|
||||||
}
|
|
||||||
if (p_arguments_.get())
|
|
||||||
p_arguments_.reset();
|
|
||||||
if (p_state_.get())
|
|
||||||
p_state_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LuaThread::detach() noexcept {
|
|
||||||
p_thread_->detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LuaThread::work() noexcept {
|
|
||||||
ASSERT(p_state_.get() && p_arguments_.get()) << "invalid thread Lua state\n";
|
|
||||||
|
|
||||||
std::string func_owner = std::move(str_function_);
|
|
||||||
std::shared_ptr<sol::state> state_owner = p_state_;
|
|
||||||
std::shared_ptr<std::vector<sol::object>> arguments_owner = p_arguments_;
|
|
||||||
sol::function_result func = (*state_owner)["loadstring"](func_owner);
|
|
||||||
func.get<sol::function>()(sol::as_args(*arguments_owner));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string LuaThread::threadId() noexcept {
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::this_thread::get_id();
|
ss << std::this_thread::get_id();
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void yield() noexcept
|
||||||
|
{
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep(int64_t time, sol::optional<std::string> period) noexcept
|
||||||
|
{
|
||||||
|
std::string metric = period ? period.value() : "s";
|
||||||
|
if (metric == "ms")
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(time));
|
||||||
|
else if (metric == "s")
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(time));
|
||||||
|
else if (metric == "m")
|
||||||
|
std::this_thread::sleep_for(std::chrono::minutes(time));
|
||||||
|
else
|
||||||
|
throw sol::error("invalid time identificator: " + metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local LuaThread::ThreadData* LuaThread::pThreadLocalData = NULL;
|
||||||
|
|
||||||
|
// class LuaThread
|
||||||
|
|
||||||
|
LuaThread::LuaThread(std::shared_ptr<ThreadData> threadData, const std::string& function, const sol::variadic_args& args) {
|
||||||
|
pThreadData_ = threadData;
|
||||||
|
ASSERT(pThreadData_.get());
|
||||||
|
pThreadData_->command = ThreadCommand::Nothing;
|
||||||
|
pThreadData_->status = ThreadStatus::Running;
|
||||||
|
|
||||||
|
std::vector<sol::object> arguments;
|
||||||
|
for(const auto& iter: args) {
|
||||||
|
StoredObject store(iter.get<sol::object>());
|
||||||
|
arguments.push_back(store.unpack(sol::this_state{pThreadData_->luaState}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pThread_.reset(new std::thread(&LuaThread::work, pThreadData_, function, std::move(arguments)));
|
||||||
|
ASSERT(pThread_.get() != nullptr);
|
||||||
|
pThread_->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaThread::luaHook(lua_State*, lua_Debug*)
|
||||||
|
{
|
||||||
|
if (pThreadLocalData)
|
||||||
|
{
|
||||||
|
switch (pThreadLocalData->command)
|
||||||
|
{
|
||||||
|
case ThreadCommand::Pause:
|
||||||
|
{
|
||||||
|
pThreadLocalData->status = ThreadStatus::Paused;
|
||||||
|
ThreadCommand cmd = pThreadLocalData->command;
|
||||||
|
while (cmd == ThreadCommand::Pause) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
cmd = pThreadLocalData->command;
|
||||||
|
}
|
||||||
|
assert(cmd != ThreadCommand::Nothing);
|
||||||
|
if (cmd == ThreadCommand::Resume)
|
||||||
|
{
|
||||||
|
pThreadLocalData->status = ThreadStatus::Running;
|
||||||
|
break; // Just go out of the function
|
||||||
|
}
|
||||||
|
else { /* HOOK_STOP - do nothing and go to the next case */}
|
||||||
|
}
|
||||||
|
case ThreadCommand::Cancel:
|
||||||
|
throw LuaHookStopException();
|
||||||
|
default:
|
||||||
|
case ThreadCommand::Nothing:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaThread::work(std::shared_ptr<ThreadData> threadData, const std::string strFunction, std::vector<sol::object>&& arguments) noexcept {
|
||||||
|
try {
|
||||||
|
pThreadLocalData = threadData.get();
|
||||||
|
ASSERT(threadData.get()) << "invalid internal thread state\n";
|
||||||
|
const sol::object& stringLoader = threadData->luaState["loadstring"];
|
||||||
|
ASSERT(stringLoader.valid() && stringLoader.get_type() == sol::type::function);
|
||||||
|
sol::function userFuncObj = static_cast<const sol::function&>(stringLoader)(strFunction);
|
||||||
|
sol::function_result results = userFuncObj(sol::as_args(arguments));
|
||||||
|
(void)results; // TODO: try to avoid use of useless sol::function_result here
|
||||||
|
sol::variadic_args args(threadData->luaState, -lua_gettop(threadData->luaState));
|
||||||
|
for(const auto& iter: args) {
|
||||||
|
StoredObject store(iter.get<sol::object>());
|
||||||
|
threadData->results.emplace_back(std::move(store));
|
||||||
|
}
|
||||||
|
threadData->status = ThreadStatus::Completed;
|
||||||
|
}
|
||||||
|
catch (const LuaHookStopException&) {
|
||||||
|
threadData->status = ThreadStatus::Canceled;
|
||||||
|
}
|
||||||
|
catch (const sol::error& err) {
|
||||||
|
threadData->status = ThreadStatus::Failed;
|
||||||
|
sol::stack::push(threadData->luaState, err.what());
|
||||||
|
StoredObject store(sol::stack::pop<sol::object>(threadData->luaState));
|
||||||
|
threadData->results.emplace_back(std::move(store));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaThread::cancel() noexcept
|
||||||
|
{
|
||||||
|
pThreadData_->command = ThreadCommand::Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaThread::pause() noexcept
|
||||||
|
{
|
||||||
|
pThreadData_->command = ThreadCommand::Pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaThread::resume() noexcept
|
||||||
|
{
|
||||||
|
pThreadData_->command = ThreadCommand::Resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<sol::object, sol::table> LuaThread::wait(sol::this_state state) const noexcept
|
||||||
|
{
|
||||||
|
|
||||||
|
ThreadStatus stat = pThreadData_->status;
|
||||||
|
while (stat == ThreadStatus::Running) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
stat = pThreadData_->status;
|
||||||
|
}
|
||||||
|
sol::table returns = sol::state_view(state).create_table();
|
||||||
|
if (stat == ThreadStatus::Completed)
|
||||||
|
{
|
||||||
|
for (const StoredObject& obj: pThreadData_->results)
|
||||||
|
{
|
||||||
|
returns.add(obj.unpack(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_tuple(sol::make_object(state, threadStatusToString(stat)), std::move(returns));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LuaThread::threadStatusToString(ThreadStatus stat) const noexcept
|
||||||
|
{
|
||||||
|
switch(stat)
|
||||||
|
{
|
||||||
|
case ThreadStatus::Running: return "running";
|
||||||
|
case ThreadStatus::Paused: return "paused";
|
||||||
|
case ThreadStatus::Canceled: return "canceled";
|
||||||
|
case ThreadStatus::Completed: return "completed";
|
||||||
|
case ThreadStatus::Failed: return "failed";
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LuaThread::status() const noexcept
|
||||||
|
{
|
||||||
|
return threadStatusToString(pThreadData_->status);
|
||||||
|
}
|
||||||
|
|
||||||
sol::object LuaThread::getUserType(sol::state_view &lua) noexcept
|
sol::object LuaThread::getUserType(sol::state_view &lua) noexcept
|
||||||
{
|
{
|
||||||
static sol::usertype<LuaThread> type(
|
static sol::usertype<LuaThread> type(
|
||||||
sol::call_construction(), sol::constructors<sol::types<sol::function, sol::variadic_args>>(),
|
"new", sol::no_constructor,
|
||||||
"join", &LuaThread::join,
|
"cancel", &LuaThread::cancel,
|
||||||
"detach", &LuaThread::detach,
|
"pause", &LuaThread::pause,
|
||||||
"thread_id", &LuaThread::threadId
|
"resume", &LuaThread::resume,
|
||||||
|
"status", &LuaThread::status,
|
||||||
|
"wait", &LuaThread::wait
|
||||||
|
);
|
||||||
|
sol::stack::push(lua, type);
|
||||||
|
return sol::stack::pop<sol::object>(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
// class ThreadFactory
|
||||||
|
|
||||||
|
ThreadFactory::ThreadFactory(const sol::function& func) : stepwise_(false), step_(100U) {
|
||||||
|
sol::state_view lua(func.lua_state());
|
||||||
|
const sol::object& dumper = lua["string"]["dump"];
|
||||||
|
ASSERT(dumper.valid() && dumper.get_type() == sol::type::function)
|
||||||
|
<< "Unable to get string.dump()";
|
||||||
|
strFunction_ = static_cast<const sol::function&>(dumper)(func);
|
||||||
|
|
||||||
|
// Inherit all pathes from parent state by default
|
||||||
|
packagePath_ = lua["package"]["path"].get<std::string>();
|
||||||
|
packageCPath_ = lua["package"]["cpath"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<LuaThread> ThreadFactory::runThread(const sol::variadic_args& args) {
|
||||||
|
std::shared_ptr<LuaThread::ThreadData> threadData(new LuaThread::ThreadData);
|
||||||
|
ASSERT(threadData.get());
|
||||||
|
threadData->luaState.open_libraries(
|
||||||
|
sol::lib::base, sol::lib::string,
|
||||||
|
sol::lib::package, sol::lib::io, sol::lib::os
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stepwise_)
|
||||||
|
lua_sethook(threadData->luaState, LuaThread::luaHook, LUA_MASKCOUNT, step_);
|
||||||
|
|
||||||
|
threadData->luaState["package"]["path"] = packagePath_;
|
||||||
|
threadData->luaState["package"]["cpath"] = packageCPath_;
|
||||||
|
|
||||||
|
// Inherit all pathes from parent state
|
||||||
|
effil::LuaThread::getUserType(threadData->luaState);
|
||||||
|
effil::ThreadFactory::getUserType(threadData->luaState);
|
||||||
|
effil::SharedTable::getUserType(threadData->luaState);
|
||||||
|
|
||||||
|
return std::make_unique<LuaThread>(threadData, strFunction_, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ThreadFactory::stepwise(const sol::optional<bool>& value)
|
||||||
|
{
|
||||||
|
bool ret = stepwise_ ;
|
||||||
|
if (value)
|
||||||
|
stepwise_ = value.value();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ThreadFactory::step(const sol::optional<unsigned int>& value)
|
||||||
|
{
|
||||||
|
bool ret = step_;
|
||||||
|
if (value)
|
||||||
|
step_ = value.value();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ThreadFactory::packagePath(const sol::optional<std::string>& value)
|
||||||
|
{
|
||||||
|
std::string& ret = packagePath_;
|
||||||
|
if (value)
|
||||||
|
packagePath_ = value.value();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ThreadFactory::packageCPath(const sol::optional<std::string>& value)
|
||||||
|
{
|
||||||
|
std::string& ret = packageCPath_;
|
||||||
|
if (value)
|
||||||
|
packageCPath_ = value.value();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::object ThreadFactory::getUserType(sol::state_view &lua) noexcept
|
||||||
|
{
|
||||||
|
static sol::usertype<ThreadFactory> type(
|
||||||
|
"new", sol::no_constructor,
|
||||||
|
sol::meta_function::call, &ThreadFactory::runThread,
|
||||||
|
"stepwise", &ThreadFactory::stepwise,
|
||||||
|
"step", &ThreadFactory::step,
|
||||||
|
"package_path", &ThreadFactory::packagePath,
|
||||||
|
"package_cpath", &ThreadFactory::packageCPath
|
||||||
);
|
);
|
||||||
sol::stack::push(lua, type);
|
sol::stack::push(lua, type);
|
||||||
return sol::stack::pop<sol::object>(lua);
|
return sol::stack::pop<sol::object>(lua);
|
||||||
|
|||||||
@ -8,24 +8,78 @@
|
|||||||
|
|
||||||
namespace effil {
|
namespace effil {
|
||||||
|
|
||||||
|
// Lua this thread API
|
||||||
|
std::string threadId() noexcept;
|
||||||
|
void yield() noexcept;
|
||||||
|
void sleep(int64_t, sol::optional<std::string>) noexcept;
|
||||||
|
|
||||||
class LuaThread {
|
class LuaThread {
|
||||||
public:
|
public:
|
||||||
LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept;
|
enum class ThreadStatus {
|
||||||
virtual ~LuaThread() noexcept = default;
|
Running,
|
||||||
void join() noexcept;
|
Paused,
|
||||||
void detach() noexcept;
|
Canceled,
|
||||||
|
Completed,
|
||||||
|
Failed,
|
||||||
|
};
|
||||||
|
|
||||||
static std::string threadId() noexcept;
|
enum class ThreadCommand
|
||||||
|
{
|
||||||
|
Nothing = 1,
|
||||||
|
Cancel,
|
||||||
|
Pause,
|
||||||
|
Resume,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadData{
|
||||||
|
sol::state luaState;
|
||||||
|
std::atomic<ThreadStatus> status;
|
||||||
|
std::atomic<ThreadCommand> command;
|
||||||
|
std::vector<StoredObject> results;
|
||||||
|
};
|
||||||
|
|
||||||
|
LuaThread(std::shared_ptr<ThreadData> threadData, const std::string& function, const sol::variadic_args& args);
|
||||||
static sol::object getUserType(sol::state_view &lua) noexcept;
|
static sol::object getUserType(sol::state_view &lua) noexcept;
|
||||||
|
static void luaHook(lua_State*, lua_Debug*);
|
||||||
|
|
||||||
|
/* Public lua methods*/
|
||||||
|
void cancel() noexcept;
|
||||||
|
void pause() noexcept;
|
||||||
|
void resume() noexcept;
|
||||||
|
std::string status() const noexcept;
|
||||||
|
std::tuple<sol::object, sol::table> wait(sol::this_state state) const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void work() noexcept;
|
LuaThread(const LuaThread&) = delete;
|
||||||
void storeArgs(const sol::variadic_args &args) noexcept;
|
LuaThread& operator=(const LuaThread&) = delete;
|
||||||
|
|
||||||
std::string str_function_;
|
std::string threadStatusToString(ThreadStatus stat) const noexcept;
|
||||||
std::shared_ptr<sol::state> p_state_;
|
static void work(std::shared_ptr<ThreadData> threadData, const std::string strFunction, std::vector<sol::object>&& arguments) noexcept;
|
||||||
std::shared_ptr<std::thread> p_thread_;
|
|
||||||
std::shared_ptr<std::vector<sol::object>> p_arguments_;
|
std::shared_ptr<ThreadData> pThreadData_;
|
||||||
|
std::shared_ptr<std::thread> pThread_;
|
||||||
|
|
||||||
|
static thread_local LuaThread::ThreadData* pThreadLocalData;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ThreadFactory {
|
||||||
|
public:
|
||||||
|
ThreadFactory(const sol::function& func);
|
||||||
|
static sol::object getUserType(sol::state_view &lua) noexcept;
|
||||||
|
|
||||||
|
/* Public lua methods*/
|
||||||
|
std::unique_ptr<LuaThread> runThread(const sol::variadic_args& args);
|
||||||
|
bool stepwise(const sol::optional<bool>&);
|
||||||
|
unsigned int step(const sol::optional<unsigned int>&);
|
||||||
|
std::string packagePath(const sol::optional<std::string>&);
|
||||||
|
std::string packageCPath(const sol::optional<std::string>&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string strFunction_;
|
||||||
|
bool stepwise_;
|
||||||
|
unsigned int step_;
|
||||||
|
std::string packagePath_;
|
||||||
|
std::string packageCPath_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // effil
|
} // effil
|
||||||
|
|||||||
@ -1,11 +1,57 @@
|
|||||||
#!/usr/bin/env lua
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
-- TODO: remove hardcode
|
-- TODO: remove hardcode
|
||||||
package.path = package.path .. ";../libs/luaunit/?.lua"
|
package.path = package.path .. ";../libs/luaunit/?.lua;../tests/lua/?.lua"
|
||||||
package.cpath = package.cpath .. ";./?.so;./?.dylib"
|
package.cpath = package.cpath .. ";./?.so;./?.dylib"
|
||||||
|
|
||||||
test = require "luaunit"
|
test = require "luaunit"
|
||||||
|
|
||||||
|
function log(...)
|
||||||
|
local msg = '@\t\t' .. os.date('%Y-%m-%d %H:%M:%S ',os.time())
|
||||||
|
for _, val in ipairs({...}) do
|
||||||
|
msg = msg .. tostring(val) .. ' '
|
||||||
|
end
|
||||||
|
io.write(msg .. '\n')
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function wait(timeInSec, condition, silent)
|
||||||
|
if not silent then
|
||||||
|
log("Start waiting for " .. tostring(timeInSec) .. "sec...")
|
||||||
|
end
|
||||||
|
local result = false
|
||||||
|
local startTime = os.time()
|
||||||
|
while ( (os.time() - startTime) <= timeInSec) do
|
||||||
|
if condition ~= nil then
|
||||||
|
if type(condition) == 'function' then
|
||||||
|
if condition() then
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if condition then
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not silent then
|
||||||
|
log "Give up"
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function sleep(timeInSec, silent)
|
||||||
|
if not silent then
|
||||||
|
log("Start sleep for " .. tostring(timeInSec) .. "sec...")
|
||||||
|
end
|
||||||
|
wait(timeInSec, nil, true)
|
||||||
|
if not silent then
|
||||||
|
log "Wake up"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
-- Hack input arguments to make tests verbose by default
|
-- Hack input arguments to make tests verbose by default
|
||||||
local found = false
|
local found = false
|
||||||
@ -21,37 +67,6 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function log(...)
|
|
||||||
local msg = '@\t\t' .. os.date('%Y-%m-%d %H:%M:%S ',os.time())
|
|
||||||
for _, val in ipairs({...}) do
|
|
||||||
msg = msg .. tostring(val) .. ' '
|
|
||||||
end
|
|
||||||
io.write(msg .. '\n')
|
|
||||||
io.flush()
|
|
||||||
end
|
|
||||||
|
|
||||||
function wait(timeInSec, condition)
|
|
||||||
local startTime = os.time()
|
|
||||||
while ( (os.time() - startTime) <= timeInSec) do
|
|
||||||
if condition ~= nil then
|
|
||||||
if type(condition) == 'function' then
|
|
||||||
if condition() then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if condition then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function sleep(timeInSec)
|
|
||||||
wait(timeInMsec, nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
-- TESTS --
|
-- TESTS --
|
||||||
-----------
|
-----------
|
||||||
|
|||||||
@ -1,52 +1,220 @@
|
|||||||
TestSmoke = {}
|
TestSmoke = {}
|
||||||
|
|
||||||
function TestSmoke:testGeneralWorkability()
|
function TestSmoke:tearDown()
|
||||||
|
log "TearDown() collect garbage"
|
||||||
|
collectgarbage()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testSharedTableTypes()
|
||||||
local effil = require('libeffil')
|
local effil = require('libeffil')
|
||||||
local share = effil.share()
|
local share = effil.share()
|
||||||
|
|
||||||
share["number"] = 100500
|
share["number"] = 100500
|
||||||
share["string"] = "string value"
|
share["string"] = "string value"
|
||||||
share["bool"] = true
|
share["bool"] = true
|
||||||
|
share["function"] = function(left, right) return left + right end
|
||||||
|
|
||||||
log "Start thread"
|
local thread_factory = effil.thread(
|
||||||
local thread = effil.thread(
|
|
||||||
function(share)
|
function(share)
|
||||||
share["child.number"] = share["number"]
|
share["child.number"] = share["number"]
|
||||||
share["child.string"] = share["string"]
|
share["child.string"] = share["string"]
|
||||||
share["child.bool"] = share["bool"]
|
share["child.bool"] = share["bool"]
|
||||||
end,
|
share["child.function"] = share["function"](11,45)
|
||||||
share
|
end
|
||||||
)
|
)
|
||||||
log "Join thread"
|
local thread = thread_factory(share)
|
||||||
thread:join()
|
thread:wait()
|
||||||
|
|
||||||
log "Check values"
|
log "Check values"
|
||||||
test.assertEquals(share["child.number"], share["number"],
|
test.assertEquals(share["child.number"], share["number"])
|
||||||
"'number' fields are not equal")
|
test.assertEquals(share["child.string"], share["string"])
|
||||||
test.assertEquals(share["child.string"], share["string"],
|
test.assertEquals(share["child.bool"], share["bool"])
|
||||||
"'string' fields are not equal")
|
test.assertEquals(share["child.function"], share["function"](11,45))
|
||||||
test.assertEquals(share["child.bool"], share["bool"],
|
|
||||||
"'bool' fields are not equal")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TestSmoke:testDetach()
|
function TestSmoke:testThreadCancel()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
local thread_runner = effil.thread(
|
||||||
|
function()
|
||||||
|
local startTime = os.time()
|
||||||
|
while ( (os.time() - startTime) <= 10) do --[[ Just sleep ]] end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
test.assertFalse(thread_runner:stepwise(true))
|
||||||
|
local thread = thread_runner()
|
||||||
|
sleep(2) -- let thread starts working
|
||||||
|
thread:cancel()
|
||||||
|
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() ~= 'running' end))
|
||||||
|
test.assertEquals(thread:status(), 'canceled')
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testThreadPauseAndResume()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
local data = effil.share()
|
||||||
|
data.value = 0
|
||||||
|
local thread_runner = effil.thread(
|
||||||
|
function(data)
|
||||||
|
while true do
|
||||||
|
data.value = data.value + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
test.assertFalse(thread_runner:stepwise(true))
|
||||||
|
|
||||||
|
local thread = thread_runner(data)
|
||||||
|
test.assertTrue(wait(2, function() return data.value > 100 end))
|
||||||
|
thread:pause()
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() == "paused" end))
|
||||||
|
local savedValue = data.value
|
||||||
|
sleep(3)
|
||||||
|
test.assertEquals(data.value, savedValue)
|
||||||
|
|
||||||
|
thread:resume()
|
||||||
|
test.assertTrue(wait(5, function() return (data.value - savedValue) > 100 end))
|
||||||
|
thread:cancel()
|
||||||
|
thread:wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testThreadPauseAndStop()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
log "Create thread"
|
||||||
|
local data = effil.share()
|
||||||
|
data.value = 0
|
||||||
|
local thread_runner = effil.thread(
|
||||||
|
function(data)
|
||||||
|
while true do
|
||||||
|
data.value = data.value + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
test.assertFalse(thread_runner:stepwise(true))
|
||||||
|
|
||||||
|
local thread = thread_runner(data)
|
||||||
|
test.assertTrue(wait(2, function() return data.value > 100 end))
|
||||||
|
thread:pause()
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() == "paused" end))
|
||||||
|
local savedValue = data.value
|
||||||
|
sleep(3)
|
||||||
|
test.assertEquals(data.value, savedValue)
|
||||||
|
|
||||||
|
thread:cancel()
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() == "canceled" end))
|
||||||
|
thread:wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testThreadPauseAndStop()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
log "Create thread"
|
||||||
|
local data = effil.share()
|
||||||
|
data.value = 0
|
||||||
|
local thread_runner = effil.thread(
|
||||||
|
function(data)
|
||||||
|
while true do
|
||||||
|
data.value = data.value + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
test.assertFalse(thread_runner:stepwise(true))
|
||||||
|
|
||||||
|
local thread = thread_runner(data)
|
||||||
|
test.assertTrue(wait(2, function() return data.value > 100 end))
|
||||||
|
thread:pause()
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() == "paused" end))
|
||||||
|
local savedValue = data.value
|
||||||
|
sleep(3)
|
||||||
|
test.assertEquals(data.value, savedValue)
|
||||||
|
|
||||||
|
thread:cancel()
|
||||||
|
test.assertTrue(wait(2, function() return thread:status() == "canceled" end))
|
||||||
|
thread:wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testRecursiveTables()
|
||||||
local effil = require('libeffil')
|
local effil = require('libeffil')
|
||||||
local share = effil.share()
|
local share = effil.share()
|
||||||
|
|
||||||
share["finished"] = false
|
local magic_number = 42
|
||||||
log "Start thread"
|
share["subtable1"] = effil.share()
|
||||||
local thread = effil.thread(
|
share["subtable1"]["subtable1"] = effil.share()
|
||||||
function(share)
|
share["subtable1"]["subtable2"] = share["subtable1"]["subtable1"]
|
||||||
local startTime = os.time()
|
share["subtable2"] = share["subtable1"]["subtable1"]
|
||||||
while ( (os.time() - startTime) <= 3) do --[[ Like we are working 3sec ... ]] end
|
share["magic_number"] = magic_number
|
||||||
share["finished"] = true
|
|
||||||
end,
|
|
||||||
share
|
|
||||||
)
|
|
||||||
log "Detach thread"
|
|
||||||
thread:detach()
|
|
||||||
|
|
||||||
log "Waiting for thread completion..."
|
local thread_factory = effil.thread(
|
||||||
test.assertEquals(wait(4, function() return share["finished"] end) , true)
|
function(share)
|
||||||
log "Stop waiting"
|
share["subtable1"]["subtable1"]["magic_number"] = share["magic_number"]
|
||||||
|
share["magic_number"] = nil
|
||||||
|
end
|
||||||
|
)
|
||||||
|
local thread = thread_factory(share)
|
||||||
|
thread:wait()
|
||||||
|
|
||||||
|
log "Check values"
|
||||||
|
test.assertEquals(share["subtable1"]["subtable1"]["magic_number"], magic_number)
|
||||||
|
test.assertEquals(share["subtable1"]["subtable2"]["magic_number"], magic_number)
|
||||||
|
test.assertEquals(share["subtable2"]["magic_number"], magic_number)
|
||||||
|
test.assertEquals(share["magic_number"], nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testThisThreadFunctions()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
local share = effil.share()
|
||||||
|
|
||||||
|
local thread_factory = effil.thread(
|
||||||
|
function(share)
|
||||||
|
share["child.id"] = require('libeffil').thread_id()
|
||||||
|
end
|
||||||
|
)
|
||||||
|
local thread = thread_factory(share)
|
||||||
|
thread:wait()
|
||||||
|
|
||||||
|
log "Check values"
|
||||||
|
test.assertString(share["child.id"])
|
||||||
|
test.assertNumber(tonumber(share["child.id"]))
|
||||||
|
test.assertNotEquals(share["child.id"], effil.thread_id())
|
||||||
|
effil.yield() -- just call it
|
||||||
|
|
||||||
|
local function check_time(real_time, use_time, metric)
|
||||||
|
local start_time = os.time()
|
||||||
|
effil.sleep(use_time, metric)
|
||||||
|
test.assertAlmostEquals(os.time(), start_time + real_time, 1)
|
||||||
|
end
|
||||||
|
check_time(4, 4, nil) -- seconds by default
|
||||||
|
check_time(4, 4, 's')
|
||||||
|
check_time(4, 4000, 'ms')
|
||||||
|
check_time(60, 1, 'm')
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestSmoke:testCheckThreadReturns()
|
||||||
|
local effil = require('libeffil')
|
||||||
|
local share = effil.share()
|
||||||
|
share.value = "some value"
|
||||||
|
|
||||||
|
local thread_factory = effil.thread(
|
||||||
|
function(share)
|
||||||
|
return 100500, "string value", true, share, function(a,b) return a + b end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
local thread = thread_factory(share)
|
||||||
|
local status, returns = thread:wait()
|
||||||
|
|
||||||
|
log "Check values"
|
||||||
|
test.assertEquals(status, "completed")
|
||||||
|
|
||||||
|
test.assertNumber(returns[1])
|
||||||
|
test.assertEquals(returns[1], 100500)
|
||||||
|
|
||||||
|
test.assertString(returns[2])
|
||||||
|
test.assertEquals(returns[2], "string value")
|
||||||
|
|
||||||
|
test.assertBoolean(returns[3])
|
||||||
|
test.assertTrue(returns[3])
|
||||||
|
|
||||||
|
test.assertUserdata(returns[4])
|
||||||
|
test.assertEquals(returns[4].value, share.value)
|
||||||
|
|
||||||
|
test.assertFunction(returns[5])
|
||||||
|
test.assertEquals(returns[5](11, 89), 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user