diff --git a/client/connection.go b/client/connection.go index c5ccc7b..21e6342 100644 --- a/client/connection.go +++ b/client/connection.go @@ -22,7 +22,9 @@ type Conn struct { cfg *Config // Handlers - handlers *hSet + intHandlers *hSet + fgHandlers *hSet + bgHandlers *hSet // State tracker for nicks and channels st state.Tracker @@ -116,12 +118,14 @@ func Client(cfg *Config) *Conn { cfg.Me.Name = "Powered by GoIRC" } conn := &Conn{ - cfg: cfg, - in: make(chan *Line, 32), - out: make(chan string, 32), - handlers: handlerSet(), - stRemovers: make([]Remover, 0, len(stHandlers)), - lastsent: time.Now(), + cfg: cfg, + in: make(chan *Line, 32), + out: make(chan string, 32), + intHandlers: handlerSet(), + fgHandlers: handlerSet(), + bgHandlers: handlerSet(), + stRemovers: make([]Remover, 0, len(stHandlers)), + lastsent: time.Now(), } conn.addIntHandlers() conn.initialise() diff --git a/client/connection_test.go b/client/connection_test.go index 03f6102..32bfac5 100644 --- a/client/connection_test.go +++ b/client/connection_test.go @@ -87,7 +87,7 @@ func TestClientAndStateTracking(t *testing.T) { } // Check that the internal handlers are correctly set up for k, _ := range intHandlers { - if _, ok := c.handlers.set[strings.ToLower(k)]; !ok { + if _, ok := c.intHandlers.set[strings.ToLower(k)]; !ok { t.Errorf("Missing internal handler for '%s'.", k) } } @@ -95,7 +95,7 @@ func TestClientAndStateTracking(t *testing.T) { // Now enable the state tracking code and check its handlers c.EnableStateTracking() for k, _ := range stHandlers { - if _, ok := c.handlers.set[strings.ToLower(k)]; !ok { + if _, ok := c.intHandlers.set[strings.ToLower(k)]; !ok { t.Errorf("Missing state handler for '%s'.", k) } } @@ -124,7 +124,7 @@ func TestClientAndStateTracking(t *testing.T) { // Finally, check state tracking handlers were all removed correctly for k, _ := range stHandlers { - if _, ok := c.handlers.set[strings.ToLower(k)]; ok && k != "NICK" { + if _, ok := c.intHandlers.set[strings.ToLower(k)]; ok && k != "NICK" { // A bit leaky, because intHandlers adds a NICK handler. t.Errorf("State handler for '%s' not removed correctly.", k) } diff --git a/client/dispatch.go b/client/dispatch.go index 844383f..b40dd0b 100644 --- a/client/dispatch.go +++ b/client/dispatch.go @@ -121,9 +121,15 @@ func (hs *hSet) dispatch(conn *Conn, line *Line) { if !ok { return } + wg := &sync.WaitGroup{} for hn := list.start; hn != nil; hn = hn.next { - go hn.Handle(conn, line.Copy()) + wg.Add(1) + go func(hn *hNode) { + hn.Handle(conn, line.Copy()) + wg.Done() + }(hn) } + wg.Wait() } // Handlers are triggered on incoming Lines from the server, with the handler @@ -133,7 +139,15 @@ func (hs *hSet) dispatch(conn *Conn, line *Line) { // strings of digits like "332" (mainly because I really didn't feel like // putting massive constant tables in). func (conn *Conn) Handle(name string, h Handler) Remover { - return conn.handlers.add(name, h) + return conn.fgHandlers.add(name, h) +} + +func (conn *Conn) HandleBG(name string, h Handler) Remover { + return conn.bgHandlers.add(name, h) +} + +func (conn *Conn) handle(name string, h Handler) Remover { + return conn.intHandlers.add(name, h) } func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover { @@ -141,7 +155,18 @@ func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover { } func (conn *Conn) dispatch(line *Line) { - conn.handlers.dispatch(conn, line) + // We run the internal handlers first, including all state tracking ones. + // This ensures that user-supplied handlers that use the tracker have a + // consistent view of the connection state in handlers that mutate it. + conn.intHandlers.dispatch(conn, line) + // Background handlers are run in parallel and do not block the event loop. + // This is useful for things that may need to do significant work. + go conn.bgHandlers.dispatch(conn, line) + // Foreground handlers have a guarantee of protocol consistency: all the + // handlers for one event will have finished before the handlers for the + // next start processing. They are run in parallel but block the event + // loop, so care should be taken to ensure these handlers are quick :-) + conn.fgHandlers.dispatch(conn, line) } func (conn *Conn) LogPanic(line *Line) { diff --git a/client/handlers.go b/client/handlers.go index a1c0676..af6a6e2 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -21,7 +21,7 @@ func (conn *Conn) addIntHandlers() { for n, h := range intHandlers { // internal handlers are essential for the IRC client // to function, so we don't save their Removers here - conn.Handle(n, h) + conn.handle(n, h) } } diff --git a/client/state_handlers.go b/client/state_handlers.go index 4c99083..cb45c9c 100644 --- a/client/state_handlers.go +++ b/client/state_handlers.go @@ -26,7 +26,7 @@ var stHandlers = map[string]HandlerFunc{ func (conn *Conn) addSTHandlers() { for n, h := range stHandlers { - conn.stRemovers = append(conn.stRemovers, conn.Handle(n, h)) + conn.stRemovers = append(conn.stRemovers, conn.handle(n, h)) } }