Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gracefully close connection fixes: #448 #487

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 27 additions & 2 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ var validReceivedCloseCodes = map[int]bool{
CloseTLSHandshake: false,
}

func isValidReceivedCloseCode(code int) bool {
func isValidCloseCode(code int) bool {
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
}

Expand All @@ -242,6 +242,7 @@ type Conn struct {
conn net.Conn
isServer bool
subprotocol string
isClosed chan bool

// Write fields
mu chan bool // used as mutex to protect write to conn
Expand Down Expand Up @@ -331,6 +332,28 @@ func (c *Conn) Close() error {
return c.conn.Close()
}


// Shutdown sends a close frame to the peer and waits for close frame in resopnse.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Shutdown sends a close frame to the peer and waits for close frame in resopnse.
// Shutdown sends a close frame to the peer and waits for close frame in response.

// Shutdown assumes that the application is reading the connection in another
// goroutine and hence it does not try to read close frame itself
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording is not strong enough here. The documentation should say "The application must not call Shutdown from the reading goroutine."

func (c *Conn) Shutdown(closeCode int, closeMessage string, timeout time.Duration) error {
if !isValidCloseCode(closeCode) {
// we do not shutdown connection
return errors.New("invalid close code received")
}
if !utf8.ValidString(closeMessage) {
return errors.New("invalid utf8 payload for shutdown message")
}

message := FormatCloseMessage(closeCode, closeMessage)
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
select {
case <-time.After(timeout): // if nothing happens and we timeout
case <-c.isClosed: // if existing reader encounters close frame
}
return c.Close()
}

// LocalAddr returns the local network address.
func (c *Conn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
Expand Down Expand Up @@ -496,6 +519,7 @@ func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
// PongMessage) are supported.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {

var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return nil, err
Expand Down Expand Up @@ -898,11 +922,12 @@ func (c *Conn) advanceFrame() (int, error) {
return noFrame, err
}
case CloseMessage:
c.isClosed <- true
closeCode := CloseNoStatusReceived
closeText := ""
if len(payload) >= 2 {
closeCode = int(binary.BigEndian.Uint16(payload))
if !isValidReceivedCloseCode(closeCode) {
if !isValidCloseCode(closeCode) {
return noFrame, c.handleProtocolError("invalid close code")
}
closeText = string(payload[2:])
Expand Down