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

RPC Client doesn't gracefully close Websocket Connections #30482

Open
moorow opened this issue Sep 20, 2024 · 4 comments
Open

RPC Client doesn't gracefully close Websocket Connections #30482

moorow opened this issue Sep 20, 2024 · 4 comments
Labels

Comments

@moorow
Copy link

moorow commented Sep 20, 2024

System information

Geth version: 1.14.8
OS & Version: OSX

Expected behaviour

When rpc.Client Close() is called, I would expect a message like this to be sent

err := cli.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))

Actual behaviour

The client closes the TCP conn without sending a WS Close message, leading to websocket: close 1006 (abnormal closure): unexpected EOF on the server side.

Steps to reproduce the behaviour

Open a WS connection with the rpc.Client. Close the connection.

@holiman
Copy link
Contributor

holiman commented Sep 23, 2024

Odd. In rpc/client.go, the dispatch loop calls close on the underlying conn

	defer func() {
		close(c.closing)
		if reading {
			conn.close(ErrClientQuit, nil)
			c.drainRead()
		}
		close(c.didClose)
	}()

At some point, I'd have expected that to trickle down to the gorilla/websocket conn, which has the following default close-handler:

func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
	if h == nil {
		h = func(code int, text string) error {
			message := FormatCloseMessage(code, "")
			c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
			return nil
		}
	}
	c.handleClose = h
}

@moorow
Copy link
Author

moorow commented Sep 23, 2024

So conn.close(ErrClientQuit, nil) eventually calls close on the jsonCodec

func (c *jsonCodec) close() {
	c.closer.Do(func() {
		close(c.closeCh)
		c.conn.Close()
	})
}

This in turn calls Close on the Gorrillas WS conn as you say.

// Close closes the underlying network connection without sending or waiting
// for a close message.
func (c *Conn) Close() error {
	return c.conn.Close()
}

This function unfortunately just closes the TPC connection.

The Close Handler only gets called if a CloseMessage is received.

@holiman
Copy link
Contributor

holiman commented Oct 2, 2024

Hm, it seems this is a long-standing issue in gorilla/websocket: gorilla/websocket#448

@moorow
Copy link
Author

moorow commented Oct 2, 2024

Yeah not sure if they are going to include or if they expect devs to implement themselves. When I use gorilla/websocket I tend to do something like this to solve the issue:

func (cli *Client) close() {
	// Gracefully close the connection by sending a close message
	if err := cli.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
              //handle err
	}

	// Wait for the server to acknowledge the close
	select {
	case <-cli.done:
	case <-time.After(2 * time.Second):
	
            //Close the underlying connection
            if err := cli.conn.Close(); err != nil {
	            //handle err
            }
	}
}

Unfortunately this isn't possible with rpc Client as I don't have access to the WS conn itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants