From 9e471b5b5d1a9879089c23efc2e613685e4f1aac Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Tue, 18 Feb 2014 13:25:21 -0800 Subject: [PATCH] solve quit/connection close race --- irc/client.go | 80 +++++++++++++++++++++++++++++++++++---------------- irc/reply.go | 4 +-- irc/server.go | 32 +++++++++++++-------- 3 files changed, 77 insertions(+), 39 deletions(-) diff --git a/irc/client.go b/irc/client.go index 6004490c..352e4548 100644 --- a/irc/client.go +++ b/irc/client.go @@ -19,6 +19,7 @@ type Client struct { ctime time.Time flags map[UserMode]bool friends map[*Client]uint + hasQuit bool hops uint hostname string idleTimer *time.Timer @@ -46,12 +47,16 @@ func NewClient(server *Server, conn net.Conn) *Client { socket: NewSocket(conn), } - client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionClosed) + client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout) go client.readCommands() return client } +// +// socket read gorountine +// + func (client *Client) readCommands() { for line := range client.socket.Read() { msg, err := ParseCommand(line) @@ -68,12 +73,46 @@ func (client *Client) readCommands() { client.server.commands <- msg } - client.server.toDestroy <- client + client.connectionClosed() +} + +func (client *Client) connectionClosed() { + msg := &QuitCommand{ + message: "connection closed", + } + msg.SetClient(client) + client.server.commands <- msg +} + +// +// idle timer goroutine +// + +func (client *Client) connectionIdle() { + client.server.idle <- client +} + +// +// quit timer goroutine +// + +func (client *Client) connectionTimeout() { + msg := &QuitCommand{ + message: "connection timeout", + } + msg.SetClient(client) + client.server.commands <- msg +} + +// +// server goroutine +// + +func (client *Client) Active() { + client.atime = time.Now() } func (client *Client) Touch() { - client.atime = time.Now() - if client.quitTimer != nil { client.quitTimer.Stop() } @@ -95,24 +134,11 @@ func (client *Client) Idle() { } } -func (client *Client) connectionIdle() { - client.server.idle <- client -} - -func (client *Client) connectionTimeout() { - msg := &QuitCommand{ - message: "connection timeout", - } - msg.SetClient(client) - client.server.commands <- msg -} - -func (client *Client) connectionClosed() { - msg := &QuitCommand{ - message: "connection closed", - } - msg.SetClient(client) - client.server.commands <- msg +func (client *Client) Register() { + client.phase = Normal + client.loginTimer.Stop() + client.AddFriend(client) + client.Touch() } func (client *Client) Destroy() { @@ -225,6 +251,13 @@ func (client *Client) ChangeNickname(nickname string) { } func (client *Client) Quit(message string) { + if client.hasQuit { + return + } + client.hasQuit = true + client.Reply(RplError(client.server, client.Nick())) + client.Destroy() + if len(client.friends) > 0 { reply := RplQuit(client, message) for friend := range client.friends { @@ -234,7 +267,4 @@ func (client *Client) Quit(message string) { friend.Reply(reply) } } - - client.Reply(RplError(client.server, client)) - client.socket.Close() } diff --git a/irc/reply.go b/irc/reply.go index 8585ff1e..57a359a5 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -238,8 +238,8 @@ func RplQuit(client *Client, message string) Reply { return NewStringReply(client, QUIT, ":%s", message) } -func RplError(server *Server, target Identifier) Reply { - return NewStringReply(server, ERROR, target.Nick()) +func RplError(server *Server, message string) Reply { + return NewStringReply(server, ERROR, message) } func RplInviteMsg(channel *Channel, inviter *Client) Reply { diff --git a/irc/server.go b/irc/server.go index 5491f71d..a4baae06 100644 --- a/irc/server.go +++ b/irc/server.go @@ -24,7 +24,6 @@ type Server struct { name string operators map[string]string password string - toDestroy chan *Client } func NewServer(config *Config) *Server { @@ -39,7 +38,6 @@ func NewServer(config *Config) *Server { name: config.Name, operators: make(map[string]string), password: config.Password, - toDestroy: make(chan *Client), } for _, opConf := range config.Operators { @@ -59,9 +57,6 @@ func (server *Server) ReceiveCommands() { case conn := <-server.conns: NewClient(server, conn) - case client := <-server.toDestroy: - client.Destroy() - case client := <-server.idle: client.Idle() @@ -75,7 +70,7 @@ func (server *Server) ReceiveCommands() { case Authorization: authCmd, ok := cmd.(AuthServerCommand) if !ok { - client.socket.Close() + client.Quit("unexpected command") continue } authCmd.HandleAuthServer(server) @@ -83,7 +78,7 @@ func (server *Server) ReceiveCommands() { case Registration: regCmd, ok := cmd.(RegServerCommand) if !ok { - client.socket.Close() + client.Quit("unexpected command") continue } regCmd.HandleRegServer(server) @@ -94,7 +89,15 @@ func (server *Server) ReceiveCommands() { client.Reply(ErrUnknownCommand(server, cmd.Code())) continue } - client.Touch() + switch srvCmd.(type) { + case *PingCommand, *PongCommand: + client.Touch() + case *QuitCommand: + // no-op + default: + client.Active() + client.Touch() + } srvCmd.HandleServer(server) } } @@ -184,10 +187,7 @@ func (s *Server) GenerateGuestNick() string { func (s *Server) tryRegister(c *Client) { if c.HasNick() && c.HasUsername() { - c.phase = Normal - c.loginTimer.Stop() - c.AddFriend(c) - + c.Register() c.Reply(RplWelcome(s, c)) c.Reply(RplYourHost(s)) c.Reply(RplCreated(s)) @@ -270,6 +270,10 @@ func (m *PassCommand) HandleAuthServer(s *Server) { client.phase = Registration } +func (msg *QuitCommand) HandleAuthServer(server *Server) { + msg.Client().Quit(msg.message) +} + // // registration commands // @@ -319,6 +323,10 @@ func (msg *UserCommand) HandleRegServer2(server *Server) { server.tryRegister(client) } +func (msg *QuitCommand) HandleRegServer(server *Server) { + msg.Client().Quit(msg.message) +} + // // normal commands //