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:
parent
6717f8002c
commit
35e143a99f
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue