-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http: document errors more (*Transport, Client's wrapper errors, etc), how to check canceled, ... #9424
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
Comments
On further investigation, it appears that the ECONNRESET considers itself Temporary after all - but EPIPE does not. |
Do we expect programmers to memorize every error in the standard library, then? Or to read the implementation of every package to figure out whether there are certain errors they should be on the lookout for? For example: without knowing about url.Error, it's easy for one to see net.Error and try to use that for detecting temporary errors. But that doesn't work because url.Error doesn't implement net.Error. In order to handle errors - and the existence of net.Error is a witness to the fact that there are useful properties of errors needed to handle them correctly! - the user must know that they exist in the first place. |
Or, to put it a different way: handling errors should not put an O(N^2) burden on the programmer. |
In random order:
In summary, we can improve some docs (where improve likely means make more vague), but for compatibility there's little we can do. |
I believe it is the opposite. The general principal is the error value, if not nil is opaque, the caller is not supposed to assign any meaning to it other than a failure. If, and these are the exceptions, a method identifies that the error value returned conforms to an interface, say providing a Temporary() bool method, then that should be called out and is part of the contract for the method. Again, this is the exception, not the rule. I've only seen that pattern in the net package, and close derivatives of that package.
|
Yes, if a func returns But because we're so nice and care about compatibility, we also try pretty hard to not change the underlying type if it was exported in weekly-tagged release in 2010 and people are probably still relying on it somewhere. And sometimes we even add tests for such cases, especially if somebody asks if they can depend on it, or report that we broke them. Stability sucks, I know. |
@davecheney It is not possible to use http.Client robustly without being able to check for (and retry) temporary errors, which are not part of the method's signature. Unless you are proposing a broader refactoring to move the retry logic into the Client implementation, documenting errors is about the best we can do. I can understand not wanting to promise that http.Client returns only certain errors (e.g. only url.Error), but it should at least have some indication in that direction. Without knowing to check for that and net.Error, one cannot implement anything resembling a reasonable caller-side retry loop. |
Use a type assertion to see if the error value implements Temporary() bool
|
I tend to think that the retry should be done by the the net/http package |
@minux references the good ol' Issue #4677 @bcmills, in general, if it's possible to retry safely, the logic is roughly:
The exact err rarely matters if you didn't even get an HTTP response at all. If it's an idempotent GET or HEAD (as they all should be), then just retry away. But if it's a POST, you're already kinda screwed on knowing whether it got there. Which specific errors do you care about, ambiguities in net/http etc's return values notwithstanding? |
@davecheney A simple type-assertion doesn't work - while most of the errors in the "net" package implement a Temporary() method, url.Error - the one returned most of the type by (http.Client).Get - does not. In order to unpack the errors correctly, you need to know about both url.Error (for its Err field) and net.Error (for the Temporary method). And neither of those is prominently mentioned in the net/http docs. @minux I tend to agree, but getting to that state would require being much more careful about which errors to retry. (For example: the spurious io.EOF errors would need to be fixed, and some more syscall errors - EPIPE in particular - would probably need to be marked Temporary.) Documenting and fixing the errors seems like a prerequisite for retrying them. @bradfitz Yes, that's basically what I'm doing - but I'm not retrying most of the HTTP status codes, because most of them indicate permanent errors for typical HTTP usage. And note that there's a bit more subtlety to it if you want to handle the Retry-After header correctly. The problem is, there is a class of net errors that are not temporary - why else would there be a Temporary method on net.Error? - and it seems wrong to retry in those cases. (For example, what if the URL I passed in was not even syntactically valid?) |
You are correct that there's basically no consistency on the use of url.Error, net.Error, http.ProtocolError, etc, or which takes precedence when multiple apply. This is kinda one of those "Unfortunate" situations. One could imagine a new func in I don't have solid suggestions. If anything, I would file lots of separate concrete bugs and address them one at a time. Bugs like this risk getting depressing and non-actionable, which is further depressing in that I used "actionable". |
On 23 Dec 2014 15:58, "Bryan C. Mills" [email protected] wrote:
I believe that returning the *url.Error was fixed in #9405.
I'm a bit confused where the EPIPE entered the conversation, but IMO that
|
@minux After further thought, I don't think that *http.Client itself should do the retries, at least not without an explicit field to enable them. Generally you don't want lots of layers of competing retry loops - one end-to-end retry loop is sufficient, and it puts less steady-state load on intermediate nodes in the system during the cooldown between retries - only the caller knows whether they are the topmost layer. |
I would sometimes see errors like: graphiteBand: graphite RequestError (http://....): Get failed: Get http://... : read tcp 10.90.128.100:80: use of closed network connection This kind of error is not something that should bubble up to the caller of a http client library, but it does. see also: golang/go#8946 golang/go#9424 there's a bunch more issues about the broken state of error handling in net/http. So anyway the http client tries to reuse an existing connection which has broken. Somehow this is the caller's problem, so we address it by not keeping any idle connection and opening a new connection each time. This should get rid of these errors without adding much overhead. Note that the used http transport is, other than the MaxIdleConnsPerHost setting, the same as the default transport.
I have a bit of a conundrum. I am writing code that executes payment transactions against various payment processors. It has strict timeout and other requirements, and I need to re-queue failed transactions for specific types of failures. I really need to know how/why a request failed, not just that it failed, and parsing the It is important for me to know that the request timed out, or even that it was a temporary failure that may benefit from an immediate re-try. Reading through this thread, and via some experimentation, I have this to determine temp/timeout condition (seems only slightly better than parsing the full func IsTempOrTimeout(err error) (temp bool, timeout bool) {
if urlerr, ok := err.(*url.Error); ok {
return checkNetError(urlerr.Err)
}
return chenkNetError(err)
}
func checkNetError(err error) (temp bool, timeout bool) {
if nerr, ok := err.(net.Error); ok {
return nerr.Temporary(), nerr.Timeout()
}
return false, false
} I'm looking for feedback on the best way to proceed here:
|
@davisford, you didn't mention which mechanism(s) you're using to specify timeouts. There are a number of options. |
@bradfitz that's a good point. I'm trying to navigate through them all. I'm actually concerned with all possible timeout values I can set. I have a finite time period by which I can retry a transaction, so I'd like to set every timeout. Also all tx are run through TLS. But here is what I'm basically doing to the client before sending: type Config struct {
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
}
func timeoutDialer(config *Config) func(network, add string) (c net.Conn, err error) {
return func(network, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, config.ConnectTimeout)
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(config.ReadWriteTimeout))
return conn, nil
}
}
}
client := &http.Client{
Transport: &http.Transport{
Proxy: nil,
Dial: timeoutDialer(config),
},
} Question: would this configuration cover TLS handshake timeout or no? |
You want Client.Timeout probably. It is the nuclear option of timeouts, if you don't want something specific. It is well documented. Keeping this bug discussion on-topic, let me know if there's something specific in the https://tip.golang.org/pkg/net/http/ documentation which is not clear. |
Hi guys! Just wanted to share that this issue is happening to me. I started receiving as error value "EOF". go version go1.4.2 darwin/amd64 I copied this Google go-github Do function and made little modifications to it like: func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Do(req)
... I have even followed the go-github implementation creating a The thing is that this code worked, I am working with an API and I test using curl and it works and somehow I just continued adding more code that worked with that resp, err := c.client.Do(req)
fmt.Println(resp) // has the response: &{201 Created 201 HTTP/1.0 1 0....
if err != nil {
fmt.Println("failed do...")
return nil, err
} The response is actually there, so can you please point me out where could be a good place to start researching about this? Thanks and sorry for this newbie question, but I am blank right now. |
@vanhalt Please take this question to the mailing list. |
Which one specifically? |
https://groups.google.com/forum/#!forum/golang-nuts On Fri, Jun 5, 2015 at 12:08 PM, Rafa [email protected] wrote:
|
Thank you very much! |
@bcmills I would be very interested in seeing the code you came up with to decode the possible errors. Is that available somewhere? (I know it was 6 months ago). |
I don't have it handy, but to sketch roughly: |
OK thanks, I was interested in seeing all the possible cases but that puts me on the right track. For the record I agree that having good error categorization for an HTTP client library is important. I'm working on a system that makes generic HTTP requests and it needs to understand the nature of the failure and not just "can it be retried or not?" (i.e. was it a connection error, a timeout before receiving headers, after receiving headers, a disconnection etc...). So it seems I need to go deep inside the rabbit hole... |
Removing the Go2 label. Documentation improvements do not have to wait for Go2. Perhaps there is a larger cleanup of the error handling in net/http that could be done in an overhaul of the overall package. |
Maybe we can document for Go 1.11 what we want to guarantee and document what we're not guaranteeing either. There have been some CLs in the past few cycles that didn't attach to this issue, for Client and Transport. |
Change https://golang.org/cl/125575 mentions this issue: |
Updates #9424 Change-Id: If117ba3e7d031f84b30d3a721ef99fe622734de2 Reviewed-on: https://go-review.googlesource.com/125575 Reviewed-by: Ian Lance Taylor <[email protected]>
I've been doing some experimenting with Go HTTP clients and servers under load.
One of the curious things I've discovered is that calls to (*http.Client).Get occasionally return unusual errors.
The documentation at http://golang.org/pkg/net/http/#Client says:
"An error is returned if the Client's CheckRedirect function fails or if there was an HTTP protocol error."
I've been leaving CheckRedirect nil and testing against a local server that never serves redirects. From the comment, that would imply that I should only receive HTTP protocol errors - which would correspond to the type *http.ProtocolError.
For typical socket errors, the clients in fact return a *url.Error - but the only place in the net/http documentation where url.Error is even mentioned is for the client.CheckRedirect field, which is completely unrelated to the errors I'm testing.
But that's not all!
The *url.Error usually wraps a net.OpError, which seems reasonable enough. In fact, net.Error is what I would have expected in the first place, since I didn't know about url.Error at all before I started these experiments.
But instead, the *url.Error occasionally wraps io.EOF. I'm guessing that happens when the socket happens to close at exactly the wrong moment, and because of the poor documentation it's not at all clear to me whether that's the intended behavior of the Client methods or an outright bug in the library.
But that's not all either!
The error wrapped by the OpError, one would expect, is a net.Error describing the underlying network error for the op. But that's not the case either - it's often a syscall.Errno instead, and syscall.Errno does not implement net.Error. So for temporary conditions, such as EPIPE or ECONNRESET, the net package's preferred mechanism for indicating that the condition is temporary does not work as expected.
So I end up needing a big pile of user code to sort through the whole stack of errors, find the root error, and check for particular syscall.Errno values, and that whole big pile of code is now relying on undocumented error spaces that could theoretically change completely with the next Go release.
To summarize: error handling in the http package is a mess. Someone familiar with the intended behavior of the package should clarify the documentation at least to the point where it's sensible to say whether the more subtle behaviors (e.g. io.EOF in a url.Error) are bugs or not.
The text was updated successfully, but these errors were encountered: