diff --git a/server/websocket/writer.go b/server/websocket/writer.go index 4fac8be..1fb84e9 100644 --- a/server/websocket/writer.go +++ b/server/websocket/writer.go @@ -10,22 +10,34 @@ import ( ) type writer struct { - conn *websocket.Conn - channel chan ServerMessage + // conn is the websocket connection that this writer is responsible for writing on. + conn *websocket.Conn + // channel is the channel used to receive server messages to be sent to the client. + // When it receives a SocketClosed, the process on the sending end promises not to send any further messages, as the writer will close it right after. + channel chan ServerMessage + // readNotifications is the channel used to receive pings when the reader receives a message, so that a ping will be sent out before the reader is ready to time out. + // When it is closed, the reader has shut down. readNotifications <-chan time.Duration - timer *time.Ticker - logger *zap.Logger + // timer is the timer used to send pings when the reader is close to timing out, to make sure the other end of the connection is still listening. + timer *time.Timer + // logger is the logger used to record the state of the writer, primarily in Debug level. + logger *zap.Logger } +// act is the function responsible for actually doing the writing. func (w *writer) act() { defer w.gracefulShutdown() w.logger.Debug("Starting up") - w.timer = time.NewTicker(PingDelay) + w.timer = time.NewTimer(PingDelay) for { select { case _, open := <-w.readNotifications: if open { w.logger.Debug("Received reader read, extending ping") + if !w.timer.Stop() { + // The timer went off while we were doing this, so drain the channel before resetting + <-w.timer.C + } w.timer.Reset(PingDelay) } else { w.logger.Debug("Received reader close, shutting down") @@ -46,6 +58,7 @@ func (w *writer) act() { } case <-w.timer.C: w.sendPing() + w.timer.Reset(PingDelay) } w.logger.Debug("Awakening handled, resuming listening") } @@ -83,6 +96,8 @@ func (w *writer) send(msg ServerMessage) { } } +// sendClose sends a close message on the websocket connection, but does not actually close the connection. +// It does, however, close the incoming message channel. func (w *writer) sendClose(msg SocketClosed) { w.logger.Debug("Shutting down the writer channel") close(w.channel) @@ -94,6 +109,7 @@ func (w *writer) sendClose(msg SocketClosed) { } } +// sendPing sends a ping message on the websocket connection. The content is arbitrary. func (w *writer) sendPing() { w.logger.Debug("Sending ping") err := w.conn.WriteControl(websocket.PingMessage, []byte("are you still there?"), time.Now().Add(ControlTimeLimit)) @@ -104,12 +120,12 @@ func (w *writer) sendPing() { // gracefulShutdown causes the writer to wait for the close handshake to finish and then shut down. // It waits for the reader's readNotifications to close, indicating that it has also shut down, and for the channel to -// receive a SocketClosed message indicating that the client has shut down. +// receive a SocketClosed message indicating that the main process has shut down. // During this time, the writer ignores all other messages from the channel and sends no pings. func (w *writer) gracefulShutdown() { defer w.finalShutdown() + w.timer = nil w.logger.Debug("Waiting for all channels to shut down") - w.timer.Stop() for { if w.channel == nil && w.readNotifications == nil { w.logger.Debug("All channels closed, beginning final shutdown") @@ -134,6 +150,7 @@ func (w *writer) gracefulShutdown() { } } +// finalShutdown closes the socket and finishes cleanup. func (w *writer) finalShutdown() { w.logger.Debug("Closing WebSocket connection") err := w.conn.Close()