Fix strong reference race in GC (#65)
* Fix strong reference race in GC * Add regress test * Fix type stored in StoredObject * Fix thread input arguments lifetime
This commit is contained in:
parent
5aa43c0b8a
commit
641d111c23
@ -36,6 +36,7 @@ bool Channel::push(const sol::variadic_args& args) {
|
|||||||
auto obj = createStoredObject(arg.get<sol::object>());
|
auto obj = createStoredObject(arg.get<sol::object>());
|
||||||
if (obj->gcHandle())
|
if (obj->gcHandle())
|
||||||
refs_->insert(obj->gcHandle());
|
refs_->insert(obj->gcHandle());
|
||||||
|
obj->releaseStrongReference();
|
||||||
array.emplace_back(obj);
|
array.emplace_back(obj);
|
||||||
}
|
}
|
||||||
if (data_->channel_.empty())
|
if (data_->channel_.empty())
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public:
|
|||||||
: refs_(new std::set<GCObjectHandle>) {}
|
: refs_(new std::set<GCObjectHandle>) {}
|
||||||
GCObject(const GCObject& init) = default;
|
GCObject(const GCObject& init) = default;
|
||||||
GCObject(GCObject&& init) = default;
|
GCObject(GCObject&& init) = default;
|
||||||
|
GCObject& operator=(const GCObject& init) = default;
|
||||||
virtual ~GCObject() = default;
|
virtual ~GCObject() = default;
|
||||||
|
|
||||||
GCObjectHandle handle() const noexcept { return reinterpret_cast<GCObjectHandle>(refs_.get()); }
|
GCObjectHandle handle() const noexcept { return reinterpret_cast<GCObjectHandle>(refs_.get()); }
|
||||||
|
|||||||
@ -54,6 +54,8 @@ void SharedTable::set(StoredObject&& key, StoredObject&& value) {
|
|||||||
refs_->insert(key->gcHandle());
|
refs_->insert(key->gcHandle());
|
||||||
if (value->gcHandle())
|
if (value->gcHandle())
|
||||||
refs_->insert(value->gcHandle());
|
refs_->insert(value->gcHandle());
|
||||||
|
key->releaseStrongReference();
|
||||||
|
value->releaseStrongReference();
|
||||||
|
|
||||||
data_->entries[std::move(key)] = std::move(value);
|
data_->entries[std::move(key)] = std::move(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public:
|
|||||||
SharedTable();
|
SharedTable();
|
||||||
SharedTable(SharedTable&&) = default;
|
SharedTable(SharedTable&&) = default;
|
||||||
SharedTable(const SharedTable& init);
|
SharedTable(const SharedTable& init);
|
||||||
|
SharedTable& operator=(const SharedTable&) = default;
|
||||||
virtual ~SharedTable() = default;
|
virtual ~SharedTable() = default;
|
||||||
|
|
||||||
static void getUserType(sol::state_view& lua);
|
static void getUserType(sol::state_view& lua);
|
||||||
|
|||||||
@ -74,12 +74,15 @@ public:
|
|||||||
template <typename SolType>
|
template <typename SolType>
|
||||||
GCObjectHolder(const SolType& luaObject) {
|
GCObjectHolder(const SolType& luaObject) {
|
||||||
assert(luaObject.template is<T>());
|
assert(luaObject.template is<T>());
|
||||||
handle_ = luaObject.template as<T>().handle();
|
strongRef_ = luaObject.template as<T>();
|
||||||
|
handle_ = strongRef_->handle();
|
||||||
assert(GC::instance().has(handle_));
|
assert(GC::instance().has(handle_));
|
||||||
}
|
}
|
||||||
|
|
||||||
GCObjectHolder(GCObjectHandle handle)
|
GCObjectHolder(GCObjectHandle handle)
|
||||||
: handle_(handle) {}
|
: handle_(handle) {
|
||||||
|
strongRef_ = GC::instance().get<T>(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
bool rawCompare(const BaseHolder* other) const final {
|
bool rawCompare(const BaseHolder* other) const final {
|
||||||
return handle_ < static_cast<const GCObjectHolder<T>*>(other)->handle_;
|
return handle_ < static_cast<const GCObjectHolder<T>*>(other)->handle_;
|
||||||
@ -91,8 +94,13 @@ public:
|
|||||||
|
|
||||||
GCObjectHandle gcHandle() const override { return handle_; }
|
GCObjectHandle gcHandle() const override { return handle_; }
|
||||||
|
|
||||||
|
void releaseStrongReference() override {
|
||||||
|
strongRef_ = sol::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCObjectHandle handle_;
|
GCObjectHandle handle_;
|
||||||
|
sol::optional<T> strongRef_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This class is used as a storage for visited sol::tables
|
// This class is used as a storage for visited sol::tables
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public:
|
|||||||
virtual const std::type_info& type() { return typeid(*this); }
|
virtual const std::type_info& type() { return typeid(*this); }
|
||||||
virtual sol::object unpack(sol::this_state state) const = 0;
|
virtual sol::object unpack(sol::this_state state) const = 0;
|
||||||
virtual GCObjectHandle gcHandle() const { return GCNull; }
|
virtual GCObjectHandle gcHandle() const { return GCNull; }
|
||||||
|
virtual void releaseStrongReference() { }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BaseHolder(const BaseHolder&) = delete;
|
BaseHolder(const BaseHolder&) = delete;
|
||||||
|
|||||||
@ -171,10 +171,11 @@ void runThread(std::shared_ptr<ThreadHandle> handle,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
{
|
{
|
||||||
ScopeGuard reportComplete([=](){
|
ScopeGuard reportComplete([handle, &arguments](){
|
||||||
DEBUG << "Finished " << std::endl;
|
DEBUG << "Finished " << std::endl;
|
||||||
// Let's destroy accociated state
|
// Let's destroy accociated state
|
||||||
// to release all resources as soon as possible
|
// to release all resources as soon as possible
|
||||||
|
arguments.clear();
|
||||||
handle->destroyLua();
|
handle->destroyLua();
|
||||||
});
|
});
|
||||||
sol::function userFuncObj = loadString(handle->lua(), strFunction);
|
sol::function userFuncObj = loadString(handle->lua(), strFunction);
|
||||||
|
|||||||
40
tests/lua/gc-stress.lua
Normal file
40
tests/lua/gc-stress.lua
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
require "bootstrap-tests"
|
||||||
|
|
||||||
|
test.gc_stress.tear_down = default_tear_down
|
||||||
|
|
||||||
|
-- Regress test for simultaneous object creation and removing
|
||||||
|
-- may cause SIGFAULT, so it's marked as "stress"
|
||||||
|
test.gc_stress.create_and_collect_in_parallel = function ()
|
||||||
|
function worker()
|
||||||
|
effil = require "effil"
|
||||||
|
local nested_table = {
|
||||||
|
{}, --[[1 level]]
|
||||||
|
{{}}, --[[2 levels]]
|
||||||
|
{{{}}}, --[[3 levels]]
|
||||||
|
{{{{}}}} --[[4 levels]]
|
||||||
|
}
|
||||||
|
for i = 1, 100 do
|
||||||
|
for t = 1, 10 do
|
||||||
|
local tbl = effil.table(nested_table)
|
||||||
|
for l = 1, 10 do
|
||||||
|
tbl[l] = nested_table
|
||||||
|
end
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
effil.gc.collect()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local thread_num = 10
|
||||||
|
local threads = {}
|
||||||
|
|
||||||
|
for i = 1, thread_num do
|
||||||
|
threads[i] = effil.thread(worker)(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, thread_num do
|
||||||
|
test.equal(threads[i]:wait(), "completed")
|
||||||
|
end
|
||||||
|
|
||||||
|
test.equal(effil.gc.count(), 1)
|
||||||
|
end
|
||||||
@ -12,6 +12,7 @@ require "metatable"
|
|||||||
if os.getenv("STRESS") then
|
if os.getenv("STRESS") then
|
||||||
require "channel-stress"
|
require "channel-stress"
|
||||||
require "thread-stress"
|
require "thread-stress"
|
||||||
|
require "gc-stress"
|
||||||
end
|
end
|
||||||
|
|
||||||
test.summary()
|
test.summary()
|
||||||
Loading…
x
Reference in New Issue
Block a user