This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch main
in repository ego.

View the commit online.

commit 544cc4b7244e455d3aa95da6f3345f215f212b6e
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 9 15:14:59 2026 -0600

    fix: clean up event callbacks on object finalization and Unref
    
    Extract installFinalizer as a reusable helper to register the GC finalizer
    with ClearCallbacks integrated. Call ClearCallbacks in three locations:
    - In the GC finalizer before efl_unref (catches collection without Unref)
    - In Unref before efl_unref (catches explicit cleanup)
    - In SetEo, disarm the previous finalizer before reassigning the pointer
    
    This ensures all registered event callbacks are cleaned up when objects are
    finalized or unreferenced, preventing stale entries in the callback registry
    that could fire after the underlying Eo object is destroyed.
    
    Also add TestUnrefClearsCallbacks to verify callbacks do not fire after Unref.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 efl/event_test.go | 22 ++++++++++++++++++++++
 efl/object.go     | 39 +++++++++++++++++++++++----------------
 2 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/efl/event_test.go b/efl/event_test.go
index 132be05..cc2cf11 100644
--- a/efl/event_test.go
+++ b/efl/event_test.go
@@ -122,6 +122,28 @@ func TestClearCallbacksNoOp(t *testing.T) {
 	})
 }
 
+// TestUnrefClearsCallbacks verifies that calling Unref on an Object removes all
+// registered callbacks for that object before the Eo reference is released, so
+// none of them fire after Unref returns.
+func TestUnrefClearsCallbacks(t *testing.T) {
+	var count atomic.Int32
+	efl.Sync(func() {
+		raw := efl.NewEoObject()
+		if raw == nil {
+			t.Fatal("NewEoObject returned nil")
+		}
+		obj := efl.WrapObject(raw)
+		efl.RegisterCallback(raw, efl.EFLEventDelDesc(), func(_ unsafe.Pointer) {
+			count.Add(1)
+		})
+		// Unref should clear callbacks before releasing the Eo reference.
+		obj.Unref()
+	})
+	if n := count.Load(); n != 0 {
+		t.Fatalf("callback fired %d time(s) after Unref, want 0", n)
+	}
+}
+
 // TestRegisterMultipleCallbacks verifies that multiple callbacks registered for
 // the same event all fire when the event occurs.
 func TestRegisterMultipleCallbacks(t *testing.T) {
diff --git a/efl/object.go b/efl/object.go
index 8d38233..6559f84 100644
--- a/efl/object.go
+++ b/efl/object.go
@@ -62,6 +62,21 @@ type Object struct {
 	ptr unsafe.Pointer // Eo*; nil means no underlying object
 }
 
+// installFinalizer registers a GC finalizer on o that posts an Unref to the
+// EFL thread when o is collected. It must only be called with a non-nil o.ptr.
+func installFinalizer(o *Object) {
+	runtime.SetFinalizer(o, func(obj *Object) {
+		// Post the Unref so it runs on the EFL thread. The closed guard inside
+		// Post means this is a no-op after Shutdown — acceptable because the EFL
+		// runtime itself cleans up remaining objects during elm_shutdown.
+		p := obj.ptr
+		Post(func() {
+			ClearCallbacks(p)
+			C._ego_efl_unref((*C.Eo)(p))
+		})
+	})
+}
+
 // WrapObject creates an Object that wraps the given raw Eo pointer. It returns
 // nil when ptr is nil. For non-nil pointers it registers a finalizer that posts
 // an Unref to the EFL thread if the Object is garbage-collected without an
@@ -71,15 +86,7 @@ func WrapObject(ptr unsafe.Pointer) *Object {
 		return nil
 	}
 	o := &Object{ptr: ptr}
-	runtime.SetFinalizer(o, func(obj *Object) {
-		// Post the Unref so it runs on the EFL thread. The closed guard inside
-		// Post means this is a no-op after Shutdown — acceptable because the EFL
-		// runtime itself cleans up remaining objects during elm_shutdown.
-		p := obj.ptr
-		Post(func() {
-			C._ego_efl_unref((*C.Eo)(p))
-		})
-	})
+	installFinalizer(o)
 	return o
 }
 
@@ -104,16 +111,15 @@ func (o *Object) Eo() unsafe.Pointer {
 }
 
 // SetEo sets the underlying raw Eo pointer and registers a GC finalizer.
-// Used by generated bindings to initialise wrapper structs.
+// If the Object was already initialised, the previous finalizer is disarmed
+// before the new pointer is stored; the caller is responsible for the lifetime
+// of the old Eo pointer (e.g. calling Unref on it explicitly before calling
+// SetEo). Used by generated bindings to initialise wrapper structs.
 func (o *Object) SetEo(ptr unsafe.Pointer) {
+	runtime.SetFinalizer(o, nil)
 	o.ptr = ptr
 	if ptr != nil {
-		runtime.SetFinalizer(o, func(obj *Object) {
-			p := obj.ptr
-			Post(func() {
-				C._ego_efl_unref((*C.Eo)(p))
-			})
-		})
+		installFinalizer(o)
 	}
 }
 
@@ -148,6 +154,7 @@ func (o *Object) Unref() {
 	runtime.SetFinalizer(o, nil)
 	p := o.ptr
 	o.ptr = nil
+	ClearCallbacks(p)
 	C._ego_efl_unref((*C.Eo)(p))
 }
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to