Fix GC related bugs (#73)

* Fix GC related bugs
* Fix GCC compilation
* Remove semicolons
* Remove refs_ from SharedTable
This commit is contained in:
Ilia 2017-09-28 11:45:43 +03:00 committed by mihacooper
parent 5a69ad0a97
commit b8ab05f667
26 changed files with 195 additions and 738 deletions

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
namespace effil {
void Channel::getUserType(sol::state_view& lua) {
void Channel::exportAPI(sol::state_view& lua) {
sol::usertype<Channel> 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<sol::object>(lua);
}
Channel::Channel(sol::optional<int> capacity) : data_(std::make_shared<SharedData>()){
Channel::Channel(sol::optional<int> capacity) : data_(std::make_shared<SharedData>()) {
if (capacity) {
REQUIRE(capacity.value() >= 0) << "Invalid capacity value = " << capacity.value();
data_->capacity_ = static_cast<size_t>(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<sol::object>());
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<int>& 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;
}

View File

@ -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<int> 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<int>& duration,

View File

@ -2,7 +2,6 @@
#include "utils.h"
#include <vector>
#include <cassert>
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<std::mutex> 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<std::mutex> g(lock_);
std::vector<GCObjectHandle> grey;
std::map<GCObjectHandle, std::shared_ptr<GCObject>> black;
std::unordered_set<GCObjectHandle> grey;
std::unordered_map<GCObjectHandle, std::unique_ptr<GCObject>> 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(); };

View File

@ -1,42 +1,18 @@
#pragma once
#include "spin-mutex.h"
#include "gc-object.h"
#include <sol.hpp>
#include <mutex>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
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<GCObjectHandle>) {}
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<GCObjectHandle>(refs_.get()); }
size_t instances() const noexcept { return refs_.use_count(); }
const std::set<GCObjectHandle>& refers() const { return *refs_; }
protected:
std::shared_ptr<std::set<GCObjectHandle>> 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 <typename ObjectType, typename... Args>
@ -44,46 +20,46 @@ public:
if (enabled_ && lastCleanup_.fetch_add(1) == step_)
collect();
auto object = std::make_shared<ObjectType>(std::forward<Args>(args)...);
auto object = std::make_unique<ObjectType>(std::forward<Args>(args)...);
auto copy = *object;
std::lock_guard<std::mutex> g(lock_);
objects_[object->handle()] = object;
return *object;
objects_.emplace(object->handle(), std::move(object));
return copy;
}
template <typename ObjectType>
ObjectType get(GCObjectHandle handle) {
std::lock_guard<std::mutex> g(lock_);
// TODO: add dynamic cast to check?
return *static_cast<ObjectType*>(findObject(handle));
auto it = objects_.find(handle);
assert(it != objects_.end());
auto result = dynamic_cast<ObjectType*>(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<size_t> lastCleanup_;
size_t step_;
std::map<GCObjectHandle, std::shared_ptr<GCObject>> objects_;
private:
GCObject* findObject(GCObjectHandle handle);
std::unordered_map<GCObjectHandle, std::unique_ptr<GCObject>> 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

42
src/cpp/gc-object.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "gc-object.h"
#include <mutex>
#include <cassert>
namespace effil {
GCObject::GCObject()
: data_(std::make_shared<SharedData>()) {}
GCObjectHandle GCObject::handle() const {
return reinterpret_cast<GCObjectHandle>(data_.get());
}
size_t GCObject::instances() const {
return data_.use_count();
}
const std::unordered_set<GCObjectHandle> GCObject::refers() const {
std::lock_guard<SpinMutex> lock(data_->mutex_);
return std::unordered_set<GCObjectHandle>(
data_->weakRefs_.begin(),
data_->weakRefs_.end());
}
void GCObject::addReference(GCObjectHandle handle) {
if (handle == nullptr) return;
std::lock_guard<SpinMutex> lock(data_->mutex_);
data_->weakRefs_.insert(handle);
}
void GCObject::removeReference(GCObjectHandle handle) {
if (handle == GCNull) return;
std::lock_guard<SpinMutex> lock(data_->mutex_);
auto hit = data_->weakRefs_.find(handle);
assert(hit != std::end(data_->weakRefs_));
data_->weakRefs_.erase(hit);
}
} // namespace effil

47
src/cpp/gc-object.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include "spin-mutex.h"
#include <unordered_set>
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<GCObjectHandle> refers() const;
protected:
void addReference(GCObjectHandle handle);
void removeReference(GCObjectHandle handle);
private:
struct SharedData {
mutable SpinMutex mutex_;
std::unordered_multiset<GCObjectHandle> weakRefs_;
};
std::shared_ptr<SharedData> data_;
};
} // namespace effil

View File

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

View File

@ -14,15 +14,11 @@ bool isSharedTable(const SolObject& obj) {
return obj.valid() && obj.get_type() == sol::type::userdata && obj.template is<SharedTable>();
}
}
} // namespace
SharedTable::SharedTable() : GCObject(), data_(std::make_shared<SharedData>()) {}
SharedTable::SharedTable() : data_(std::make_shared<SharedData>()) {}
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<SharedTable> 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<SpinMutex> 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<SpinMutex> 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<SpinMutex> lock(stable.data_->lock);
return stable.data_->metatable == GCNull ? sol::nil :
sol::make_object(state, GC::instance().get<SharedTable>(stable.data_->metatable));

View File

@ -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 <sol.hpp>
#include <unordered_map>
#include <map>
#include <memory>
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);

View File

@ -76,7 +76,6 @@ public:
assert(luaObject.template is<T>());
strongRef_ = luaObject.template as<T>();
handle_ = strongRef_->handle();
assert(GC::instance().has(handle_));
}
GCObjectHolder(GCObjectHandle handle)

View File

@ -7,6 +7,7 @@
namespace effil {
// Represents an interface for lua type stored at C++ code
class BaseHolder {
public:
BaseHolder() = default;

View File

@ -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<Thread> type(
"new", sol::no_constructor,
"get", &Thread::get,

View File

@ -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<sol::object, sol::object> status(const sol::this_state& state);
std::pair<sol::object, sol::object> wait(const sol::this_state& state,

View File

@ -1,162 +0,0 @@
#include <gtest/gtest.h>
#include "test-utils.h"
#include "garbage-collector.h"
#include <thread>
#include <vector>
using namespace effil;
TEST(gc, GCObject) {
GCObject o1;
EXPECT_EQ(o1.instances(), (size_t)1);
GCObject o2 = GC::instance().create<GCObject>();
EXPECT_EQ(o2.instances(), (size_t)2);
GCObject o3 = GC::instance().create<GCObject>();
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>();
GCObject o2 = GC::instance().create<GCObject>();
}
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>();
{
Dummy orphan = GC::instance().create<Dummy>();
for (size_t i = 0; i < 3; i++) {
Dummy child = GC::instance().create<Dummy>();
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<std::thread> 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<GCObject>();
});
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<SharedTable>();
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<SharedTable>();
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<SharedTable>();
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);
}

View File

@ -1,80 +0,0 @@
#include <gtest/gtest.h>
#include "notifier.h"
#include <thread>
#include <atomic>
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<std::thread> vt;
std::atomic<size_t> 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();
}

View File

@ -1,301 +0,0 @@
#include <gtest/gtest.h>
#include "test-utils.h"
#include "shared-table.h"
#include "garbage-collector.h"
#include <thread>
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<SharedTable>();
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<std::thread> 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<SharedTable>();
lua["st1"] = GC::instance().create<SharedTable>();
lua["st2"] = GC::instance().create<SharedTable>();
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>(), 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<std::thread> 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<sol::object>(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<sol::lightuserdata_value>().value, &lightUserdata);
}

View File

@ -1,6 +0,0 @@
#include "gtest/gtest.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "shared-table.h"
#include "lua-helpers.h"
#include <sol.hpp>
namespace effil {
inline void bootstrapState(sol::state& lua) {
luaL_openlibs(lua);
SharedTable::getUserType(lua);
}
} // namespace effil

View File

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

View File

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

View File

@ -38,4 +38,20 @@ test.gc.disable = function ()
test.equal(gc.count(), 1)
gc.resume()
end
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

View File

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

View File

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