diff --git a/irc.go b/irc.go index 3981761..3b99bbd 100644 --- a/irc.go +++ b/irc.go @@ -74,7 +74,9 @@ func (irc *Connection) readLoop() { irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) } + irc.Lock() irc.lastMessage = time.Now() + irc.Unlock() event, err := parseToEvent(msg) event.Connection = irc if err == nil { @@ -171,10 +173,12 @@ func (irc *Connection) pingLoop() { //Ping at the ping frequency irc.SendRawf("PING %d", time.Now().UnixNano()) //Try to recapture nickname if it's not as configured. + irc.Lock() if irc.nick != irc.nickcurrent { irc.nickcurrent = irc.nick irc.SendRawf("NICK %s", irc.nick) } + irc.Unlock() case <-irc.end: ticker.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. func (irc *Connection) Loop() { errChan := irc.ErrorChan() - for !irc.quit { + for !irc.isQuitting() { err := <-errChan - irc.Log.Printf("Error, disconnected: %s\n", err) - for !irc.quit { + irc.Wait() + for !irc.isQuitting() { + irc.Log.Printf("Error, disconnected: %s\n", err) if err = irc.Reconnect(); err != nil { irc.Log.Printf("Error while reconnecting: %s\n", err) time.Sleep(60 * time.Second) @@ -211,8 +222,10 @@ func (irc *Connection) Quit() { } irc.SendRaw(quit) + irc.Lock() irc.stopped = true irc.quit = true + irc.Unlock() } // 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), // stops all goroutines and then closes the socket. 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 { irc.socket.Close() } - irc.socket = nil + close(irc.end) irc.ErrorChan() <- ErrDisconnected } // Reconnect to a server using the current connection. 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{}) return irc.Connect(irc.Server) } diff --git a/irc_callback.go b/irc_callback.go index b562236..d389f73 100644 --- a/irc_callback.go +++ b/irc_callback.go @@ -136,9 +136,8 @@ func (irc *Connection) RunCallbacks(event *Event) { func (irc *Connection) setupCallbacks() { irc.events = make(map[string]map[int]func(*Event)) - //Handle error events. This has to be called in a new thred to allow - //readLoop to exit - irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() }) + //Handle error events. + irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() }) //Handle ping events 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) delta := time.Duration(time.Now().UnixNano() - ns) 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 !@" // Set irc.nickcurrent to the actually used nick in this connection. irc.AddCallback("001", func(e *Event) { + irc.Lock() irc.nickcurrent = e.Arguments[0] + irc.Unlock() }) } diff --git a/irc_struct.go b/irc_struct.go index 33db846..a188d9d 100644 --- a/irc_struct.go +++ b/irc_struct.go @@ -13,6 +13,7 @@ import ( ) type Connection struct { + sync.Mutex sync.WaitGroup Debug bool Error chan error @@ -46,7 +47,7 @@ type Connection struct { Log *log.Logger stopped bool - quit bool + quit bool //User called Quit, do not reconnect. } // A struct to represent an event. diff --git a/irc_test.go b/irc_test.go index aed002c..4ef2a2b 100644 --- a/irc_test.go +++ b/irc_test.go @@ -12,6 +12,10 @@ const serverssl = "irc.freenode.net:7000" const channel = "#go-eventirc-test" const dict = "abcdefghijklmnopqrstuvwxyz" +//Spammy +const verbose_tests = false +const debug_tests = true + func TestConnectionEmtpyServer(t *testing.T) { irccon := IRC("go-eventirc", "go-eventirc") err := irccon.Connect("") @@ -91,8 +95,7 @@ func TestConnectionEmptyNick(t *testing.T) { func TestRemoveCallback(t *testing.T) { irccon := IRC("go-eventirc", "go-eventirc") - irccon.VerboseCallbackHandler = true - irccon.Debug = true + debugTest(irccon) done := make(chan int, 10) @@ -119,8 +122,7 @@ func TestRemoveCallback(t *testing.T) { func TestWildcardCallback(t *testing.T) { irccon := IRC("go-eventirc", "go-eventirc") - irccon.VerboseCallbackHandler = true - irccon.Debug = true + debugTest(irccon) done := make(chan int, 10) @@ -143,8 +145,7 @@ func TestWildcardCallback(t *testing.T) { func TestClearCallback(t *testing.T) { irccon := IRC("go-eventirc", "go-eventirc") - irccon.VerboseCallbackHandler = true - irccon.Debug = true + debugTest(irccon) done := make(chan int, 10) @@ -188,14 +189,16 @@ func TestConnection(t *testing.T) { ircnick1 := randStr(8) ircnick2 := randStr(8) irccon1 := IRC(ircnick1, "IRCTest1") - irccon1.VerboseCallbackHandler = true - irccon1.Debug = true + + irccon1.PingFreq = time.Second * 3 + + debugTest(irccon1) + irccon2 := IRC(ircnick2, "IRCTest2") - irccon2.VerboseCallbackHandler = true - irccon2.Debug = true + debugTest(irccon2) teststr := randStr(20) - testmsgok := false + testmsgok := make(chan bool, 1) irccon1.AddCallback("001", func(e *Event) { irccon1.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) i := 10 for { - <-tick.C - irccon1.Privmsgf(channel, "%s\n", teststr) - if testmsgok { + select { + case <-tick.C: + irccon1.Privmsgf(channel, "%s\n", teststr) + if i == 0 { + t.Errorf("Timeout while wating for test message from the other thread.") + return + } + + case <-testmsgok: tick.Stop() irccon1.Quit() - } else if i == 0 { - t.Fatal("Timeout while wating for test message from the other thread.") + return } i -= 1 } @@ -223,43 +231,76 @@ func TestConnection(t *testing.T) { }) irccon2.AddCallback("PRIVMSG", func(e *Event) { - t.Log(e.Message()) if e.Message() == teststr { if e.Nick == ircnick1 { - testmsgok = true + testmsgok <- true irccon2.Quit() } 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) { if irccon2.nickcurrent == ircnick2 { - t.Fatal("Nick change did not work!") + t.Errorf("Nick change did not work!") } }) err := irccon1.Connect(server) if err != nil { t.Log(err.Error()) - t.Fatal("Can't connect to freenode.") + t.Errorf("Can't connect to freenode.") } err = irccon2.Connect(server) if err != nil { t.Log(err.Error()) - t.Fatal("Can't connect to freenode.") + t.Errorf("Can't connect to freenode.") } go irccon2.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) { ircnick1 := randStr(8) irccon := IRC(ircnick1, "IRCTestSSL") - irccon.VerboseCallbackHandler = true - irccon.Debug = true + debugTest(irccon) irccon.UseTLS = true irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} irccon.AddCallback("001", func(e *Event) { irccon.Join(channel) }) @@ -272,7 +313,7 @@ func TestConnectionSSL(t *testing.T) { err := irccon.Connect(serverssl) if err != nil { t.Log(err.Error()) - t.Fatal("Can't connect to freenode.") + t.Errorf("Can't connect to freenode.") } irccon.Loop() @@ -286,3 +327,9 @@ func randStr(n int) string { } return string(b) } + +func debugTest(irccon *Connection) *Connection { + irccon.VerboseCallbackHandler = verbose_tests + irccon.Debug = debug_tests + return irccon +}