diff --git a/.travis.yml b/.travis.yml index c066346..009b777 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,4 +105,4 @@ matrix: install: - brew install luajit script: - - LUA_BIN=luajit SKIP_CPP_TESTS=1 ci/test_all.sh -DLUA_INCLUDE_DIR="/usr/local/Cellar/luajit/2.0.5/include/luajit-2.0" -DLUA_LIBRARY="/usr/local/Cellar/luajit/2.0.5/lib/libluajit.dylib" + - LUA_BIN=luajit ci/test_all.sh -DLUA_INCLUDE_DIR="/usr/local/Cellar/luajit/2.0.5/include/luajit-2.0" -DLUA_LIBRARY="/usr/local/Cellar/luajit/2.0.5/lib/libluajit.dylib" diff --git a/CMakeLists.txt b/CMakeLists.txt index 675d782..a3f47a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,19 +34,6 @@ set(CMAKE_CXX_FLAGS "${EXTRA_FLAGS} ${CMAKE_CXX_FLAGS} ${GENERAL} ${ENABLE_WARNI set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -O0 -g -UNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -g0 -DNDEBUG") -#---------- -# TESTS --- -#---------- -if (NOT BUILD_ROCK) - FILE(GLOB TEST_SOURCES tests/cpp/*.cpp tests/cpp/*.h) - set(GTEST_DIR libs/gtest/googletest) - set(LUA_TESTS tests/lua/tests.lua) - - include_directories(${GTEST_DIR}/include ${GTEST_DIR}) - add_executable(tests ${TEST_SOURCES} ${GTEST_DIR}/src/gtest-all.cc) - target_link_libraries(tests effil ${LUA_LIBRARY}) -endif() - #---------- # INSTALL - #---------- diff --git a/ci/test_all.sh b/ci/test_all.sh index ad5de3e..110935d 100755 --- a/ci/test_all.sh +++ b/ci/test_all.sh @@ -9,14 +9,5 @@ for build_type in debug release; do cmake -H. -B$build_type -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=$build_type $@ cmake --build $build_type --config Release - # FIXME: creation of sol::state with luajit in c++ tests - # leads to memory corruption segmentation fault - # this is temporary workaround - if [ -z "$SKIP_CPP_TESTS" ]; then - (cd $build_type && ./tests) - else - echo "C++ tests skipped!" - fi - (cd $build_type && STRESS=1 $LUA_BIN ../tests/lua/run_tests) done diff --git a/src/cpp/channel.cpp b/src/cpp/channel.cpp index 77565d1..8d7bd13 100644 --- a/src/cpp/channel.cpp +++ b/src/cpp/channel.cpp @@ -4,7 +4,7 @@ namespace effil { -void Channel::getUserType(sol::state_view& lua) { +void Channel::exportAPI(sol::state_view& lua) { sol::usertype type("new", sol::no_constructor, "push", &Channel::push, "pop", &Channel::pop, @@ -14,7 +14,7 @@ void Channel::getUserType(sol::state_view& lua) { sol::stack::pop(lua); } -Channel::Channel(sol::optional capacity) : data_(std::make_shared()){ +Channel::Channel(sol::optional capacity) : data_(std::make_shared()) { if (capacity) { REQUIRE(capacity.value() >= 0) << "Invalid capacity value = " << capacity.value(); data_->capacity_ = static_cast(capacity.value()); @@ -34,8 +34,8 @@ bool Channel::push(const sol::variadic_args& args) { effil::StoredArray array; for (const auto& arg : args) { auto obj = createStoredObject(arg.get()); - if (obj->gcHandle()) - refs_->insert(obj->gcHandle()); + + addReference(obj->gcHandle()); obj->releaseStrongReference(); array.emplace_back(obj); } @@ -59,10 +59,9 @@ StoredArray Channel::pop(const sol::optional& duration, } auto ret = data_->channel_.front(); - for (const auto& obj: ret) { - if (obj->gcHandle()) - refs_->erase(obj->gcHandle()); - } + for (const auto& obj: ret) + removeReference(obj->gcHandle()); + data_->channel_.pop(); return ret; } diff --git a/src/cpp/channel.h b/src/cpp/channel.h index 4ac7c3d..98ba676 100644 --- a/src/cpp/channel.h +++ b/src/cpp/channel.h @@ -1,5 +1,6 @@ #pragma once +#include "gc-object.h" #include "notifier.h" #include "stored-object.h" #include "lua-helpers.h" @@ -10,7 +11,7 @@ namespace effil { class Channel : public GCObject { public: Channel(sol::optional capacity); - static void getUserType(sol::state_view& lua); + static void exportAPI(sol::state_view& lua); bool push(const sol::variadic_args& args); StoredArray pop(const sol::optional& duration, diff --git a/src/cpp/garbage-collector.cpp b/src/cpp/garbage-collector.cpp index 731d4db..3e122ab 100644 --- a/src/cpp/garbage-collector.cpp +++ b/src/cpp/garbage-collector.cpp @@ -2,7 +2,6 @@ #include "utils.h" -#include #include namespace effil { @@ -12,41 +11,29 @@ GC::GC() , lastCleanup_(0) , step_(200) {} -GCObject* GC::findObject(GCObjectHandle handle) { - auto it = objects_.find(handle); - if (it == objects_.end()) { - DEBUG << "Null handle " << handle << std::endl; - return nullptr; - } - return it->second.get(); -} - -bool GC::has(GCObjectHandle handle) const { - std::lock_guard g(lock_); - return objects_.find(handle) != objects_.end(); -} - // Here is the naive tri-color marking // garbage collecting algorithm implementation. void GC::collect() { std::lock_guard g(lock_); - std::vector grey; - std::map> black; + std::unordered_set grey; + std::unordered_map> black; for (const auto& handleAndObject : objects_) if (handleAndObject.second->instances() > 1) - grey.push_back(handleAndObject.first); + grey.insert(handleAndObject.first); while (!grey.empty()) { - GCObjectHandle handle = grey.back(); - grey.pop_back(); + auto it = grey.begin(); + GCObjectHandle handle = *it; + grey.erase(it); - auto object = objects_[handle]; - black[handle] = object; - for (GCObjectHandle refHandle : object->refers()) - if (black.find(refHandle) == black.end()) - grey.push_back(refHandle); + black[handle] = std::move(objects_[handle]); + for (GCObjectHandle refHandle : black[handle]->refers()) { + assert(objects_.count(refHandle)); + if (black.count(refHandle) == 0 && grey.count(refHandle) == 0) + grey.insert(refHandle); + } } DEBUG << "Removing " << (objects_.size() - black.size()) << " out of " << objects_.size() << std::endl; @@ -67,15 +54,13 @@ size_t GC::count() { } GC& GC::instance() { - static GC pool; - return pool; + static GC gc; + return gc; } -sol::table GC::getLuaApi(sol::state_view& lua) { +sol::table GC::exportAPI(sol::state_view& lua) { sol::table api = lua.create_table_with(); - api["collect"] = [=] { - instance().collect(); - }; + api["collect"] = [=] { instance().collect(); }; api["pause"] = [] { instance().pause(); }; api["resume"] = [] { instance().resume(); }; api["enabled"] = [] { return instance().enabled(); }; diff --git a/src/cpp/garbage-collector.h b/src/cpp/garbage-collector.h index 35dbe31..3c6dc55 100644 --- a/src/cpp/garbage-collector.h +++ b/src/cpp/garbage-collector.h @@ -1,42 +1,18 @@ #pragma once -#include "spin-mutex.h" +#include "gc-object.h" #include #include -#include -#include +#include +#include namespace effil { -// Unique handle for all objects spawned from one object. -typedef void* GCObjectHandle; - -static const GCObjectHandle GCNull = nullptr; - -// All effil objects that owned in lua code have to inherit this class. -// This type o object can persist in multiple threads and in multiple lua states. -// Child has to care about storing data and concurrent access. -class GCObject { -public: - GCObject() noexcept - : refs_(new std::set) {} - GCObject(const GCObject& init) = default; - GCObject(GCObject&& init) = default; - GCObject& operator=(const GCObject& init) = default; - virtual ~GCObject() = default; - - GCObjectHandle handle() const noexcept { return reinterpret_cast(refs_.get()); } - size_t instances() const noexcept { return refs_.use_count(); } - const std::set& refers() const { return *refs_; } - -protected: - std::shared_ptr> refs_; -}; - class GC { public: - GC(); - ~GC() = default; + // global gc instance + static GC& instance(); + static sol::table exportAPI(sol::state_view& lua); // This method is used to create all managed objects. template @@ -44,46 +20,46 @@ public: if (enabled_ && lastCleanup_.fetch_add(1) == step_) collect(); - auto object = std::make_shared(std::forward(args)...); + auto object = std::make_unique(std::forward(args)...); + auto copy = *object; std::lock_guard g(lock_); - objects_[object->handle()] = object; - return *object; + objects_.emplace(object->handle(), std::move(object)); + return copy; } template ObjectType get(GCObjectHandle handle) { std::lock_guard g(lock_); - // TODO: add dynamic cast to check? - return *static_cast(findObject(handle)); + + auto it = objects_.find(handle); + assert(it != objects_.end()); + + auto result = dynamic_cast(it->second.get()); + assert(result); + return *result; } - bool has(GCObjectHandle handle) const; - - void collect(); - size_t size() const; - void pause() { enabled_ = false; }; - void resume() { enabled_ = true; }; - size_t step() const { return step_; } - void step(size_t newStep) { step_ = newStep; } - bool enabled() { return enabled_; }; - size_t count(); - - static GC& instance(); - static sol::table getLuaApi(sol::state_view& lua); private: mutable std::mutex lock_; bool enabled_; std::atomic lastCleanup_; size_t step_; - std::map> objects_; - -private: - GCObject* findObject(GCObjectHandle handle); + std::unordered_map> objects_; private: + GC(); GC(GC&&) = delete; GC(const GC&) = delete; + + void collect(); + size_t size() const; + void pause() { enabled_ = false; } + void resume() { enabled_ = true; } + size_t step() const { return step_; } + void step(size_t newStep) { step_ = newStep; } + bool enabled() { return enabled_; } + size_t count(); }; } // effil diff --git a/src/cpp/gc-object.cpp b/src/cpp/gc-object.cpp new file mode 100644 index 0000000..910524e --- /dev/null +++ b/src/cpp/gc-object.cpp @@ -0,0 +1,42 @@ +#include "gc-object.h" + +#include +#include + +namespace effil { + +GCObject::GCObject() + : data_(std::make_shared()) {} + +GCObjectHandle GCObject::handle() const { + return reinterpret_cast(data_.get()); +} + +size_t GCObject::instances() const { + return data_.use_count(); +} + +const std::unordered_set GCObject::refers() const { + std::lock_guard lock(data_->mutex_); + return std::unordered_set( + data_->weakRefs_.begin(), + data_->weakRefs_.end()); +} + +void GCObject::addReference(GCObjectHandle handle) { + if (handle == nullptr) return; + + std::lock_guard lock(data_->mutex_); + data_->weakRefs_.insert(handle); +} + +void GCObject::removeReference(GCObjectHandle handle) { + if (handle == GCNull) return; + + std::lock_guard lock(data_->mutex_); + auto hit = data_->weakRefs_.find(handle); + assert(hit != std::end(data_->weakRefs_)); + data_->weakRefs_.erase(hit); +} + +} // namespace effil \ No newline at end of file diff --git a/src/cpp/gc-object.h b/src/cpp/gc-object.h new file mode 100644 index 0000000..3943404 --- /dev/null +++ b/src/cpp/gc-object.h @@ -0,0 +1,47 @@ +#pragma once + +#include "spin-mutex.h" + +#include + +namespace effil { + +// Unique handle for all objects spawned from one object. +using GCObjectHandle = void*; + +static const GCObjectHandle GCNull = nullptr; + +// All effil objects that owned in lua code have to inherit this class. +// This type o object can persist in multiple threads and in multiple lua states. +// Childes have to care about storing data, concurrent access and +// weak references (GCHandle) to other GCObjects. +class GCObject { +public: + GCObject(); + virtual ~GCObject() = default; + + // Unique handle for any copy of GCObject in any lua state + GCObjectHandle handle() const; + + // Number of instance copies + // always greater than 1 + // GC holds one copy + size_t instances() const; + + // List of weak references to nested objects + const std::unordered_set refers() const; + +protected: + void addReference(GCObjectHandle handle); + void removeReference(GCObjectHandle handle); + +private: + struct SharedData { + mutable SpinMutex mutex_; + std::unordered_multiset weakRefs_; + }; + + std::shared_ptr data_; +}; + +} // namespace effil \ No newline at end of file diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index a89f154..dd200fc 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -54,10 +54,10 @@ extern "C" __declspec(dllexport) #endif int luaopen_libeffil(lua_State* L) { - sol::state_view lua(L); - Thread::getUserType(lua); - SharedTable::getUserType(lua); - Channel::getUserType(lua); + sol::state_view lua(L); + Thread::exportAPI(lua); + SharedTable::exportAPI(lua); + Channel::exportAPI(lua); sol::table publicApi = lua.create_table_with( "thread", createThread, "thread_id", threadId, @@ -70,7 +70,7 @@ int luaopen_libeffil(lua_State* L) { "setmetatable", SharedTable::luaSetMetatable, "getmetatable", SharedTable::luaGetMetatable, "G", sol::make_object(lua, globalTable), - "gc", GC::getLuaApi(lua), + "gc", GC::exportAPI(lua), "channel", createChannel, "userdata_type", userdataType, "pairs", SharedTable::globalLuaPairs, diff --git a/src/cpp/shared-table.cpp b/src/cpp/shared-table.cpp index 25f9096..c288f39 100644 --- a/src/cpp/shared-table.cpp +++ b/src/cpp/shared-table.cpp @@ -14,15 +14,11 @@ bool isSharedTable(const SolObject& obj) { return obj.valid() && obj.get_type() == sol::type::userdata && obj.template is(); } -} +} // namespace -SharedTable::SharedTable() : GCObject(), data_(std::make_shared()) {} +SharedTable::SharedTable() : data_(std::make_shared()) {} -SharedTable::SharedTable(const SharedTable& init) - : GCObject(init) - , data_(init.data_) {} - -void SharedTable::getUserType(sol::state_view& lua) { +void SharedTable::exportAPI(sol::state_view& lua) { sol::usertype type("new", sol::no_constructor, "__pairs", &SharedTable::luaPairs, "__ipairs", &SharedTable::luaIPairs, @@ -50,10 +46,9 @@ void SharedTable::getUserType(sol::state_view& lua) { void SharedTable::set(StoredObject&& key, StoredObject&& value) { std::lock_guard g(data_->lock); - if (key->gcHandle()) - refs_->insert(key->gcHandle()); - if (value->gcHandle()) - refs_->insert(value->gcHandle()); + addReference(key->gcHandle()); + addReference(value->gcHandle()); + key->releaseStrongReference(); value->releaseStrongReference(); @@ -80,10 +75,8 @@ void SharedTable::rawSet(const sol::stack_object& luaKey, const sol::stack_objec // in this case object is not obligatory to own data auto it = data_->entries.find(key); if (it != data_->entries.end()) { - if (it->first->gcHandle()) - refs_->erase(it->first->gcHandle()); - if (it->second->gcHandle()) - refs_->erase(it->second->gcHandle()); + removeReference(it->first->gcHandle()); + removeReference(it->second->gcHandle()); data_->entries.erase(it); } @@ -266,21 +259,20 @@ SharedTable::PairsIterator SharedTable::luaIPairs(sol::this_state state) { */ SharedTable SharedTable::luaSetMetatable(SharedTable& stable, const sol::stack_object& mt) { - REQUIRE((!mt.valid()) || mt.get_type() == sol::type::table || isSharedTable(mt)) << "Unexpected type of setmetatable argument"; + REQUIRE(mt.get_type() == sol::type::table || isSharedTable(mt)) << "Unexpected type of setmetatable argument"; std::lock_guard lock(stable.data_->lock); - if (stable.data_->metatable != GCNull) - { - stable.refs_->erase(stable.data_->metatable); + if (stable.data_->metatable != GCNull) { + stable.removeReference(stable.data_->metatable); stable.data_->metatable = GCNull; } - if (mt.valid()) { - stable.data_->metatable = createStoredObject(mt)->gcHandle(); - stable.refs_->insert(stable.data_->metatable); - } + + stable.data_->metatable = createStoredObject(mt)->gcHandle(); + stable.addReference(stable.data_->metatable); + return stable; } -sol::object SharedTable::luaGetMetatable(const SharedTable& stable, sol::this_state state) { +sol::object SharedTable::luaGetMetatable(const SharedTable& stable, const sol::this_state& state) { std::lock_guard lock(stable.data_->lock); return stable.data_->metatable == GCNull ? sol::nil : sol::make_object(state, GC::instance().get(stable.data_->metatable)); diff --git a/src/cpp/shared-table.h b/src/cpp/shared-table.h index d4ad95f..ae341d0 100644 --- a/src/cpp/shared-table.h +++ b/src/cpp/shared-table.h @@ -1,6 +1,6 @@ #pragma once -#include "garbage-collector.h" +#include "gc-object.h" #include "stored-object.h" #include "spin-mutex.h" #include "utils.h" @@ -8,7 +8,7 @@ #include -#include +#include #include namespace effil { @@ -20,12 +20,9 @@ private: public: SharedTable(); - SharedTable(SharedTable&&) = default; - SharedTable(const SharedTable& init); SharedTable& operator=(const SharedTable&) = default; - virtual ~SharedTable() = default; - static void getUserType(sol::state_view& lua); + static void exportAPI(sol::state_view& lua); void set(StoredObject&&, StoredObject&&); void rawSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue); @@ -56,7 +53,7 @@ public: // Stand alone functions for effil::table available in Lua static SharedTable luaSetMetatable(SharedTable& stable, const sol::stack_object& mt); - static sol::object luaGetMetatable(const SharedTable& stable, const sol::this_state state); + static sol::object luaGetMetatable(const SharedTable& stable, const sol::this_state& state); static sol::object luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state); static SharedTable luaRawSet(SharedTable& stable, const sol::stack_object& key, const sol::stack_object& value); static size_t luaSize(SharedTable& stable); diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index 46e8026..e8e6f22 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -76,7 +76,6 @@ public: assert(luaObject.template is()); strongRef_ = luaObject.template as(); handle_ = strongRef_->handle(); - assert(GC::instance().has(handle_)); } GCObjectHolder(GCObjectHandle handle) diff --git a/src/cpp/stored-object.h b/src/cpp/stored-object.h index 4b9ac30..49e5156 100644 --- a/src/cpp/stored-object.h +++ b/src/cpp/stored-object.h @@ -7,6 +7,7 @@ namespace effil { +// Represents an interface for lua type stored at C++ code class BaseHolder { public: BaseHolder() = default; diff --git a/src/cpp/threading.cpp b/src/cpp/threading.cpp index 8cdfb31..b07c844 100644 --- a/src/cpp/threading.cpp +++ b/src/cpp/threading.cpp @@ -248,7 +248,7 @@ Thread::Thread(const std::string& path, thr.detach(); } -void Thread::getUserType(sol::state_view& lua) { +void Thread::exportAPI(sol::state_view& lua) { sol::usertype type( "new", sol::no_constructor, "get", &Thread::get, diff --git a/src/cpp/threading.h b/src/cpp/threading.h index e29d0db..44204ba 100644 --- a/src/cpp/threading.h +++ b/src/cpp/threading.h @@ -20,7 +20,7 @@ public: const sol::function& function, const sol::variadic_args& args); - static void getUserType(sol::state_view& lua); + static void exportAPI(sol::state_view& lua); std::pair status(const sol::this_state& state); std::pair wait(const sol::this_state& state, diff --git a/tests/cpp/garbage-collector.cpp b/tests/cpp/garbage-collector.cpp deleted file mode 100644 index c3a3902..0000000 --- a/tests/cpp/garbage-collector.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include - -#include "test-utils.h" -#include "garbage-collector.h" - -#include -#include - -using namespace effil; - -TEST(gc, GCObject) { - GCObject o1; - EXPECT_EQ(o1.instances(), (size_t)1); - - GCObject o2 = GC::instance().create(); - EXPECT_EQ(o2.instances(), (size_t)2); - - GCObject o3 = GC::instance().create(); - GCObject o4(o3); - GCObject o5(o4); - EXPECT_EQ(o5.instances(), o3.instances()); - EXPECT_EQ(o5.instances(), (size_t)4); - EXPECT_EQ(o5.handle(), o3.handle()); - EXPECT_NE(o1.handle(), o5.handle()); -} - -TEST(gc, collect) { - GC::instance().collect(); - ASSERT_EQ(GC::instance().size(), (size_t)1); - { - GCObject o1 = GC::instance().create(); - GCObject o2 = GC::instance().create(); - } - EXPECT_EQ(GC::instance().size(), (size_t)3); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)1); -} - -namespace { - -struct Dummy : public GCObject { - void add(GCObjectHandle ref) { refs_->insert(ref); } -}; - -} - -TEST(gc, withRefs) { - GC::instance().collect(); - { - Dummy root = GC::instance().create(); - - { - Dummy orphan = GC::instance().create(); - for (size_t i = 0; i < 3; i++) { - Dummy child = GC::instance().create(); - root.add(child.handle()); - } - } - EXPECT_EQ(GC::instance().size(), (size_t)6); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)5); - } - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)1); -} - -TEST(gc, autoCleanup) { - std::vector threads; - size_t objectsPerThread = 1000; - - for (size_t i = 0; i < 5; i++) - threads.emplace_back([=] { - for (size_t i = 0; i < objectsPerThread; i++) - GC::instance().create(); - }); - - for (auto& thread : threads) - thread.join(); - - EXPECT_LT(GC::instance().size(), GC::instance().step()); -} - -TEST(gc, gcInLuaState) { - sol::state lua; - bootstrapState(lua); - - lua["st"] = GC::instance().create(); - lua.script(R"( -for i=1,1000 do -st[i] = {"Wow"} -end -)"); - EXPECT_EQ(GC::instance().size(), (size_t)1002); - - lua.script(R"( -for i=1,1000 do -st[i] = nil -end -)"); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)2); -} - -TEST(gc, cycles) { - { - sol::state lua; - bootstrapState(lua); - GC::instance().collect(); - - lua["st"] = GC::instance().create(); - lua.script(R"( -st.parent = {} -st.parent.child = { ref = st.parent } -st[4] = { one = 1 } -st[5] = { flag = true } -)"); - EXPECT_EQ(GC::instance().size(), (size_t)6); - - lua.script("st.parent = nil"); - - lua.collect_garbage(); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)4); - } - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)1); -} - -TEST(gc, multipleStates) { - sol::state lua1; - sol::state lua2; - bootstrapState(lua1); - bootstrapState(lua2); - - { - SharedTable st = GC::instance().create(); - lua1["st"] = st; - lua2["st"] = st; - } - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)2); - - lua1.script(R"( -st.men = { name = "John", age = 22 } -st.car = { name = "Lada", model = 12 } -st.cat = { name = "Tomas" } -st.fish = { name = "Herbert" } - -st.men.car = st.car -st.men.cat = st.cat -st.men.fish = st.fish -)"); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)6); - - lua2.script("copy = { st.men } st = nil"); - lua1.script("st = nil"); - lua1.collect_garbage(); - lua2.collect_garbage(); - GC::instance().collect(); - EXPECT_EQ(GC::instance().size(), (size_t)5); -} diff --git a/tests/cpp/notifier.cpp b/tests/cpp/notifier.cpp deleted file mode 100644 index 2a548bc..0000000 --- a/tests/cpp/notifier.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include - -#include "notifier.h" - -#include -#include - -using namespace effil; - -TEST(notifier, wait) { - Notifier n; - bool done = false; - auto t = std::thread([&]{ - done = true; - n.notify(); - }); - - n.wait(); - EXPECT_TRUE(done); - t.join(); -} - -TEST(notifier, waitMany) { - const size_t nfutures = 32; - std::vector vt; - std::atomic counter(0); - Notifier n; - - for(size_t i = 0; i < nfutures; i++) - vt.emplace_back(std::thread([&]{ - n.wait(); - counter++; - })); - - EXPECT_EQ(counter.load(), (size_t)0); - - n.notify(); - for(auto& t : vt) t.join(); - EXPECT_EQ(counter.load(), nfutures); -} - -TEST(notifier, waitFor) { - Notifier n; - auto t = std::thread([&] { - std::this_thread::sleep_for(std::chrono::seconds(2)); - n.notify(); - }); - - EXPECT_FALSE(n.waitFor(std::chrono::seconds(1))); - EXPECT_TRUE(n.waitFor(std::chrono::seconds(2))); - t.join(); -} - -TEST(notifier, reset) { - const size_t iterations = 1024; - Notifier readyToProcess; - Notifier needNew; - size_t resource = 0; - - std::thread producer([&]() { - for (size_t i = 0; i < iterations; i++) { - resource++; - readyToProcess.notify(); - needNew.wait(); - needNew.reset(); - } - }); - - std::thread consumer([&](){ - for (size_t i = 0; i < iterations; i++) { - readyToProcess.wait(); - readyToProcess.reset(); - EXPECT_EQ(resource, i + 1); - needNew.notify(); - } - }); - - producer.join(); - consumer.join(); -} \ No newline at end of file diff --git a/tests/cpp/shared-table.cpp b/tests/cpp/shared-table.cpp deleted file mode 100644 index e174b76..0000000 --- a/tests/cpp/shared-table.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include - -#include "test-utils.h" -#include "shared-table.h" -#include "garbage-collector.h" - -#include - -using namespace effil; - -TEST(sharedTable, primitiveTypes) { - sol::state lua; - bootstrapState(lua); - - lua["st"] = SharedTable(); - - auto res1 = lua.script(R"( - st.fst = "first" - st.snd = 2 - st.thr = true - st.del = "secret" - st.del = nil - )"); - - EXPECT_TRUE(res1.valid()) << "Set res1 failed"; - EXPECT_EQ(lua["st"]["fst"], std::string("first")); - EXPECT_EQ(lua["st"]["snd"], (double)2); - EXPECT_EQ(lua["st"]["thr"], true); - EXPECT_EQ(lua["st"]["del"], sol::nil); - EXPECT_EQ(lua["st"]["nex"], sol::nil); - - auto res2 = lua.script(R"( - st[1] = 3 - st[2] = "number" - st[-1] = false - st[42] = "answer" - st[42] = nil - st.deleted = st[42] == nil - )"); - - EXPECT_TRUE(res2.valid()) << "Set res2 failed"; - EXPECT_EQ(lua["st"][1], 3); - EXPECT_EQ(lua["st"][2], std::string("number")); - EXPECT_EQ(lua["st"][-1], false); - EXPECT_EQ(lua["st"]["deleted"], true); - - auto res3 = lua.script(R"( - st[true] = false - st[false] = 9 - )"); - - EXPECT_TRUE(res3.valid()) << "Set res3 failed"; - EXPECT_EQ(lua["st"][true], false); - EXPECT_EQ(lua["st"][false], 9); -} - -TEST(sharedTable, multipleStates) { - sol::state lua1, lua2; - bootstrapState(lua1); - bootstrapState(lua2); - - auto st = std::make_unique(); - - lua1["cats"] = st.get(); - lua2["dogs"] = st.get(); - - auto res1 = lua1.script(R"( - cats.fluffy = "gav" - cats.sparky = false - cats.wow = 3 - )"); - - EXPECT_EQ(lua2["dogs"]["fluffy"], std::string("gav")); - EXPECT_EQ(lua2["dogs"]["sparky"], false); - EXPECT_EQ(lua2["dogs"]["wow"], 3); -} - -TEST(sharedTable, multipleThreads) { - SharedTable st; - - std::vector threads; - - threads.emplace_back([=]() { - sol::state lua; - bootstrapState(lua); - ; - lua["st"] = st; - lua.script(R"( - while not st.ready do end - st.fst = true - )"); - }); - - threads.emplace_back([=]() { - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - lua.script(R"( - while not st.ready do end - st.snd = true - )"); - }); - - threads.emplace_back([=]() { - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - lua.script(R"( - while not st.ready do end - st.thr = true - )"); - }); - - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - lua.script("st.ready = true"); - - for (auto& thread : threads) { - thread.join(); - } - - EXPECT_EQ(lua["st"]["fst"], true); - EXPECT_EQ(lua["st"]["snd"], true); - EXPECT_EQ(lua["st"]["thr"], true); -} - -TEST(sharedTable, playingWithSharedTables) { - sol::state lua; - bootstrapState(lua); - - lua["recursive"] = GC::instance().create(); - lua["st1"] = GC::instance().create(); - lua["st2"] = GC::instance().create(); - - lua.script(R"( - st1.proxy = st2 - st1.proxy.value = true - recursive.next = recursive - recursive.val = "yes" - )"); - EXPECT_EQ(lua["st2"]["value"], true); - EXPECT_EQ(lua["recursive"]["next"]["next"]["next"]["val"], std::string("yes")); -} - -TEST(sharedTable, playingWithFunctions) { - SharedTable st; - sol::state lua; - bootstrapState(lua); - - lua["st"] = st; - - lua.script(R"( - st.fn = function () - print "Hello C++" - return true - end - st.fn() - )"); - - sol::function sf = lua["st"]["fn"]; - EXPECT_TRUE((bool)sf()); - - sol::state lua2; - bootstrapState(lua2); - - lua2["st2"] = st; - lua2.script(R"( - st2.fn2 = function(str) - return "*" .. str .. "*" - end - )"); - - sol::function sf2 = lua["st"]["fn2"]; - - EXPECT_EQ(sf2(std::string("SUCCESS")).get(), std::string("*SUCCESS*")); -} - -TEST(sharedTable, playingWithTables) { - SharedTable st; - sol::state lua; - bootstrapState(lua); - - lua["st"] = st; - auto res = lua.script(R"( - st.works = "fine" - st.person = {name = 'John Doe', age = 25} - pet = { - type = "cat", - name = "Tomas", - real = "Яша", - owner = "Mama", - spec = { colour = "grey", legs = 4, eyes = 2 } - } - st.pet = pet - recursive = {} - recursive.next = recursive - recursive.prev = recursive - recursive.val = "recursive" - st.recursive = recursive - )"); - - EXPECT_TRUE(res.valid()); - EXPECT_EQ(lua["st"]["person"]["name"], std::string("John Doe")); - EXPECT_EQ(lua["st"]["person"]["age"], 25); - EXPECT_EQ(lua["st"]["pet"]["type"], std::string("cat")); - EXPECT_EQ(lua["st"]["pet"]["name"], std::string("Tomas")); - EXPECT_EQ(lua["st"]["pet"]["real"], std::string("Яша")); - EXPECT_EQ(lua["st"]["pet"]["spec"]["colour"], std::string("grey")); - EXPECT_EQ(lua["st"]["pet"]["spec"]["legs"], 4); - EXPECT_EQ(lua["st"]["recursive"]["prev"]["next"]["next"]["val"], std::string("recursive")); -} - -TEST(sharedTable, stress) { - sol::state lua; - bootstrapState(lua); - SharedTable st; - - lua["st"] = st; - - auto res1 = lua.script(R"( - for i = 1, 1000000 do - st[i] = tostring(i) - end - )"); - - EXPECT_TRUE(res1.valid()); - EXPECT_TRUE(SharedTable::luaSize(st) == 1'000'000); - - auto res2 = lua.script(R"( - for i = 1000000, 1, -1 do - st[i] = nil - end - )"); - EXPECT_TRUE(res2.valid()); - EXPECT_TRUE(SharedTable::luaSize(st) == 0); -} - -TEST(sharedTable, stressWithThreads) { - SharedTable st; - - const size_t threadCount = 10; - std::vector threads; - for (size_t i = 0; i < threadCount; i++) { - threads.emplace_back([=] { - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - std::stringstream ss; - ss << "st[" << i << "] = 1" << std::endl; - ss << "for i = 1, 100000 do" << std::endl; - ss << " st[" << i << "] = " - << "st[" << i << "] + 1" << std::endl; - ss << "end" << std::endl; - lua.script(ss.str()); - }); - } - - for (auto& thread : threads) { - thread.join(); - } - - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - for (size_t i = 0; i < threadCount; i++) { - EXPECT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl; - } -} - -TEST(sharedTable, ExternalUserdata) { - SharedTable st; - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - - struct TestUserdata - { - int field; - }; - - lua["udata"] = TestUserdata{17}; - // FIXME: fails on gcc-5 and LuaJIT - // Right check is EXPECT_THROW(lua.script("st.userdata = udata"), sol::error); - EXPECT_ANY_THROW(lua.script("st.userdata = udata")); -} - -TEST(sharedTable, LightUserdata) { - SharedTable st; - sol::state lua; - bootstrapState(lua); - lua["st"] = st; - - int lightUserdata = 19; - lua_pushlightuserdata(lua, (void*)&lightUserdata); - lua["light_udata"] = sol::stack::pop(lua); - EXPECT_TRUE((bool)lua.script("st.light_userdata = light_udata; return st.light_userdata == light_udata")); - std::string strRet = lua.script("return type(light_udata)"); - EXPECT_EQ(strRet, "userdata"); - EXPECT_EQ(lua["light_udata"].get().value, &lightUserdata); -} diff --git a/tests/cpp/test-main.cpp b/tests/cpp/test-main.cpp deleted file mode 100644 index 8401258..0000000 --- a/tests/cpp/test-main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "gtest/gtest.h" - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/tests/cpp/test-utils.h b/tests/cpp/test-utils.h deleted file mode 100644 index d2d7118..0000000 --- a/tests/cpp/test-utils.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "shared-table.h" -#include "lua-helpers.h" -#include - -namespace effil { - -inline void bootstrapState(sol::state& lua) { - luaL_openlibs(lua); - SharedTable::getUserType(lua); -} - -} // namespace effil diff --git a/tests/lua/channel-stress.lua b/tests/lua/channel-stress.lua index c228b6e..18a2e49 100644 --- a/tests/lua/channel-stress.lua +++ b/tests/lua/channel-stress.lua @@ -6,8 +6,9 @@ test.channel_stress.with_multiple_threads = function () local exchange_channel, result_channel = effil.channel(), effil.channel() local threads_number = 1000 + local threads = {} for i = 1, threads_number do - effil.thread(function(exchange_channel, result_channel, indx) + threads[i] = effil.thread(function(exchange_channel, result_channel, indx) if indx % 2 == 0 then for i = 1, 10000 do exchange_channel:push(indx .. "_".. i) @@ -38,6 +39,10 @@ test.channel_stress.with_multiple_threads = function () test.is_true(data[thr_id .. "_".. iter]) end end + + for _, thread in ipairs(threads) do + thread:wait() + end end test.channel_stress.timed_read = function () diff --git a/tests/lua/gc-stress.lua b/tests/lua/gc-stress.lua index b270f5d..d8a6799 100644 --- a/tests/lua/gc-stress.lua +++ b/tests/lua/gc-stress.lua @@ -3,7 +3,6 @@ require "bootstrap-tests" test.gc_stress.tear_down = default_tear_down -- Regress test for simultaneous object creation and removing --- may cause SIGFAULT, so it's marked as "stress" test.gc_stress.create_and_collect_in_parallel = function () function worker() effil = require "effil" diff --git a/tests/lua/gc.lua b/tests/lua/gc.lua index 12ec068..fc55129 100644 --- a/tests/lua/gc.lua +++ b/tests/lua/gc.lua @@ -38,4 +38,20 @@ test.gc.disable = function () test.equal(gc.count(), 1) gc.resume() -end \ No newline at end of file +end + +test.gc.store_same_value = function() + local fill = function (c) + local a = effil.table {} + c:push(a) + c:push(a) + end + + local c = effil.channel {} + fill(c) + + c:pop() + collectgarbage() + effil.gc.collect() + c:pop()[1] = 0 +end diff --git a/tests/lua/run_tests b/tests/lua/run_tests index c9c8122..ee8cd28 100755 --- a/tests/lua/run_tests +++ b/tests/lua/run_tests @@ -5,6 +5,7 @@ local src_path = scripts_path .. "/../.." package.path = ";" .. scripts_path .. "/?.lua;" .. src_path .. "/src/lua/?.lua;" .. src_path .. "/libs/u-test/?.lua" +package.cpath = "./?.so;" .. package.cpath require "type" require "gc" diff --git a/tests/lua/tests.lua b/tests/lua/tests.lua deleted file mode 100755 index 12c0d1d..0000000 --- a/tests/lua/tests.lua +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env lua - -package.path = ";../tests/lua/?.lua;../libs/u-test/?.lua;../src/lua/?.lua" - -require "type" -require "gc" -require "channel" -require "thread" -require "shared-table" -require "metatable" - -if os.getenv("STRESS") then - require "channel-stress" - require "thread-stress" - require "gc-stress" -end - -test.summary() \ No newline at end of file