1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-28 01:33:10 +00:00

Handler: Added blocking send cleanup to avoid goroutine leak

This commit is contained in:
diamondburned 2020-07-15 23:11:20 -07:00
parent 6717f8002c
commit 35e143a99f
2 changed files with 75 additions and 20 deletions

View file

@ -173,6 +173,9 @@ func (h *Handler) ChanFor(fn func(interface{}) bool) (out <-chan interface{}, ca
// should not be closed at all. The caller function WILL PANIC if the channel is // should not be closed at all. The caller function WILL PANIC if the channel is
// closed! // closed!
// //
// When the rm callback that is returned is called, it will also guarantee that
// all blocking sends will be cancelled. This helps prevent dangling goroutines.
//
// // An example of a valid channel handler. // // An example of a valid channel handler.
// ch := make(chan *gateway.MessageCreateEvent) // ch := make(chan *gateway.MessageCreateEvent)
// h.AddHandler(ch) // h.AddHandler(ch)
@ -232,7 +235,13 @@ func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
h.hmutex.Lock() h.hmutex.Lock()
defer h.hmutex.Unlock() defer h.hmutex.Unlock()
// Delete the handler from the map: // Take and delete the handler from the map, but return if we can't find
// it.
hd, ok := h.handlers[serial]
if !ok {
return
}
delete(h.handlers, serial) delete(h.handlers, serial)
// Delete the key from the orders slice: // Delete the key from the orders slice:
@ -242,14 +251,17 @@ func (h *Handler) addHandler(fn interface{}) (rm func(), err error) {
break break
} }
} }
// Clean up the handler.
hd.cleanup()
}, nil }, nil
} }
type handler struct { type handler struct {
event reflect.Type // underlying type; arg0 or chan underlying type event reflect.Type // underlying type; arg0 or chan underlying type
callback reflect.Value callback reflect.Value
isChan bool isIface bool
isIface bool chanclose reflect.Value // IsValid() if chan
} }
// newHandler reflects either a channel or a function into a handler. A function // newHandler reflects either a channel or a function into a handler. A function
@ -260,8 +272,9 @@ func newHandler(unknown interface{}) (*handler, error) {
fnT := fnV.Type() fnT := fnV.Type()
// underlying event type // underlying event type
var argT reflect.Type var handler = handler{
var isch bool callback: fnV,
}
switch fnT.Kind() { switch fnT.Kind() {
case reflect.Func: case reflect.Func:
@ -273,29 +286,26 @@ func newHandler(unknown interface{}) (*handler, error) {
return nil, errors.New("function can't accept returns") return nil, errors.New("function can't accept returns")
} }
argT = fnT.In(0) handler.event = fnT.In(0)
case reflect.Chan: case reflect.Chan:
argT = fnT.Elem() handler.event = fnT.Elem()
isch = true handler.chanclose = reflect.ValueOf(make(chan struct{}))
default: default:
return nil, errors.New("given interface is not a function or channel") return nil, errors.New("given interface is not a function or channel")
} }
var kind = argT.Kind() var kind = handler.event.Kind()
// Accept either pointer type or interface{} type // Accept either pointer type or interface{} type
if kind != reflect.Ptr && kind != reflect.Interface { if kind != reflect.Ptr && kind != reflect.Interface {
return nil, errors.New("first argument is not pointer") return nil, errors.New("first argument is not pointer")
} }
return &handler{ handler.isIface = kind == reflect.Interface
event: argT,
callback: fnV, return &handler, nil
isChan: isch,
isIface: kind == reflect.Interface,
}, nil
} }
func (h handler) not(event reflect.Type) bool { func (h handler) not(event reflect.Type) bool {
@ -306,10 +316,21 @@ func (h handler) not(event reflect.Type) bool {
return h.event != event return h.event != event
} }
func (h handler) call(event reflect.Value) { func (h *handler) call(event reflect.Value) {
if h.isChan { if h.chanclose.IsValid() {
h.callback.Send(event) reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectSend, Chan: h.callback, Send: event},
{Dir: reflect.SelectRecv, Chan: h.chanclose},
})
} else { } else {
h.callback.Call([]reflect.Value{event}) h.callback.Call([]reflect.Value{event})
} }
} }
func (h *handler) cleanup() {
if h.chanclose.IsValid() {
// Closing this channel will force all ongoing selects to return
// immediately.
h.chanclose.Close()
}
}

View file

@ -112,6 +112,40 @@ func TestHandlerChan(t *testing.T) {
} }
} }
func TestHandlerChanCancel(t *testing.T) {
// Never receive from this channel.
var results = make(chan *gateway.MessageCreateEvent)
h, err := newHandler(results)
if err != nil {
t.Fatal(err)
}
const result = "Hime Arikawa"
var msg = newMessage(result)
var msgV = reflect.ValueOf(msg)
var msgT = msgV.Type()
if h.not(msgT) {
t.Fatal("Event type mismatch")
}
// Call in a goroutine, which would trigger a close.
go h.call(msgV)
// Call the cleanup function, which should stop the send.
h.cleanup()
// Check if we still have things being sent.
select {
case <-results:
t.Fatal("Unexpected dangling goroutine")
case <-time.After(200 * time.Millisecond):
return
}
}
func TestHandlerInterface(t *testing.T) { func TestHandlerInterface(t *testing.T) {
var results = make(chan interface{}) var results = make(chan interface{})