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 LUA_TEST_SOURCES tests/lua/*.lua)
|
||||
FILE(GLOB LUA_TEST_SOURCES tests/lua/run_tests.lua)
|
||||
set(GTEST_DIR libs/gtest/googletest)
|
||||
|
||||
include_directories(${GTEST_DIR}/include ${GTEST_DIR})
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
|
||||
namespace {
|
||||
|
||||
static sol::object createThread(sol::this_state lua, sol::function func, const sol::variadic_args &args) noexcept {
|
||||
return sol::make_object(lua, std::make_unique<effil::LuaThread>(func, args));
|
||||
sol::object createThreadFactory(sol::this_state lua, const sol::function& func) {
|
||||
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>());
|
||||
}
|
||||
|
||||
@ -19,8 +19,12 @@ extern "C" int luaopen_libeffil(lua_State *L) {
|
||||
sol::state_view lua(L);
|
||||
effil::LuaThread::getUserType(lua);
|
||||
effil::SharedTable::getUserType(lua);
|
||||
effil::ThreadFactory::getUserType(lua);
|
||||
sol::table public_api = lua.create_table_with(
|
||||
"thread", createThread,
|
||||
"thread", createThreadFactory,
|
||||
"thread_id", effil::threadId,
|
||||
"sleep", effil::sleep,
|
||||
"yield", effil::yield,
|
||||
"share", createShare
|
||||
);
|
||||
sol::stack::push(lua, public_api);
|
||||
|
||||
@ -7,7 +7,7 @@ namespace effil {
|
||||
|
||||
sol::object SharedTable::getUserType(sol::state_view &lua) noexcept {
|
||||
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::index, &SharedTable::luaGet,
|
||||
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 {
|
||||
assert(key.valid());
|
||||
sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_state& state) const {
|
||||
ASSERT(key.valid());
|
||||
|
||||
StoredObject cppKey(key);
|
||||
std::lock_guard<SpinMutex> g(lock_);
|
||||
|
||||
@ -22,7 +22,7 @@ public:
|
||||
|
||||
public: // lua bindings
|
||||
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:
|
||||
mutable SpinMutex lock_;
|
||||
|
||||
@ -33,7 +33,7 @@ public:
|
||||
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_);
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ public:
|
||||
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::function loader = lua["loadstring"];
|
||||
ASSERT(loader.valid());
|
||||
@ -77,9 +77,9 @@ private:
|
||||
// TODO: Trick is - sol::object has only operator==:/
|
||||
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) {
|
||||
sol::table luaTable = luaObject;
|
||||
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) {
|
||||
target->set(makeStoredObject(row.first, visited), makeStoredObject(row.second, visited));
|
||||
}
|
||||
@ -150,11 +150,11 @@ StoredObject::StoredObject(SharedTable* table) noexcept
|
||||
: data_(new PrimitiveHolder<SharedTable*>(table)) {
|
||||
}
|
||||
|
||||
StoredObject::StoredObject(const sol::object& object) noexcept
|
||||
StoredObject::StoredObject(const sol::object& object)
|
||||
: data_(fromSolObject(object)) {
|
||||
}
|
||||
|
||||
StoredObject::StoredObject(const sol::stack_object& object) noexcept
|
||||
StoredObject::StoredObject(const sol::stack_object& object)
|
||||
: data_(fromSolObject(object)) {
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ std::size_t StoredObject::hash() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sol::object StoredObject::unpack(sol::this_state state) const noexcept {
|
||||
sol::object StoredObject::unpack(sol::this_state state) const {
|
||||
if (data_)
|
||||
return data_->unpack(state);
|
||||
else
|
||||
|
||||
@ -17,7 +17,7 @@ public:
|
||||
}
|
||||
|
||||
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:
|
||||
BaseHolder(const BaseHolder&) = delete;
|
||||
@ -31,12 +31,12 @@ public:
|
||||
StoredObject() = default;
|
||||
StoredObject(StoredObject&& init) noexcept;
|
||||
StoredObject(SharedTable*) noexcept;
|
||||
StoredObject(const sol::object&) noexcept;
|
||||
StoredObject(const sol::stack_object&) noexcept;
|
||||
StoredObject(const sol::object&);
|
||||
StoredObject(const sol::stack_object&);
|
||||
|
||||
operator bool() 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;
|
||||
bool operator==(const StoredObject& o) const noexcept;
|
||||
|
||||
|
||||
@ -1,76 +1,258 @@
|
||||
#include "threading.h"
|
||||
#include "stored-object.h"
|
||||
|
||||
namespace effil {
|
||||
|
||||
LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept {
|
||||
// 1. Dump function to string
|
||||
sol::state_view lua(function.lua_state());
|
||||
str_function_ = lua["string"]["dump"](function);
|
||||
class LuaHookStopException : public std::exception {};
|
||||
|
||||
// 2. Create new state
|
||||
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::string threadId() noexcept
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
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
|
||||
{
|
||||
static sol::usertype<LuaThread> type(
|
||||
sol::call_construction(), sol::constructors<sol::types<sol::function, sol::variadic_args>>(),
|
||||
"join", &LuaThread::join,
|
||||
"detach", &LuaThread::detach,
|
||||
"thread_id", &LuaThread::threadId
|
||||
"new", sol::no_constructor,
|
||||
"cancel", &LuaThread::cancel,
|
||||
"pause", &LuaThread::pause,
|
||||
"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);
|
||||
return sol::stack::pop<sol::object>(lua);
|
||||
|
||||
@ -8,24 +8,78 @@
|
||||
|
||||
namespace effil {
|
||||
|
||||
// Lua this thread API
|
||||
std::string threadId() noexcept;
|
||||
void yield() noexcept;
|
||||
void sleep(int64_t, sol::optional<std::string>) noexcept;
|
||||
|
||||
class LuaThread {
|
||||
public:
|
||||
LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept;
|
||||
virtual ~LuaThread() noexcept = default;
|
||||
void join() noexcept;
|
||||
void detach() noexcept;
|
||||
enum class ThreadStatus {
|
||||
Running,
|
||||
Paused,
|
||||
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 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:
|
||||
void work() noexcept;
|
||||
void storeArgs(const sol::variadic_args &args) noexcept;
|
||||
LuaThread(const LuaThread&) = delete;
|
||||
LuaThread& operator=(const LuaThread&) = delete;
|
||||
|
||||
std::string str_function_;
|
||||
std::shared_ptr<sol::state> p_state_;
|
||||
std::shared_ptr<std::thread> p_thread_;
|
||||
std::shared_ptr<std::vector<sol::object>> p_arguments_;
|
||||
std::string threadStatusToString(ThreadStatus stat) const noexcept;
|
||||
static void work(std::shared_ptr<ThreadData> threadData, const std::string strFunction, std::vector<sol::object>&& arguments) noexcept;
|
||||
|
||||
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
|
||||
|
||||
@ -1,11 +1,57 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
-- 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"
|
||||
|
||||
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
|
||||
-- Hack input arguments to make tests verbose by default
|
||||
local found = false
|
||||
@ -21,37 +67,6 @@ do
|
||||
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 --
|
||||
-----------
|
||||
|
||||
@ -1,52 +1,220 @@
|
||||
TestSmoke = {}
|
||||
|
||||
function TestSmoke:testGeneralWorkability()
|
||||
function TestSmoke:tearDown()
|
||||
log "TearDown() collect garbage"
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
function TestSmoke:testSharedTableTypes()
|
||||
local effil = require('libeffil')
|
||||
local share = effil.share()
|
||||
|
||||
share["number"] = 100500
|
||||
share["string"] = "string value"
|
||||
share["bool"] = true
|
||||
share["function"] = function(left, right) return left + right end
|
||||
|
||||
log "Start thread"
|
||||
local thread = effil.thread(
|
||||
local thread_factory = effil.thread(
|
||||
function(share)
|
||||
share["child.number"] = share["number"]
|
||||
share["child.string"] = share["string"]
|
||||
share["child.bool"] = share["bool"]
|
||||
end,
|
||||
share
|
||||
share["child.number"] = share["number"]
|
||||
share["child.string"] = share["string"]
|
||||
share["child.bool"] = share["bool"]
|
||||
share["child.function"] = share["function"](11,45)
|
||||
end
|
||||
)
|
||||
log "Join thread"
|
||||
thread:join()
|
||||
local thread = thread_factory(share)
|
||||
thread:wait()
|
||||
|
||||
log "Check values"
|
||||
test.assertEquals(share["child.number"], share["number"],
|
||||
"'number' fields are not equal")
|
||||
test.assertEquals(share["child.string"], share["string"],
|
||||
"'string' fields are not equal")
|
||||
test.assertEquals(share["child.bool"], share["bool"],
|
||||
"'bool' fields are not equal")
|
||||
test.assertEquals(share["child.number"], share["number"])
|
||||
test.assertEquals(share["child.string"], share["string"])
|
||||
test.assertEquals(share["child.bool"], share["bool"])
|
||||
test.assertEquals(share["child.function"], share["function"](11,45))
|
||||
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 share = effil.share()
|
||||
|
||||
share["finished"] = false
|
||||
log "Start thread"
|
||||
local thread = effil.thread(
|
||||
function(share)
|
||||
local startTime = os.time()
|
||||
while ( (os.time() - startTime) <= 3) do --[[ Like we are working 3sec ... ]] end
|
||||
share["finished"] = true
|
||||
end,
|
||||
share
|
||||
)
|
||||
log "Detach thread"
|
||||
thread:detach()
|
||||
local magic_number = 42
|
||||
share["subtable1"] = effil.share()
|
||||
share["subtable1"]["subtable1"] = effil.share()
|
||||
share["subtable1"]["subtable2"] = share["subtable1"]["subtable1"]
|
||||
share["subtable2"] = share["subtable1"]["subtable1"]
|
||||
share["magic_number"] = magic_number
|
||||
|
||||
log "Waiting for thread completion..."
|
||||
test.assertEquals(wait(4, function() return share["finished"] end) , true)
|
||||
log "Stop waiting"
|
||||
local thread_factory = effil.thread(
|
||||
function(share)
|
||||
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
|
||||
|
||||
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