Bug #7492

Updated by Tom Clegg over 4 years ago

Keepproxy (which uses keepclient) is failing requests because one of the upstream servers returned "read tcp use of closed network connection". This is being returned to the client as a 404, but actually should be a 502 (Bad Gateway) which would trigger retry logic on the client.

h3. Requirements

If an error occurs in (*keepclient.KeepClient)Get() then keepproxy's http response status must be:
* 404 if every keepstore service returned 404
* 422 if every keepstore service returned a non-retryable error (but some didn't return 404)
* 502 if any keepstore service had still returned a retryable error when @tries_remaining@ ran out

Also, the list of errors received from the upstream keepstore services should be propagated to keepproxy's client in the response body. (Currently this information is logged by the SDK, so it shows up in keepproxy's log file but it is not available to the originator of the request.)


We need to update @(*keepclient.KeepClient)Get()@ to make it _possible_ for keepproxy to distinguish between the above outcomes.

* create an Error interface (similar to https://golang.org/pkg/net/#Error)
** <pre><code>type Error struct {
Temporary() bool // Is the error temporary?
* create a multipleResponseError type that implements Error
** <pre><code>type multipleResponseError struct {
isTemp bool
func (e *multipleResponseError) Temporary() bool {
return e.isTemp
* create a NotFoundError type that implements Error
** <pre><code>type NotFoundError struct {
* Change BlockNotFound to a NotFoundError
** <pre><code>var BlockNotFound = &NotFoundError{multipleResponseError{
error: errors.New("Block not found"),
isTemp: false,
* in Get(), count how many responses are http.StatusNotFound
* when returning from Get() after all attempts have failed, make a NotFoundError or a multipleResponseError:
** <pre><code>
if count404 == len(serversToTry) {
err = BlockNotFound
} else {
err = &multipleResponseError{
error: fmt.Errorf("%s %s failed: %v", method, locator, errs),
isTemp: len(serversToTry) > 0,

(*Defer:* add a way for clients to see a slice of all errors encountered, or at least the last error from each server. For now, that information is only available as a string, via the usual Error() method.)

* Do a type select on the error returned from keepclient.Get() to determine the appropriate HTTP response
** <pre><code>
switch err := err.(type) {
case keepclient.NotFoundError:
// respond 404
case keepclient.Error:
if err.Temporary() {
// respond 502
} else {
// respond 422
// respond 500
* In all cases, return err.Error() in the response body.