Fix GC related bugs (#73)
* Fix GC related bugs * Fix GCC compilation * Remove semicolons * Remove refs_ from SharedTable
This commit is contained in:
parent
5a69ad0a97
commit
b8ab05f667
@ -105,4 +105,4 @@ matrix:
|
||||
install:
|
||||
- brew install luajit
|
||||
script:
|
||||
- LUA_BIN=luajit SKIP_CPP_TESTS=1 ci/test_all.sh -DLUA_INCLUDE_DIR="/usr/local/Cellar/luajit/2.0.5/include/luajit-2.0" -DLUA_LIBRARY="/usr/local/Cellar/luajit/2.0.5/lib/libluajit.dylib"
|
||||
- LUA_BIN=luajit ci/test_all.sh -DLUA_INCLUDE_DIR="/usr/local/Cellar/luajit/2.0.5/include/luajit-2.0" -DLUA_LIBRARY="/usr/local/Cellar/luajit/2.0.5/lib/libluajit.dylib"
|
||||
|
||||
@ -34,19 +34,6 @@ set(CMAKE_CXX_FLAGS "${EXTRA_FLAGS} ${CMAKE_CXX_FLAGS} ${GENERAL} ${ENABLE_WARNI
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -O0 -g -UNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -g0 -DNDEBUG")
|
||||
|
||||
#----------
|
||||
# TESTS ---
|
||||
#----------
|
||||
if (NOT BUILD_ROCK)
|
||||
FILE(GLOB TEST_SOURCES tests/cpp/*.cpp tests/cpp/*.h)
|
||||
set(GTEST_DIR libs/gtest/googletest)
|
||||
set(LUA_TESTS tests/lua/tests.lua)
|
||||
|
||||
include_directories(${GTEST_DIR}/include ${GTEST_DIR})
|
||||
add_executable(tests ${TEST_SOURCES} ${GTEST_DIR}/src/gtest-all.cc)
|
||||
target_link_libraries(tests effil ${LUA_LIBRARY})
|
||||
endif()
|
||||
|
||||
#----------
|
||||
# INSTALL -
|
||||
#----------
|
||||
|
||||
@ -9,14 +9,5 @@ for build_type in debug release; do
|
||||
cmake -H. -B$build_type -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=$build_type $@
|
||||
cmake --build $build_type --config Release
|
||||
|
||||
# FIXME: creation of sol::state with luajit in c++ tests
|
||||
# leads to memory corruption segmentation fault
|
||||
# this is temporary workaround
|
||||
if [ -z "$SKIP_CPP_TESTS" ]; then
|
||||
(cd $build_type && ./tests)
|
||||
else
|
||||
echo "C++ tests skipped!"
|
||||
fi
|
||||
|
||||
(cd $build_type && STRESS=1 $LUA_BIN ../tests/lua/run_tests)
|
||||
done
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
namespace effil {
|
||||
|
||||
void Channel::getUserType(sol::state_view& lua) {
|
||||
void Channel::exportAPI(sol::state_view& lua) {
|
||||
sol::usertype<Channel> type("new", sol::no_constructor,
|
||||
"push", &Channel::push,
|
||||
"pop", &Channel::pop,
|
||||
@ -14,7 +14,7 @@ void Channel::getUserType(sol::state_view& lua) {
|
||||
sol::stack::pop<sol::object>(lua);
|
||||
}
|
||||
|
||||
Channel::Channel(sol::optional<int> capacity) : data_(std::make_shared<SharedData>()){
|
||||
Channel::Channel(sol::optional<int> capacity) : data_(std::make_shared<SharedData>()) {
|
||||
if (capacity) {
|
||||
REQUIRE(capacity.value() >= 0) << "Invalid capacity value = " << capacity.value();
|
||||
data_->capacity_ = static_cast<size_t>(capacity.value());
|
||||
@ -34,8 +34,8 @@ bool Channel::push(const sol::variadic_args& args) {
|
||||
effil::StoredArray array;
|
||||
for (const auto& arg : args) {
|
||||
auto obj = createStoredObject(arg.get<sol::object>());
|
||||
if (obj->gcHandle())
|
||||
refs_->insert(obj->gcHandle());
|
||||
|
||||
addReference(obj->gcHandle());
|
||||
obj->releaseStrongReference();
|
||||
array.emplace_back(obj);
|
||||
}
|
||||
@ -59,10 +59,9 @@ StoredArray Channel::pop(const sol::optional<int>& duration,
|
||||
}
|
||||
|
||||
auto ret = data_->channel_.front();
|
||||
for (const auto& obj: ret) {
|
||||
if (obj->gcHandle())
|
||||
refs_->erase(obj->gcHandle());
|
||||
}
|
||||
for (const auto& obj: ret)
|
||||
removeReference(obj->gcHandle());
|
||||
|
||||
data_->channel_.pop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "gc-object.h"
|
||||
#include "notifier.h"
|
||||
#include "stored-object.h"
|
||||
#include "lua-helpers.h"
|
||||
@ -10,7 +11,7 @@ namespace effil {
|
||||
class Channel : public GCObject {
|
||||
public:
|
||||
Channel(sol::optional<int> capacity);
|
||||
static void getUserType(sol::state_view& lua);
|
||||
static void exportAPI(sol::state_view& lua);
|
||||
|
||||
bool push(const sol::variadic_args& args);
|
||||
StoredArray pop(const sol::optional<int>& duration,
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace effil {
|
||||
@ -12,41 +11,29 @@ GC::GC()
|
||||
, lastCleanup_(0)
|
||||
, step_(200) {}
|
||||
|
||||
GCObject* GC::findObject(GCObjectHandle handle) {
|
||||
auto it = objects_.find(handle);
|
||||
if (it == objects_.end()) {
|
||||
DEBUG << "Null handle " << handle << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
bool GC::has(GCObjectHandle handle) const {
|
||||
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 GC::collect() {
|
||||
std::lock_guard<std::mutex> g(lock_);
|
||||
|
||||
std::vector<GCObjectHandle> grey;
|
||||
std::map<GCObjectHandle, std::shared_ptr<GCObject>> black;
|
||||
std::unordered_set<GCObjectHandle> grey;
|
||||
std::unordered_map<GCObjectHandle, std::unique_ptr<GCObject>> black;
|
||||
|
||||
for (const auto& handleAndObject : objects_)
|
||||
if (handleAndObject.second->instances() > 1)
|
||||
grey.push_back(handleAndObject.first);
|
||||
grey.insert(handleAndObject.first);
|
||||
|
||||
while (!grey.empty()) {
|
||||
GCObjectHandle handle = grey.back();
|
||||
grey.pop_back();
|
||||
auto it = grey.begin();
|
||||
GCObjectHandle handle = *it;
|
||||
grey.erase(it);
|
||||
|
||||
auto object = objects_[handle];
|
||||
black[handle] = object;
|
||||
for (GCObjectHandle refHandle : object->refers())
|
||||
if (black.find(refHandle) == black.end())
|
||||
grey.push_back(refHandle);
|
||||
black[handle] = std::move(objects_[handle]);
|
||||
for (GCObjectHandle refHandle : black[handle]->refers()) {
|
||||
assert(objects_.count(refHandle));
|
||||
if (black.count(refHandle) == 0 && grey.count(refHandle) == 0)
|
||||
grey.insert(refHandle);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG << "Removing " << (objects_.size() - black.size()) << " out of " << objects_.size() << std::endl;
|
||||
@ -67,15 +54,13 @@ size_t GC::count() {
|
||||
}
|
||||
|
||||
GC& GC::instance() {
|
||||
static GC pool;
|
||||
return pool;
|
||||
static GC gc;
|
||||
return gc;
|
||||
}
|
||||
|
||||
sol::table GC::getLuaApi(sol::state_view& lua) {
|
||||
sol::table GC::exportAPI(sol::state_view& lua) {
|
||||
sol::table api = lua.create_table_with();
|
||||
api["collect"] = [=] {
|
||||
instance().collect();
|
||||
};
|
||||
api["collect"] = [=] { instance().collect(); };
|
||||
api["pause"] = [] { instance().pause(); };
|
||||
api["resume"] = [] { instance().resume(); };
|
||||
api["enabled"] = [] { return instance().enabled(); };
|
||||
|
||||
@ -1,42 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "spin-mutex.h"
|
||||
#include "gc-object.h"
|
||||
#include <sol.hpp>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace effil {
|
||||
|
||||
// Unique handle for all objects spawned from one object.
|
||||
typedef void* GCObjectHandle;
|
||||
|
||||
static const GCObjectHandle GCNull = nullptr;
|
||||
|
||||
// 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;
|
||||
GCObject& operator=(const 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_;
|
||||
};
|
||||
|
||||
class GC {
|
||||
public:
|
||||
GC();
|
||||
~GC() = default;
|
||||
// global gc instance
|
||||
static GC& instance();
|
||||
static sol::table exportAPI(sol::state_view& lua);
|
||||
|
||||
// This method is used to create all managed objects.
|
||||
template <typename ObjectType, typename... Args>
|
||||
@ -44,46 +20,46 @@ public:
|
||||
if (enabled_ && lastCleanup_.fetch_add(1) == step_)
|
||||
collect();
|
||||
|
||||
auto object = std::make_shared<ObjectType>(std::forward<Args>(args)...);
|
||||
auto object = std::make_unique<ObjectType>(std::forward<Args>(args)...);
|
||||
auto copy = *object;
|
||||
|
||||
std::lock_guard<std::mutex> g(lock_);
|
||||
objects_[object->handle()] = object;
|
||||
return *object;
|
||||
objects_.emplace(object->handle(), std::move(object));
|
||||
return copy;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
auto it = objects_.find(handle);
|
||||
assert(it != objects_.end());
|
||||
|
||||
auto result = dynamic_cast<ObjectType*>(it->second.get());
|
||||
assert(result);
|
||||
return *result;
|
||||
}
|
||||
bool has(GCObjectHandle handle) const;
|
||||
|
||||
void collect();
|
||||
size_t size() const;
|
||||
void pause() { enabled_ = false; };
|
||||
void resume() { enabled_ = true; };
|
||||
size_t step() const { return step_; }
|
||||
void step(size_t newStep) { step_ = newStep; }
|
||||
bool enabled() { return enabled_; };
|
||||
size_t count();
|
||||
|
||||
static GC& instance();
|
||||
static sol::table getLuaApi(sol::state_view& lua);
|
||||
|
||||
private:
|
||||
mutable std::mutex lock_;
|
||||
bool enabled_;
|
||||
std::atomic<size_t> lastCleanup_;
|
||||
size_t step_;
|
||||
std::map<GCObjectHandle, std::shared_ptr<GCObject>> objects_;
|
||||
|
||||
private:
|
||||
GCObject* findObject(GCObjectHandle handle);
|
||||
std::unordered_map<GCObjectHandle, std::unique_ptr<GCObject>> objects_;
|
||||
|
||||
private:
|
||||
GC();
|
||||
GC(GC&&) = delete;
|
||||
GC(const GC&) = delete;
|
||||
|
||||
void collect();
|
||||
size_t size() const;
|
||||
void pause() { enabled_ = false; }
|
||||
void resume() { enabled_ = true; }
|
||||
size_t step() const { return step_; }
|
||||
void step(size_t newStep) { step_ = newStep; }
|
||||
bool enabled() { return enabled_; }
|
||||
size_t count();
|
||||
};
|
||||
|
||||
} // effil
|
||||
|
||||
42
src/cpp/gc-object.cpp
Normal file
42
src/cpp/gc-object.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "gc-object.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
|
||||
namespace effil {
|
||||
|
||||
GCObject::GCObject()
|
||||
: data_(std::make_shared<SharedData>()) {}
|
||||
|
||||
GCObjectHandle GCObject::handle() const {
|
||||
return reinterpret_cast<GCObjectHandle>(data_.get());
|
||||
}
|
||||
|
||||
size_t GCObject::instances() const {
|
||||
return data_.use_count();
|
||||
}
|
||||
|
||||
const std::unordered_set<GCObjectHandle> GCObject::refers() const {
|
||||
std::lock_guard<SpinMutex> lock(data_->mutex_);
|
||||
return std::unordered_set<GCObjectHandle>(
|
||||
data_->weakRefs_.begin(),
|
||||
data_->weakRefs_.end());
|
||||
}
|
||||
|
||||
void GCObject::addReference(GCObjectHandle handle) {
|
||||
if (handle == nullptr) return;
|
||||
|
||||
std::lock_guard<SpinMutex> lock(data_->mutex_);
|
||||
data_->weakRefs_.insert(handle);
|
||||
}
|
||||
|
||||
void GCObject::removeReference(GCObjectHandle handle) {
|
||||
if (handle == GCNull) return;
|
||||
|
||||
std::lock_guard<SpinMutex> lock(data_->mutex_);
|
||||
auto hit = data_->weakRefs_.find(handle);
|
||||
assert(hit != std::end(data_->weakRefs_));
|
||||
data_->weakRefs_.erase(hit);
|
||||
}
|
||||
|
||||
} // namespace effil
|
||||
47
src/cpp/gc-object.h
Normal file
47
src/cpp/gc-object.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "spin-mutex.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace effil {
|
||||
|
||||
// Unique handle for all objects spawned from one object.
|
||||
using GCObjectHandle = void*;
|
||||
|
||||
static const GCObjectHandle GCNull = nullptr;
|
||||
|
||||
// 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.
|
||||
// Childes have to care about storing data, concurrent access and
|
||||
// weak references (GCHandle) to other GCObjects.
|
||||
class GCObject {
|
||||
public:
|
||||
GCObject();
|
||||
virtual ~GCObject() = default;
|
||||
|
||||
// Unique handle for any copy of GCObject in any lua state
|
||||
GCObjectHandle handle() const;
|
||||
|
||||
// Number of instance copies
|
||||
// always greater than 1
|
||||
// GC holds one copy
|
||||
size_t instances() const;
|
||||
|
||||
// List of weak references to nested objects
|
||||
const std::unordered_set<GCObjectHandle> refers() const;
|
||||
|
||||
protected:
|
||||
void addReference(GCObjectHandle handle);
|
||||
void removeReference(GCObjectHandle handle);
|
||||
|
||||
private:
|
||||
struct SharedData {
|
||||
mutable SpinMutex mutex_;
|
||||
std::unordered_multiset<GCObjectHandle> weakRefs_;
|
||||
};
|
||||
|
||||
std::shared_ptr<SharedData> data_;
|
||||
};
|
||||
|
||||
} // namespace effil
|
||||
@ -54,10 +54,10 @@ extern "C"
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int luaopen_libeffil(lua_State* L) {
|
||||
sol::state_view lua(L);
|
||||
Thread::getUserType(lua);
|
||||
SharedTable::getUserType(lua);
|
||||
Channel::getUserType(lua);
|
||||
sol::state_view lua(L);
|
||||
Thread::exportAPI(lua);
|
||||
SharedTable::exportAPI(lua);
|
||||
Channel::exportAPI(lua);
|
||||
sol::table publicApi = lua.create_table_with(
|
||||
"thread", createThread,
|
||||
"thread_id", threadId,
|
||||
@ -70,7 +70,7 @@ int luaopen_libeffil(lua_State* L) {
|
||||
"setmetatable", SharedTable::luaSetMetatable,
|
||||
"getmetatable", SharedTable::luaGetMetatable,
|
||||
"G", sol::make_object(lua, globalTable),
|
||||
"gc", GC::getLuaApi(lua),
|
||||
"gc", GC::exportAPI(lua),
|
||||
"channel", createChannel,
|
||||
"userdata_type", userdataType,
|
||||
"pairs", SharedTable::globalLuaPairs,
|
||||
|
||||
@ -14,15 +14,11 @@ bool isSharedTable(const SolObject& obj) {
|
||||
return obj.valid() && obj.get_type() == sol::type::userdata && obj.template is<SharedTable>();
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SharedTable::SharedTable() : GCObject(), data_(std::make_shared<SharedData>()) {}
|
||||
SharedTable::SharedTable() : data_(std::make_shared<SharedData>()) {}
|
||||
|
||||
SharedTable::SharedTable(const SharedTable& init)
|
||||
: GCObject(init)
|
||||
, data_(init.data_) {}
|
||||
|
||||
void SharedTable::getUserType(sol::state_view& lua) {
|
||||
void SharedTable::exportAPI(sol::state_view& lua) {
|
||||
sol::usertype<SharedTable> type("new", sol::no_constructor,
|
||||
"__pairs", &SharedTable::luaPairs,
|
||||
"__ipairs", &SharedTable::luaIPairs,
|
||||
@ -50,10 +46,9 @@ void SharedTable::getUserType(sol::state_view& lua) {
|
||||
void SharedTable::set(StoredObject&& key, StoredObject&& value) {
|
||||
std::lock_guard<SpinMutex> g(data_->lock);
|
||||
|
||||
if (key->gcHandle())
|
||||
refs_->insert(key->gcHandle());
|
||||
if (value->gcHandle())
|
||||
refs_->insert(value->gcHandle());
|
||||
addReference(key->gcHandle());
|
||||
addReference(value->gcHandle());
|
||||
|
||||
key->releaseStrongReference();
|
||||
value->releaseStrongReference();
|
||||
|
||||
@ -80,10 +75,8 @@ void SharedTable::rawSet(const sol::stack_object& luaKey, const sol::stack_objec
|
||||
// in this case object is not obligatory to own data
|
||||
auto it = data_->entries.find(key);
|
||||
if (it != data_->entries.end()) {
|
||||
if (it->first->gcHandle())
|
||||
refs_->erase(it->first->gcHandle());
|
||||
if (it->second->gcHandle())
|
||||
refs_->erase(it->second->gcHandle());
|
||||
removeReference(it->first->gcHandle());
|
||||
removeReference(it->second->gcHandle());
|
||||
data_->entries.erase(it);
|
||||
}
|
||||
|
||||
@ -266,21 +259,20 @@ SharedTable::PairsIterator SharedTable::luaIPairs(sol::this_state state) {
|
||||
*/
|
||||
|
||||
SharedTable SharedTable::luaSetMetatable(SharedTable& stable, const sol::stack_object& mt) {
|
||||
REQUIRE((!mt.valid()) || mt.get_type() == sol::type::table || isSharedTable(mt)) << "Unexpected type of setmetatable argument";
|
||||
REQUIRE(mt.get_type() == sol::type::table || isSharedTable(mt)) << "Unexpected type of setmetatable argument";
|
||||
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
||||
if (stable.data_->metatable != GCNull)
|
||||
{
|
||||
stable.refs_->erase(stable.data_->metatable);
|
||||
if (stable.data_->metatable != GCNull) {
|
||||
stable.removeReference(stable.data_->metatable);
|
||||
stable.data_->metatable = GCNull;
|
||||
}
|
||||
if (mt.valid()) {
|
||||
stable.data_->metatable = createStoredObject(mt)->gcHandle();
|
||||
stable.refs_->insert(stable.data_->metatable);
|
||||
}
|
||||
|
||||
stable.data_->metatable = createStoredObject(mt)->gcHandle();
|
||||
stable.addReference(stable.data_->metatable);
|
||||
|
||||
return stable;
|
||||
}
|
||||
|
||||
sol::object SharedTable::luaGetMetatable(const SharedTable& stable, sol::this_state state) {
|
||||
sol::object SharedTable::luaGetMetatable(const SharedTable& stable, const sol::this_state& state) {
|
||||
std::lock_guard<SpinMutex> lock(stable.data_->lock);
|
||||
return stable.data_->metatable == GCNull ? sol::nil :
|
||||
sol::make_object(state, GC::instance().get<SharedTable>(stable.data_->metatable));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "garbage-collector.h"
|
||||
#include "gc-object.h"
|
||||
#include "stored-object.h"
|
||||
#include "spin-mutex.h"
|
||||
#include "utils.h"
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
#include <sol.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace effil {
|
||||
@ -20,12 +20,9 @@ private:
|
||||
|
||||
public:
|
||||
SharedTable();
|
||||
SharedTable(SharedTable&&) = default;
|
||||
SharedTable(const SharedTable& init);
|
||||
SharedTable& operator=(const SharedTable&) = default;
|
||||
virtual ~SharedTable() = default;
|
||||
|
||||
static void getUserType(sol::state_view& lua);
|
||||
static void exportAPI(sol::state_view& lua);
|
||||
|
||||
void set(StoredObject&&, StoredObject&&);
|
||||
void rawSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue);
|
||||
@ -56,7 +53,7 @@ public:
|
||||
|
||||
// Stand alone functions for effil::table available in Lua
|
||||
static SharedTable luaSetMetatable(SharedTable& stable, const sol::stack_object& mt);
|
||||
static sol::object luaGetMetatable(const SharedTable& stable, const sol::this_state state);
|
||||
static sol::object luaGetMetatable(const SharedTable& stable, const sol::this_state& state);
|
||||
static sol::object luaRawGet(const SharedTable& stable, const sol::stack_object& key, sol::this_state state);
|
||||
static SharedTable luaRawSet(SharedTable& stable, const sol::stack_object& key, const sol::stack_object& value);
|
||||
static size_t luaSize(SharedTable& stable);
|
||||
|
||||
@ -76,7 +76,6 @@ public:
|
||||
assert(luaObject.template is<T>());
|
||||
strongRef_ = luaObject.template as<T>();
|
||||
handle_ = strongRef_->handle();
|
||||
assert(GC::instance().has(handle_));
|
||||
}
|
||||
|
||||
GCObjectHolder(GCObjectHandle handle)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
namespace effil {
|
||||
|
||||
// Represents an interface for lua type stored at C++ code
|
||||
class BaseHolder {
|
||||
public:
|
||||
BaseHolder() = default;
|
||||
|
||||
@ -248,7 +248,7 @@ Thread::Thread(const std::string& path,
|
||||
thr.detach();
|
||||
}
|
||||
|
||||
void Thread::getUserType(sol::state_view& lua) {
|
||||
void Thread::exportAPI(sol::state_view& lua) {
|
||||
sol::usertype<Thread> type(
|
||||
"new", sol::no_constructor,
|
||||
"get", &Thread::get,
|
||||
|
||||
@ -20,7 +20,7 @@ public:
|
||||
const sol::function& function,
|
||||
const sol::variadic_args& args);
|
||||
|
||||
static void getUserType(sol::state_view& lua);
|
||||
static void exportAPI(sol::state_view& lua);
|
||||
|
||||
std::pair<sol::object, sol::object> status(const sol::this_state& state);
|
||||
std::pair<sol::object, sol::object> wait(const sol::this_state& state,
|
||||
|
||||
@ -1,162 +0,0 @@
|
||||
#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 = GC::instance().create<GCObject>();
|
||||
EXPECT_EQ(o2.instances(), (size_t)2);
|
||||
|
||||
GCObject o3 = GC::instance().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) {
|
||||
GC::instance().collect();
|
||||
ASSERT_EQ(GC::instance().size(), (size_t)1);
|
||||
{
|
||||
GCObject o1 = GC::instance().create<GCObject>();
|
||||
GCObject o2 = GC::instance().create<GCObject>();
|
||||
}
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)3);
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Dummy : public GCObject {
|
||||
void add(GCObjectHandle ref) { refs_->insert(ref); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST(gc, withRefs) {
|
||||
GC::instance().collect();
|
||||
{
|
||||
Dummy root = GC::instance().create<Dummy>();
|
||||
|
||||
{
|
||||
Dummy orphan = GC::instance().create<Dummy>();
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
Dummy child = GC::instance().create<Dummy>();
|
||||
root.add(child.handle());
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)5);
|
||||
}
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||
}
|
||||
|
||||
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++)
|
||||
GC::instance().create<GCObject>();
|
||||
});
|
||||
|
||||
for (auto& thread : threads)
|
||||
thread.join();
|
||||
|
||||
EXPECT_LT(GC::instance().size(), GC::instance().step());
|
||||
}
|
||||
|
||||
TEST(gc, gcInLuaState) {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
|
||||
lua["st"] = GC::instance().create<SharedTable>();
|
||||
lua.script(R"(
|
||||
for i=1,1000 do
|
||||
st[i] = {"Wow"}
|
||||
end
|
||||
)");
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)1002);
|
||||
|
||||
lua.script(R"(
|
||||
for i=1,1000 do
|
||||
st[i] = nil
|
||||
end
|
||||
)");
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)2);
|
||||
}
|
||||
|
||||
TEST(gc, cycles) {
|
||||
{
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
GC::instance().collect();
|
||||
|
||||
lua["st"] = GC::instance().create<SharedTable>();
|
||||
lua.script(R"(
|
||||
st.parent = {}
|
||||
st.parent.child = { ref = st.parent }
|
||||
st[4] = { one = 1 }
|
||||
st[5] = { flag = true }
|
||||
)");
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||
|
||||
lua.script("st.parent = nil");
|
||||
|
||||
lua.collect_garbage();
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)4);
|
||||
}
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)1);
|
||||
}
|
||||
|
||||
TEST(gc, multipleStates) {
|
||||
sol::state lua1;
|
||||
sol::state lua2;
|
||||
bootstrapState(lua1);
|
||||
bootstrapState(lua2);
|
||||
|
||||
{
|
||||
SharedTable st = GC::instance().create<SharedTable>();
|
||||
lua1["st"] = st;
|
||||
lua2["st"] = st;
|
||||
}
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)2);
|
||||
|
||||
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
|
||||
)");
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)6);
|
||||
|
||||
lua2.script("copy = { st.men } st = nil");
|
||||
lua1.script("st = nil");
|
||||
lua1.collect_garbage();
|
||||
lua2.collect_garbage();
|
||||
GC::instance().collect();
|
||||
EXPECT_EQ(GC::instance().size(), (size_t)5);
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "notifier.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
using namespace effil;
|
||||
|
||||
TEST(notifier, wait) {
|
||||
Notifier n;
|
||||
bool done = false;
|
||||
auto t = std::thread([&]{
|
||||
done = true;
|
||||
n.notify();
|
||||
});
|
||||
|
||||
n.wait();
|
||||
EXPECT_TRUE(done);
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(notifier, waitMany) {
|
||||
const size_t nfutures = 32;
|
||||
std::vector<std::thread> vt;
|
||||
std::atomic<size_t> counter(0);
|
||||
Notifier n;
|
||||
|
||||
for(size_t i = 0; i < nfutures; i++)
|
||||
vt.emplace_back(std::thread([&]{
|
||||
n.wait();
|
||||
counter++;
|
||||
}));
|
||||
|
||||
EXPECT_EQ(counter.load(), (size_t)0);
|
||||
|
||||
n.notify();
|
||||
for(auto& t : vt) t.join();
|
||||
EXPECT_EQ(counter.load(), nfutures);
|
||||
}
|
||||
|
||||
TEST(notifier, waitFor) {
|
||||
Notifier n;
|
||||
auto t = std::thread([&] {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
n.notify();
|
||||
});
|
||||
|
||||
EXPECT_FALSE(n.waitFor(std::chrono::seconds(1)));
|
||||
EXPECT_TRUE(n.waitFor(std::chrono::seconds(2)));
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(notifier, reset) {
|
||||
const size_t iterations = 1024;
|
||||
Notifier readyToProcess;
|
||||
Notifier needNew;
|
||||
size_t resource = 0;
|
||||
|
||||
std::thread producer([&]() {
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
resource++;
|
||||
readyToProcess.notify();
|
||||
needNew.wait();
|
||||
needNew.reset();
|
||||
}
|
||||
});
|
||||
|
||||
std::thread consumer([&](){
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
readyToProcess.wait();
|
||||
readyToProcess.reset();
|
||||
EXPECT_EQ(resource, i + 1);
|
||||
needNew.notify();
|
||||
}
|
||||
});
|
||||
|
||||
producer.join();
|
||||
consumer.join();
|
||||
}
|
||||
@ -1,301 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "test-utils.h"
|
||||
#include "shared-table.h"
|
||||
#include "garbage-collector.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace effil;
|
||||
|
||||
TEST(sharedTable, primitiveTypes) {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
|
||||
lua["st"] = SharedTable();
|
||||
|
||||
auto res1 = lua.script(R"(
|
||||
st.fst = "first"
|
||||
st.snd = 2
|
||||
st.thr = true
|
||||
st.del = "secret"
|
||||
st.del = nil
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res1.valid()) << "Set res1 failed";
|
||||
EXPECT_EQ(lua["st"]["fst"], std::string("first"));
|
||||
EXPECT_EQ(lua["st"]["snd"], (double)2);
|
||||
EXPECT_EQ(lua["st"]["thr"], true);
|
||||
EXPECT_EQ(lua["st"]["del"], sol::nil);
|
||||
EXPECT_EQ(lua["st"]["nex"], sol::nil);
|
||||
|
||||
auto res2 = lua.script(R"(
|
||||
st[1] = 3
|
||||
st[2] = "number"
|
||||
st[-1] = false
|
||||
st[42] = "answer"
|
||||
st[42] = nil
|
||||
st.deleted = st[42] == nil
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res2.valid()) << "Set res2 failed";
|
||||
EXPECT_EQ(lua["st"][1], 3);
|
||||
EXPECT_EQ(lua["st"][2], std::string("number"));
|
||||
EXPECT_EQ(lua["st"][-1], false);
|
||||
EXPECT_EQ(lua["st"]["deleted"], true);
|
||||
|
||||
auto res3 = lua.script(R"(
|
||||
st[true] = false
|
||||
st[false] = 9
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res3.valid()) << "Set res3 failed";
|
||||
EXPECT_EQ(lua["st"][true], false);
|
||||
EXPECT_EQ(lua["st"][false], 9);
|
||||
}
|
||||
|
||||
TEST(sharedTable, multipleStates) {
|
||||
sol::state lua1, lua2;
|
||||
bootstrapState(lua1);
|
||||
bootstrapState(lua2);
|
||||
|
||||
auto st = std::make_unique<SharedTable>();
|
||||
|
||||
lua1["cats"] = st.get();
|
||||
lua2["dogs"] = st.get();
|
||||
|
||||
auto res1 = lua1.script(R"(
|
||||
cats.fluffy = "gav"
|
||||
cats.sparky = false
|
||||
cats.wow = 3
|
||||
)");
|
||||
|
||||
EXPECT_EQ(lua2["dogs"]["fluffy"], std::string("gav"));
|
||||
EXPECT_EQ(lua2["dogs"]["sparky"], false);
|
||||
EXPECT_EQ(lua2["dogs"]["wow"], 3);
|
||||
}
|
||||
|
||||
TEST(sharedTable, multipleThreads) {
|
||||
SharedTable st;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
threads.emplace_back([=]() {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
;
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.fst = true
|
||||
)");
|
||||
});
|
||||
|
||||
threads.emplace_back([=]() {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.snd = true
|
||||
)");
|
||||
});
|
||||
|
||||
threads.emplace_back([=]() {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
lua.script(R"(
|
||||
while not st.ready do end
|
||||
st.thr = true
|
||||
)");
|
||||
});
|
||||
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
lua.script("st.ready = true");
|
||||
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(lua["st"]["fst"], true);
|
||||
EXPECT_EQ(lua["st"]["snd"], true);
|
||||
EXPECT_EQ(lua["st"]["thr"], true);
|
||||
}
|
||||
|
||||
TEST(sharedTable, playingWithSharedTables) {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
|
||||
lua["recursive"] = GC::instance().create<SharedTable>();
|
||||
lua["st1"] = GC::instance().create<SharedTable>();
|
||||
lua["st2"] = GC::instance().create<SharedTable>();
|
||||
|
||||
lua.script(R"(
|
||||
st1.proxy = st2
|
||||
st1.proxy.value = true
|
||||
recursive.next = recursive
|
||||
recursive.val = "yes"
|
||||
)");
|
||||
EXPECT_EQ(lua["st2"]["value"], true);
|
||||
EXPECT_EQ(lua["recursive"]["next"]["next"]["next"]["val"], std::string("yes"));
|
||||
}
|
||||
|
||||
TEST(sharedTable, playingWithFunctions) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
|
||||
lua["st"] = st;
|
||||
|
||||
lua.script(R"(
|
||||
st.fn = function ()
|
||||
print "Hello C++"
|
||||
return true
|
||||
end
|
||||
st.fn()
|
||||
)");
|
||||
|
||||
sol::function sf = lua["st"]["fn"];
|
||||
EXPECT_TRUE((bool)sf());
|
||||
|
||||
sol::state lua2;
|
||||
bootstrapState(lua2);
|
||||
|
||||
lua2["st2"] = st;
|
||||
lua2.script(R"(
|
||||
st2.fn2 = function(str)
|
||||
return "*" .. str .. "*"
|
||||
end
|
||||
)");
|
||||
|
||||
sol::function sf2 = lua["st"]["fn2"];
|
||||
|
||||
EXPECT_EQ(sf2(std::string("SUCCESS")).get<std::string>(), std::string("*SUCCESS*"));
|
||||
}
|
||||
|
||||
TEST(sharedTable, playingWithTables) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
|
||||
lua["st"] = st;
|
||||
auto res = lua.script(R"(
|
||||
st.works = "fine"
|
||||
st.person = {name = 'John Doe', age = 25}
|
||||
pet = {
|
||||
type = "cat",
|
||||
name = "Tomas",
|
||||
real = "Яша",
|
||||
owner = "Mama",
|
||||
spec = { colour = "grey", legs = 4, eyes = 2 }
|
||||
}
|
||||
st.pet = pet
|
||||
recursive = {}
|
||||
recursive.next = recursive
|
||||
recursive.prev = recursive
|
||||
recursive.val = "recursive"
|
||||
st.recursive = recursive
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res.valid());
|
||||
EXPECT_EQ(lua["st"]["person"]["name"], std::string("John Doe"));
|
||||
EXPECT_EQ(lua["st"]["person"]["age"], 25);
|
||||
EXPECT_EQ(lua["st"]["pet"]["type"], std::string("cat"));
|
||||
EXPECT_EQ(lua["st"]["pet"]["name"], std::string("Tomas"));
|
||||
EXPECT_EQ(lua["st"]["pet"]["real"], std::string("Яша"));
|
||||
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"));
|
||||
}
|
||||
|
||||
TEST(sharedTable, stress) {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
SharedTable st;
|
||||
|
||||
lua["st"] = st;
|
||||
|
||||
auto res1 = lua.script(R"(
|
||||
for i = 1, 1000000 do
|
||||
st[i] = tostring(i)
|
||||
end
|
||||
)");
|
||||
|
||||
EXPECT_TRUE(res1.valid());
|
||||
EXPECT_TRUE(SharedTable::luaSize(st) == 1'000'000);
|
||||
|
||||
auto res2 = lua.script(R"(
|
||||
for i = 1000000, 1, -1 do
|
||||
st[i] = nil
|
||||
end
|
||||
)");
|
||||
EXPECT_TRUE(res2.valid());
|
||||
EXPECT_TRUE(SharedTable::luaSize(st) == 0);
|
||||
}
|
||||
|
||||
TEST(sharedTable, stressWithThreads) {
|
||||
SharedTable st;
|
||||
|
||||
const size_t threadCount = 10;
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
threads.emplace_back([=] {
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
std::stringstream ss;
|
||||
ss << "st[" << i << "] = 1" << std::endl;
|
||||
ss << "for i = 1, 100000 do" << std::endl;
|
||||
ss << " st[" << i << "] = "
|
||||
<< "st[" << i << "] + 1" << std::endl;
|
||||
ss << "end" << std::endl;
|
||||
lua.script(ss.str());
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
EXPECT_TRUE(lua["st"][i] == 100'001) << (double)lua["st"][i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(sharedTable, ExternalUserdata) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
|
||||
struct TestUserdata
|
||||
{
|
||||
int field;
|
||||
};
|
||||
|
||||
lua["udata"] = TestUserdata{17};
|
||||
// FIXME: fails on gcc-5 and LuaJIT
|
||||
// Right check is EXPECT_THROW(lua.script("st.userdata = udata"), sol::error);
|
||||
EXPECT_ANY_THROW(lua.script("st.userdata = udata"));
|
||||
}
|
||||
|
||||
TEST(sharedTable, LightUserdata) {
|
||||
SharedTable st;
|
||||
sol::state lua;
|
||||
bootstrapState(lua);
|
||||
lua["st"] = st;
|
||||
|
||||
int lightUserdata = 19;
|
||||
lua_pushlightuserdata(lua, (void*)&lightUserdata);
|
||||
lua["light_udata"] = sol::stack::pop<sol::object>(lua);
|
||||
EXPECT_TRUE((bool)lua.script("st.light_userdata = light_udata; return st.light_userdata == light_udata"));
|
||||
std::string strRet = lua.script("return type(light_udata)");
|
||||
EXPECT_EQ(strRet, "userdata");
|
||||
EXPECT_EQ(lua["light_udata"].get<sol::lightuserdata_value>().value, &lightUserdata);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "shared-table.h"
|
||||
#include "lua-helpers.h"
|
||||
#include <sol.hpp>
|
||||
|
||||
namespace effil {
|
||||
|
||||
inline void bootstrapState(sol::state& lua) {
|
||||
luaL_openlibs(lua);
|
||||
SharedTable::getUserType(lua);
|
||||
}
|
||||
|
||||
} // namespace effil
|
||||
@ -6,8 +6,9 @@ test.channel_stress.with_multiple_threads = function ()
|
||||
local exchange_channel, result_channel = effil.channel(), effil.channel()
|
||||
|
||||
local threads_number = 1000
|
||||
local threads = {}
|
||||
for i = 1, threads_number do
|
||||
effil.thread(function(exchange_channel, result_channel, indx)
|
||||
threads[i] = effil.thread(function(exchange_channel, result_channel, indx)
|
||||
if indx % 2 == 0 then
|
||||
for i = 1, 10000 do
|
||||
exchange_channel:push(indx .. "_".. i)
|
||||
@ -38,6 +39,10 @@ test.channel_stress.with_multiple_threads = function ()
|
||||
test.is_true(data[thr_id .. "_".. iter])
|
||||
end
|
||||
end
|
||||
|
||||
for _, thread in ipairs(threads) do
|
||||
thread:wait()
|
||||
end
|
||||
end
|
||||
|
||||
test.channel_stress.timed_read = function ()
|
||||
|
||||
@ -3,7 +3,6 @@ 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"
|
||||
|
||||
@ -38,4 +38,20 @@ test.gc.disable = function ()
|
||||
test.equal(gc.count(), 1)
|
||||
|
||||
gc.resume()
|
||||
end
|
||||
end
|
||||
|
||||
test.gc.store_same_value = function()
|
||||
local fill = function (c)
|
||||
local a = effil.table {}
|
||||
c:push(a)
|
||||
c:push(a)
|
||||
end
|
||||
|
||||
local c = effil.channel {}
|
||||
fill(c)
|
||||
|
||||
c:pop()
|
||||
collectgarbage()
|
||||
effil.gc.collect()
|
||||
c:pop()[1] = 0
|
||||
end
|
||||
|
||||
@ -5,6 +5,7 @@ local src_path = scripts_path .. "/../.."
|
||||
package.path = ";" .. scripts_path .. "/?.lua;"
|
||||
.. src_path .. "/src/lua/?.lua;"
|
||||
.. src_path .. "/libs/u-test/?.lua"
|
||||
package.cpath = "./?.so;" .. package.cpath
|
||||
|
||||
require "type"
|
||||
require "gc"
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
package.path = ";../tests/lua/?.lua;../libs/u-test/?.lua;../src/lua/?.lua"
|
||||
|
||||
require "type"
|
||||
require "gc"
|
||||
require "channel"
|
||||
require "thread"
|
||||
require "shared-table"
|
||||
require "metatable"
|
||||
|
||||
if os.getenv("STRESS") then
|
||||
require "channel-stress"
|
||||
require "thread-stress"
|
||||
require "gc-stress"
|
||||
end
|
||||
|
||||
test.summary()
|
||||
Loading…
x
Reference in New Issue
Block a user