Merge pull request #14 from loud-hound/extended_thread_api

Extend threading API
This commit is contained in:
mihacooper 2017-02-07 11:21:03 +03:00 committed by GitHub
commit 43dc94df8a
10 changed files with 579 additions and 156 deletions

View File

@ -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})

View File

@ -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);

View File

@ -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_);

View File

@ -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_;

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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 --
----------- -----------

View File

@ -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