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 69f00e173b2af2ba6a026a365aa3863a9eccdc65
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 9 15:08:06 2026 -0600
feat: add ClearCallbacks for event lifecycle cleanup
Add a new ClearCallbacks function that removes all registered event
callbacks for an object when it is finalized, preventing callbacks from
firing on deleted Eo pointers. This is essential for safe object cleanup.
The implementation adds:
- cbEntry struct to track object/event-desc pairs for each callback
- cbObj map paralleling cbMap to enable bulk callback removal
- ClearCallbacks function that safely sweeps callbacks for an object
- Full test coverage: TestClearCallbacks and TestClearCallbacksNoOp
Callbacks are cleared under the write lock to prevent races with
concurrent Disconnect calls, and C-side deletions happen while holding
the lock to ensure atomic Go-side state management.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
efl/event.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++
efl/event_test.go | 38 ++++++++++++++++++++++++++++++++++++++
efl/export_test.go | 1 +
3 files changed, 86 insertions(+)
diff --git a/efl/event.go b/efl/event.go
index 36757fb..4993bcd 100644
--- a/efl/event.go
+++ b/efl/event.go
@@ -40,11 +40,20 @@ import (
"unsafe"
)
+// cbEntry records the EFL object and event description a callback was
+// registered on, allowing ClearCallbacks to find all callbacks for an object.
+type cbEntry struct {
+ obj unsafe.Pointer // Eo* the callback is registered on
+ desc unsafe.Pointer // Efl_Event_Description* for the event
+}
+
// cbMap holds all registered Go event callbacks keyed by their unique ID.
+// cbObj maps the same ID to the (obj, desc) pair needed for bulk removal.
// RLock is used during dispatch; Lock is used for register and disconnect.
var (
cbMu sync.RWMutex
cbMap = make(map[uintptr]func(unsafe.Pointer))
+ cbObj = make(map[uintptr]cbEntry)
cbSeq atomic.Uintptr
)
@@ -64,6 +73,7 @@ func RegisterCallback(obj unsafe.Pointer, desc unsafe.Pointer, fn func(eventInfo
cbMu.Lock()
cbMap[id] = fn
+ cbObj[id] = cbEntry{obj: obj, desc: desc}
cbMu.Unlock()
C._ego_event_cb_add(
@@ -91,6 +101,7 @@ func (h *Handle) Disconnect() {
cbMu.Lock()
delete(cbMap, h.id)
+ delete(cbObj, h.id)
cbMu.Unlock()
// Zero the handle so subsequent calls are no-ops.
@@ -99,6 +110,42 @@ func (h *Handle) Disconnect() {
h.desc = nil
}
+// ClearCallbacks removes all callbacks registered on obj from both the EFL
+// object and the Go maps. It is intended for use during object finalization,
+// before the underlying Eo pointer becomes invalid. Must be called on the EFL
+// thread while obj is still a valid Eo pointer.
+func ClearCallbacks(obj unsafe.Pointer) {
+ if obj == nil {
+ return
+ }
+ // Collect the IDs to remove under the write lock so no dispatch can race
+ // with the deletion. Unlike Disconnect (which calls C before locking),
+ // ClearCallbacks holds the lock across the C calls to prevent a concurrent
+ // Disconnect on the same IDs from double-deleting. Both orderings are safe
+ // because the EFL-thread contract makes concurrent execution impossible.
+ cbMu.Lock()
+ var toRemove []uintptr
+ for id, entry := range cbObj {
+ if entry.obj == obj {
+ toRemove = append(toRemove, id)
+ }
+ }
+ for _, id := range toRemove {
+ entry := cbObj[id]
+ delete(cbMap, id)
+ delete(cbObj, id)
+ // Call into C while still holding the Go lock. The lock only
+ // coordinates Go-side state; the EFL callback system uses its own
+ // internal locking and this call is safe from the EFL thread.
+ C._ego_event_cb_del(
+ (*C.Eo)(entry.obj),
+ (*C.Efl_Event_Description)(entry.desc),
+ C.uintptr_t(id),
+ )
+ }
+ cbMu.Unlock()
+}
+
// eflEventDelDesc returns the EFL_EVENT_DEL event description pointer as an
// unsafe.Pointer. Used by tests that need to register against EFL_EVENT_DEL
// without importing cgo directly.
diff --git a/efl/event_test.go b/efl/event_test.go
index f4975b9..132be05 100644
--- a/efl/event_test.go
+++ b/efl/event_test.go
@@ -84,6 +84,44 @@ func TestDisconnectIdempotent(t *testing.T) {
})
}
+// TestClearCallbacks verifies that ClearCallbacks purges all callbacks for an
+// object so that none of them fire when the object is subsequently deleted.
+func TestClearCallbacks(t *testing.T) {
+ var count atomic.Int32
+
+ efl.Sync(func() {
+ raw := newTestObject(t)
+
+ // Register two separate callbacks for the DEL event.
+ efl.RegisterCallback(raw, efl.EFLEventDelDesc(), func(_ unsafe.Pointer) {
+ count.Add(1)
+ })
+ efl.RegisterCallback(raw, efl.EFLEventDelDesc(), func(_ unsafe.Pointer) {
+ count.Add(1)
+ })
+
+ // Purge all callbacks for this object before it is deleted.
+ efl.ClearCallbacks(raw)
+
+ // Deleting the object must NOT fire any of the cleared callbacks.
+ efl.DelEoObject(raw)
+ })
+
+ if n := count.Load(); n != 0 {
+ t.Fatalf("cleared callbacks fired %d time(s), want 0", n)
+ }
+}
+
+// TestClearCallbacksNoOp verifies that calling ClearCallbacks on an object with
+// no registered callbacks is a safe no-op.
+func TestClearCallbacksNoOp(t *testing.T) {
+ efl.Sync(func() {
+ raw := newTestObject(t)
+ efl.ClearCallbacks(raw) // must not panic
+ efl.DelEoObject(raw)
+ })
+}
+
// 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/export_test.go b/efl/export_test.go
index 2eb85ad..b5d5d78 100644
--- a/efl/export_test.go
+++ b/efl/export_test.go
@@ -19,3 +19,4 @@ func DelEoObject(ptr unsafe.Pointer) { delEoObject(ptr) }
// reference. Use DelEoObject to delete it and trigger EFL_EVENT_DEL synchronously.
// Must be called on the EFL thread.
func NewEoObjectOwned() unsafe.Pointer { return newEoObjectOwned() }
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.