2013-02-16 00:17:31 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2013-09-27 21:19:40 +00:00
|
|
|
"github.com/fluffle/goirc/logging"
|
2013-03-10 13:30:00 +00:00
|
|
|
"runtime"
|
2013-02-16 00:17:31 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// Handlers are triggered on incoming Lines from the server, with the handler
|
|
|
|
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
|
|
|
|
// replies could come from the server. They'll generally be things like
|
|
|
|
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
|
|
|
|
// strings of digits like "332" (mainly because I really didn't feel like
|
|
|
|
// putting massive constant tables in).
|
|
|
|
//
|
|
|
|
// 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 :-)
|
|
|
|
//
|
|
|
|
// 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.
|
2013-02-16 00:17:31 +00:00
|
|
|
type Handler interface {
|
|
|
|
Handle(*Conn, *Line)
|
|
|
|
}
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// Removers allow for a handler that has been previously added to the client
|
2015-11-02 21:41:41 +00:00
|
|
|
// to be removed.
|
2013-02-16 00:17:31 +00:00
|
|
|
type Remover interface {
|
|
|
|
Remove()
|
|
|
|
}
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// HandlerFunc allows a bare function with this signature to implement the
|
|
|
|
// Handler interface. It is used by Conn.HandleFunc.
|
2013-02-16 00:17:31 +00:00
|
|
|
type HandlerFunc func(*Conn, *Line)
|
|
|
|
|
|
|
|
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
|
|
|
|
hf(conn, line)
|
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// Handlers are organised using a map of lockless singly linked lists, with
|
|
|
|
// each map key representing an IRC verb or numeric, and the linked list
|
|
|
|
// values being handlers that are executed in parallel when a Line from the
|
2013-03-10 13:16:14 +00:00
|
|
|
// server with that verb or numeric arrives.
|
|
|
|
type hSet struct {
|
|
|
|
set map[string]*hList
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
func (hs *hSet) getList(ev string) (hl *hList, ok bool) {
|
|
|
|
ev = strings.ToLower(ev)
|
|
|
|
hs.RLock()
|
|
|
|
defer hs.RUnlock()
|
|
|
|
hl, ok = hs.set[ev]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hs *hSet) getOrMakeList(ev string) (hl *hList) {
|
|
|
|
ev = strings.ToLower(ev)
|
|
|
|
hs.Lock()
|
|
|
|
defer hs.Unlock()
|
|
|
|
hl, ok := hs.set[ev]
|
|
|
|
if !ok {
|
|
|
|
hl = makeHList()
|
|
|
|
hs.set[ev] = hl
|
|
|
|
}
|
|
|
|
return hl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lists are lockless thanks to atomic pointers. (which hNodePtr wraps)
|
2013-02-16 00:17:31 +00:00
|
|
|
type hList struct {
|
2017-03-04 17:04:22 +00:00
|
|
|
first, last hNodePtr
|
|
|
|
}
|
|
|
|
|
|
|
|
// In order for the whole thing to be goroutine-safe, each list must contain a
|
|
|
|
// zero-valued node at any given time as its last element. You'll see why
|
|
|
|
// later down.
|
|
|
|
func makeHList() (hl *hList) {
|
|
|
|
hl, hn0 := &hList{}, &hNode{}
|
|
|
|
hl.first.store(hn0)
|
|
|
|
hl.last.store(hn0)
|
|
|
|
return
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// (hNodeState is also an atomic wrapper.)
|
2013-02-16 00:17:31 +00:00
|
|
|
type hNode struct {
|
2017-03-04 17:04:22 +00:00
|
|
|
next hNodePtr
|
|
|
|
state hNodeState
|
|
|
|
handler Handler
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// Nodes progress through these three stages in order as the program runs.
|
|
|
|
const (
|
|
|
|
unready hNodeState = iota
|
|
|
|
active
|
|
|
|
unlinkable
|
|
|
|
)
|
|
|
|
|
2013-03-10 13:30:00 +00:00
|
|
|
// A hNode implements both Handler (with configurable panic recovery)...
|
2013-02-16 00:17:31 +00:00
|
|
|
func (hn *hNode) Handle(conn *Conn, line *Line) {
|
2013-03-10 13:30:00 +00:00
|
|
|
defer conn.cfg.Recover(conn, line)
|
2013-02-16 00:17:31 +00:00
|
|
|
hn.handler.Handle(conn, line)
|
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// ... and Remover, which works by flagging the node so the goroutines running
|
|
|
|
// hSet.dispatch know to ignore its handler and to dispose of it.
|
2013-02-16 00:17:31 +00:00
|
|
|
func (hn *hNode) Remove() {
|
2017-03-04 17:04:22 +00:00
|
|
|
hn.state.store(unlinkable)
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func handlerSet() *hSet {
|
|
|
|
return &hSet{set: make(map[string]*hList)}
|
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// When a new Handler is added for an event, it is assigned into a hNode,
|
|
|
|
// which is returned as a Remover so the caller can remove it at a later time.
|
|
|
|
//
|
|
|
|
// Concerning goroutine-safety, the point is that the atomic swap there
|
|
|
|
// reserves the previous last node for this handler and puts up a new one.
|
|
|
|
// The former node has the desirable property that the rest of the list points
|
|
|
|
// to it, and the latter inherits this property once the former becomes part
|
|
|
|
// of the list. It's also the case that handler should't be read by
|
|
|
|
// hSet.dispatch before the node is marked as ready via state.
|
2013-02-16 00:17:31 +00:00
|
|
|
func (hs *hSet) add(ev string, h Handler) Remover {
|
2017-03-04 17:04:22 +00:00
|
|
|
hl := hs.getOrMakeList(ev)
|
|
|
|
hn0 := &hNode{}
|
|
|
|
hn := hl.last.swap(hn0)
|
|
|
|
hn.next.store(hn0)
|
|
|
|
hn.handler = h
|
|
|
|
hn.state.compareAndSwap(unready, active)
|
2013-02-16 00:17:31 +00:00
|
|
|
return hn
|
|
|
|
}
|
|
|
|
|
2017-03-04 17:04:22 +00:00
|
|
|
// And finally, dispatch works like so: it goes through the whole list while
|
|
|
|
// remembering the adress of the pointer that led it to the current node,
|
|
|
|
// which allows it to unlink it if it must be. Since the pointers are atomic,
|
|
|
|
// if many goroutine enter the same unlinkable node at the same time, they
|
|
|
|
// will all end up writing the same value to the pointer anyway. Even in
|
|
|
|
// cases where consecutive nodes are flagged and unlinking node n revives node
|
|
|
|
// n+1 which had been unlinked by making node n point to n+2 without the
|
|
|
|
// unlinker of n+1 noticing, all dead nodes are unmistakable and will
|
|
|
|
// eventually be definitely unlinked and garbage-collected. Also note that
|
|
|
|
// the fact that the last node is always a zero node, as well as letting the
|
|
|
|
// list grow concurrently, allows the next-to-last node to be unlinked safely.
|
2013-02-16 00:17:31 +00:00
|
|
|
func (hs *hSet) dispatch(conn *Conn, line *Line) {
|
2017-03-04 17:04:22 +00:00
|
|
|
hl, ok := hs.getList(line.Cmd)
|
2013-02-16 10:33:15 +00:00
|
|
|
if !ok {
|
2017-03-04 17:04:22 +00:00
|
|
|
return // nothing to do
|
2013-02-16 10:33:15 +00:00
|
|
|
}
|
2013-09-27 23:15:54 +00:00
|
|
|
wg := &sync.WaitGroup{}
|
2017-03-04 17:04:22 +00:00
|
|
|
hn, hnptr := hl.first.load(), &hl.first
|
|
|
|
for hn != nil {
|
|
|
|
switch hn.state.load() {
|
|
|
|
case active:
|
|
|
|
wg.Add(1)
|
|
|
|
go func(hn *hNode) {
|
|
|
|
hn.Handle(conn, line.Copy())
|
|
|
|
wg.Done()
|
|
|
|
}(hn)
|
|
|
|
fallthrough
|
|
|
|
case unready:
|
|
|
|
hnptr = &hn.next
|
|
|
|
hn = hnptr.load()
|
|
|
|
case unlinkable:
|
|
|
|
hn = hn.next.load()
|
|
|
|
hnptr.store(hn)
|
|
|
|
}
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
2013-09-27 23:15:54 +00:00
|
|
|
wg.Wait()
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// Handle adds the provided handler to the foreground set for the named event.
|
|
|
|
// It will return a Remover that allows that handler to be removed again.
|
2013-02-16 00:17:31 +00:00
|
|
|
func (conn *Conn) Handle(name string, h Handler) Remover {
|
2013-09-27 23:15:54 +00:00
|
|
|
return conn.fgHandlers.add(name, h)
|
|
|
|
}
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// HandleBG adds the provided handler to the background set for the named
|
|
|
|
// event. It may go away in the future.
|
|
|
|
// It will return a Remover that allows that handler to be removed again.
|
2013-09-27 23:15:54 +00:00
|
|
|
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)
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// HandleFunc adds the provided function as a handler in the foreground set
|
|
|
|
// for the named event.
|
|
|
|
// It will return a Remover that allows that handler to be removed again.
|
2013-02-16 00:17:31 +00:00
|
|
|
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
|
|
|
|
return conn.Handle(name, hf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (conn *Conn) dispatch(line *Line) {
|
2013-09-27 23:15:54 +00:00
|
|
|
// 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)
|
|
|
|
go conn.bgHandlers.dispatch(conn, line)
|
|
|
|
conn.fgHandlers.dispatch(conn, line)
|
2013-02-16 00:17:31 +00:00
|
|
|
}
|
2013-03-10 13:30:00 +00:00
|
|
|
|
2015-04-15 21:27:50 +00:00
|
|
|
// LogPanic is used as the default panic catcher for the client. If, like me,
|
|
|
|
// you are not good with computer, and you'd prefer your bot not to vanish into
|
|
|
|
// the ether whenever you make unfortunate programming mistakes, you may find
|
|
|
|
// this useful: it will recover panics from handler code and log the errors.
|
2013-03-10 13:30:00 +00:00
|
|
|
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)
|
2013-03-10 13:31:04 +00:00
|
|
|
}
|
2013-03-10 13:30:00 +00:00
|
|
|
}
|