mirror of
				https://github.com/fluffle/goirc
				synced 2025-11-04 03:58:03 +00:00 
			
		
		
		
	Set a maximum duration for fg handlers to run
This commit is contained in:
		
							parent
							
								
									329a62d7d9
								
							
						
					
					
						commit
						f72772b72a
					
				
					 2 changed files with 59 additions and 14 deletions
				
			
		| 
						 | 
					@ -110,6 +110,10 @@ type Config struct {
 | 
				
			||||||
	// Split PRIVMSGs, NOTICEs and CTCPs longer than SplitLen characters
 | 
						// Split PRIVMSGs, NOTICEs and CTCPs longer than SplitLen characters
 | 
				
			||||||
	// over multiple lines. Default to 450 if not set.
 | 
						// over multiple lines. Default to 450 if not set.
 | 
				
			||||||
	SplitLen int
 | 
						SplitLen int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The duration before a handler timeout is triggered. Defaults to 1m.
 | 
				
			||||||
 | 
						// Set to 0 to wait indefinitely.
 | 
				
			||||||
 | 
						HandlerTimeout time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewConfig creates a Config struct containing sensible defaults.
 | 
					// NewConfig creates a Config struct containing sensible defaults.
 | 
				
			||||||
| 
						 | 
					@ -119,11 +123,12 @@ type Config struct {
 | 
				
			||||||
func NewConfig(nick string, args ...string) *Config {
 | 
					func NewConfig(nick string, args ...string) *Config {
 | 
				
			||||||
	cfg := &Config{
 | 
						cfg := &Config{
 | 
				
			||||||
		Me:             &state.Nick{Nick: nick},
 | 
							Me:             &state.Nick{Nick: nick},
 | 
				
			||||||
		PingFreq: 3 * time.Minute,
 | 
							PingFreq:       2 * time.Minute,
 | 
				
			||||||
		NewNick:        func(s string) string { return s + "_" },
 | 
							NewNick:        func(s string) string { return s + "_" },
 | 
				
			||||||
		Recover:        (*Conn).LogPanic, // in dispatch.go
 | 
							Recover:        (*Conn).LogPanic, // in dispatch.go
 | 
				
			||||||
		SplitLen:       defaultSplit,
 | 
							SplitLen:       defaultSplit,
 | 
				
			||||||
		Timeout:        60 * time.Second,
 | 
							Timeout:        60 * time.Second,
 | 
				
			||||||
 | 
							HandlerTimeout: 60 * time.Second,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cfg.Me.Ident = "goirc"
 | 
						cfg.Me.Ident = "goirc"
 | 
				
			||||||
	if len(args) > 0 && args[0] != "" {
 | 
						if len(args) > 0 && args[0] != "" {
 | 
				
			||||||
| 
						 | 
					@ -458,13 +463,19 @@ func (conn *Conn) ping() {
 | 
				
			||||||
// It pulls Lines from the input channel and dispatches them to any
 | 
					// It pulls Lines from the input channel and dispatches them to any
 | 
				
			||||||
// handlers that have been registered for that IRC verb.
 | 
					// handlers that have been registered for that IRC verb.
 | 
				
			||||||
func (conn *Conn) runLoop() {
 | 
					func (conn *Conn) runLoop() {
 | 
				
			||||||
	defer conn.wg.Done()
 | 
					 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case line := <-conn.in:
 | 
							case line := <-conn.in:
 | 
				
			||||||
			conn.dispatch(line)
 | 
								err := conn.dispatch(line)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									// Close() will wait on this, can't defer it.
 | 
				
			||||||
 | 
									conn.wg.Done()
 | 
				
			||||||
 | 
									conn.Close()
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		case <-conn.die:
 | 
							case <-conn.die:
 | 
				
			||||||
			// control channel closed, bail out
 | 
								// control channel closed, bail out
 | 
				
			||||||
 | 
								conn.wg.Done()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,13 @@
 | 
				
			||||||
package client
 | 
					package client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"github.com/fluffle/goirc/logging"
 | 
						"github.com/fluffle/goirc/logging"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Handlers are triggered on incoming Lines from the server, with the handler
 | 
					// Handlers are triggered on incoming Lines from the server, with the handler
 | 
				
			||||||
| 
						 | 
					@ -59,6 +62,7 @@ type hNode struct {
 | 
				
			||||||
	set        *hSet
 | 
						set        *hSet
 | 
				
			||||||
	event      string
 | 
						event      string
 | 
				
			||||||
	handler    Handler
 | 
						handler    Handler
 | 
				
			||||||
 | 
						name       string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A hNode implements both Handler (with configurable panic recovery)...
 | 
					// A hNode implements both Handler (with configurable panic recovery)...
 | 
				
			||||||
| 
						 | 
					@ -90,6 +94,7 @@ func (hs *hSet) add(ev string, h Handler) Remover {
 | 
				
			||||||
		set:     hs,
 | 
							set:     hs,
 | 
				
			||||||
		event:   ev,
 | 
							event:   ev,
 | 
				
			||||||
		handler: h,
 | 
							handler: h,
 | 
				
			||||||
 | 
							name:    runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		l.start = hn
 | 
							l.start = hn
 | 
				
			||||||
| 
						 | 
					@ -128,23 +133,45 @@ func (hs *hSet) remove(hn *hNode) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (hs *hSet) dispatch(conn *Conn, line *Line) {
 | 
					func (hs *hSet) dispatch(conn *Conn, line *Line) error {
 | 
				
			||||||
	hs.RLock()
 | 
						hs.RLock()
 | 
				
			||||||
	defer hs.RUnlock()
 | 
						defer hs.RUnlock()
 | 
				
			||||||
	ev := strings.ToLower(line.Cmd)
 | 
						ev := strings.ToLower(line.Cmd)
 | 
				
			||||||
	list, ok := hs.set[ev]
 | 
						list, ok := hs.set[ev]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	wg := &sync.WaitGroup{}
 | 
						wg := &sync.WaitGroup{}
 | 
				
			||||||
	for hn := list.start; hn != nil; hn = hn.next {
 | 
						for hn := list.start; hn != nil; hn = hn.next {
 | 
				
			||||||
		wg.Add(1)
 | 
							wg.Add(1)
 | 
				
			||||||
		go func(hn *hNode) {
 | 
							go func(hn *hNode) {
 | 
				
			||||||
 | 
								logging.Debug("Starting %s handler for event %s", hn.name, line.Cmd)
 | 
				
			||||||
			hn.Handle(conn, line.Copy())
 | 
								hn.Handle(conn, line.Copy())
 | 
				
			||||||
 | 
								logging.Debug("Finished %s handler for event %s", hn.name, line.Cmd)
 | 
				
			||||||
			wg.Done()
 | 
								wg.Done()
 | 
				
			||||||
		}(hn)
 | 
							}(hn)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If we don't care about how long handlers run, wait and bail out early
 | 
				
			||||||
 | 
						if conn.cfg.HandlerTimeout == 0 {
 | 
				
			||||||
		wg.Wait()
 | 
							wg.Wait()
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Limit the amount of time we wait.
 | 
				
			||||||
 | 
						endChan := make(chan struct{})
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer close(endChan)
 | 
				
			||||||
 | 
							wg.Wait()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-endChan:
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						case <-time.After(conn.cfg.HandlerTimeout):
 | 
				
			||||||
 | 
							msg := "Timeout waiting for handlers to complete"
 | 
				
			||||||
 | 
							logging.Error(msg)
 | 
				
			||||||
 | 
							return errors.New(msg)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Handle adds the provided handler to the foreground set for the named event.
 | 
					// Handle adds the provided handler to the foreground set for the named event.
 | 
				
			||||||
| 
						 | 
					@ -171,13 +198,20 @@ func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
 | 
				
			||||||
	return conn.Handle(name, hf)
 | 
						return conn.Handle(name, hf)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (conn *Conn) dispatch(line *Line) {
 | 
					func (conn *Conn) dispatch(line *Line) error {
 | 
				
			||||||
	// We run the internal handlers first, including all state tracking ones.
 | 
						// We run the internal handlers first, including all state tracking ones.
 | 
				
			||||||
	// This ensures that user-supplied handlers that use the tracker have a
 | 
						// This ensures that user-supplied handlers that use the tracker have a
 | 
				
			||||||
	// consistent view of the connection state in handlers that mutate it.
 | 
						// consistent view of the connection state in handlers that mutate it.
 | 
				
			||||||
	conn.intHandlers.dispatch(conn, line)
 | 
						err := conn.intHandlers.dispatch(conn, line)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	go conn.bgHandlers.dispatch(conn, line)
 | 
						go conn.bgHandlers.dispatch(conn, line)
 | 
				
			||||||
	conn.fgHandlers.dispatch(conn, line)
 | 
						err = conn.fgHandlers.dispatch(conn, line)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogPanic is used as the default panic catcher for the client. If, like me,
 | 
					// LogPanic is used as the default panic catcher for the client. If, like me,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue