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

Conversation

sanketplus
Copy link

Creating PR to request and accommodate comments. There are multiple areas which requires decision making.

  1. whether to maintain backwards compatibility with variadic args, or is there a better approach.
  2. after sending a close frame, what to do exactly. Wait for sometime and give up? How long to wait?
  3. Do we need to set kind of read lock for public read methods once close has been initiated?
  4. How exactly do I interpret output of wstest?

<- suggestions

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

How about doing something like this instead:

// Shutdown sends a close message to the peer, waits for the peer to complete
// the closing handshake and closes the connection.  Shutdown assumes that the
// application is reading the connection in another goroutine.
func (c *Conn) Shutdown(closeCode int, closeMessage string, timeout time.Duration) error {
      // check for proper code and message
      message := FormatCloseMessage(closeCode, closeMessage)
      c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))  // ignore error
      select {
      case <- time.After(timeout):
      case <- c.done:
      }
      return c.Close()
}

The done field is a channel that's closed on receipt of a close message.

conn.go Outdated Show resolved Hide resolved
conn.go Outdated Show resolved Hide resolved
conn.go Outdated Show resolved Hide resolved
@sanketplus
Copy link
Author

Accommodated comments @stevenscott89
I was experimenting with intercepting readers when they finish reading (ie: at start of NextReader and when we break out of loop in Read) and hand control over to Shutdown from there if shutdown has initiated. But it again had two flaws.

  1. If there was no reader in first place, we cannot detect shutdown and hand control over
  2. handing control over does not do much because existing reader can read Close frame and inform shutdown.

So the question remains is, how do we detect if there is no reader and try to read close frame. And do we want to do this? RFC says after receiving CLOSE an endpoint shall close connection. But if we don't try to read and there is no reader also, we violate this and close conn only on timeout.

@ghost
Copy link

ghost commented Mar 2, 2019

The new channel field should be grouped with other read related fields and should be closed when c.readErr transitions from nil to non-nil. A function should be added for setting c.readErr instead of sprinkling the logic throughout the code.

It's impossible to detect if the application is reading the connection or not. I think the application must specify this when calling shutdown. The API is getting cumbersome with yet another argument to shutdown.

@sanketplus
Copy link
Author

I agree, approaches would be debatable. Do you think this can be merged now and can later be matured when we discover more suitable ways to get it closer to RFC?

@sanketplus sanketplus marked this pull request as ready for review March 4, 2019 06:39
@garyburd
Copy link
Contributor

garyburd commented Mar 5, 2019

I left comments in #448. The PR is not ready for merge:

  • To avoid breaking changes, the API must have the correct design from the get go. Specially, there must be a plan for handling shutdown from reading goroutine and shutdown from some other goroutine.
  • I agree with @stevenscott89's comments regarding the channel.

This PR may need to wait for a new project maintainer to review.

@sanketplus
Copy link
Author

thanks @garyburd for suggestions. I had some ideas implemented on my dev setup to detect a read end (similar to readDone that you mentioned). I'll try to have it added to this PR. Putting it on back burner for now as we do not see it getting merged soon.

@feliperyan
Copy link

Would this achieve a graceful shutdown? https://gist.github.com/feliperyan/2160d6df63f502079d7995372bc09e5c

@IngCr3at1on
Copy link

IngCr3at1on commented May 6, 2019

@feliperyan unless I'm crazy that gist will block in at least some cases because you are never receiving on the joining channel and it's not buffered.

@feliperyan
Copy link

@IngCr3at1on Joining channel receives in the manageConns func. What im not certain about is: let’s say there’s 1000 clients and as I start iterating through them all, closing connections more and more new clients keep joining? I might never get to zero! How can I stop accepting new connections then start closing the existing connections?

@ghost
Copy link

ghost commented May 6, 2019

@feliperyan Yes, the program gracefully closes the connections. You may want to shutdown the server before waiting for the connections to close.

Your question is not on the topic of this PR. Please open a new issue if you want to continue discussion about your program.

@sanketplus
Copy link
Author

its been some time. have we found new maintainer by any chance?

@nhooyr
Copy link

nhooyr commented Aug 27, 2019

Bunch of discussion on a very similar feature request in my library. nhooyr/websocket#103

You might find it useful.

@jaitaiwan
Copy link

I'm assigning myself to this PR, it may take some time to get familiar with the problem space and understand it to make a decision. If you feel that this PR is superseded by another or no longer relevant please reach out.

@ekovacs
Copy link

ekovacs commented Mar 22, 2024

@jaitaiwan when can we expect this to be merged?

Copy link

@jaitaiwan jaitaiwan left a comment

Choose a reason for hiding this comment

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

LGTM, however I'd like either @apoorvajagtap or @AlexVulaj to review before a final final merge. From what I understand of the problem space it's okay to merge but it's a complex issue so just making sure that merging is the right move.

@FZambia
Copy link
Contributor

FZambia commented Mar 22, 2024

Please let's make sure concerns mentioned by Gary Burd in #487 (comment) and #448 (comment) are taken into account in this PR. @jaitaiwan do I understand correctly you investigated this before approving, right?

@jaitaiwan
Copy link

Yep I've seen those. So far as I can tell, there's two arguments here:

  1. The RFC requires this shutdown procedure but it's implementation needs to be carefully thought out (which it seems this version is)
  2. Don't add anything and make it a requirement of implementing libraries to perform the RFC shutdown process.

It doesn't look like this PR changes any existing API interfaces, but it does add more - of which I don't see any issues with supporting especially considering it's a part of the RFC spec.

I'll hold on this and await more feedback

@@ -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.

@AlexVulaj
Copy link
Member

AlexVulaj commented Apr 2, 2024

@jaitaiwan

Don't add anything and make it a requirement of implementing libraries to perform the RFC shutdown process.

I'm inclined to go with this option unless there's a strong reason not to, as this change seems a bit controversial.

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

This PR does not provide a way for the the read goroutine to gracefully execute the closing handshake.

There is a way implement graceful shutdown without the pitfalls described in my comments and others.


// Shutdown sends a close frame to the peer and waits for close frame in resopnse.
// 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."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

None yet