-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
transport: fix race between applying and acking flow-control settings frames #1630
Conversation
fd05b66
to
73d71a0
Compare
transport/http2_client.go
Outdated
} | ||
t.applySettings(i.rs) | ||
t.controlBuf.put(&settingsAck{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not apply settings before enqueuing the ack the first time?
This results in more delay in processing the ack (it has to make it through the control buffer twice), and it splits up the code that handles settings, which makes it harder to reason about.
If there's some reason we have to do it this way, that's fine but we should put comments in to explain it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was in fact thinking about that last night. The code sort of evolved to be this way and you're right it makes more sense to apply the settings in the settings handler itself.
transport/http2_client.go
Outdated
t.mu.Unlock() | ||
case http2.SettingInitialWindowSize: | ||
t.mu.Lock() | ||
if s.Val < t.streamSendQuota { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we be comparing this to t.initialWindowSize
?
Also, do we have to take a lock for this field or t.maxStreams
? I was thinking the only thing that should be changing these fields is the handling of a settings frame (so, this code here)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.initialWindowSize
controls our inbound window size while t.streamSendQuota
controls our outbound window size. So, when we get a settings frame to update our initial window size( the outbound one) we update this variable.
You're right about t.maxStreams
; updating it never required a lock. I copied the original code and made the same mistake!
68889f2
to
8c58e9f
Compare
transport/http2_client.go
Outdated
func (t *http2Client) isRestrictive(s http2.Setting) bool { | ||
switch s.ID { | ||
case http2.SettingMaxConcurrentStreams: | ||
if int(s.Val) < t.maxStreams { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional (but recommended): return int(s.Val) < t.maxStreams
rs = append(rs, s) | ||
} else { | ||
ps = append(ps, s) | ||
} | ||
return nil | ||
}) | ||
if isFirst && isMaxConcurrentStreamsMissing { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional, maybe for a later refactor:
The code might be cleaner with two implementations: handleSettings
and handleInitialSettings
. handleInitialSettings
doesn't need to check restrictive/permissive -- just apply all the settings and write the ack. Then the special-case code checking for max concurrent streams is removed from the ongoing handleSettings
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same logic, acking after applying restrictive settings and before applying permissive settings, holds for handleInitialSettings. The client doesn't wait for the server to send initial settings before starting streams.
We'll still need to check if http2.SettingMaxConcurrentStreams
is present or not in the initial settings. So we will only be able to save one conditional check by creating this other function.
fixes #1626 #1564