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:
Ilia 2017-04-16 14:08:24 +03:00 committed by mihacooper
parent 6d20b0ff03
commit 64628c1757
12 changed files with 188 additions and 112 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
} }

View File

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

View File

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

View File

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