@ -10,22 +10,34 @@ import (
)
)
type writer struct {
type writer struct {
// conn is the websocket connection that this writer is responsible for writing on.
conn * websocket . Conn
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
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
readNotifications <- chan time . Duration
timer * time . Ticker
// 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
logger * zap . Logger
}
}
// act is the function responsible for actually doing the writing.
func ( w * writer ) act ( ) {
func ( w * writer ) act ( ) {
defer w . gracefulShutdown ( )
defer w . gracefulShutdown ( )
w . logger . Debug ( "Starting up" )
w . logger . Debug ( "Starting up" )
w . timer = time . NewTick er ( PingDelay )
w . timer = time . NewTim er ( PingDelay )
for {
for {
select {
select {
case _ , open := <- w . readNotifications :
case _ , open := <- w . readNotifications :
if open {
if open {
w . logger . Debug ( "Received reader read, extending ping" )
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 )
w . timer . Reset ( PingDelay )
} else {
} else {
w . logger . Debug ( "Received reader close, shutting down" )
w . logger . Debug ( "Received reader close, shutting down" )
@ -46,6 +58,7 @@ func (w *writer) act() {
}
}
case <- w . timer . C :
case <- w . timer . C :
w . sendPing ( )
w . sendPing ( )
w . timer . Reset ( PingDelay )
}
}
w . logger . Debug ( "Awakening handled, resuming listening" )
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 ) {
func ( w * writer ) sendClose ( msg SocketClosed ) {
w . logger . Debug ( "Shutting down the writer channel" )
w . logger . Debug ( "Shutting down the writer channel" )
close ( w . 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 ( ) {
func ( w * writer ) sendPing ( ) {
w . logger . Debug ( "Sending ping" )
w . logger . Debug ( "Sending ping" )
err := w . conn . WriteControl ( websocket . PingMessage , [ ] byte ( "are you still there?" ) , time . Now ( ) . Add ( ControlTimeLimit ) )
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.
// 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
// 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.
// During this time, the writer ignores all other messages from the channel and sends no pings.
func ( w * writer ) gracefulShutdown ( ) {
func ( w * writer ) gracefulShutdown ( ) {
defer w . finalShutdown ( )
defer w . finalShutdown ( )
w . timer = nil
w . logger . Debug ( "Waiting for all channels to shut down" )
w . logger . Debug ( "Waiting for all channels to shut down" )
w . timer . Stop ( )
for {
for {
if w . channel == nil && w . readNotifications == nil {
if w . channel == nil && w . readNotifications == nil {
w . logger . Debug ( "All channels closed, beginning final shutdown" )
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 ( ) {
func ( w * writer ) finalShutdown ( ) {
w . logger . Debug ( "Closing WebSocket connection" )
w . logger . Debug ( "Closing WebSocket connection" )
err := w . conn . Close ( )
err := w . conn . Close ( )