mirror of https://github.com/fluffle/goirc
Fix issues/9 by implementing a client-side ping loop.
This commit is contained in:
parent
907560b599
commit
8fa5e5624e
|
@ -47,15 +47,18 @@ type Conn struct {
|
||||||
Connected bool
|
Connected bool
|
||||||
|
|
||||||
// Control channels to goroutines
|
// Control channels to goroutines
|
||||||
cSend, cLoop chan bool
|
cSend, cLoop, cPing chan bool
|
||||||
|
|
||||||
// Misc knobs to tweak client behaviour:
|
// Misc knobs to tweak client behaviour:
|
||||||
// Are we connecting via SSL? Do we care about certificate validity?
|
// Are we connecting via SSL? Do we care about certificate validity?
|
||||||
SSL bool
|
SSL bool
|
||||||
SSLConfig *tls.Config
|
SSLConfig *tls.Config
|
||||||
|
|
||||||
// Socket timeout, in seconds. Defaulted to 5m in New().
|
// Client->server ping frequency, in seconds. Defaults to 3m.
|
||||||
Timeout int64
|
PingFreq int64
|
||||||
|
|
||||||
|
// Socket timeout, in seconds. Default to 5m.
|
||||||
|
Timeout int64
|
||||||
|
|
||||||
// Set this to true to disable flood protection and false to re-enable
|
// Set this to true to disable flood protection and false to re-enable
|
||||||
Flood bool
|
Flood bool
|
||||||
|
@ -95,8 +98,10 @@ func Client(nick, ident, name string,
|
||||||
out: make(chan string, 32),
|
out: make(chan string, 32),
|
||||||
cSend: make(chan bool),
|
cSend: make(chan bool),
|
||||||
cLoop: make(chan bool),
|
cLoop: make(chan bool),
|
||||||
|
cPing: make(chan bool),
|
||||||
SSL: false,
|
SSL: false,
|
||||||
SSLConfig: nil,
|
SSLConfig: nil,
|
||||||
|
PingFreq: 180,
|
||||||
Timeout: 300,
|
Timeout: 300,
|
||||||
Flood: false,
|
Flood: false,
|
||||||
badness: 0,
|
badness: 0,
|
||||||
|
@ -194,6 +199,7 @@ func (conn *Conn) postConnect() {
|
||||||
conn.sock.SetTimeout(conn.Timeout * second)
|
conn.sock.SetTimeout(conn.Timeout * second)
|
||||||
go conn.send()
|
go conn.send()
|
||||||
go conn.recv()
|
go conn.recv()
|
||||||
|
go conn.ping()
|
||||||
go conn.runLoop()
|
go conn.runLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +242,20 @@ func (conn *Conn) recv() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeatedly pings the server every PingFreq seconds (no matter what)
|
||||||
|
func (conn *Conn) ping() {
|
||||||
|
tick := time.NewTicker(conn.PingFreq * second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
conn.Raw(fmt.Sprintf("PING :%d", time.Nanoseconds()))
|
||||||
|
case <-conn.cPing:
|
||||||
|
tick.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// goroutine to dispatch events for lines received on input channel
|
// goroutine to dispatch events for lines received on input channel
|
||||||
func (conn *Conn) runLoop() {
|
func (conn *Conn) runLoop() {
|
||||||
for {
|
for {
|
||||||
|
@ -303,6 +323,7 @@ func (conn *Conn) shutdown() {
|
||||||
conn.sock.Close()
|
conn.sock.Close()
|
||||||
conn.cSend <- true
|
conn.cSend <- true
|
||||||
conn.cLoop <- true
|
conn.cLoop <- true
|
||||||
|
conn.cPing <- true
|
||||||
// reinit datastructures ready for next connection
|
// reinit datastructures ready for next connection
|
||||||
// do this here rather than after runLoop()'s for due to race
|
// do this here rather than after runLoop()'s for due to race
|
||||||
conn.initialise()
|
conn.initialise()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/fluffle/golog/logging"
|
"github.com/fluffle/golog/logging"
|
||||||
"github.com/fluffle/goirc/state"
|
"github.com/fluffle/goirc/state"
|
||||||
"gomock.googlecode.com/hg/gomock"
|
"gomock.googlecode.com/hg/gomock"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -260,6 +261,7 @@ func TestRecv(t *testing.T) {
|
||||||
// channels manually for recv() to be able to call shutdown correctly.
|
// channels manually for recv() to be able to call shutdown correctly.
|
||||||
<-c.cSend
|
<-c.cSend
|
||||||
<-c.cLoop
|
<-c.cLoop
|
||||||
|
<-c.cPing
|
||||||
// Give things time to shake themselves out...
|
// Give things time to shake themselves out...
|
||||||
<-time.After(1e6)
|
<-time.After(1e6)
|
||||||
if !exited {
|
if !exited {
|
||||||
|
@ -272,6 +274,74 @@ func TestRecv(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
// Passing a second value to setUp inhibits postConnect()
|
||||||
|
c, s := setUp(t, false)
|
||||||
|
// We can't use tearDown here, as it will cause a deadlock in shutdown()
|
||||||
|
// trying to send kill messages down channels to nonexistent goroutines.
|
||||||
|
defer s.ctrl.Finish()
|
||||||
|
|
||||||
|
// Set a low ping frequency for testing.
|
||||||
|
// This still increases testing time by a good few seconds :-/
|
||||||
|
c.PingFreq = 1
|
||||||
|
|
||||||
|
// reader is a helper to do a "non-blocking" read of c.out
|
||||||
|
reader := func() string {
|
||||||
|
select {
|
||||||
|
case <-time.After(1e6):
|
||||||
|
case s := <-c.out:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if s := reader(); s != "" {
|
||||||
|
t.Errorf("Line output before ping started.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start ping loop.
|
||||||
|
exited := false
|
||||||
|
go func() {
|
||||||
|
c.ping()
|
||||||
|
exited = true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// The first ping should be after a second,
|
||||||
|
// so we don't expect anything now on c.in
|
||||||
|
if s := reader(); s != "" {
|
||||||
|
t.Errorf("Line output directly after ping started.")
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(1e9)
|
||||||
|
if s := reader(); s == "" || !strings.HasPrefix(s, "PING :") {
|
||||||
|
t.Errorf("Line not output after 1 second.")
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(1e7)
|
||||||
|
if s := reader(); s != "" {
|
||||||
|
t.Errorf("Line output under a second after last ping.")
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(1e9)
|
||||||
|
if s := reader(); s == "" || !strings.HasPrefix(s, "PING :") {
|
||||||
|
t.Errorf("Line not output after another second.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now kill the ping loop.
|
||||||
|
if exited {
|
||||||
|
t.Errorf("Exited before signal sent.")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cPing <- true
|
||||||
|
<-time.After(1e9)
|
||||||
|
if s := reader(); s != "" {
|
||||||
|
t.Errorf("Line output after ping stopped.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exited {
|
||||||
|
t.Errorf("Didn't exit after signal.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunLoop(t *testing.T) {
|
func TestRunLoop(t *testing.T) {
|
||||||
// Passing a second value to setUp inhibits postConnect()
|
// Passing a second value to setUp inhibits postConnect()
|
||||||
c, s := setUp(t, false)
|
c, s := setUp(t, false)
|
||||||
|
@ -364,6 +434,7 @@ func TestWrite(t *testing.T) {
|
||||||
go func() {
|
go func() {
|
||||||
<-c.cSend
|
<-c.cSend
|
||||||
<-c.cLoop
|
<-c.cLoop
|
||||||
|
<-c.cPing
|
||||||
}()
|
}()
|
||||||
s.nc.Close()
|
s.nc.Close()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue