From 1752ee3c8b832fbca21b030c805260c7c2b882ce Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Fri, 29 Apr 2016 16:42:08 +0100 Subject: [PATCH] Retry errors which indicate the connection closed prematurely. See discussion in #442 --- amazonclouddrive/amazonclouddrive.go | 3 --- fs/closed_conn.go | 9 +++++++++ fs/closed_conn_win.go | 27 ++++++++++++++++++++++++++ fs/error.go | 29 +++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 fs/closed_conn.go create mode 100644 fs/closed_conn_win.go diff --git a/amazonclouddrive/amazonclouddrive.go b/amazonclouddrive/amazonclouddrive.go index 40f852a34..80b2643bf 100644 --- a/amazonclouddrive/amazonclouddrive.go +++ b/amazonclouddrive/amazonclouddrive.go @@ -142,9 +142,6 @@ var retryErrorCodes = []int{ // shouldRetry returns a boolean as to whether this resp and err // deserve to be retried. It returns the err as a convenience func shouldRetry(resp *http.Response, err error) (bool, error) { - if err == io.EOF { - return true, err - } return fs.ShouldRetry(err) || fs.ShouldRetryHTTP(resp, retryErrorCodes), err } diff --git a/fs/closed_conn.go b/fs/closed_conn.go new file mode 100644 index 000000000..dcf4974f9 --- /dev/null +++ b/fs/closed_conn.go @@ -0,0 +1,9 @@ +// +build !windows + +package fs + +// isClosedConnErrorPlatform reports whether err is an error from use +// of a closed network connection using platform specific error codes. +func isClosedConnErrorPlatform(err error) bool { + return false +} diff --git a/fs/closed_conn_win.go b/fs/closed_conn_win.go new file mode 100644 index 000000000..6c843aace --- /dev/null +++ b/fs/closed_conn_win.go @@ -0,0 +1,27 @@ +// +build windows + +package fs + +import ( + "net" + "os" + "syscall" +) + +// isClosedConnErrorPlatform reports whether err is an error from use +// of a closed network connection using platform specific error codes. +// +// Code adapted from net/http +func isClosedConnErrorPlatform(err error) bool { + if oe, ok := err.(*net.OpError); ok && oe.Op == "read" { + if se, ok := oe.Err.(*os.SyscallError); ok && se.Syscall == "wsarecv" { + if errno, ok := se.Err.(syscall.Errno); ok { + const WSAECONNABORTED syscall.Errno = 10053 + if errno == syscall.WSAECONNRESET || errno == WSAECONNABORTED { + return true + } + } + } + } + return false +} diff --git a/fs/error.go b/fs/error.go index 820dd64b0..66d32af18 100644 --- a/fs/error.go +++ b/fs/error.go @@ -4,8 +4,10 @@ package fs import ( "fmt" + "io" "net/http" "net/url" + "strings" ) // Retry is an optional interface for error as to whether the @@ -56,14 +58,39 @@ func RetryError(err error) error { return plainRetryError{err} } +// isClosedConnError reports whether err is an error from use of a closed +// network connection. +// +// Code adapted from net/http +func isClosedConnError(err error) bool { + if err == nil { + return false + } + + // Note that this error isn't exported so we have to do a + // string comparison :-( + str := err.Error() + if strings.Contains(str, "use of closed network connection") { + return true + } + + return isClosedConnErrorPlatform(err) +} + // ShouldRetry looks at an error and tries to work out if retrying the // operation that caused it would be a good idea. It returns true if -// the error implements Timeout() or Temporary() and it returns true. +// the error implements Timeout() or Temporary() or if the error +// indicates a premature closing of the connection. func ShouldRetry(err error) bool { if err == nil { return false } + // Look for premature closing of connection + if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) { + return true + } + // Unwrap url.Error if urlErr, ok := err.(*url.Error); ok { err = urlErr.Err