split out connection info and throttling into ircConn; move conn from state to client

This commit is contained in:
Liam Stanley 2017-02-12 02:22:41 -05:00
parent 1090fd92a8
commit ed6e266cba
3 changed files with 55 additions and 61 deletions

@ -35,6 +35,8 @@ type Client struct {
// CTCP is a handler which manages internal and external CTCP handlers. // CTCP is a handler which manages internal and external CTCP handlers.
CTCP *CTCP CTCP *CTCP
// conn is a net.Conn reference to the IRC server.
conn *ircConn
// tries represents the internal reconnect count to the IRC server. // tries represents the internal reconnect count to the IRC server.
tries int tries int
// reconnecting is true if the client is reconnecting, used so multiple // reconnecting is true if the client is reconnecting, used so multiple
@ -203,9 +205,7 @@ func (c *Client) Connect() error {
return err return err
} }
c.state.mu.Lock() c.conn = conn
c.state.conn = conn
c.state.mu.Unlock()
// Send a virtual event allowing hooks for successful socket connection. // Send a virtual event allowing hooks for successful socket connection.
c.Events <- &Event{Command: INITIALIZED, Trailing: c.Server()} c.Events <- &Event{Command: INITIALIZED, Trailing: c.Server()}
@ -226,12 +226,6 @@ func (c *Client) Connect() error {
c.tries = 0 c.tries = 0
c.reconnecting = false c.reconnecting = false
c.state.mu.Lock()
ctime := time.Now()
c.state.connTime = &ctime
c.state.connected = true
c.state.mu.Unlock()
// Start read loop to process messages from the server. // Start read loop to process messages from the server.
var rctx, ectx context.Context var rctx, ectx context.Context
rctx, c.closeRead = context.WithCancel(context.Background()) rctx, c.closeRead = context.WithCancel(context.Background())
@ -313,12 +307,10 @@ func (c *Client) Reconnect() error {
func (c *Client) cleanup(all bool) { func (c *Client) cleanup(all bool) {
c.cmux.Lock() c.cmux.Lock()
c.state.mu.Lock()
// Close any connections they have open. // Close any connections they have open.
if c.state.conn != nil { if c.conn != nil {
c.state.conn.Close() c.conn.Close()
} }
c.state.mu.Unlock()
if c.closeRead != nil { if c.closeRead != nil {
c.closeRead() c.closeRead()
@ -380,8 +372,8 @@ func (c *Client) readLoop(ctx context.Context) {
case <-ctx.Done(): case <-ctx.Done():
return return
default: default:
c.state.conn.lconn.SetDeadline(time.Now().Add(300 * time.Second)) c.conn.setTimeout(300 * time.Second)
event, err = c.state.conn.Decode() event, err = c.conn.Decode()
if err != nil { if err != nil {
// Attempt a reconnect (if applicable). If it fails, send // Attempt a reconnect (if applicable). If it fails, send
// the error to c.Config.HandleError to be dealt with, if // the error to c.Config.HandleError to be dealt with, if
@ -483,7 +475,7 @@ func (c *Client) Lifetime() time.Duration {
// simply looking to trigger handlers with an event. // simply looking to trigger handlers with an event.
func (c *Client) Send(event *Event) error { func (c *Client) Send(event *Event) error {
if !c.Config.AllowFlood { if !c.Config.AllowFlood {
<-time.After(c.state.rate(event.Len())) <-time.After(c.conn.rate(event.Len()))
} }
return c.write(event) return c.write(event)
@ -491,14 +483,14 @@ func (c *Client) Send(event *Event) error {
// write is the lower level function to write an event. // write is the lower level function to write an event.
func (c *Client) write(event *Event) error { func (c *Client) write(event *Event) error {
c.state.lastWrite = time.Now() c.conn.lastWrite = time.Now()
// log the event // log the event
if !event.Sensitive { if !event.Sensitive {
c.debug.Print("> ", StripRaw(event.String())) c.debug.Print("> ", StripRaw(event.String()))
} }
return c.state.conn.Encode(event) return c.conn.Encode(event)
} }
// Uptime is the time at which the client successfully connected to the // Uptime is the time at which the client successfully connected to the
@ -508,9 +500,7 @@ func (c *Client) Uptime() (up *time.Time, err error) {
return nil, ErrNotConnected return nil, ErrNotConnected
} }
c.state.mu.RLock() up = c.conn.connTime
up = c.state.connTime
c.state.mu.RUnlock()
return up, nil return up, nil
} }
@ -522,20 +512,17 @@ func (c *Client) ConnSince() (since *time.Duration, err error) {
return nil, ErrNotConnected return nil, ErrNotConnected
} }
c.state.mu.RLock() timeSince := time.Since(*c.conn.connTime)
timeSince := time.Since(*c.state.connTime)
c.state.mu.RUnlock()
return &timeSince, nil return &timeSince, nil
} }
// IsConnected returns true if the client is connected to the server. // IsConnected returns true if the client is connected to the server.
func (c *Client) IsConnected() (connected bool) { func (c *Client) IsConnected() (connected bool) {
c.state.mu.RLock() if c.conn == nil {
connected = c.state.connected return false
c.state.mu.RUnlock() }
return c.conn.connected
return connected
} }
// GetNick returns the current nickname of the active connection. Returns // GetNick returns the current nickname of the active connection. Returns

40
conn.go

@ -29,10 +29,22 @@ var endline = []byte("\r\n")
type ircConn struct { type ircConn struct {
ircEncoder ircEncoder
ircDecoder ircDecoder
lconn net.Conn lconn net.Conn
// lastWrite is used ot keep track of when we last wrote to the server.
lastWrite time.Time
// writeDelay is used to keep track of rate limiting of events sent to
// the server.
writeDelay time.Duration
// connected is true if we're actively connected to a server.
connected bool
// connTime is the time at which the client has connected to a server.
connTime *time.Time
} }
// newConn sets up and returns a new connection to the server. This includes
// setting up things like proxies, ssl/tls, and other misc. things.
func newConn(conf Config, addr string) (*ircConn, error) { func newConn(conf Config, addr string) (*ircConn, error) {
// Sanity check a few options. // Sanity check a few options.
if conf.Server == "" { if conf.Server == "" {
@ -103,10 +115,14 @@ func newConn(conf Config, addr string) (*ircConn, error) {
conn = tlsConn conn = tlsConn
} }
ctime := time.Now()
return &ircConn{ return &ircConn{
ircEncoder: ircEncoder{writer: conn}, ircEncoder: ircEncoder{writer: conn},
ircDecoder: ircDecoder{reader: bufio.NewReader(conn)}, ircDecoder: ircDecoder{reader: bufio.NewReader(conn)},
lconn: conn, lconn: conn,
connTime: &ctime,
connected: true,
}, nil }, nil
} }
@ -115,6 +131,28 @@ func (c *ircConn) Close() error {
return c.lconn.Close() return c.lconn.Close()
} }
// setTimeout applies a deadline that the connection must respond back with,
// within the specified time.
func (c *ircConn) setTimeout(timeout time.Duration) {
c.lconn.SetDeadline(time.Now().Add(timeout))
}
// rate allows limiting events based on how frequent the event is being sent,
// as well as how many characters each event has.
func (c *ircConn) rate(chars int) time.Duration {
_time := time.Second + ((time.Duration(chars) * time.Second) / 100)
elapsed := time.Now().Sub(c.lastWrite)
if c.writeDelay += _time - elapsed; c.writeDelay < 0 {
c.writeDelay = 0
}
if c.writeDelay > (8 * time.Second) {
return _time
}
return 0
}
// ircDecoder reads Event objects from an input stream. // ircDecoder reads Event objects from an input stream.
type ircDecoder struct { type ircDecoder struct {
reader *bufio.Reader reader *bufio.Reader

@ -17,20 +17,6 @@ type state struct {
// m is a RW mutex lock, used to guard the state from goroutines causing // m is a RW mutex lock, used to guard the state from goroutines causing
// corruption. // corruption.
mu sync.RWMutex mu sync.RWMutex
// conn is a net.Conn reference to the IRC server.
conn *ircConn
// lastWrite is used ot keep track of when we last wrote to the server.
lastWrite time.Time
// writeDelay is used to keep track of rate limiting of events sent to
// the server.
writeDelay time.Duration
// connected is true if we're actively connected to a server.
connected bool
// connTime is the time at which the client has connected to a server.
connTime *time.Time
// nick is the tracker for our nickname on the server. // nick is the tracker for our nickname on the server.
nick string nick string
// channels represents all channels we're active in. // channels represents all channels we're active in.
@ -180,7 +166,6 @@ func newState() *state {
s.channels = make(map[string]*Channel) s.channels = make(map[string]*Channel)
s.serverOptions = make(map[string]string) s.serverOptions = make(map[string]string)
s.connected = false
return s return s
} }
@ -338,19 +323,3 @@ func (s *state) lookupUsers(matchType, toMatch string) []*User {
return users return users
} }
// rate allows limiting events based on how frequent the event is being sent,
// as well as how many characters each event has.
func (s *state) rate(chars int) time.Duration {
_time := time.Second + ((time.Duration(chars) * time.Second) / 100)
elapsed := time.Now().Sub(s.lastWrite)
if s.writeDelay += _time - elapsed; s.writeDelay < 0 {
s.writeDelay = 0
}
if s.writeDelay > (8 * time.Second) {
return _time
}
return 0
}