Skip to content

Commit

Permalink
handler: Add AllCallersForType for low-level control
Browse files Browse the repository at this point in the history
This is needed in more silly main thread applications to prevent mutex
deadlocks from uncontrollable recursions.
  • Loading branch information
diamondburned committed Dec 28, 2023
1 parent fce16ff commit 9809321
Showing 1 changed file with 39 additions and 17 deletions.
56 changes: 39 additions & 17 deletions utils/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,48 @@ func New() *Handler {
// Call calls all handlers with the given event. This is an internal method; use
// with care.
func (h *Handler) Call(ev interface{}) {
v := reflect.ValueOf(ev)
t := reflect.TypeOf(ev)

h.mutex.RLock()
defer h.mutex.RUnlock()
all := h.AllCallersForType(t)
all(func(caller Caller) bool {
caller.Call(v)
return true
})
}

typedHandlers := h.events[t].Entries
anyHandlers := h.events[nil].Entries
// AllCallersForType returns all callers for the given event type. This is an
// internal method that is rarely useful for external use and should be used
// with care.
func (h *Handler) AllCallersForType(t reflect.Type) func(yield func(Caller) bool) {
return func(yield func(Caller) bool) {
h.mutex.RLock()
defer h.mutex.RUnlock()

if len(typedHandlers) == 0 && len(anyHandlers) == 0 {
return
}
typedHandlers := h.events[t].Entries
anyHandlers := h.events[nil].Entries

v := reflect.ValueOf(ev)
if len(typedHandlers) == 0 && len(anyHandlers) == 0 {
return
}

for _, entry := range typedHandlers {
if entry.isInvalid() {
continue
for _, entry := range typedHandlers {
if entry.isInvalid() {
continue
}
if !yield(entry) {
return
}
}
entry.Call(v)
}

for _, entry := range anyHandlers {
if entry.isInvalid() || entry.not(t) {
continue
for _, entry := range anyHandlers {
if entry.isInvalid() || entry.not(t) {
continue
}
if !yield(entry) {
return
}
}
entry.Call(v)
}
}

Expand Down Expand Up @@ -239,6 +255,10 @@ func (h *Handler) addHandler(fn interface{}, sync bool) (rm func(), err error) {
}, nil
}

// Caller is an interface that can be used to call a handler.
// It directly accepts a reflect.Value, which is the event.
type Caller interface{ Call(ev reflect.Value) }

type handler struct {
event reflect.Type // underlying type; arg0 or chan underlying type
callback reflect.Value
Expand All @@ -248,6 +268,8 @@ type handler struct {
isOnce bool
}

var _ Caller = (*handler)(nil)

// newHandler reflects either a channel or a function into a handler. A function
// must only have a single argument being the event and no return, and a channel
// must have the event type as the underlying type.
Expand Down

0 comments on commit 9809321

Please sign in to comment.