-
Notifications
You must be signed in to change notification settings - Fork 296
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
Documentation Request about Ping Pong #265
Comments
Hi @Zamiell I've been a little busy but I did actually add a ping/pong example into the The library responds to pongs automatically as long as you have a goroutine in read but you must send pings yourself if you need them. Also see https://pkg.go.dev/nhooyr.io/websocket#Conn.Ping and https://pkg.go.dev/nhooyr.io/websocket#Conn.CloseRead |
Sorry, I meant I added the example to the dev branch. |
Thanks for the quick response.
Forgive me, but I am new to this library. What does a "goroutine in read" mean? I opened up the chat example, and it doesn't seem to have any goroutines that are reading stuff.
Can you go into a bit more detail here? Apologies, I have not read the WebSocket RFC myself, but presumably a WebSocket server needs to send ping messages to the client after some interval. Which means that presumably everybody would need to send pings, right? |
A goroutine that is currently in a
There's no requirement to send ping messages to the client. Most of the time TCP keep alives are enough. If you need to send pings, you have to start a goroutine that calls If that doesn't make sense, I can update the chat example for you to see what it's like. |
So yea to be clear, the chat example at the moment does not do a ping/pong because it doesn't need to. Go's HTTP server has TCP keep alives enabled by default. As part of #227 I was thinking of adding a second example demonstrating ping/pongs and given this has popped up on the issue tracker multiple times now, it's definitely a good idea. Also give #226 a read as well, I clarified the behaviour in that thread as well. |
Thanks. This is all a little confusing because the gorilla/websocket chat example has code for doing pings, and it looks to be important for managing a proper read deadline, correct me if I am wrong. Perhaps a short discussion of this in the README.md might be useful for those who are shopping around for Golang WebSocket frameworks. |
@Zamiell The following features are the same between the current package and gorilla:
The difference is in the API for detecting the pong. In the current package, the Ping method sends a ping and returns on receipt of the corresponding pong or cancelation of the context. If the error returned from Ping is nil, then the pong was received. The gorilla package has a lower-level API. The API to write a ping does not wait for the pong. Instead, the application registers a pong handler to receive pongs. The API in the current package can be written using the gorilla API, but not vice versa. An application cannot use ping/pong to manage the read deadline in the current package because the package does not expose an API for setting the read deadline. |
Everything @TaylorRex has said is correct except for this:
It doesn't directly but you can still do it! See #87 I documented it at some point but I guess I accidentally removed it when refactoring the docs. |
@nhooyr I didn't mean to imply that the application cannot put a time bound on reading data. Let me rephrase the last point: The gorilla examples use ping/pong and the read deadline on the underlying network connection to detect dead clients. That approach is not possible with the current package because the current package does not have an API for setting the read deadline on the underlying network connection. Applications using the current package can use ping/pong to handle dead clients, but the code is different. |
Thanks for the discussion.
Presumably, when using nhooyr/websocket, dead clients will automatically be cleaned up and have the corresponding Can you elaborate on why application-authors would want to write extra code to handle ping pongs in order to handle dead clients? What application use-case would not be able to rely on a TCP keepalive? |
Ping/pong more reliably detects dead peer applications when proxies are in play. Also, ping/pong helps with proxies that close inactive connections. |
Both of those sound like general enough & common enough things such that every application would want to have ping pongs. Or is that not true? Maybe some specialized application would want to omit ping/pong if they were optimizing for the minimum amount of network packets sent, or something? |
Is the tradeoff here simply more reliable connections through proxies versus a little bit of extra network traffic? If so, it seems like a no brainer to take that tradeoff. If it is indeed the case that the vast majority of non-specialized Golang applications would want to handle the relatively-common case of handling WebSocket users behind proxies, then why doesn't the nhooyr/websocket library just handle sending pings to clients for me? Ideally, I would not want to stick ping pong code in my app - those are the kind of low-level details that I would prefer a WebSocket library abstract away from me. Or is nhooyr/websocket meant to be only be a lower level library, mimicking the functionality of gorilla? (Hopefully it doesn't sound like I am complaining, I am just legitimately curious. =p) |
You're posing great questions. These are things I considered writing the library. You can see the full archive of my research/discussions/issue links in #1 I'm quite honestly still not sure if it was the right decision to not build in some keep alive loop that does ping/pong for you. There really isn't much data I could find on how much it really matters. Theoretically use TCP keep alives are not guaranteed to work between proxies but in practice, it doesn't seem to matter much. There's plenty of sites that use persistent HTTP connections without a ping/pong and rely on TCP keep alives and things seem to work fine. And I've personally never seen any such issues. Any app should be somewhat resilient to disconnections and reconnections anyway as servers will go down and connections will break. The Go HTTP/2 library also by default only uses TCP keep alives. And, it's very very easy to implement a ping loop this library. Looks like this: package main
import "time"
import "context"
import "nhooyr.io/websocket"
func main() {
// setup conn
go heartbeat(context.Background(), nil, time.Minute)
// read/write to conn
}
func heartbeat(ctx context.Context, c *websocket.Conn, d time.Duration) {
t := time.NewTimer(d)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
err := c.Ping(ctx)
if err != nil {
return
}
t.Reset(time.Minute)
}
} Maybe we should add that to the library as While much of the web is moving to HTTP/2 which also has a protocol level ping/pong, they remain optional. I'm not sure if there are any statistics on how many HTTP/2 servers have a ping/pong loop implemented. My theory is a ping/pong api in the protocol is mainly for being able to easily check the peer's latency. But I'm not really sure. |
(edited my previous comment quite a bit to make things clearer) |
Yea I think it's time to add |
Opened #267 |
Thanks again nhooyr for the discussion. |
Fully agreed! I've been lacking a bit in maintenance on this repository this year but I'm hoping to pick back up during the holidays. But yea, I'm good with adding a link to this issue from the README for people who want to understand the Ping/Pong API better. |
So I checked out your PR and started reading https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html It turns out, TCP Keepalives aren't very useful by default, at least on Linux. Even though Go sets the period to 15 seconds, the initial idle time before the keepalive loop begins is 2 hours!!! And then the keep alive packet must be unacknowledged by the peer 9 times for a total of another 2 minutes. By default anyway, you can configure the kernel to lower the times. It's documented in the above URL! Before adding any new docs, I think we need to do a survey. See how many cloud vendors have TCP keepalives enabled and at what configuration. If many don't or they do but at subpar configurations, then I think we add |
Ok, sounds good. |
Sorry, I interject here because my problem is related to Ping/Pong functionality. I'm trying to implement a client that connects to a python server. So, when the server sends pings to me, we reply back with a pong. (I can see the reply on a WebSocket dump) But for some reason, the server doesn't accept my pong and breaks the connection. My question is - what would be the way to debug this (to send process/send pongs manually somehow)? |
If the pong is being sent correctly and you can see it in the dump, there shouldn't be anything additional you need to debug on the client side. I'd check the server. But if you really need to, I'd use gohack to create a modifiable source and then add a log statement at https://github.com/nhooyr/websocket/blob/c9f314abd11b749d43bb61fd214171f8bb4e4173/read.go#L150 You'd want to log the variable To manually send pongs, you can export Using something like wireshark to see the frames being passed is probably easier. Perhaps the server expects a specific pong payload? The library as of now just echos the ping payload. |
But I'll keep the above in mind for future additions to the library. A dump of all frames read/written would be quite nice when debugging and it's something I've had to use all the time when developing the library itself. |
Thank you for the quick response! So, the problem is somewhere deeper in the pythong library. And it's an excellent opportunity to try gohack 👍 to debug it. |
To make matters more complicated, some web servers like NGINX have default timeouts that are reliant on PINGs/PONGs [1]:
Furthermore, keep-alive pings won't mitigate this [2]:
[1] http://nginx.org/en/docs/http/websocket.html This means that if somebody isn't explicitly sending out pings with I created a little demo showcasing NGINX proxy timeouts using |
- KeepAlive heartbeat implemented using coder/websocket#265 (comment) - Only spot websocket API switched to nhooyr websocket atm - Close the websocket using StatusNormalClosure
* feat(websocket): switch to nhooyr.io/websocket for wasm support - KeepAlive heartbeat implemented using coder/websocket#265 (comment) - Only spot websocket API switched to nhooyr websocket atm - Close the websocket using StatusNormalClosure * feat(websocket): apply same change for delivery and futures * feat(websocket): update go.mod * feat(websocket): update go.mod & go.sum (go get -u && go mod.tidy)
* feat(websocket): switch to nhooyr.io/websocket for wasm support - KeepAlive heartbeat implemented using coder/websocket#265 (comment) - Only spot websocket API switched to nhooyr websocket atm - Close the websocket using StatusNormalClosure * feat(websocket): apply same change for delivery and futures * feat(websocket): update go.mod * feat(websocket): update go.mod & go.sum (go get -u && go mod.tidy)
* feat(websocket): switch to nhooyr.io/websocket for wasm support - KeepAlive heartbeat implemented using coder/websocket#265 (comment) - Only spot websocket API switched to nhooyr websocket atm - Close the websocket using StatusNormalClosure * feat(websocket): apply same change for delivery and futures * feat(websocket): update go.mod * feat(websocket): update go.mod & go.sum (go get -u && go mod.tidy)
Hello nhooyr,
In gorilla/websocket, end-users must explicitly handle pings and pongs. From what I am reading on the issues forum, nhooyr/websocket handles this automatically. This is also implied by reading the source code from the chat example, which doesn't seem to have any ping pong code.
Can this please be documented front and center in the README? Right now, the README simply states that there is a "Ping pong API", but this doesn't communicate to the end user whether or not they should be writing pong pong code in their application or not.
Maybe the text of "Ping pong API" should be changed to "Automatic handling of pings and pongs, with an API exposing functionality for advanced users", or something along those lines. I would write a PR, but it's a one line change and you can probably word it the way you want yourself.
The reason that this seems important is that I expect typical writers of Go software will have used gorilla/websocket in the past, and are used to handling ping/pongs, so this will be a repeated point of confusion as more and more users use this library.
In closing, thanks for the library.
The text was updated successfully, but these errors were encountered: