-
-
Notifications
You must be signed in to change notification settings - Fork 231
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
Check max_concurrent_streams before trying to open a new http2 stream #246
Comments
The solution in the PR is wrong because it checks against options and not against the current settings for the connection. The real fix should be made in I don't think it's a problem not to queue up, because if we did there would be the tough question of what to do when disconnects occur, because not all requests can be safely replayed or safely executed right after previous requests have failed to complete. Therefore if we did queue up we would have to |
Indeed, looking at cow_http2_machine again, the PR doesn't make any sense. Closed.
A stream_error on disconnect would be expected, and not an issue (at least not in my use case). I mean, for the connection to suddenly close is not on the normal, happy path.
The problem is that there is not a way to know what is too many requests in advance, as the SETTINGS_MAX_CONCURRENT_STREAMS is variable. The http2 spec even allows for a peer to lower this setting during a connections lifetime. If assuming a value of 100 (the http2 spec recommendation), that wouldn't use all the available connection throughput on servers that allow for more concurrent streams.
Some kind of mechanism to request a stream, and wait for it to be available, would be very convenient. Repeating the example from above, imagine there is a sudden load spike, and 101 Erlang processes who are trying to send a request over a connection that has a peer setting SETTINGS_MAX_CONCURRENT_STREAMS of 100. |
The problem with the max setting is that it can change at any time and therefore even if we tell the user process about it this wouldn't guarantee that a stream is available by the time the request message is sent. You could still get a stream_error even after Gun tells you there's a stream available if the server lowers the max in-between. A mechanism to request a stream would have the same problems. Anyway I think it's best to focus on practical applications. Right now your APNS case is the only occurence of such a problem, and therefore it's best to focus on that. In your case the solution could be to do a single request immediately after opening the connection before allowing the connection to be used for more. In effect you'd have an intermediate state where the connection can only be used once, before it becomes usable for concurrent requests. The It could be useful to expose the current settings via |
While I appreciate your suggestions for how to solve my specific apns problem, I opened this issue more from a http2 spec point of view.
Sorry, I confounded multiple different issues in this discussion:
I agree that 4) here is only really relevant to APNS. Virtually every other service tend to set the limit to the recommenced 100 or higher.
Actually there are some provisions to solve this particular problem in the HTTP/2 spec, under 5.1.2. Stream Concurrency.
When you have a moment, please check what you think about #247 and ninenines/cowlib#107 which adds the remote settings to gun:info. |
There's no doubt that it's something that will need to be better handled eventually, but there's a lot of things that need to be better handled (or just handled in any shape or form) and until someone runs into this it's not worth doing much about it. Not to mention the better insight we get from real world feedback.
You missed my point. There's no problem in that area with Gun. The problem comes from the Erlang side of things. You may ask for a stream or the number of streams available or whatever, and then Gun receives a settings change via the socket before you actually create the stream on the wire. If streams were reserved, waiting for them to be used before sending the ack would just lead to potential timeouts. If you checked the setting, there's not much Gun could do about you sending too many streams. The solution is probably to postpone the relevant message(s) until a stream is freed, but it's much easier said than done. I don't think we can use the gen_statem interface for that. Also since the problem will also apply to HTTP/3 the solution is most likely not specific to HTTP/2. |
The I'll close this for now, and if it turns out that more is needed, like a guarantee that a reserved stream "succeeds", please open a new ticket. Thanks! Note that the gun:info changes are still interesting to have, I'll get to them soon. |
According to the http/2 spec, https://tools.ietf.org/html/rfc7540:
Also:
The SETTINGS_MAX_CONCURRENT_STREAMS parameter, just like the other settings, can change during the duration of a connection.
Now, Apple's HTTP/2 APNS service is slightly unusual in that it sets the SETTINGS_MAX_CONCURRENT_STREAMS to 1 initially.
After an initial, successfully authenticated, request on the connection, this setting value is raised, typically to 1000.
If you perform two gun:post requests simultaneously on a newly opened connection to the Apple APNS service, the second one will fail with a stream_error. Worse, since gun does not check the stream count against the setting before opening a new stream, the second stream open attempt hits the remote peer before being rejected.
Since HTTP spec recommendation is to have a higher initial value of SETTINGS_MAX_CONCURRENT_STREAMS, this issue won't typically be seen at low levels of load. But if you use gun in a server that is under high load, it's not unthinkable that 101 processes are waiting for a connection to open to perform a request. Then this could fail also for the http2 spec recommended initial SETTINGS_MAX_CONCURRENT_STREAMS value of 100.
I have opened a proof of concept PR #245 that checks the max_concurrent_streams before opening a new stream. The PR may solve part of the problem in that we don't hit the remote peer for streams over the limit.
But also there's may a more profound issue here since the gun API design implies that you can perform requests "without worrying", while the gun process will ensure there is an open connection. But since gun internally doesn't queue up requests to wait for an available stream slot, as it does for a connection, you actually have to worry a bit.
Sample code (non including all dependencies, only for purposes of illustrating API usage):
Fails with:
The text was updated successfully, but these errors were encountered: