Initial Garbage collector impl

This commit is contained in:
Ilia Udalov 2017-02-08 00:06:26 +03:00
parent 43dc94df8a
commit 9d9d1d2e36
13 changed files with 493 additions and 128 deletions

View File

@ -0,0 +1,89 @@
#include "garbage-collector.h"
#include "utils.h"
#include <vector>
#include <cassert>
namespace effil {
GarbageCollector::GarbageCollector() noexcept
: state_(GCState::Idle),
lastCleanup_(0),
step_(200) {}
GCObject* GarbageCollector::get(GCObjectHandle handle) noexcept {
std::lock_guard<std::mutex> g(lock_);
auto it = objects_.find(handle);
if (it == objects_.end()) {
DEBUG << "Null handle " << handle << std::endl;
return nullptr;
}
return it->second.get();
}
bool GarbageCollector::has(GCObjectHandle handle) const noexcept {
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 GarbageCollector::cleanup() {
std::lock_guard<std::mutex> g(lock_);
if (state_ == GCState::Stopped) return;
assert(state_ != GCState::Running);
state_ = GCState::Running;
std::vector<GCObjectHandle> grey;
std::map<GCObjectHandle, std::shared_ptr<GCObject>> black;
for(const auto& handleAndObject : objects_)
if (handleAndObject.second->instances() > 1)
grey.push_back(handleAndObject.first);
while(!grey.empty()) {
GCObjectHandle handle = grey.back();
grey.pop_back();
auto object = objects_[handle];
black[handle] = object;
for(GCObjectHandle refHandle : object->refers())
if (black.find(refHandle) == black.end())
grey.push_back(refHandle);
}
DEBUG << "Removing " << (objects_.size() - black.size())
<< " out of " << objects_.size() << std::endl;
// Sweep phase
objects_ = std::move(black);
state_ = GCState::Idle;
lastCleanup_.store(0);
}
size_t GarbageCollector::size() const noexcept {
std::lock_guard<std::mutex> g(lock_);
return objects_.size();
}
void GarbageCollector::stop() noexcept {
std::lock_guard<std::mutex> g(lock_);
assert(state_ == GCState::Idle || state_ == GCState::Stopped);
state_ = GCState::Stopped;
}
void GarbageCollector::resume() noexcept {
std::lock_guard<std::mutex> g(lock_);
assert(state_ == GCState::Idle || state_ == GCState::Stopped);
state_ = GCState::Idle;
}
GarbageCollector& getGC() noexcept {
static GarbageCollector pool;
return pool;
}
} // effil

View File

@ -0,0 +1,79 @@
#pragma once
#include "spin-mutex.h"
#include <mutex>
#include <map>
#include <set>
namespace effil {
// Unique handle for all objects spawned from one object.
typedef void* GCObjectHandle;
// 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;
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_;
};
enum class GCState {
Idle,
Running,
Stopped
};
class GarbageCollector {
public:
GarbageCollector() noexcept;
~GarbageCollector() = default;
// This method is used to create all managed objects.
template<typename ObjectType,typename... Args>
ObjectType create(Args&&... args) {
if (lastCleanup_.fetch_add(1) == step_) cleanup();
auto object = std::make_shared<ObjectType>(std::forward<Args>(args)...);
std::lock_guard<std::mutex> g(lock_);
objects_[object->handle()] = object;
return *object;
}
GCObject* get(GCObjectHandle handle) noexcept;
bool has(GCObjectHandle handle) const noexcept;
void cleanup();
size_t size() const noexcept;
void stop() noexcept;
void resume() noexcept;
size_t step() const noexcept { return step_; }
void step(size_t newStep) noexcept { step_ = newStep; }
GCState state() const noexcept { return state_; }
private:
mutable std::mutex lock_;
GCState state_;
std::atomic<size_t> lastCleanup_;
size_t step_;
std::map<GCObjectHandle, std::shared_ptr<GCObject>> objects_;
private:
GarbageCollector(GarbageCollector&&) = delete;
GarbageCollector(const GarbageCollector&) = delete;
};
GarbageCollector& getGC() noexcept;
} // effil

View File

@ -1,16 +1,19 @@
#include "threading.h"
#include "shared-table.h"
#include "garbage-collector.h"
#include <lua.hpp>
using namespace effil;
namespace {
sol::object createThreadFactory(sol::this_state lua, const sol::function& func) {
return sol::make_object(lua, std::make_unique<effil::ThreadFactory>(func));
return sol::make_object(lua, std::make_unique<ThreadFactory>(func));
}
sol::object createShare(sol::this_state lua) {
return sol::make_object(lua, std::make_unique<effil::SharedTable>());
return sol::make_object(lua, getGC().create<SharedTable>());
}
} // namespace
@ -18,13 +21,13 @@ sol::object createShare(sol::this_state lua) {
extern "C" int luaopen_libeffil(lua_State *L) {
sol::state_view lua(L);
effil::LuaThread::getUserType(lua);
effil::SharedTable::getUserType(lua);
effil::ThreadFactory::getUserType(lua);
SharedTable::getUserType(lua);
ThreadFactory::getUserType(lua);
sol::table public_api = lua.create_table_with(
"thread", createThreadFactory,
"thread_id", effil::threadId,
"sleep", effil::sleep,
"yield", effil::yield,
"thread_id", threadId,
"sleep", sleep,
"yield", yield,
"share", createShare
);
sol::stack::push(lua, public_api);

View File

@ -1,10 +1,24 @@
#include "shared-table.h"
#include <cassert>
#include "utils.h"
#include <mutex>
#include <cassert>
namespace effil {
SharedTable::SharedTable() noexcept
: GCObject(),
lock_(new SpinMutex()),
data_(new std::unordered_map<StoredObject, StoredObject>()){
}
SharedTable::SharedTable(const SharedTable& init) noexcept
: GCObject(init),
lock_(init.lock_),
data_(init.data_) {
}
sol::object SharedTable::getUserType(sol::state_view &lua) noexcept {
static sol::usertype<SharedTable> type(
"new", sol::no_constructor,
@ -17,8 +31,12 @@ sol::object SharedTable::getUserType(sol::state_view &lua) noexcept {
}
void SharedTable::set(StoredObject&& key, StoredObject&& value) noexcept {
std::lock_guard<SpinMutex> g(lock_);
data_[std::move(key)] = std::move(value);
std::lock_guard<SpinMutex> g(*lock_);
if (key.isGCObject()) refs_->insert(key.gcHandle());
if (value.isGCObject()) refs_->insert(value.gcHandle());
(*data_)[std::move(key)] = std::move(value);
}
void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue) {
@ -26,9 +44,16 @@ void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_objec
StoredObject key(luaKey);
if (luaValue.get_type() == sol::type::nil) {
std::lock_guard<SpinMutex> g(lock_);
std::lock_guard<SpinMutex> g(*lock_);
// in this case object is not obligatory to own data
data_.erase(key);
auto it = (*data_).find(key);
if (it != (*data_).end()) {
if (it->first.isGCObject()) refs_->erase(it->first.gcHandle());
if (it->second.isGCObject()) refs_->erase(it->second.gcHandle());
(*data_).erase(it);
}
} else {
set(std::move(key), StoredObject(luaValue));
}
@ -38,9 +63,9 @@ sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_st
ASSERT(key.valid());
StoredObject cppKey(key);
std::lock_guard<SpinMutex> g(lock_);
auto val = data_.find(cppKey);
if (val == data_.end()) {
std::lock_guard<SpinMutex> g(*lock_);
auto val = (*data_).find(cppKey);
if (val == (*data_).end()) {
return sol::nil;
} else {
return val->second.unpack(state);
@ -48,29 +73,8 @@ sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_st
}
size_t SharedTable::size() const noexcept {
std::lock_guard<SpinMutex> g(lock_);
return data_.size();
}
SharedTable* TablePool::getNew() noexcept {
SharedTable* ptr = new SharedTable();
std::lock_guard<SpinMutex> g(lock_);
data_.emplace_back(ptr);
return ptr;
}
std::size_t TablePool::size() const noexcept {
std::lock_guard<SpinMutex> g(lock_);
return data_.size();
}
void TablePool::clear() noexcept {
std::lock_guard<SpinMutex> g(lock_);
data_.clear();
}
TablePool& defaultPool() noexcept {
static TablePool pool;
return pool;
std::lock_guard<SpinMutex> g(*lock_);
return data_->size();
}
} // effil

View File

@ -1,5 +1,6 @@
#pragma once
#include "garbage-collector.h"
#include "stored-object.h"
#include "spin-mutex.h"
@ -7,47 +8,28 @@
#include <unordered_map>
#include <memory>
#include <vector>
namespace effil {
class SharedTable {
class SharedTable : public GCObject {
public:
SharedTable() = default;
SharedTable() noexcept;
SharedTable(SharedTable&&) = default;
SharedTable(const SharedTable& init) noexcept;
virtual ~SharedTable() = default;
static sol::object getUserType(sol::state_view &lua) noexcept;
void set(StoredObject&&, StoredObject&&) noexcept;
size_t size() const noexcept;
public: // lua bindings
// These functions could be invoked from lua scripts
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;
protected:
mutable SpinMutex lock_;
std::unordered_map<StoredObject, StoredObject> data_;
size_t size() const noexcept;
private:
SharedTable(const SharedTable&) = delete;
SharedTable& operator=(const SharedTable&) = delete;
mutable std::shared_ptr<SpinMutex> lock_;
std::shared_ptr<std::unordered_map<StoredObject, StoredObject>> data_;
};
class TablePool {
public:
TablePool() = default;
SharedTable* getNew() noexcept;
std::size_t size() const noexcept;
void clear() noexcept;
private:
mutable SpinMutex lock_;
std::vector<std::unique_ptr<SharedTable>> data_;
private:
TablePool(const TablePool&) = delete;
};
TablePool& defaultPool() noexcept;
} // effil

View File

@ -72,26 +72,55 @@ private:
std::string function_;
};
class TableHolder : public BaseHolder {
public:
template<typename SolType>
TableHolder(const SolType& luaObject) noexcept {
assert(luaObject.template is<SharedTable>());
handle_ = luaObject.template as<SharedTable>().handle();
assert(getGC().has(handle_));
}
TableHolder(GCObjectHandle handle) noexcept
: handle_(handle) {}
bool compare(const BaseHolder *other) const noexcept final {
return BaseHolder::compare(other) && static_cast<const TableHolder*>(other)->handle_ == handle_;
}
std::size_t hash() const noexcept final {
return std::hash<GCObjectHandle>()(handle_);
}
sol::object unpack(sol::this_state state) const noexcept final {
return sol::make_object(state, *static_cast<SharedTable*>(getGC().get(handle_)));
}
GCObjectHandle handle() const noexcept { return handle_; }
private:
GCObjectHandle handle_;
};
// This class is used as a storage for visited sol::tables
// TODO: try to use map or unordered map instead of linear search in vector
// TODO: Trick is - sol::object has only operator==:/
typedef std::vector<std::pair<sol::object, SharedTable*>> SolTableToShared;
typedef std::vector<std::pair<sol::object, GCObjectHandle>> SolTableToShared;
void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited);
StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) {
if (luaObject.get_type() == sol::type::table) {
sol::table luaTable = luaObject;
auto comparator = [&luaTable](const std::pair<sol::table, SharedTable*>& element){
auto comparator = [&luaTable](const std::pair<sol::table, GCObjectHandle>& element){
return element.first == luaTable;
};
auto st = std::find_if(visited.begin(), visited.end(), comparator);
if (st == std::end(visited)) {
SharedTable* table = defaultPool().getNew();
visited.emplace_back(std::make_pair(luaTable, table));
dumpTable(table, luaTable, visited);
return StoredObject(table);
SharedTable table = getGC().create<SharedTable>();
visited.emplace_back(std::make_pair(luaTable, table.handle()));
dumpTable(&table, luaTable, visited);
return StoredObject(table.handle());
} else {
return StoredObject(st->second);
}
@ -118,7 +147,7 @@ std::unique_ptr<BaseHolder> fromSolObject(const SolObject& luaObject) {
case sol::type::string:
return std::make_unique<PrimitiveHolder<std::string>>(luaObject);
case sol::type::userdata:
return std::make_unique<PrimitiveHolder<SharedTable*>>(luaObject);
return std::make_unique<TableHolder>(luaObject);
case sol::type::function:
return std::make_unique<FunctionHolder>(luaObject);
case sol::type::table:
@ -126,14 +155,14 @@ std::unique_ptr<BaseHolder> fromSolObject(const SolObject& luaObject) {
sol::table luaTable = luaObject;
// Tables pool is used to store tables.
// Right now not defiantly clear how ownership between states works.
SharedTable* table = defaultPool().getNew();
SolTableToShared visited{{luaTable, table}};
SharedTable table = getGC().create<SharedTable>();
SolTableToShared visited{{luaTable, table.handle()}};
// Let's dump table and all subtables
// SolTableToShared is used to prevent from infinity recursion
// in recursive tables
dumpTable(table, luaTable, visited);
return std::make_unique<PrimitiveHolder<SharedTable*>>(table);
dumpTable(&table, luaTable, visited);
return std::make_unique<TableHolder>(table.handle());
}
default:
ERROR << "Unable to store object of that type: " << (int)luaObject.get_type() << "\n";
@ -146,8 +175,8 @@ std::unique_ptr<BaseHolder> fromSolObject(const SolObject& luaObject) {
StoredObject::StoredObject(StoredObject&& init) noexcept
: data_(std::move(init.data_)) {}
StoredObject::StoredObject(SharedTable* table) noexcept
: data_(new PrimitiveHolder<SharedTable*>(table)) {
StoredObject::StoredObject(GCObjectHandle handle) noexcept
: data_(new TableHolder(handle)) {
}
StoredObject::StoredObject(const sol::object& object)
@ -176,6 +205,14 @@ sol::object StoredObject::unpack(sol::this_state state) const {
return sol::nil;
}
bool StoredObject::isGCObject() const noexcept {
return data_->type() == typeid(TableHolder);
}
GCObjectHandle StoredObject::gcHandle() const noexcept {
return ((TableHolder*)data_.get())->handle();
}
StoredObject& StoredObject::operator=(StoredObject&& o) noexcept {
data_ = std::move(o.data_);
return *this;

View File

@ -1,9 +1,8 @@
#pragma once
#include <utils.h>
#include "garbage-collector.h"
#include <utility>
#include <iostream>
#include <sol.hpp>
namespace effil {
@ -12,9 +11,8 @@ public:
BaseHolder() = default;
virtual ~BaseHolder() = default;
virtual bool compare(const BaseHolder* other) const noexcept {
return typeid(*this) == typeid(*other);
}
virtual bool compare(const BaseHolder* other) const noexcept { return typeid(*this) == typeid(*other); }
virtual const std::type_info& type() { return typeid(*this); }
virtual std::size_t hash() const noexcept = 0;
virtual sol::object unpack(sol::this_state state) const = 0;
@ -24,19 +22,22 @@ private:
BaseHolder(BaseHolder&) = delete;
};
class SharedTable;
class StoredObject {
public:
StoredObject() = default;
StoredObject(StoredObject&& init) noexcept;
StoredObject(SharedTable*) noexcept;
StoredObject(GCObjectHandle) noexcept;
StoredObject(const sol::object&);
StoredObject(const sol::stack_object&);
operator bool() const noexcept;
std::size_t hash() const noexcept;
sol::object unpack(sol::this_state state) const;
bool isGCObject() const noexcept;
GCObjectHandle gcHandle() const noexcept;
StoredObject& operator=(StoredObject&& o) noexcept;
bool operator==(const StoredObject& o) const noexcept;

View File

@ -1,6 +1,8 @@
#include "threading.h"
#include "stored-object.h"
#include "utils.h"
namespace effil {
class LuaHookStopException : public std::exception {};

View File

@ -6,7 +6,6 @@
#include <sol.hpp>
namespace effil {
namespace utils {
class Exception : public sol::error {
public:
@ -28,8 +27,13 @@ private:
std::string message_;
};
} // utils
} // effil
#define ERROR throw effil::utils::Exception() << __FILE__ << ":" << __LINE__
#define ERROR throw effil::Exception() << __FILE__ << ":" << __LINE__
#define ASSERT(cond) if (!(cond)) ERROR << "In condition '" << #cond << "': "
#ifdef NDEBUG
# define DEBUG if (false) std::cout
#else
# define DEBUG std::cout
#endif

View File

@ -0,0 +1,162 @@
#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 = getGC().create<GCObject>();
EXPECT_EQ(o2.instances(), (size_t)2);
GCObject o3 = getGC().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) {
getGC().cleanup();
ASSERT_EQ(getGC().size(), (size_t)0);
{
GCObject o1 = getGC().create<GCObject>();;
GCObject o2 = getGC().create<GCObject>();;
}
EXPECT_EQ(getGC().size(), (size_t)2);
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)0);
}
namespace {
struct Dummy : public GCObject {
void add(GCObjectHandle ref) { refs_->insert(ref); }
};
}
TEST(gc, withRefs) {
getGC().cleanup();
{
Dummy root = getGC().create<Dummy>();
{
Dummy orphan = getGC().create<Dummy>();
for(size_t i = 0; i < 3; i++) {
Dummy child = getGC().create<Dummy>();
root.add(child.handle());
}
}
EXPECT_EQ(getGC().size(), (size_t) 5);
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t) 4);
}
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)0);
}
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++)
getGC().create<GCObject>();
});
for(auto& thread : threads) thread.join();
EXPECT_LT(getGC().size(), getGC().step());
}
TEST(gc, gcInLuaState) {
sol::state lua;
bootstrapState(lua);
lua["st"] = getGC().create<SharedTable>();
lua.script(R"(
for i=1,1000 do
st[i] = {"Wow"}
end
)");
EXPECT_EQ(getGC().size(), (size_t)1001);
lua.script(R"(
for i=1,1000 do
st[i] = nil
end
)");
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)1);
}
TEST(gc, cycles) {
{
sol::state lua;
bootstrapState(lua);
getGC().cleanup();
lua["st"] = getGC().create<SharedTable>();
lua.script(R"(
st.parent = {}
st.parent.child = { ref = st.parent }
st[4] = { one = 1 }
st[5] = { flag = true }
)");
EXPECT_EQ(getGC().size(), (size_t)5);
lua.script("st.parent = nil");
lua.collect_garbage();
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)3);
}
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)0);
}
TEST(gc, multipleStates) {
sol::state lua1;
sol::state lua2;
bootstrapState(lua1);
bootstrapState(lua2);
{
SharedTable st = getGC().create<SharedTable>();
lua1["st"] = st;
lua2["st"] = st;
}
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)1);
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
)");
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)5);
lua2.script("copy = { st.men } st = nil");
lua1.script("st = nil");
lua1.collect_garbage();
lua2.collect_garbage();
getGC().cleanup();
EXPECT_EQ(getGC().size(), (size_t)4);
}

View File

@ -1,30 +1,18 @@
#include <gtest/gtest.h>
#include "test-utils.h"
#include "shared-table.h"
#include "garbage-collector.h"
#include <thread>
using namespace effil;
namespace {
void bootstrapState(sol::state& lua) {
lua.open_libraries(
sol::lib::base,
sol::lib::string,
sol::lib::table
);
SharedTable::getUserType(lua);
}
} // namespace
TEST(sharedTable, primitiveTypes) {
SharedTable st;
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = SharedTable();
auto res1 = lua.script(R"(
st.fst = "first"
@ -92,28 +80,28 @@ TEST(sharedTable, multipleThreads) {
std::vector<std::thread> threads;
threads.emplace_back([&](){
threads.emplace_back([=](){
sol::state lua;
bootstrapState(lua);;
lua["st"] = &st;
lua["st"] = st;
lua.script(R"(
while not st.ready do end
st.fst = true)");
});
threads.emplace_back([&](){
threads.emplace_back([=](){
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
lua.script(R"(
while not st.ready do end
st.snd = true)");
});
threads.emplace_back([&](){
threads.emplace_back([=](){
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
lua.script(R"(
while not st.ready do end
st.thr = true)");
@ -121,7 +109,7 @@ st.thr = true)");
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
lua.script("st.ready = true");
for(auto& thread : threads) { thread.join(); }
@ -132,13 +120,12 @@ st.thr = true)");
}
TEST(sharedTable, playingWithSharedTables) {
SharedTable recursive, st1, st2;
sol::state lua;
bootstrapState(lua);
lua["recursive"] = &recursive;
lua["st1"] = &st1;
lua["st2"] = &st2;
lua["recursive"] = getGC().create<SharedTable>();
lua["st1"] = getGC().create<SharedTable>();
lua["st2"] = getGC().create<SharedTable>();
lua.script(R"(
st1.proxy = st2
@ -155,7 +142,7 @@ TEST(sharedTable, playingWithFunctions) {
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
lua.script(R"(
st.fn = function ()
@ -171,7 +158,7 @@ st.fn()
sol::state lua2;
bootstrapState(lua2);
lua2["st2"] = &st;
lua2["st2"] = st;
lua2.script(R"(
st2.fn2 = function(str)
return "*" .. str .. "*"
@ -188,7 +175,7 @@ TEST(sharedTable, playingWithTables) {
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
auto res = lua.script(R"(
st.works = "fine"
st.person = {name = 'John Doe', age = 25}
@ -216,8 +203,6 @@ st.recursive = recursive
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"));
defaultPool().clear();
}
TEST(sharedTable, stress) {
@ -225,7 +210,7 @@ TEST(sharedTable, stress) {
bootstrapState(lua);
SharedTable st;
lua["st"] = &st;
lua["st"] = st;
auto res1 = lua.script(R"(
for i = 1, 1000000 do
@ -251,14 +236,14 @@ TEST(sharedTable, stressWithThreads) {
const size_t threadCount = 10;
std::vector<std::thread> threads;
for(size_t i = 0; i < threadCount; i++) {
threads.emplace_back([&st, thrId(i)] {
threads.emplace_back([=] {
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
std::stringstream ss;
ss << "st[" << thrId << "] = 1" << std::endl;
ss << "st[" << i << "] = 1" << std::endl;
ss << "for i = 1, 100000 do" << std::endl;
ss << " st[" << thrId << "] = " << "st[" << thrId << "] + 1" << std::endl;
ss << " st[" << i << "] = " << "st[" << i << "] + 1" << std::endl;
ss << "end" << std::endl;
lua.script(ss.str());
});
@ -270,7 +255,7 @@ TEST(sharedTable, stressWithThreads) {
sol::state lua;
bootstrapState(lua);
lua["st"] = &st;
lua["st"] = st;
for(size_t i = 0; i < threadCount; i++) {
EXPECT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl;
}

17
tests/cpp/test-utils.h Normal file
View File

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