Port sp0rkle's panic recovery back into goirc.

This commit is contained in:
Alex Bramley 2013-03-10 13:30:00 +00:00
parent 5f2665dde8
commit ac9d05efa2
3 changed files with 44 additions and 6 deletions

View File

@ -68,6 +68,9 @@ type Config struct {
// Sent as the QUIT message. // Sent as the QUIT message.
QuitMessage string QuitMessage string
// Configurable panic recovery for all handlers.
Recover func(*Conn, *Line)
} }
func NewConfig(nick string, args ...string) *Config { func NewConfig(nick string, args ...string) *Config {
@ -75,6 +78,7 @@ func NewConfig(nick string, args ...string) *Config {
Me: state.NewNick(nick), Me: state.NewNick(nick),
PingFreq: 3 * time.Minute, PingFreq: 3 * time.Minute,
NewNick: func(s string) string { return s + "_" }, NewNick: func(s string) string { return s + "_" },
Recover: (*Conn).LogPanic, // in dispatch.go
} }
cfg.Me.Ident = "goirc" cfg.Me.Ident = "goirc"
if len(args) > 0 && args[0] != "" { if len(args) > 0 && args[0] != "" {

View File

@ -2,6 +2,7 @@ package client
import ( import (
"github.com/fluffle/golog/logging" "github.com/fluffle/golog/logging"
"runtime"
"strings" "strings"
"sync" "sync"
) )
@ -45,8 +46,9 @@ type hNode struct {
handler Handler handler Handler
} }
// A hNode implements both Handler... // A hNode implements both Handler (with configurable panic recovery)...
func (hn *hNode) Handle(conn *Conn, line *Line) { func (hn *hNode) Handle(conn *Conn, line *Line) {
defer conn.cfg.Recover(conn, line)
hn.handler.Handle(conn, line) hn.handler.Handle(conn, line)
} }
@ -141,3 +143,10 @@ func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
func (conn *Conn) dispatch(line *Line) { func (conn *Conn) dispatch(line *Line) {
conn.handlers.dispatch(conn, line) conn.handlers.dispatch(conn, line)
} }
func (conn *Conn) LogPanic(line *Line) {
if err := recover(); err != nil {
_, f, l, _ := runtime.Caller(2)
logging.Error("%s:%d: panic: %v", f, l, err)
}
}

View File

@ -6,6 +6,11 @@ import (
) )
func TestHandlerSet(t *testing.T) { func TestHandlerSet(t *testing.T) {
// A Conn is needed here because the previous behaviour of passing nil to
// hset.dispatch causes a nil pointer dereference with panic recovery.
c, s := setUp(t)
defer s.tearDown()
hs := handlerSet() hs := handlerSet()
if len(hs.set) != 0 { if len(hs.set) != 0 {
t.Errorf("New set contains things!") t.Errorf("New set contains things!")
@ -83,7 +88,7 @@ func TestHandlerSet(t *testing.T) {
if callcount != 0 { if callcount != 0 {
t.Errorf("Something incremented call count before we were expecting it.") t.Errorf("Something incremented call count before we were expecting it.")
} }
hs.dispatch(nil, &Line{Cmd: "One"}) hs.dispatch(c, &Line{Cmd: "One"})
<-time.After(time.Millisecond) <-time.After(time.Millisecond)
if callcount != 4 { if callcount != 4 {
t.Errorf("Our handler wasn't called four times :-(") t.Errorf("Our handler wasn't called four times :-(")
@ -107,7 +112,7 @@ func TestHandlerSet(t *testing.T) {
} }
// Dispatch should result in 3 additions. // Dispatch should result in 3 additions.
hs.dispatch(nil, &Line{Cmd: "One"}) hs.dispatch(c, &Line{Cmd: "One"})
<-time.After(time.Millisecond) <-time.After(time.Millisecond)
if callcount != 7 { if callcount != 7 {
t.Errorf("Our handler wasn't called three times :-(") t.Errorf("Our handler wasn't called three times :-(")
@ -129,7 +134,7 @@ func TestHandlerSet(t *testing.T) {
} }
// Dispatch should result in 2 additions. // Dispatch should result in 2 additions.
hs.dispatch(nil, &Line{Cmd: "One"}) hs.dispatch(c, &Line{Cmd: "One"})
<-time.After(time.Millisecond) <-time.After(time.Millisecond)
if callcount != 9 { if callcount != 9 {
t.Errorf("Our handler wasn't called two times :-(") t.Errorf("Our handler wasn't called two times :-(")
@ -151,7 +156,7 @@ func TestHandlerSet(t *testing.T) {
} }
// Dispatch should result in 1 addition. // Dispatch should result in 1 addition.
hs.dispatch(nil, &Line{Cmd: "One"}) hs.dispatch(c, &Line{Cmd: "One"})
<-time.After(time.Millisecond) <-time.After(time.Millisecond)
if callcount != 10 { if callcount != 10 {
t.Errorf("Our handler wasn't called once :-(") t.Errorf("Our handler wasn't called once :-(")
@ -170,9 +175,29 @@ func TestHandlerSet(t *testing.T) {
} }
// Dispatch should result in NO additions. // Dispatch should result in NO additions.
hs.dispatch(nil, &Line{Cmd: "One"}) hs.dispatch(c, &Line{Cmd: "One"})
<-time.After(time.Millisecond) <-time.After(time.Millisecond)
if callcount != 10 { if callcount != 10 {
t.Errorf("Our handler was called?") t.Errorf("Our handler was called?")
} }
} }
func TestPanicRecovery(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
recovered := false
c.cfg.Recover = func(conn *Conn, line *Line) {
if err, ok := recover().(string); ok && err == "panic!" {
recovered = true
}
}
c.HandleFunc(PRIVMSG, func(conn *Conn, line *Line) {
panic("panic!")
})
c.in <- parseLine(":nick!user@host.com PRIVMSG #channel :OH NO PIGEONS")
<-time.After(time.Millisecond)
if recovered != true {
t.Errorf("Failed to recover panic!")
}
}