Commit e82b4fab authored by Kirill Smelkov's avatar Kirill Smelkov

libgolang: Objects refcounting (initial draft)

Since we are going to move more pygolang functionality into C++ (timers,
context, ...), and since C++ does not have garbage collector, we will
need to find a way to automatically manage memory in leak/error free way.

Let's do this via refptr<T> smart pointer (inspired by WebKit's RefPtr<T>),
which, similarly to chan<T> automatically manages pointed object's
lifetime through reference counting.

refptr<T> will be used in follow-up patches.

Top-level documentation is TODO.
parent 07f9430d
...@@ -123,6 +123,17 @@ cdef extern from "golang/libgolang.h" namespace "golang" nogil: ...@@ -123,6 +123,17 @@ cdef extern from "golang/libgolang.h" namespace "golang" nogil:
int select(_selcase casev[]) int select(_selcase casev[])
# memory management of C++ nogil classes
cppclass refptr[T]:
# compare wrt nil; =nil
cbool operator== (nullptr_t) const
cbool operator!= (nullptr_t) const
void operator= (nullptr_t) const
# get raw pointer
T* _ptr() const
# ---- python bits ---- # ---- python bits ----
cdef void topyexc() except * cdef void topyexc() except *
......
...@@ -178,6 +178,7 @@ cdef extern from * nogil: ...@@ -178,6 +178,7 @@ cdef extern from * nogil:
extern void _test_select_win_while_queue(); extern void _test_select_win_while_queue();
extern void _test_select_inplace(); extern void _test_select_inplace();
extern void _test_defer(); extern void _test_defer();
extern void _test_refptr();
extern void _test_sync_once_cpp(); extern void _test_sync_once_cpp();
""" """
void _test_chan_cpp_refcount() except +topyexc void _test_chan_cpp_refcount() except +topyexc
...@@ -189,6 +190,7 @@ cdef extern from * nogil: ...@@ -189,6 +190,7 @@ cdef extern from * nogil:
void _test_select_win_while_queue() except +topyexc void _test_select_win_while_queue() except +topyexc
void _test_select_inplace() except +topyexc void _test_select_inplace() except +topyexc
void _test_defer() except +topyexc void _test_defer() except +topyexc
void _test_refptr() except +topyexc
void _test_sync_once_cpp() except +topyexc void _test_sync_once_cpp() except +topyexc
def test_chan_cpp_refcount(): def test_chan_cpp_refcount():
with nogil: with nogil:
...@@ -217,6 +219,9 @@ def test_select_inplace(): ...@@ -217,6 +219,9 @@ def test_select_inplace():
def test_defer(): def test_defer():
with nogil: with nogil:
_test_defer() _test_defer()
def test_refptr():
with nogil:
_test_refptr()
def test_sync_once_cpp(): # TODO move -> _sync_test.pyx def test_sync_once_cpp(): # TODO move -> _sync_test.pyx
with nogil: with nogil:
_test_sync_once_cpp() _test_sync_once_cpp()
......
...@@ -288,6 +288,7 @@ LIBGOLANG_API extern void (*_tblockforever)(void); ...@@ -288,6 +288,7 @@ LIBGOLANG_API extern void (*_tblockforever)(void);
#ifdef __cplusplus #ifdef __cplusplus
#include <atomic>
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <initializer_list> #include <initializer_list>
...@@ -456,6 +457,147 @@ private: ...@@ -456,6 +457,147 @@ private:
}; };
// ---- reference-counted objects ----
// C++ does not have garbage-collector -> libgolang provides illusion of GC
// via reference counting support.
template<typename T> class refptr;
template<typename T> refptr<T> adoptref(T *_obj);
template<typename T> refptr<T> newref (T *_obj);
// refptr<T> is smart pointer to T which manages T lifetime via reference counting.
//
// T must provide incref/decref methods for example via inheriting from refobj.
// incref/decref must be safe to use from multiple threads simultaneously.
template<typename T>
class refptr {
T *_obj;
public:
// nil if not explicitly initialized
inline refptr() { _obj = NULL; }
inline ~refptr() {
if (_obj != NULL) {
_obj->decref();
_obj = NULL;
}
}
// = nil
inline refptr(nullptr_t) { _obj = NULL; }
inline refptr& operator=(nullptr_t) {
if (_obj != NULL)
_obj->decref();
_obj = NULL;
return *this;
}
// copy
inline refptr(const refptr& from) {
_obj = from._obj;
if(_obj != NULL)
_obj->incref();
}
inline refptr& operator=(const refptr& from) {
if (this != &from) {
if (_obj != NULL)
_obj->decref();
_obj = from._obj;
if (_obj != NULL)
_obj->incref();
}
return *this;
}
// move
inline refptr(refptr&& from) {
_obj = from._obj;
from._obj = NULL;
}
inline refptr& operator=(refptr&& from) {
if (this != &from) {
if (_obj != NULL)
_obj->decref();
_obj = from._obj;
from._obj = NULL;
}
return *this;
}
// create from raw pointer
friend refptr<T> adoptref<T>(T *_obj);
friend refptr<T> newref<T> (T *_obj);
// compare wrt nil
inline bool operator==(nullptr_t) const { return (_obj == NULL); }
inline bool operator!=(nullptr_t) const { return (_obj != NULL); }
// compare wrt refptr
inline bool operator==(const refptr& p2) const { return (_obj == p2._obj); }
inline bool operator!=(const refptr& p2) const { return (_obj != p2._obj); }
// dereference, so that e.g. p->method() automatically works as p._obj->method().
inline T* operator-> () const { return _obj; }
inline T& operator* () const { return *_obj; }
// access to raw pointer
inline T *_ptr() const { return _obj; }
};
// adoptref wraps raw T* pointer into refptr<T> and transfers object ownership to it.
//
// The object is assumed to have reference 1 initially.
// Usage example:
//
// refptr<MyClass> p = adoptref(new MyClass());
// ...
// // the object will be deleted when p goes out of scope
template<typename T>
inline refptr<T> adoptref(T *_obj) {
refptr<T> p;
p._obj = _obj;
return p;
}
// newref wraps raw T* pointer into refptr<T>.
//
// Created refptr holds new reference onto wrapped object.
// Usage example:
//
// doSomething(MyClass *obj) {
// refptr<MyClass> p = newref(obj);
// ...
// // obj is guaranteed to stay alive until p stays alive
// }
template<typename T>
inline refptr<T> newref(T *_obj) {
refptr<T> p;
p._obj = _obj;
if (_obj != NULL)
_obj->incref();
return p;
}
// refobj provides base-functionality for reference-counted objects.
//
// It provides incref & __decref - the user must implement decref(*).
//
// (*) this way we don't require destructor to be virtual.
class refobj {
std::atomic<int> _refcnt; // reference counter for the object
protected:
LIBGOLANG_API refobj();
LIBGOLANG_API ~refobj();
LIBGOLANG_API bool __decref();
public:
LIBGOLANG_API void incref();
LIBGOLANG_API int refcnt() const;
};
} // golang:: } // golang::
#endif // __cplusplus #endif // __cplusplus
......
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
#include "golang/time.h" #include "golang/time.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <limits> #include <limits>
...@@ -201,6 +200,39 @@ struct _with_lock_guard { ...@@ -201,6 +200,39 @@ struct _with_lock_guard {
bool once() { bool _ = !done; done = true; return _; } bool once() { bool _ = !done; done = true; return _; }
}; };
// ---- reference-counted objects ----
refobj::refobj() : _refcnt(1) {}
refobj::~refobj() {
refobj *obj = this;
if (obj->_refcnt != 0)
panic("~refobj: refcnt != 0");
}
void refobj::incref() {
refobj *obj = this;
int refcnt_was = obj->_refcnt.fetch_add(+1);
if (refcnt_was < 1)
panic("incref: refcnt was < 1");
}
bool refobj::__decref() {
refobj *obj = this;
int refcnt_was = obj->_refcnt.fetch_add(-1);
if (refcnt_was < 1)
panic("decref: refcnt was < 1");
if (refcnt_was != 1)
return false;
return true; // should destroy
}
int refobj::refcnt() const {
const refobj *obj = this;
return obj->_refcnt;
}
// ---- channels ----- // ---- channels -----
......
...@@ -480,6 +480,111 @@ void _test_defer() { ...@@ -480,6 +480,111 @@ void _test_defer() {
} }
// verify refptr/refobj
class MyObj : public refobj {
public:
void decref() {
if (__decref())
delete this;
}
int i;
int myfunc(int j) { return i + j; }
};
void _test_refptr() {
refptr<MyObj> p;
ASSERT(p == NULL);
ASSERT(!(p != NULL));
ASSERT(p._ptr() == NULL);
MyObj *obj = new MyObj();
ASSERT(obj->refcnt() == 1);
obj->i = 3;
// adoptref
p = adoptref(obj);
ASSERT(obj->refcnt() == 1);
ASSERT(p._ptr() == obj);
ASSERT(p->i == 3); // ->
ASSERT(p->myfunc(4) == 7);
p->i = 2;
ASSERT(obj->i == 2);
ASSERT((*p).i == 2); // *
ASSERT((*p).myfunc(3) == 5);
(*p).i = 3;
ASSERT(obj->i == 3);
// newref
{
refptr<MyObj> q = newref(obj);
ASSERT(obj->refcnt() == 2);
ASSERT(p._ptr() == obj);
ASSERT(q._ptr() == obj);
ASSERT(q->i == 3);
obj->i = 4;
ASSERT(q->i == 4);
// q goes out of scope - obj decref'ed
}
ASSERT(obj->refcnt() == 1);
// copy ctor
{
refptr<MyObj> q(p);
ASSERT(obj->refcnt() == 2);
ASSERT(p._ptr() == obj);
ASSERT(q._ptr() == obj);
ASSERT(p == q);
// q goes out of scope - obj decref'ed
}
ASSERT(obj->refcnt() == 1);
// copy =
{
refptr<MyObj> q;
ASSERT(obj->refcnt() == 1);
ASSERT(q == NULL);
ASSERT(q._ptr() == NULL);
ASSERT(!(p == q));
ASSERT(p != q);
q = p;
ASSERT(obj->refcnt() == 2);
ASSERT(p._ptr() == obj);
ASSERT(q._ptr() == obj);
ASSERT(p == q);
ASSERT(!(p != q));
q = NULL;
ASSERT(obj->refcnt() == 1);
ASSERT(p._ptr() == obj);
ASSERT(q._ptr() == NULL);
ASSERT(!(p == q));
ASSERT(p != q);
}
ASSERT(obj->refcnt() == 1);
// move ctor
refptr<MyObj> q(move(p));
ASSERT(obj->refcnt() == 1);
ASSERT(p == NULL);
ASSERT(p._ptr() == NULL);
ASSERT(q != NULL);
ASSERT(q._ptr() == obj);
// move =
p = move(q);
ASSERT(obj->refcnt() == 1);
ASSERT(p != NULL);
ASSERT(p._ptr() == obj);
ASSERT(q == NULL);
ASSERT(q._ptr() == NULL);
// p goes out of scope and destroys obj
}
// ---- sync:: ---- // ---- sync:: ----
// verify that sync::Once works. // verify that sync::Once works.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment