GC API for lua (#43)
Almost old gc api, but: rebased on new master GC::State -> enabled_ flag release lua state as soon as possible
This commit is contained in:
parent
6d20b0ff03
commit
64628c1757
@ -7,13 +7,12 @@
|
|||||||
|
|
||||||
namespace effil {
|
namespace effil {
|
||||||
|
|
||||||
GarbageCollector::GarbageCollector()
|
GC::GC()
|
||||||
: state_(GCState::Idle)
|
: enabled_(true)
|
||||||
, lastCleanup_(0)
|
, lastCleanup_(0)
|
||||||
, step_(200) {}
|
, step_(200) {}
|
||||||
|
|
||||||
GCObject* GarbageCollector::get(GCObjectHandle handle) {
|
GCObject* GC::findObject(GCObjectHandle handle) {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
|
||||||
auto it = objects_.find(handle);
|
auto it = objects_.find(handle);
|
||||||
if (it == objects_.end()) {
|
if (it == objects_.end()) {
|
||||||
DEBUG << "Null handle " << handle << std::endl;
|
DEBUG << "Null handle " << handle << std::endl;
|
||||||
@ -22,21 +21,16 @@ GCObject* GarbageCollector::get(GCObjectHandle handle) {
|
|||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GarbageCollector::has(GCObjectHandle handle) const {
|
bool GC::has(GCObjectHandle handle) const {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
std::lock_guard<std::mutex> g(lock_);
|
||||||
return objects_.find(handle) != objects_.end();
|
return objects_.find(handle) != objects_.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here is the naive tri-color marking
|
// Here is the naive tri-color marking
|
||||||
// garbage collecting algorithm implementation.
|
// garbage collecting algorithm implementation.
|
||||||
void GarbageCollector::cleanup() {
|
void GC::collect() {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
std::lock_guard<std::mutex> g(lock_);
|
||||||
|
|
||||||
if (state_ == GCState::Stopped)
|
|
||||||
return;
|
|
||||||
assert(state_ != GCState::Running);
|
|
||||||
state_ = GCState::Running;
|
|
||||||
|
|
||||||
std::vector<GCObjectHandle> grey;
|
std::vector<GCObjectHandle> grey;
|
||||||
std::map<GCObjectHandle, std::shared_ptr<GCObject>> black;
|
std::map<GCObjectHandle, std::shared_ptr<GCObject>> black;
|
||||||
|
|
||||||
@ -59,30 +53,44 @@ void GarbageCollector::cleanup() {
|
|||||||
// Sweep phase
|
// Sweep phase
|
||||||
objects_ = std::move(black);
|
objects_ = std::move(black);
|
||||||
|
|
||||||
state_ = GCState::Idle;
|
|
||||||
lastCleanup_.store(0);
|
lastCleanup_.store(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GarbageCollector::size() const {
|
size_t GC::size() const {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
std::lock_guard<std::mutex> g(lock_);
|
||||||
return objects_.size();
|
return objects_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GarbageCollector::stop() {
|
size_t GC::count() {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
std::lock_guard<std::mutex> g(lock_);
|
||||||
assert(state_ == GCState::Idle || state_ == GCState::Stopped);
|
return objects_.size();
|
||||||
state_ = GCState::Stopped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GarbageCollector::resume() {
|
GC& GC::instance() {
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
static GC pool;
|
||||||
assert(state_ == GCState::Idle || state_ == GCState::Stopped);
|
|
||||||
state_ = GCState::Idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
GarbageCollector& getGC() {
|
|
||||||
static GarbageCollector pool;
|
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sol::table GC::getLuaApi(sol::state_view& lua) {
|
||||||
|
sol::table api = lua.create_table_with();
|
||||||
|
api["collect"] = [=] {
|
||||||
|
instance().collect();
|
||||||
|
};
|
||||||
|
api["pause"] = [] { instance().pause(); };
|
||||||
|
api["resume"] = [] { instance().resume(); };
|
||||||
|
api["enabled"] = [] { return instance().enabled(); };
|
||||||
|
api["step"] = [](sol::optional<int> newStep){
|
||||||
|
auto previous = instance().step();
|
||||||
|
if (newStep) {
|
||||||
|
REQUIRE(*newStep <= 0) << "gc.step have to be > 0";
|
||||||
|
instance().step(static_cast<size_t>(*newStep));
|
||||||
|
}
|
||||||
|
return previous;
|
||||||
|
};
|
||||||
|
api["count"] = [] {
|
||||||
|
return instance().count();
|
||||||
|
};
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
} // effil
|
} // effil
|
||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "spin-mutex.h"
|
#include "spin-mutex.h"
|
||||||
|
|
||||||
|
#include <sol.hpp>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
@ -32,18 +34,17 @@ protected:
|
|||||||
std::shared_ptr<std::set<GCObjectHandle>> refs_;
|
std::shared_ptr<std::set<GCObjectHandle>> refs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class GCState { Idle, Running, Stopped };
|
class GC {
|
||||||
|
|
||||||
class GarbageCollector {
|
|
||||||
public:
|
public:
|
||||||
GarbageCollector();
|
GC();
|
||||||
~GarbageCollector() = default;
|
~GC() = default;
|
||||||
|
|
||||||
// This method is used to create all managed objects.
|
// This method is used to create all managed objects.
|
||||||
template <typename ObjectType, typename... Args>
|
template <typename ObjectType, typename... Args>
|
||||||
ObjectType create(Args&&... args) {
|
ObjectType create(Args&&... args) {
|
||||||
if (lastCleanup_.fetch_add(1) == step_)
|
if (enabled_ && lastCleanup_.fetch_add(1) == step_)
|
||||||
cleanup();
|
collect();
|
||||||
|
|
||||||
auto object = std::make_shared<ObjectType>(std::forward<Args>(args)...);
|
auto object = std::make_shared<ObjectType>(std::forward<Args>(args)...);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> g(lock_);
|
std::lock_guard<std::mutex> g(lock_);
|
||||||
@ -51,28 +52,39 @@ public:
|
|||||||
return *object;
|
return *object;
|
||||||
}
|
}
|
||||||
|
|
||||||
GCObject* get(GCObjectHandle handle);
|
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));
|
||||||
|
}
|
||||||
bool has(GCObjectHandle handle) const;
|
bool has(GCObjectHandle handle) const;
|
||||||
void cleanup();
|
|
||||||
|
void collect();
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
void stop();
|
void pause() { enabled_ = false; };
|
||||||
void resume();
|
void resume() { enabled_ = true; };
|
||||||
size_t step() const { return step_; }
|
size_t step() const { return step_; }
|
||||||
void step(size_t newStep) { step_ = newStep; }
|
void step(size_t newStep) { step_ = newStep; }
|
||||||
GCState state() const { return state_; }
|
bool enabled() { return enabled_; };
|
||||||
|
size_t count();
|
||||||
|
|
||||||
|
static GC& instance();
|
||||||
|
static sol::table getLuaApi(sol::state_view& lua);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::mutex lock_;
|
mutable std::mutex lock_;
|
||||||
GCState state_;
|
bool enabled_;
|
||||||
std::atomic<size_t> lastCleanup_;
|
std::atomic<size_t> lastCleanup_;
|
||||||
size_t step_;
|
size_t step_;
|
||||||
std::map<GCObjectHandle, std::shared_ptr<GCObject>> objects_;
|
std::map<GCObjectHandle, std::shared_ptr<GCObject>> objects_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GarbageCollector(GarbageCollector&&) = delete;
|
GCObject* findObject(GCObjectHandle handle);
|
||||||
GarbageCollector(const GarbageCollector&) = delete;
|
|
||||||
|
private:
|
||||||
|
GC(GC&&) = delete;
|
||||||
|
GC(const GC&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
GarbageCollector& getGC();
|
|
||||||
|
|
||||||
} // effil
|
} // effil
|
||||||
@ -18,9 +18,11 @@ sol::object createThread(const sol::this_state& lua,
|
|||||||
return sol::make_object(lua, std::make_shared<Thread>(path, cpath, stepwise, step, function, args));
|
return sol::make_object(lua, std::make_shared<Thread>(path, cpath, stepwise, step, function, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object createTable(sol::this_state lua) { return sol::make_object(lua, getGC().create<SharedTable>()); }
|
sol::object createTable(sol::this_state lua) {
|
||||||
|
return sol::make_object(lua, GC::instance().create<SharedTable>());
|
||||||
|
}
|
||||||
|
|
||||||
SharedTable globalTable = getGC().create<SharedTable>();
|
SharedTable globalTable = GC::instance().create<SharedTable>();
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -39,7 +41,9 @@ extern "C" int luaopen_libeffil(lua_State* L) {
|
|||||||
"size", SharedTable::luaSize,
|
"size", SharedTable::luaSize,
|
||||||
"setmetatable", SharedTable::luaSetMetatable,
|
"setmetatable", SharedTable::luaSetMetatable,
|
||||||
"getmetatable", SharedTable::luaGetMetatable,
|
"getmetatable", SharedTable::luaGetMetatable,
|
||||||
"G", sol::make_object(lua, globalTable)
|
"G", sol::make_object(lua, globalTable),
|
||||||
|
"getmetatable", SharedTable::luaGetMetatable,
|
||||||
|
"gc", GC::getLuaApi(lua)
|
||||||
);
|
);
|
||||||
sol::stack::push(lua, publicApi);
|
sol::stack::push(lua, publicApi);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@ -104,7 +104,7 @@ sol::object SharedTable::rawGet(const sol::stack_object& luaKey, sol::this_state
|
|||||||
{ \
|
{ \
|
||||||
std::unique_lock<SpinMutex> lock(data_->lock); \
|
std::unique_lock<SpinMutex> lock(data_->lock); \
|
||||||
if (data_->metatable != GCNull) { \
|
if (data_->metatable != GCNull) { \
|
||||||
SharedTable tableHolder = *static_cast<SharedTable*>(getGC().get(data_->metatable)); \
|
auto tableHolder = GC::instance().get<SharedTable>(data_->metatable); \
|
||||||
lock.unlock(); \
|
lock.unlock(); \
|
||||||
sol::function handler = tableHolder.get(createStoredObject(methodName), state); \
|
sol::function handler = tableHolder.get(createStoredObject(methodName), state); \
|
||||||
if (handler.valid()) { \
|
if (handler.valid()) { \
|
||||||
@ -160,7 +160,7 @@ void SharedTable::luaNewIndex(const sol::stack_object& luaKey, const sol::stack_
|
|||||||
{
|
{
|
||||||
std::unique_lock<SpinMutex> lock(data_->lock);
|
std::unique_lock<SpinMutex> lock(data_->lock);
|
||||||
if (data_->metatable != GCNull) {
|
if (data_->metatable != GCNull) {
|
||||||
SharedTable tableHolder = *static_cast<SharedTable*>(getGC().get(data_->metatable));
|
auto tableHolder = GC::instance().get<SharedTable>(data_->metatable);
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
sol::function handler = tableHolder.get(createStoredObject("__newindex"), state);
|
sol::function handler = tableHolder.get(createStoredObject("__newindex"), state);
|
||||||
if (handler.valid()) {
|
if (handler.valid()) {
|
||||||
@ -180,7 +180,8 @@ sol::object SharedTable::luaIndex(const sol::stack_object& luaKey, sol::this_sta
|
|||||||
StoredArray SharedTable::luaCall(sol::this_state state, const sol::variadic_args& args) {
|
StoredArray SharedTable::luaCall(sol::this_state state, const sol::variadic_args& args) {
|
||||||
std::unique_lock<SpinMutex> lock(data_->lock);
|
std::unique_lock<SpinMutex> lock(data_->lock);
|
||||||
if (data_->metatable != GCNull) {
|
if (data_->metatable != GCNull) {
|
||||||
sol::function handler = static_cast<SharedTable*>(getGC().get(data_->metatable))->get(createStoredObject(std::string("__call")), state);
|
auto metatable = GC::instance().get<SharedTable>(data_->metatable);
|
||||||
|
sol::function handler = metatable.get(createStoredObject(std::string("__call")), state);
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
if (handler.valid()) {
|
if (handler.valid()) {
|
||||||
StoredArray storedResults;
|
StoredArray storedResults;
|
||||||
@ -280,7 +281,7 @@ SharedTable SharedTable::luaSetMetatable(SharedTable& stable, const sol::stack_o
|
|||||||
sol::object SharedTable::luaGetMetatable(const SharedTable& stable, sol::this_state state) {
|
sol::object SharedTable::luaGetMetatable(const SharedTable& stable, sol::this_state state) {
|
||||||
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
||||||
return stable.data_->metatable == GCNull ? sol::nil :
|
return stable.data_->metatable == GCNull ? sol::nil :
|
||||||
sol::make_object(state, *static_cast<SharedTable*>(getGC().get(stable.data_->metatable)));
|
sol::make_object(state, GC::instance().get<SharedTable>(stable.data_->metatable));
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object SharedTable::luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state) {
|
sol::object SharedTable::luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state) {
|
||||||
|
|||||||
@ -79,7 +79,7 @@ public:
|
|||||||
TableHolder(const SolType& luaObject) {
|
TableHolder(const SolType& luaObject) {
|
||||||
assert(luaObject.template is<SharedTable>());
|
assert(luaObject.template is<SharedTable>());
|
||||||
handle_ = luaObject.template as<SharedTable>().handle();
|
handle_ = luaObject.template as<SharedTable>().handle();
|
||||||
assert(getGC().has(handle_));
|
assert(GC::instance().has(handle_));
|
||||||
}
|
}
|
||||||
|
|
||||||
TableHolder(GCObjectHandle handle)
|
TableHolder(GCObjectHandle handle)
|
||||||
@ -90,7 +90,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
sol::object unpack(sol::this_state state) const final {
|
sol::object unpack(sol::this_state state) const final {
|
||||||
return sol::make_object(state, *static_cast<SharedTable*>(getGC().get(handle_)));
|
return sol::make_object(state, GC::instance().get<SharedTable>(handle_));
|
||||||
}
|
}
|
||||||
|
|
||||||
GCObjectHandle gcHandle() const override { return handle_; }
|
GCObjectHandle gcHandle() const override { return handle_; }
|
||||||
@ -115,7 +115,7 @@ StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited)
|
|||||||
auto st = std::find_if(visited.begin(), visited.end(), comparator);
|
auto st = std::find_if(visited.begin(), visited.end(), comparator);
|
||||||
|
|
||||||
if (st == std::end(visited)) {
|
if (st == std::end(visited)) {
|
||||||
SharedTable table = getGC().create<SharedTable>();
|
SharedTable table = GC::instance().create<SharedTable>();
|
||||||
visited.emplace_back(std::make_pair(luaTable, table.handle()));
|
visited.emplace_back(std::make_pair(luaTable, table.handle()));
|
||||||
dumpTable(&table, luaTable, visited);
|
dumpTable(&table, luaTable, visited);
|
||||||
return createStoredObject(table.handle());
|
return createStoredObject(table.handle());
|
||||||
@ -160,7 +160,7 @@ StoredObject fromSolObject(const SolObject& luaObject) {
|
|||||||
sol::table luaTable = luaObject;
|
sol::table luaTable = luaObject;
|
||||||
// Tables pool is used to store tables.
|
// Tables pool is used to store tables.
|
||||||
// Right now not defiantly clear how ownership between states works.
|
// Right now not defiantly clear how ownership between states works.
|
||||||
SharedTable table = getGC().create<SharedTable>();
|
SharedTable table = GC::instance().create<SharedTable>();
|
||||||
SolTableToShared visited{{luaTable, table.handle()}};
|
SolTableToShared visited{{luaTable, table.handle()}};
|
||||||
|
|
||||||
// Let's dump table and all subtables
|
// Let's dump table and all subtables
|
||||||
|
|||||||
@ -54,7 +54,6 @@ class ThreadHandle {
|
|||||||
public:
|
public:
|
||||||
const bool managed;
|
const bool managed;
|
||||||
|
|
||||||
sol::state lua;
|
|
||||||
Status status;
|
Status status;
|
||||||
StoredArray result;
|
StoredArray result;
|
||||||
|
|
||||||
@ -68,8 +67,9 @@ public:
|
|||||||
ThreadHandle(bool isManaged)
|
ThreadHandle(bool isManaged)
|
||||||
: managed(isManaged)
|
: managed(isManaged)
|
||||||
, status(Status::Running)
|
, status(Status::Running)
|
||||||
, command_(Command::Run) {
|
, command_(Command::Run)
|
||||||
luaL_openlibs(lua);
|
, lua_(std::make_unique<sol::state>()) {
|
||||||
|
luaL_openlibs(*lua_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Command command() const { return command_; }
|
Command command() const { return command_; }
|
||||||
@ -81,9 +81,18 @@ public:
|
|||||||
command_ = cmd;
|
command_ = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sol::state& lua() {
|
||||||
|
assert(lua_);
|
||||||
|
return *lua_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyLua() { lua_.reset(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SpinMutex commandMutex_;
|
SpinMutex commandMutex_;
|
||||||
Command command_;
|
Command command_;
|
||||||
|
|
||||||
|
std::unique_ptr<sol::state> lua_;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -112,13 +121,8 @@ void luaHook(lua_State*, lua_Debug*) {
|
|||||||
|
|
||||||
class ScopeGuard {
|
class ScopeGuard {
|
||||||
public:
|
public:
|
||||||
ScopeGuard(const std::function<void()>& f)
|
ScopeGuard(const std::function<void()>& f) : f_(f) {}
|
||||||
: f_(f) {
|
~ScopeGuard() { f_(); }
|
||||||
}
|
|
||||||
|
|
||||||
~ScopeGuard() {
|
|
||||||
f_();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<void()> f_;
|
std::function<void()> f_;
|
||||||
@ -130,6 +134,9 @@ void runThread(std::shared_ptr<ThreadHandle> handle,
|
|||||||
|
|
||||||
ScopeGuard reportComplete([=](){
|
ScopeGuard reportComplete([=](){
|
||||||
DEBUG << "Finished " << std::endl;
|
DEBUG << "Finished " << std::endl;
|
||||||
|
// Let's destroy accociated state
|
||||||
|
// to release all resources as soon as possible
|
||||||
|
handle->destroyLua();
|
||||||
handle->completion.notify();
|
handle->completion.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,10 +144,10 @@ void runThread(std::shared_ptr<ThreadHandle> handle,
|
|||||||
thisThreadHandle = handle.get();
|
thisThreadHandle = handle.get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sol::function userFuncObj = loadString(handle->lua, strFunction);
|
sol::function userFuncObj = loadString(handle->lua(), strFunction);
|
||||||
sol::function_result results = userFuncObj(std::move(arguments));
|
sol::function_result results = userFuncObj(std::move(arguments));
|
||||||
(void)results; // just leave all returns on the stack
|
(void)results; // just leave all returns on the stack
|
||||||
sol::variadic_args args(handle->lua, -lua_gettop(handle->lua));
|
sol::variadic_args args(handle->lua(), -lua_gettop(handle->lua()));
|
||||||
for (const auto& iter : args) {
|
for (const auto& iter : args) {
|
||||||
StoredObject store = createStoredObject(iter.get<sol::object>());
|
StoredObject store = createStoredObject(iter.get<sol::object>());
|
||||||
handle->result.emplace_back(std::move(store));
|
handle->result.emplace_back(std::move(store));
|
||||||
@ -193,12 +200,12 @@ Thread::Thread(const std::string& path,
|
|||||||
const sol::variadic_args& variadicArgs)
|
const sol::variadic_args& variadicArgs)
|
||||||
: handle_(std::make_shared<ThreadHandle>(managed)) {
|
: handle_(std::make_shared<ThreadHandle>(managed)) {
|
||||||
|
|
||||||
handle_->lua["package"]["path"] = path;
|
handle_->lua()["package"]["path"] = path;
|
||||||
handle_->lua["package"]["cpath"] = cpath;
|
handle_->lua()["package"]["cpath"] = cpath;
|
||||||
handle_->lua.script("require 'effil'");
|
handle_->lua().script("require 'effil'");
|
||||||
|
|
||||||
if (managed)
|
if (managed)
|
||||||
lua_sethook(handle_->lua, luaHook, LUA_MASKCOUNT, step);
|
lua_sethook(handle_->lua(), luaHook, LUA_MASKCOUNT, step);
|
||||||
|
|
||||||
std::string strFunction = dumpFunction(function);
|
std::string strFunction = dumpFunction(function);
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ local api = {
|
|||||||
setmetatable = capi.setmetatable,
|
setmetatable = capi.setmetatable,
|
||||||
getmetatable = capi.getmetatable,
|
getmetatable = capi.getmetatable,
|
||||||
G = capi.G,
|
G = capi.G,
|
||||||
|
gc = capi.gc
|
||||||
}
|
}
|
||||||
|
|
||||||
local function run_thread(config, f, ...)
|
local function run_thread(config, f, ...)
|
||||||
|
|||||||
@ -12,10 +12,10 @@ TEST(gc, GCObject) {
|
|||||||
GCObject o1;
|
GCObject o1;
|
||||||
EXPECT_EQ(o1.instances(), (size_t)1);
|
EXPECT_EQ(o1.instances(), (size_t)1);
|
||||||
|
|
||||||
GCObject o2 = getGC().create<GCObject>();
|
GCObject o2 = GC::instance().create<GCObject>();
|
||||||
EXPECT_EQ(o2.instances(), (size_t)2);
|
EXPECT_EQ(o2.instances(), (size_t)2);
|
||||||
|
|
||||||
GCObject o3 = getGC().create<GCObject>();
|
GCObject o3 = GC::instance().create<GCObject>();
|
||||||
GCObject o4(o3);
|
GCObject o4(o3);
|
||||||
GCObject o5(o4);
|
GCObject o5(o4);
|
||||||
EXPECT_EQ(o5.instances(), o3.instances());
|
EXPECT_EQ(o5.instances(), o3.instances());
|
||||||
@ -25,18 +25,15 @@ TEST(gc, GCObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, collect) {
|
TEST(gc, collect) {
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
ASSERT_EQ(getGC().size(), (size_t)1);
|
ASSERT_EQ(GC::instance().size(), (size_t)1);
|
||||||
|
|
||||||
{
|
{
|
||||||
GCObject o1 = getGC().create<GCObject>();
|
GCObject o1 = GC::instance().create<GCObject>();
|
||||||
;
|
GCObject o2 = GC::instance().create<GCObject>();
|
||||||
GCObject o2 = getGC().create<GCObject>();
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
EXPECT_EQ(getGC().size(), (size_t)3);
|
EXPECT_EQ(GC::instance().size(), (size_t)3);
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)1);
|
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -44,26 +41,27 @@ namespace {
|
|||||||
struct Dummy : public GCObject {
|
struct Dummy : public GCObject {
|
||||||
void add(GCObjectHandle ref) { refs_->insert(ref); }
|
void add(GCObjectHandle ref) { refs_->insert(ref); }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, withRefs) {
|
TEST(gc, withRefs) {
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
{
|
{
|
||||||
Dummy root = getGC().create<Dummy>();
|
Dummy root = GC::instance().create<Dummy>();
|
||||||
|
|
||||||
{
|
{
|
||||||
Dummy orphan = getGC().create<Dummy>();
|
Dummy orphan = GC::instance().create<Dummy>();
|
||||||
for (size_t i = 0; i < 3; i++) {
|
for (size_t i = 0; i < 3; i++) {
|
||||||
Dummy child = getGC().create<Dummy>();
|
Dummy child = GC::instance().create<Dummy>();
|
||||||
root.add(child.handle());
|
root.add(child.handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPECT_EQ(getGC().size(), (size_t)6);
|
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)5);
|
EXPECT_EQ(GC::instance().size(), (size_t)5);
|
||||||
}
|
}
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)1);
|
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, autoCleanup) {
|
TEST(gc, autoCleanup) {
|
||||||
@ -73,59 +71,59 @@ TEST(gc, autoCleanup) {
|
|||||||
for (size_t i = 0; i < 5; i++)
|
for (size_t i = 0; i < 5; i++)
|
||||||
threads.emplace_back([=] {
|
threads.emplace_back([=] {
|
||||||
for (size_t i = 0; i < objectsPerThread; i++)
|
for (size_t i = 0; i < objectsPerThread; i++)
|
||||||
getGC().create<GCObject>();
|
GC::instance().create<GCObject>();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto& thread : threads)
|
for (auto& thread : threads)
|
||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
EXPECT_LT(getGC().size(), getGC().step());
|
EXPECT_LT(GC::instance().size(), GC::instance().step());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, gcInLuaState) {
|
TEST(gc, gcInLuaState) {
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
bootstrapState(lua);
|
bootstrapState(lua);
|
||||||
|
|
||||||
lua["st"] = getGC().create<SharedTable>();
|
lua["st"] = GC::instance().create<SharedTable>();
|
||||||
lua.script(R"(
|
lua.script(R"(
|
||||||
for i=1,1000 do
|
for i=1,1000 do
|
||||||
st[i] = {"Wow"}
|
st[i] = {"Wow"}
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
EXPECT_EQ(getGC().size(), (size_t)1002);
|
EXPECT_EQ(GC::instance().size(), (size_t)1002);
|
||||||
|
|
||||||
lua.script(R"(
|
lua.script(R"(
|
||||||
for i=1,1000 do
|
for i=1,1000 do
|
||||||
st[i] = nil
|
st[i] = nil
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)2);
|
EXPECT_EQ(GC::instance().size(), (size_t)2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, cycles) {
|
TEST(gc, cycles) {
|
||||||
{
|
{
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
bootstrapState(lua);
|
bootstrapState(lua);
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
|
|
||||||
lua["st"] = getGC().create<SharedTable>();
|
lua["st"] = GC::instance().create<SharedTable>();
|
||||||
lua.script(R"(
|
lua.script(R"(
|
||||||
st.parent = {}
|
st.parent = {}
|
||||||
st.parent.child = { ref = st.parent }
|
st.parent.child = { ref = st.parent }
|
||||||
st[4] = { one = 1 }
|
st[4] = { one = 1 }
|
||||||
st[5] = { flag = true }
|
st[5] = { flag = true }
|
||||||
)");
|
)");
|
||||||
EXPECT_EQ(getGC().size(), (size_t)6);
|
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||||
|
|
||||||
lua.script("st.parent = nil");
|
lua.script("st.parent = nil");
|
||||||
|
|
||||||
lua.collect_garbage();
|
lua.collect_garbage();
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)4);
|
EXPECT_EQ(GC::instance().size(), (size_t)4);
|
||||||
}
|
}
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)1);
|
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(gc, multipleStates) {
|
TEST(gc, multipleStates) {
|
||||||
@ -135,12 +133,12 @@ TEST(gc, multipleStates) {
|
|||||||
bootstrapState(lua2);
|
bootstrapState(lua2);
|
||||||
|
|
||||||
{
|
{
|
||||||
SharedTable st = getGC().create<SharedTable>();
|
SharedTable st = GC::instance().create<SharedTable>();
|
||||||
lua1["st"] = st;
|
lua1["st"] = st;
|
||||||
lua2["st"] = st;
|
lua2["st"] = st;
|
||||||
}
|
}
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)2);
|
EXPECT_EQ(GC::instance().size(), (size_t)2);
|
||||||
|
|
||||||
lua1.script(R"(
|
lua1.script(R"(
|
||||||
st.men = { name = "John", age = 22 }
|
st.men = { name = "John", age = 22 }
|
||||||
@ -152,13 +150,13 @@ st.men.car = st.car
|
|||||||
st.men.cat = st.cat
|
st.men.cat = st.cat
|
||||||
st.men.fish = st.fish
|
st.men.fish = st.fish
|
||||||
)");
|
)");
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)6);
|
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||||
|
|
||||||
lua2.script("copy = { st.men } st = nil");
|
lua2.script("copy = { st.men } st = nil");
|
||||||
lua1.script("st = nil");
|
lua1.script("st = nil");
|
||||||
lua1.collect_garbage();
|
lua1.collect_garbage();
|
||||||
lua2.collect_garbage();
|
lua2.collect_garbage();
|
||||||
getGC().cleanup();
|
GC::instance().collect();
|
||||||
EXPECT_EQ(getGC().size(), (size_t)5);
|
EXPECT_EQ(GC::instance().size(), (size_t)5);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,9 +129,9 @@ TEST(sharedTable, playingWithSharedTables) {
|
|||||||
sol::state lua;
|
sol::state lua;
|
||||||
bootstrapState(lua);
|
bootstrapState(lua);
|
||||||
|
|
||||||
lua["recursive"] = getGC().create<SharedTable>();
|
lua["recursive"] = GC::instance().create<SharedTable>();
|
||||||
lua["st1"] = getGC().create<SharedTable>();
|
lua["st1"] = GC::instance().create<SharedTable>();
|
||||||
lua["st2"] = getGC().create<SharedTable>();
|
lua["st2"] = GC::instance().create<SharedTable>();
|
||||||
|
|
||||||
lua.script(R"(
|
lua.script(R"(
|
||||||
st1.proxy = st2
|
st1.proxy = st2
|
||||||
|
|||||||
40
tests/lua/gc.lua
Normal file
40
tests/lua/gc.lua
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
local effil = require "effil"
|
||||||
|
local gc = effil.gc
|
||||||
|
|
||||||
|
TestGC = { tearDown = tearDown }
|
||||||
|
|
||||||
|
function TestGC:testCleanup()
|
||||||
|
collectgarbage()
|
||||||
|
gc.collect()
|
||||||
|
test.assertEquals(gc.count(), 1)
|
||||||
|
|
||||||
|
for i = 0, 10000 do
|
||||||
|
local tmp = effil.table()
|
||||||
|
end
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
gc.collect()
|
||||||
|
test.assertEquals(gc.count(), 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestGC:testDisableGC()
|
||||||
|
local nobjects = 10000
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
gc.collect()
|
||||||
|
test.assertEquals(gc.count(), 1)
|
||||||
|
|
||||||
|
gc.pause()
|
||||||
|
test.assertFalse(gc.enabled())
|
||||||
|
|
||||||
|
for i = 1, nobjects do
|
||||||
|
local tmp = effil.table()
|
||||||
|
end
|
||||||
|
|
||||||
|
test.assertEquals(gc.count(), nobjects + 1)
|
||||||
|
collectgarbage()
|
||||||
|
gc.collect()
|
||||||
|
test.assertEquals(gc.count(), 1)
|
||||||
|
|
||||||
|
gc.resume()
|
||||||
|
end
|
||||||
@ -31,6 +31,7 @@ effil = require 'effil'
|
|||||||
require 'test_utils'
|
require 'test_utils'
|
||||||
require 'thread'
|
require 'thread'
|
||||||
require 'shared_table'
|
require 'shared_table'
|
||||||
|
require 'gc'
|
||||||
|
|
||||||
-- Hack tests functions to print when test starts
|
-- Hack tests functions to print when test starts
|
||||||
for suite_name, suite in pairs(_G) do
|
for suite_name, suite in pairs(_G) do
|
||||||
|
|||||||
@ -46,6 +46,10 @@ end
|
|||||||
|
|
||||||
function tearDown()
|
function tearDown()
|
||||||
collectgarbage()
|
collectgarbage()
|
||||||
|
effil.gc.collect()
|
||||||
|
-- effil.G is always present
|
||||||
|
-- thus, gc has one object
|
||||||
|
test.assertEquals(effil.gc.count(), 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function make_test_with_param(test_suite, test_case_pattern, ...)
|
function make_test_with_param(test_suite, test_case_pattern, ...)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user