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

net/http: http.Client Timeout incompatible with writable bodies #31391

Open
drigz opened this issue Apr 10, 2019 · 3 comments
Open

net/http: http.Client Timeout incompatible with writable bodies #31391

drigz opened this issue Apr 10, 2019 · 3 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@drigz
Copy link

drigz commented Apr 10, 2019

As part of #26937, Transport started returning writable bodies for 101 Switching Protocol responses. This is great, thank you!

However, if you have a non-zero Timeout on your http.Client, you can't use this, because of the following lines from client.go:

	if !deadline.IsZero() {
		resp.Body = &cancelTimerBody{
			stop:          stopTimer,
			rc:            resp.Body,
			reqDidTimeout: didTimeout,
		}
	}

This wraps the writable body in a non-writable body.

Go version: 1.12
Observed behavior: res.Body.(io.ReadWriteCloser) fails when client.Timeout > 0.
Expected behavior: timeouts and writeable bodies are compatible.

Although we have some streaming requests, we would like to set a long (eg one hour) timeout on our requests to prevent resource exhaustion. #30876 suggests that we can't even do this manually with contexts.

@bcmills bcmills added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Apr 10, 2019
@bcmills bcmills added this to the Go1.13 milestone Apr 10, 2019
@agnivade
Copy link
Contributor

@bradfitz

@nhooyr
Copy link
Contributor

nhooyr commented May 31, 2019

How about we change the context cancellation to also apply to the request body. Then, when the body is first written to, both the context cancellation and Timeout are reset to give the application full control over the protocol being spoken now?

@andybons andybons modified the milestones: Go1.13, Go1.14 Jul 8, 2019
imiric pushed a commit to grafana/k6 that referenced this issue Sep 23, 2019
The hanging behavior described in #971 happens after this Golang change:
golang/go@5416204
, released in 1.12. If a `101 Switching Protocols` response is received,
`response.Body` will be replaced by an underlying `net.Conn`, and
reading it blocks indefinitely without a protocol implementation.

This is not a big problem for k6 in this case, since we don't support
protocol upgrades with the current HTTP API as shown in the example, so
the agreed solution was to detect it and return an error.

This Golang change is currently incompatible with client timeouts[1],
though this will hopefully be resolved by the time we add support for
h2c (#970) or other protocols.

[1]: golang/go#31391

Closes #971
imiric pushed a commit to grafana/k6 that referenced this issue Sep 24, 2019
The hanging behavior described in #971 happens after this Golang change:
golang/go@5416204
, released in 1.12. If a `101 Switching Protocols` response is received,
`response.Body` will be replaced by an underlying `net.Conn`, and
reading it blocks indefinitely without a protocol implementation.

This is not a big problem for k6 in this case, since we don't support
protocol upgrades with the current HTTP API as shown in the example, so
the agreed solution was to detect it and return an error.

This Golang change is currently incompatible with client timeouts[1],
though this will hopefully be resolved by the time we add support for
h2c (#970) or other protocols.

[1]: golang/go#31391

Closes #971
imiric pushed a commit to grafana/k6 that referenced this issue Sep 26, 2019
The hanging behavior described in #971 happens after this Golang change:
golang/go@5416204
, released in 1.12. If a `101 Switching Protocols` response is received,
`response.Body` will be replaced by an underlying `net.Conn`, and
reading it blocks indefinitely without a protocol implementation.

This is not a big problem for k6 in this case, since we don't support
protocol upgrades with the current HTTP API as shown in the example, so
the agreed solution was to detect it and return an error.

This Golang change is currently incompatible with client timeouts[1],
though this will hopefully be resolved by the time we add support for
h2c (#970) or other protocols.

[1]: golang/go#31391

Closes #971
imiric pushed a commit to grafana/k6 that referenced this issue Sep 26, 2019
The hanging behavior described in #971 happens after this Golang change:
golang/go@5416204
, released in 1.12. If a `101 Switching Protocols` response is received,
`response.Body` will be replaced by an underlying `net.Conn`, and
reading it blocks indefinitely without a protocol implementation.

This is not a big problem for k6 in this case, since we don't support
protocol upgrades with the current HTTP API as shown in the example, so
the agreed solution was to detect it and return an error.

This Golang change is currently incompatible with client timeouts[1],
though this will hopefully be resolved by the time we add support for
h2c (#970) or other protocols.

[1]: golang/go#31391

Closes #971
@rsc rsc modified the milestones: Go1.14, Backlog Oct 9, 2019
cuonglm pushed a commit to grafana/k6 that referenced this issue Oct 15, 2019
The hanging behavior described in #971 happens after this Golang change:
golang/go@5416204
, released in 1.12. If a `101 Switching Protocols` response is received,
`response.Body` will be replaced by an underlying `net.Conn`, and
reading it blocks indefinitely without a protocol implementation.

This is not a big problem for k6 in this case, since we don't support
protocol upgrades with the current HTTP API as shown in the example, so
the agreed solution was to detect it and return an error.

This Golang change is currently incompatible with client timeouts[1],
though this will hopefully be resolved by the time we add support for
h2c (#970) or other protocols.

[1]: golang/go#31391

Closes #971
@jim-minter
Copy link

jim-minter commented Oct 10, 2020

I think I've been able to hack around this issue outside of the standard library as follows:

// cancelBody is a workaround for the fact that http timeouts are incompatible
// with hijacked connections (https://github.com/golang/go/issues/31391):
// net/http.cancelTimerBody does not implement Writer.
type cancelBody struct {
	io.ReadWriteCloser
	t *time.Timer
	c chan struct{}
}

func (b *cancelBody) wait() {
	select {
	case <-b.t.C:
		b.ReadWriteCloser.Close()
	case <-b.c:
		b.t.Stop()
	}
}

func (b *cancelBody) Close() error {
	select {
	case b.c <- struct{}{}:
	default:
	}

	return b.ReadWriteCloser.Close()
}

func newCancelBody(rwc io.ReadWriteCloser, d time.Duration) io.ReadWriteCloser {
	b := &cancelBody{
		ReadWriteCloser: rwc,
		t:               time.NewTimer(d),
		c:               make(chan struct{}),
	}

	go b.wait()

	return b
}

Where appropriate, add resp.Body = newCancelBody(resp.Body.(io.ReadWriteCloser), time.Hour) to wrap the old resp.Body in one that will close itself after an hour but which still allows 101 Switching Protocol responses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

7 participants