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

transport: Discard the buffer when empty after http connect handshake #7424

Merged
merged 4 commits into from
Jul 30, 2024

Conversation

arjan-bal
Copy link
Contributor

@arjan-bal arjan-bal commented Jul 18, 2024

Fixes: #7327

The buffer used to read the http connect response could contain extra bytes from the target server, so we can't discard it. However, in many cases where the server waits for the client to send the first message (e.g. when TLS is being used), the buffer will be empty, so we can avoid the overhead of reading through this buffer.

This PR also adds a unit test to ensure the buffer is not discarded when it has unread data.

RELEASE NOTES:

  • Double buffering is avoided when using an http connect proxy and the target server waits for client to send the first message.

@arjan-bal arjan-bal added the Type: Performance Performance improvements (CPU, network, memory, etc) label Jul 18, 2024
@arjan-bal arjan-bal requested a review from dfawley July 18, 2024 06:50
@arjan-bal arjan-bal added this to the 1.66 Release milestone Jul 18, 2024
Copy link

codecov bot commented Jul 18, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 81.50%. Comparing base (64adc81) to head (db50122).
Report is 14 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff            @@
##           master    #7424    +/-   ##
========================================
  Coverage   81.50%   81.50%            
========================================
  Files         350      354     +4     
  Lines       26877    27078   +201     
========================================
+ Hits        21906    22071   +165     
- Misses       3776     3813    +37     
+ Partials     1195     1194     -1     
Files Coverage Δ
internal/transport/proxy.go 71.66% <100.00%> (+4.42%) ⬆️

... and 26 files with indirect coverage changes

Comment on lines 180 to 187
c.SetReadDeadline(time.Now().Add(20 * time.Millisecond))

gotServerMessage := make([]byte, len(serverMessage))
// This call will return a deadline exceeded error if the server has nothing
// to send. This is expected.
if _, err := c.Read(gotServerMessage); err != nil {
t.Logf("Got error while reading message from server: %v", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Similar; I don't really like adding a magic deadline to things that we know will timeout sometimes.

Probably we should only do this read if len(serverMessage) != 0, and then we can apply a longer deadline while not impacting code that doesn't use it?

Copy link
Contributor Author

@arjan-bal arjan-bal Jul 26, 2024

Choose a reason for hiding this comment

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

Changed, now the proxy is configured to wait for server hello.

@@ -100,7 +118,7 @@ func (p *proxyServer) stop() {
}
}

func testHTTPConnect(t *testing.T, proxyURLModify func(*url.URL) *url.URL, proxyReqCheck func(*http.Request) error) {
func testHTTPConnect(t *testing.T, proxyURLModify func(*url.URL) *url.URL, proxyReqCheck func(*http.Request) error, serverMessage []byte) {
Copy link
Member

Choose a reason for hiding this comment

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

We probably should add a struct about now to hold all the parameters (unless we decide to test this code differently per the above comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

internal/transport/proxy_test.go Outdated Show resolved Hide resolved
@dfawley dfawley assigned arjan-bal and unassigned dfawley Jul 23, 2024
@arjan-bal arjan-bal requested a review from dfawley July 26, 2024 10:38
@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Jul 26, 2024
@@ -157,33 +181,62 @@ func testHTTPConnect(t *testing.T, proxyURLModify func(*url.URL) *url.URL, proxy
if string(recvBuf) != string(msg) {
t.Fatalf("received msg: %v, want %v", recvBuf, msg)
}

if len(args.serverMessage) > 0 {
c.SetReadDeadline(time.Now().Add(defaultTestTimeout))
Copy link
Member

Choose a reason for hiding this comment

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

There's now 3 places where we're setting this deadline.

Maybe instead when the conn is created in proxyDial we should just do c.SetDeadline(....defaultTestTimeout) (note Deadline not Read to also abort writes)?

Copy link
Contributor Author

@arjan-bal arjan-bal Jul 26, 2024

Choose a reason for hiding this comment

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

During testing, I noticed that the test can hang in proxyDial if the proxy server waits for a server hello when the server doesn't intend to send one. This was due to a logical error on my part which I fixed.

To be safe, I added read deadlines in both the places where reads could hang.

Maybe instead when the conn is created in proxyDial we should just do c.SetDeadline(....defaultTestTimeout) (note Deadline not Read to also abort writes)?

Do you mean adding a deadline in the actual proxyDial() implementation by introducing a timeout that only the tests use?
Or are you referring to adding a deadline on the conn returned by proxyDial in the test code?

The latter would not prevent proxyDial from hanging when the proxy server is stuck reading the server hello. The former would work but it involves changing non-test code, which I was trying to avoid.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I didn't mean in proxyDial... I meant the connection it returns, we could immediately, always, do a SetDeadline(...), instead of doing it later and conditionally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed SetReadDeadline to SetDeadline. Moved the SetDeadline immediately after the proxyDial call. The SetDeadline in proxyServer.run() is still present to avoid proxyDial from hanging indefinitely in case of test failures.

@dfawley dfawley assigned arjan-bal and unassigned dfawley Jul 26, 2024
@arjan-bal arjan-bal requested a review from dfawley July 26, 2024 18:52
@arjan-bal arjan-bal merged commit 0b33bfe into grpc:master Jul 30, 2024
13 checks passed
@arjan-bal arjan-bal deleted the http-connect-buffer branch July 30, 2024 16:00
printchard pushed a commit to printchard/grpc-go that referenced this pull request Jul 30, 2024
…grpc#7424)

* Discard the buffer when empty after http connect handshake

* configure the proxy to wait for server hello

* Extract test args to a struct

* Change deadline sets
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Performance Performance improvements (CPU, network, memory, etc)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proxy connection buffer necessary?
2 participants