Merge pull request #82 from thoj/testing

Changed the way Quit/Disconnect works to avoid possible DATA races.
This commit is contained in:
Thomas Jager 2016-11-05 18:55:39 +01:00 committed by GitHub
commit 491578616f
4 changed files with 96 additions and 56 deletions

41
irc.go
View File

@ -74,7 +74,9 @@ func (irc *Connection) readLoop() {
irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg))
} }
irc.Lock()
irc.lastMessage = time.Now() irc.lastMessage = time.Now()
irc.Unlock()
event, err := parseToEvent(msg) event, err := parseToEvent(msg)
event.Connection = irc event.Connection = irc
if err == nil { if err == nil {
@ -171,10 +173,12 @@ func (irc *Connection) pingLoop() {
//Ping at the ping frequency //Ping at the ping frequency
irc.SendRawf("PING %d", time.Now().UnixNano()) irc.SendRawf("PING %d", time.Now().UnixNano())
//Try to recapture nickname if it's not as configured. //Try to recapture nickname if it's not as configured.
irc.Lock()
if irc.nick != irc.nickcurrent { if irc.nick != irc.nickcurrent {
irc.nickcurrent = irc.nick irc.nickcurrent = irc.nick
irc.SendRawf("NICK %s", irc.nick) irc.SendRawf("NICK %s", irc.nick)
} }
irc.Unlock()
case <-irc.end: case <-irc.end:
ticker.Stop() ticker.Stop()
ticker2.Stop() ticker2.Stop()
@ -183,13 +187,20 @@ func (irc *Connection) pingLoop() {
} }
} }
func (irc *Connection) isQuitting() bool {
irc.Lock()
defer irc.Unlock()
return irc.quit
}
// Main loop to control the connection. // Main loop to control the connection.
func (irc *Connection) Loop() { func (irc *Connection) Loop() {
errChan := irc.ErrorChan() errChan := irc.ErrorChan()
for !irc.quit { for !irc.isQuitting() {
err := <-errChan err := <-errChan
irc.Wait()
for !irc.isQuitting() {
irc.Log.Printf("Error, disconnected: %s\n", err) irc.Log.Printf("Error, disconnected: %s\n", err)
for !irc.quit {
if err = irc.Reconnect(); err != nil { if err = irc.Reconnect(); err != nil {
irc.Log.Printf("Error while reconnecting: %s\n", err) irc.Log.Printf("Error while reconnecting: %s\n", err)
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
@ -211,8 +222,10 @@ func (irc *Connection) Quit() {
} }
irc.SendRaw(quit) irc.SendRaw(quit)
irc.Lock()
irc.stopped = true irc.stopped = true
irc.quit = true irc.quit = true
irc.Unlock()
} }
// Use the connection to join a given channel. // Use the connection to join a given channel.
@ -341,37 +354,15 @@ func (irc *Connection) Connected() bool {
// A disconnect sends all buffered messages (if possible), // A disconnect sends all buffered messages (if possible),
// stops all goroutines and then closes the socket. // stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() { func (irc *Connection) Disconnect() {
for event := range irc.events {
irc.ClearCallback(event)
}
if irc.end != nil {
close(irc.end)
}
irc.end = nil
if irc.pwrite != nil {
close(irc.pwrite)
}
irc.Wait()
if irc.socket != nil { if irc.socket != nil {
irc.socket.Close() irc.socket.Close()
} }
irc.socket = nil close(irc.end)
irc.ErrorChan() <- ErrDisconnected irc.ErrorChan() <- ErrDisconnected
} }
// Reconnect to a server using the current connection. // Reconnect to a server using the current connection.
func (irc *Connection) Reconnect() error { func (irc *Connection) Reconnect() error {
if irc.end != nil {
close(irc.end)
}
irc.end = nil
irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed
irc.end = make(chan struct{}) irc.end = make(chan struct{})
return irc.Connect(irc.Server) return irc.Connect(irc.Server)
} }

View File

@ -136,9 +136,8 @@ func (irc *Connection) RunCallbacks(event *Event) {
func (irc *Connection) setupCallbacks() { func (irc *Connection) setupCallbacks() {
irc.events = make(map[string]map[int]func(*Event)) irc.events = make(map[string]map[int]func(*Event))
//Handle error events. This has to be called in a new thred to allow //Handle error events.
//readLoop to exit irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() })
irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() })
//Handle ping events //Handle ping events
irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) })
@ -201,7 +200,7 @@ func (irc *Connection) setupCallbacks() {
ns, _ := strconv.ParseInt(e.Message(), 10, 64) ns, _ := strconv.ParseInt(e.Message(), 10, 64)
delta := time.Duration(time.Now().UnixNano() - ns) delta := time.Duration(time.Now().UnixNano() - ns)
if irc.Debug { if irc.Debug {
irc.Log.Printf("Lag: %vs\n", delta) irc.Log.Printf("Lag: %.3f s\n", delta.Seconds())
} }
}) })
@ -216,6 +215,8 @@ func (irc *Connection) setupCallbacks() {
// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>" // 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>"
// Set irc.nickcurrent to the actually used nick in this connection. // Set irc.nickcurrent to the actually used nick in this connection.
irc.AddCallback("001", func(e *Event) { irc.AddCallback("001", func(e *Event) {
irc.Lock()
irc.nickcurrent = e.Arguments[0] irc.nickcurrent = e.Arguments[0]
irc.Unlock()
}) })
} }

View File

@ -13,6 +13,7 @@ import (
) )
type Connection struct { type Connection struct {
sync.Mutex
sync.WaitGroup sync.WaitGroup
Debug bool Debug bool
Error chan error Error chan error
@ -46,7 +47,7 @@ type Connection struct {
Log *log.Logger Log *log.Logger
stopped bool stopped bool
quit bool quit bool //User called Quit, do not reconnect.
} }
// A struct to represent an event. // A struct to represent an event.

View File

@ -12,6 +12,10 @@ const serverssl = "irc.freenode.net:7000"
const channel = "#go-eventirc-test" const channel = "#go-eventirc-test"
const dict = "abcdefghijklmnopqrstuvwxyz" const dict = "abcdefghijklmnopqrstuvwxyz"
//Spammy
const verbose_tests = false
const debug_tests = true
func TestConnectionEmtpyServer(t *testing.T) { func TestConnectionEmtpyServer(t *testing.T) {
irccon := IRC("go-eventirc", "go-eventirc") irccon := IRC("go-eventirc", "go-eventirc")
err := irccon.Connect("") err := irccon.Connect("")
@ -91,8 +95,7 @@ func TestConnectionEmptyNick(t *testing.T) {
func TestRemoveCallback(t *testing.T) { func TestRemoveCallback(t *testing.T) {
irccon := IRC("go-eventirc", "go-eventirc") irccon := IRC("go-eventirc", "go-eventirc")
irccon.VerboseCallbackHandler = true debugTest(irccon)
irccon.Debug = true
done := make(chan int, 10) done := make(chan int, 10)
@ -119,8 +122,7 @@ func TestRemoveCallback(t *testing.T) {
func TestWildcardCallback(t *testing.T) { func TestWildcardCallback(t *testing.T) {
irccon := IRC("go-eventirc", "go-eventirc") irccon := IRC("go-eventirc", "go-eventirc")
irccon.VerboseCallbackHandler = true debugTest(irccon)
irccon.Debug = true
done := make(chan int, 10) done := make(chan int, 10)
@ -143,8 +145,7 @@ func TestWildcardCallback(t *testing.T) {
func TestClearCallback(t *testing.T) { func TestClearCallback(t *testing.T) {
irccon := IRC("go-eventirc", "go-eventirc") irccon := IRC("go-eventirc", "go-eventirc")
irccon.VerboseCallbackHandler = true debugTest(irccon)
irccon.Debug = true
done := make(chan int, 10) done := make(chan int, 10)
@ -188,14 +189,16 @@ func TestConnection(t *testing.T) {
ircnick1 := randStr(8) ircnick1 := randStr(8)
ircnick2 := randStr(8) ircnick2 := randStr(8)
irccon1 := IRC(ircnick1, "IRCTest1") irccon1 := IRC(ircnick1, "IRCTest1")
irccon1.VerboseCallbackHandler = true
irccon1.Debug = true irccon1.PingFreq = time.Second * 3
debugTest(irccon1)
irccon2 := IRC(ircnick2, "IRCTest2") irccon2 := IRC(ircnick2, "IRCTest2")
irccon2.VerboseCallbackHandler = true debugTest(irccon2)
irccon2.Debug = true
teststr := randStr(20) teststr := randStr(20)
testmsgok := false testmsgok := make(chan bool, 1)
irccon1.AddCallback("001", func(e *Event) { irccon1.Join(channel) }) irccon1.AddCallback("001", func(e *Event) { irccon1.Join(channel) })
irccon2.AddCallback("001", func(e *Event) { irccon2.Join(channel) }) irccon2.AddCallback("001", func(e *Event) { irccon2.Join(channel) })
@ -204,13 +207,18 @@ func TestConnection(t *testing.T) {
tick := time.NewTicker(1 * time.Second) tick := time.NewTicker(1 * time.Second)
i := 10 i := 10
for { for {
<-tick.C select {
case <-tick.C:
irccon1.Privmsgf(channel, "%s\n", teststr) irccon1.Privmsgf(channel, "%s\n", teststr)
if testmsgok { if i == 0 {
t.Errorf("Timeout while wating for test message from the other thread.")
return
}
case <-testmsgok:
tick.Stop() tick.Stop()
irccon1.Quit() irccon1.Quit()
} else if i == 0 { return
t.Fatal("Timeout while wating for test message from the other thread.")
} }
i -= 1 i -= 1
} }
@ -223,43 +231,76 @@ func TestConnection(t *testing.T) {
}) })
irccon2.AddCallback("PRIVMSG", func(e *Event) { irccon2.AddCallback("PRIVMSG", func(e *Event) {
t.Log(e.Message())
if e.Message() == teststr { if e.Message() == teststr {
if e.Nick == ircnick1 { if e.Nick == ircnick1 {
testmsgok = true testmsgok <- true
irccon2.Quit() irccon2.Quit()
} else { } else {
t.Fatal("Test message came from an unexpected nickname") t.Errorf("Test message came from an unexpected nickname")
} }
} else {
//this may fail if there are other incoming messages, unlikely.
t.Errorf("Test message mismatch")
} }
}) })
irccon2.AddCallback("NICK", func(e *Event) { irccon2.AddCallback("NICK", func(e *Event) {
if irccon2.nickcurrent == ircnick2 { if irccon2.nickcurrent == ircnick2 {
t.Fatal("Nick change did not work!") t.Errorf("Nick change did not work!")
} }
}) })
err := irccon1.Connect(server) err := irccon1.Connect(server)
if err != nil { if err != nil {
t.Log(err.Error()) t.Log(err.Error())
t.Fatal("Can't connect to freenode.") t.Errorf("Can't connect to freenode.")
} }
err = irccon2.Connect(server) err = irccon2.Connect(server)
if err != nil { if err != nil {
t.Log(err.Error()) t.Log(err.Error())
t.Fatal("Can't connect to freenode.") t.Errorf("Can't connect to freenode.")
} }
go irccon2.Loop() go irccon2.Loop()
irccon1.Loop() irccon1.Loop()
} }
func TestReconnect(t *testing.T) {
ircnick1 := randStr(8)
irccon := IRC(ircnick1, "IRCTestRe")
debugTest(irccon)
connects := 0
irccon.AddCallback("001", func(e *Event) { irccon.Join(channel) })
irccon.AddCallback("366", func(e *Event) {
connects += 1
if connects > 2 {
irccon.Privmsgf(channel, "Connection nr %d (test done)\n", connects)
irccon.Quit()
} else {
irccon.Privmsgf(channel, "Connection nr %d\n", connects)
time.Sleep(100) //Need to let the thraed actually send before closing socket
irccon.Disconnect()
}
})
err := irccon.Connect(server)
if err != nil {
t.Log(err.Error())
t.Errorf("Can't connect to freenode.")
}
irccon.Loop()
if connects != 3 {
t.Errorf("Reconnect test failed. Connects = %d", connects)
}
}
func TestConnectionSSL(t *testing.T) { func TestConnectionSSL(t *testing.T) {
ircnick1 := randStr(8) ircnick1 := randStr(8)
irccon := IRC(ircnick1, "IRCTestSSL") irccon := IRC(ircnick1, "IRCTestSSL")
irccon.VerboseCallbackHandler = true debugTest(irccon)
irccon.Debug = true
irccon.UseTLS = true irccon.UseTLS = true
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irccon.AddCallback("001", func(e *Event) { irccon.Join(channel) }) irccon.AddCallback("001", func(e *Event) { irccon.Join(channel) })
@ -272,7 +313,7 @@ func TestConnectionSSL(t *testing.T) {
err := irccon.Connect(serverssl) err := irccon.Connect(serverssl)
if err != nil { if err != nil {
t.Log(err.Error()) t.Log(err.Error())
t.Fatal("Can't connect to freenode.") t.Errorf("Can't connect to freenode.")
} }
irccon.Loop() irccon.Loop()
@ -286,3 +327,9 @@ func randStr(n int) string {
} }
return string(b) return string(b)
} }
func debugTest(irccon *Connection) *Connection {
irccon.VerboseCallbackHandler = verbose_tests
irccon.Debug = debug_tests
return irccon
}