Permalink
Browse files

Garbage collect connections if sender is destroyed

  • Loading branch information...
1 parent c7ee9a2 commit 08dd4cde07760461d447b0bf7d78666f6efacf8d @niemeyer niemeyer committed Sep 27, 2013
Showing with 51 additions and 24 deletions.
  1. +3 −3 all_test.go
  2. +2 −2 cpp/capi.cpp
  3. +3 −2 cpp/capi.h
  4. +5 −1 cpp/connector.cpp
  5. +5 −4 cpp/connector.h
  6. +22 −10 qml.go
  7. +11 −2 stats.go
View
@@ -34,7 +34,7 @@ func (s *S) SetUpTest(c *C) {
qml.ResetStats()
stats := qml.Stats()
- if stats.EnginesAlive > 0 || stats.ValuesAlive > 0 {
+ if stats.EnginesAlive > 0 || stats.ValuesAlive > 0 || stats.ConnectionsAlive > 0 {
panic(fmt.Sprintf("Test started with values alive: %#v\n", stats))
}
@@ -54,11 +54,11 @@ func (s *S) TearDownTest(c *C) {
// these objects from being deleted.
runtime.GC()
stats := qml.Stats()
- if stats.EnginesAlive == 0 && stats.ValuesAlive == 0 {
+ if stats.EnginesAlive == 0 && stats.ValuesAlive == 0 && stats.ConnectionsAlive == 0 {
break
}
if retries == 0 {
- panic(fmt.Sprintf("there are objects alive:\n%#v\n", stats))
+ panic(fmt.Sprintf("there are values alive:\n%#v\n", stats))
}
retries--
time.Sleep(100 * time.Millisecond)
View
@@ -334,7 +334,7 @@ void objectSetParent(QObject_ *object, QObject_ *parent)
qobject->setParent(qparent);
}
-error *objectConnect(QQmlEngine_ *engine, QObject_ *object, const char *signal, int signalLen, void *data, int argsLen)
+error *objectConnect(QObject_ *object, const char *signal, int signalLen, QQmlEngine_ *engine, void *func, int argsLen)
{
QObject *qobject = reinterpret_cast<QObject *>(object);
QQmlEngine *qengine = reinterpret_cast<QQmlEngine *>(engine);
@@ -351,7 +351,7 @@ error *objectConnect(QQmlEngine_ *engine, QObject_ *object, const char *signal,
// TODO Might continue looking to see if a different signal has the same name and enough arguments.
return errorf("signal \"%s\" has too few parameters for provided function", name.constData());
}
- Connector *connector = new Connector(qengine, qobject, method, data, argsLen);
+ Connector *connector = new Connector(qobject, method, qengine, func, argsLen);
const QMetaObject *connmeta = connector->metaObject();
QObject::connect(qobject, method, connector, connmeta->method(connmeta->methodOffset()));
return 0;
View
@@ -122,7 +122,7 @@ QQmlContext_ *objectContext(QObject_ *object);
int objectIsComponent(QObject_ *object);
int objectIsWindow(QObject_ *object);
int objectIsView(QObject_ *object);
-error *objectConnect(QQmlEngine_ *engine, QObject_ *object, const char *signal, int signalLen, void *data, int argsLen);
+error *objectConnect(QObject_ *object, const char *signal, int signalLen, QQmlEngine_ *engine, void *func, int argsLen);
QQmlComponent_ *newComponent(QQmlEngine_ *engine, QObject_ *parent);
void componentSetData(QQmlComponent_ *component, const char *data, int dataLen, const char *url, int urlLen);
@@ -164,7 +164,8 @@ void hookGoValueCallMethod(QQmlEngine_ *engine, GoAddr *addr, int memberIndex, D
void hookGoValueDestroyed(QQmlEngine_ *engine, GoAddr *addr);
GoAddr *hookGoValueTypeNew(GoValue_ *value, GoTypeSpec_ *spec);
void hookWindowHidden(QObject_ *addr);
-void hookSignal(QQmlEngine_ *engine, QObject_ *sender, void *data, DataValue *params);
+void hookSignalCall(QQmlEngine_ *engine, void *func, DataValue *params);
+void hookSignalDisconnect(void *func);
#ifdef __cplusplus
} // extern "C"
View
@@ -2,6 +2,10 @@
#include "connector.h"
+Connector::~Connector()
+{
+ hookSignalDisconnect(func);
+}
void Connector::invoke()
{
@@ -16,7 +20,7 @@ int Connector::qt_metacall(QMetaObject::Call c, int idx, void **a)
QVariant var(method.parameterType(i), a[1 + i]);
packDataValue(&var, &args[i]);
}
- hookSignal(engine, sender, data, args);
+ hookSignalCall(engine, func, args);
return -1;
}
return standard_qt_metacall(c, idx, a);
View
@@ -9,8 +9,10 @@ class Connector : public QObject
public:
- Connector(QQmlEngine *engine, QObject *sender, QMetaMethod method, void *data, int argsLen)
- : engine(engine), sender(sender), method(method), data(data), argsLen(argsLen) {};
+ Connector(QObject *sender, QMetaMethod method, QQmlEngine *engine, void *func, int argsLen)
+ : QObject(sender), engine(engine), method(method), func(func), argsLen(argsLen) {};
+
+ virtual ~Connector();
// MOC HACK: s/Connector::qt_metacall/Connector::standard_qt_metacall/
int standard_qt_metacall(QMetaObject::Call c, int idx, void **a);
@@ -22,9 +24,8 @@ class Connector : public QObject
private:
QQmlEngine *engine;
- QObject *sender;
QMetaMethod method;
- void *data;
+ void *func;
int argsLen;
};
View
@@ -498,8 +498,7 @@ func (obj *Object) Destroy() {
})
}
-// TODO Proper garbage collection for these.
-var funcRefs = make(map[interface{}]bool)
+var connectedFunction = make(map[*interface{}]bool)
// On connects the named signal from obj with the provided function, so that
// when obj next emits that signal, the function is called with the parameters
@@ -533,22 +532,29 @@ func (obj *Object) On(signal string, function interface{}) {
csignal, csignallen := unsafeStringData(signal)
var cerr *C.error
gui(func() {
- cerr = C.objectConnect(obj.engine.addr, obj.addr, csignal, csignallen, unsafe.Pointer(&function), C.int(funcv.Type().NumIn()))
- funcRefs[&function] = true
+ cerr = C.objectConnect(obj.addr, csignal, csignallen, obj.engine.addr, unsafe.Pointer(&function), C.int(funcv.Type().NumIn()))
+ if cerr == nil {
+ connectedFunction[&function] = true
+ stats.connectionsAlive(+1)
+ }
})
if cerr != nil {
panic(cerror(cerr).Error())
}
}
-func cerror(cerr *C.error) error {
- err := errors.New(C.GoString((*C.char)(unsafe.Pointer(cerr))))
- C.free(unsafe.Pointer(cerr))
- return err
+//export hookSignalDisconnect
+func hookSignalDisconnect(funcp unsafe.Pointer) {
+ before := len(connectedFunction)
+ delete(connectedFunction, (*interface{})(funcp))
+ if before == len(connectedFunction) {
+ panic("disconnecting unknown signal function")
+ }
+ stats.connectionsAlive(-1)
}
-//export hookSignal
-func hookSignal(enginep unsafe.Pointer, objectp unsafe.Pointer, funcp unsafe.Pointer, args *C.DataValue) {
+//export hookSignalCall
+func hookSignalCall(enginep unsafe.Pointer, funcp unsafe.Pointer, args *C.DataValue) {
engine := engines[enginep]
if engine == nil {
panic("signal called after engine was destroyed")
@@ -569,6 +575,12 @@ func hookSignal(enginep unsafe.Pointer, objectp unsafe.Pointer, funcp unsafe.Poi
funcv.Call(params[:numIn])
}
+func cerror(cerr *C.error) error {
+ err := errors.New(C.GoString((*C.char)(unsafe.Pointer(cerr))))
+ C.free(unsafe.Pointer(cerr))
+ return err
+}
+
// TODO Signal emitting support for go values.
// Window represents a QML window where components are rendered.
View
@@ -38,8 +38,9 @@ func ResetStats() {
}
type Statistics struct {
- EnginesAlive int
- ValuesAlive int
+ EnginesAlive int
+ ValuesAlive int
+ ConnectionsAlive int
}
func (stats *Statistics) enginesAlive(delta int) {
@@ -57,3 +58,11 @@ func (stats *Statistics) valuesAlive(delta int) {
statsMutex.Unlock()
}
}
+
+func (stats *Statistics) connectionsAlive(delta int) {
+ if stats != nil {
+ statsMutex.Lock()
+ stats.ConnectionsAlive += delta
+ statsMutex.Unlock()
+ }
+}

0 comments on commit 08dd4cd

Please sign in to comment.