Initial Garbage collector impl
This commit is contained in:
parent
43dc94df8a
commit
9d9d1d2e36
89
src/cpp/garbage-collector.cpp
Normal file
89
src/cpp/garbage-collector.cpp
Normal 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
|
||||
79
src/cpp/garbage-collector.h
Normal file
79
src/cpp/garbage-collector.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "threading.h"
|
||||
#include "stored-object.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace effil {
|
||||
|
||||
class LuaHookStopException : public std::exception {};
|
||||
|
||||
@ -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
|
||||
|
||||
162
tests/cpp/garbage-collector.cpp
Normal file
162
tests/cpp/garbage-collector.cpp
Normal 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);
|
||||
}
|
||||
@ -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
17
tests/cpp/test-utils.h
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user