mirror of
https://github.com/minio/minio.git
synced 2025-03-30 09:13:41 -04:00
a bunch of fixes for error handling (#19627)
- handle errFileCorrupt properly - micro-optimization of sending done() response quicker to close the goroutine. - fix logger.Event() usage in a couple of places - handle the rest of the client to return a different error other than lastErr() when the client is closed.
This commit is contained in:
parent
93b2f8a0c5
commit
a372c6a377
@ -153,6 +153,8 @@ func readMultipleFiles(ctx context.Context, disks []StorageAPI, req ReadMultiple
|
|||||||
errFileVersionNotFound,
|
errFileVersionNotFound,
|
||||||
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
||||||
io.EOF, // some times we would read without locks, ignore these errors
|
io.EOF, // some times we would read without locks, ignore these errors
|
||||||
|
context.DeadlineExceeded,
|
||||||
|
context.Canceled,
|
||||||
}
|
}
|
||||||
ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...)
|
ignoredErrs = append(ignoredErrs, objectOpIgnoredErrs...)
|
||||||
|
|
||||||
|
@ -403,7 +403,7 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, origbucket, bu
|
|||||||
if fi.IsValid() {
|
if fi.IsValid() {
|
||||||
return disks[index].WriteMetadata(ctx, origbucket, bucket, prefix, fi)
|
return disks[index].WriteMetadata(ctx, origbucket, bucket, prefix, fi)
|
||||||
}
|
}
|
||||||
return errCorruptedFormat
|
return errFileCorrupt
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,6 +739,15 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
|||||||
partPath := pathJoin(uploadIDPath, fi.DataDir, partSuffix)
|
partPath := pathJoin(uploadIDPath, fi.DataDir, partSuffix)
|
||||||
onlineDisks, err = renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, writeQuorum)
|
onlineDisks, err = renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, writeQuorum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errFileNotFound) {
|
||||||
|
// An in-quorum errFileNotFound means that client stream
|
||||||
|
// prematurely closed and we do not find any xl.meta or
|
||||||
|
// part.1's - in such a scenario we must return as if client
|
||||||
|
// disconnected. This means that erasure.Encode() CreateFile()
|
||||||
|
// did not do anything.
|
||||||
|
return pi, IncompleteBody{Bucket: bucket, Object: object}
|
||||||
|
}
|
||||||
|
|
||||||
return pi, toObjectErr(err, minioMetaMultipartBucket, partPath)
|
return pi, toObjectErr(err, minioMetaMultipartBucket, partPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1314,11 +1323,11 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
onlineDisks, versions, oldDataDir, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
|
onlineDisks, versions, oldDataDir, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
|
||||||
partsMetadata, bucket, object, writeQuorum)
|
partsMetadata, bucket, object, writeQuorum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, toObjectErr(err, bucket, object)
|
return oi, toObjectErr(err, bucket, object, uploadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = er.commitRenameDataDir(ctx, bucket, object, oldDataDir, onlineDisks); err != nil {
|
if err = er.commitRenameDataDir(ctx, bucket, object, oldDataDir, onlineDisks); err != nil {
|
||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object, uploadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.Speedtest && len(versions) > 0 {
|
if !opts.Speedtest && len(versions) > 0 {
|
||||||
|
@ -562,7 +562,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
|||||||
func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVers, allParts bool) (FileInfo, error) {
|
func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVers, allParts bool) (FileInfo, error) {
|
||||||
var xl xlMetaV2
|
var xl xlMetaV2
|
||||||
if err := xl.LoadOrConvert(ri.Buf); err != nil {
|
if err := xl.LoadOrConvert(ri.Buf); err != nil {
|
||||||
return FileInfo{}, err
|
return FileInfo{}, errFileCorrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := xl.ToFileInfo(bucket, object, "", inclFreeVers, allParts)
|
fi, err := xl.ToFileInfo(bucket, object, "", inclFreeVers, allParts)
|
||||||
@ -571,7 +571,7 @@ func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !fi.IsValid() {
|
if !fi.IsValid() {
|
||||||
return FileInfo{}, errCorruptedFormat
|
return FileInfo{}, errFileCorrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
versionID := fi.VersionID
|
versionID := fi.VersionID
|
||||||
@ -661,7 +661,7 @@ func pickLatestQuorumFilesInfo(ctx context.Context, rawFileInfos []RawFileInfo,
|
|||||||
if !lfi.IsValid() {
|
if !lfi.IsValid() {
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
if errs[i] == nil {
|
if errs[i] == nil {
|
||||||
errs[i] = errCorruptedFormat
|
errs[i] = errFileCorrupt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return metaFileInfos, errs
|
return metaFileInfos, errs
|
||||||
@ -1519,7 +1519,12 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
onlineDisks, versions, oldDataDir, err := renameData(ctx, onlineDisks, minioMetaTmpBucket, tempObj, partsMetadata, bucket, object, writeQuorum)
|
onlineDisks, versions, oldDataDir, err := renameData(ctx, onlineDisks, minioMetaTmpBucket, tempObj, partsMetadata, bucket, object, writeQuorum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errFileNotFound) {
|
if errors.Is(err, errFileNotFound) {
|
||||||
return ObjectInfo{}, toObjectErr(errErasureWriteQuorum, bucket, object)
|
// An in-quorum errFileNotFound means that client stream
|
||||||
|
// prematurely closed and we do not find any xl.meta or
|
||||||
|
// part.1's - in such a scenario we must return as if client
|
||||||
|
// disconnected. This means that erasure.Encode() CreateFile()
|
||||||
|
// did not do anything.
|
||||||
|
return ObjectInfo{}, IncompleteBody{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
@ -1237,6 +1237,7 @@ func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketNam
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error injecting faults into the request: <ERROR> %v.", err)
|
t.Fatalf("Error injecting faults into the request: <ERROR> %v.", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler,`func (api objectAPIHandlers) GetObjectHandler` handles the request.
|
// Call the ServeHTTP to execute the handler,`func (api objectAPIHandlers) GetObjectHandler` handles the request.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
|
@ -54,6 +54,9 @@ func isNetworkError(err error) bool {
|
|||||||
if down := xnet.IsNetworkOrHostDown(nerr.Err, false); down {
|
if down := xnet.IsNetworkOrHostDown(nerr.Err, false); down {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if errors.Is(nerr.Err, rest.ErrClientClosed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if errors.Is(err, grid.ErrDisconnected) {
|
if errors.Is(err, grid.ErrDisconnected) {
|
||||||
return true
|
return true
|
||||||
@ -61,7 +64,7 @@ func isNetworkError(err error) bool {
|
|||||||
// More corner cases suitable for storage REST API
|
// More corner cases suitable for storage REST API
|
||||||
switch {
|
switch {
|
||||||
// A peer node can be in shut down phase and proactively
|
// A peer node can be in shut down phase and proactively
|
||||||
// return 503 server closed error,consider it as an offline node
|
// return 503 server closed error, consider it as an offline node
|
||||||
case strings.Contains(err.Error(), http.ErrServerClosed.Error()):
|
case strings.Contains(err.Error(), http.ErrServerClosed.Error()):
|
||||||
return true
|
return true
|
||||||
// Corner case, the server closed the connection with a keep-alive timeout
|
// Corner case, the server closed the connection with a keep-alive timeout
|
||||||
|
@ -791,9 +791,26 @@ func keepHTTPReqResponseAlive(w http.ResponseWriter, r *http.Request) (resp func
|
|||||||
defer xioutil.SafeClose(doneCh)
|
defer xioutil.SafeClose(doneCh)
|
||||||
// Initiate ticker after body has been read.
|
// Initiate ticker after body has been read.
|
||||||
ticker := time.NewTicker(time.Second * 10)
|
ticker := time.NewTicker(time.Second * 10)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
// The done() might have been called
|
||||||
|
// concurrently, check for it before we
|
||||||
|
// write the filler byte.
|
||||||
|
select {
|
||||||
|
case err := <-doneCh:
|
||||||
|
if err != nil {
|
||||||
|
write([]byte{1})
|
||||||
|
write([]byte(err.Error()))
|
||||||
|
} else {
|
||||||
|
write([]byte{0})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// Response not ready, write a filler byte.
|
// Response not ready, write a filler byte.
|
||||||
write([]byte{32})
|
write([]byte{32})
|
||||||
if canWrite {
|
if canWrite {
|
||||||
@ -806,7 +823,6 @@ func keepHTTPReqResponseAlive(w http.ResponseWriter, r *http.Request) (resp func
|
|||||||
} else {
|
} else {
|
||||||
write([]byte{0})
|
write([]byte{0})
|
||||||
}
|
}
|
||||||
ticker.Stop()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -854,6 +870,21 @@ func keepHTTPResponseAlive(w http.ResponseWriter) func(error) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
// The done() might have been called
|
||||||
|
// concurrently, check for it before we
|
||||||
|
// write the filler byte.
|
||||||
|
select {
|
||||||
|
case err := <-doneCh:
|
||||||
|
if err != nil {
|
||||||
|
write([]byte{1})
|
||||||
|
write([]byte(err.Error()))
|
||||||
|
} else {
|
||||||
|
write([]byte{0})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// Response not ready, write a filler byte.
|
// Response not ready, write a filler byte.
|
||||||
write([]byte{32})
|
write([]byte{32})
|
||||||
if canWrite {
|
if canWrite {
|
||||||
|
@ -909,7 +909,8 @@ func (p *xlStorageDiskIDCheck) monitorDiskStatus(spent time.Duration, fn string)
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Event(context.Background(), "node(%s): Read/Write/Delete successful, bringing drive %s online", globalLocalNodeName, p.storage.String())
|
logger.Event(context.Background(), "healthcheck",
|
||||||
|
"node(%s): Read/Write/Delete successful, bringing drive %s online", globalLocalNodeName, p.storage.String())
|
||||||
p.health.status.Store(diskHealthOK)
|
p.health.status.Store(diskHealthOK)
|
||||||
p.health.waiting.Add(-1)
|
p.health.waiting.Add(-1)
|
||||||
return
|
return
|
||||||
|
@ -2558,8 +2558,12 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preserve all the legacy data, could be slow, but at max there can be 10,000 parts.
|
||||||
|
currentDataPath := pathJoin(dstVolumeDir, dstPath)
|
||||||
|
|
||||||
var xlMeta xlMetaV2
|
var xlMeta xlMetaV2
|
||||||
var legacyPreserved bool
|
var legacyPreserved bool
|
||||||
|
var legacyEntries []string
|
||||||
if len(dstBuf) > 0 {
|
if len(dstBuf) > 0 {
|
||||||
if isXL2V1Format(dstBuf) {
|
if isXL2V1Format(dstBuf) {
|
||||||
if err = xlMeta.Load(dstBuf); err != nil {
|
if err = xlMeta.Load(dstBuf); err != nil {
|
||||||
@ -2590,8 +2594,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
// from `xl.json` to `xl.meta`, we can avoid
|
// from `xl.json` to `xl.meta`, we can avoid
|
||||||
// one extra readdir operation here for all
|
// one extra readdir operation here for all
|
||||||
// new deployments.
|
// new deployments.
|
||||||
currentDataPath := pathJoin(dstVolumeDir, dstPath)
|
entries, err := readDir(currentDataPath)
|
||||||
entries, err := readDirN(currentDataPath, 1)
|
|
||||||
if err != nil && err != errFileNotFound {
|
if err != nil && err != errFileNotFound {
|
||||||
return res, osErrToFileErr(err)
|
return res, osErrToFileErr(err)
|
||||||
}
|
}
|
||||||
@ -2601,6 +2604,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
}
|
}
|
||||||
if strings.HasPrefix(entry, "part.") {
|
if strings.HasPrefix(entry, "part.") {
|
||||||
legacyPreserved = true
|
legacyPreserved = true
|
||||||
|
legacyEntries = entries
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2611,32 +2615,30 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
if formatLegacy {
|
if formatLegacy {
|
||||||
legacyDataPath = pathJoin(dstVolumeDir, dstPath, legacyDataDir)
|
legacyDataPath = pathJoin(dstVolumeDir, dstPath, legacyDataDir)
|
||||||
if legacyPreserved {
|
if legacyPreserved {
|
||||||
// Preserve all the legacy data, could be slow, but at max there can be 1res,000 parts.
|
if contextCanceled(ctx) {
|
||||||
currentDataPath := pathJoin(dstVolumeDir, dstPath)
|
return res, ctx.Err()
|
||||||
entries, err := readDir(currentDataPath)
|
|
||||||
if err != nil {
|
|
||||||
return res, osErrToFileErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// legacy data dir means its old content, honor system umask.
|
if len(legacyEntries) > 0 {
|
||||||
if err = mkdirAll(legacyDataPath, 0o777, dstVolumeDir); err != nil {
|
// legacy data dir means its old content, honor system umask.
|
||||||
// any failed mkdir-calls delete them.
|
if err = mkdirAll(legacyDataPath, 0o777, dstVolumeDir); err != nil {
|
||||||
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
// any failed mkdir-calls delete them.
|
||||||
return res, osErrToFileErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
// Skip xl.meta renames further, also ignore any directories such as `legacyDataDir`
|
|
||||||
if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = Rename(pathJoin(currentDataPath, entry), pathJoin(legacyDataPath, entry)); err != nil {
|
|
||||||
// Any failed rename calls un-roll previous transaction.
|
|
||||||
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
||||||
|
|
||||||
return res, osErrToFileErr(err)
|
return res, osErrToFileErr(err)
|
||||||
}
|
}
|
||||||
|
for _, entry := range legacyEntries {
|
||||||
|
// Skip xl.meta renames further, also ignore any directories such as `legacyDataDir`
|
||||||
|
if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = Rename(pathJoin(currentDataPath, entry), pathJoin(legacyDataPath, entry)); err != nil {
|
||||||
|
// Any failed rename calls un-roll previous transaction.
|
||||||
|
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
||||||
|
|
||||||
|
return res, osErrToFileErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2726,6 +2728,10 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
return res, errFileCorrupt
|
return res, errFileCorrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contextCanceled(ctx) {
|
||||||
|
return res, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
if err = s.WriteAll(ctx, srcVolume, pathJoin(srcPath, xlStorageFormatFile), newDstBuf); err != nil {
|
if err = s.WriteAll(ctx, srcVolume, pathJoin(srcPath, xlStorageFormatFile), newDstBuf); err != nil {
|
||||||
if legacyPreserved {
|
if legacyPreserved {
|
||||||
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
s.deleteFile(dstVolumeDir, legacyDataPath, true, false)
|
||||||
@ -2749,6 +2755,9 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
// on a versioned bucket.
|
// on a versioned bucket.
|
||||||
s.moveToTrash(legacyDataPath, true, false)
|
s.moveToTrash(legacyDataPath, true, false)
|
||||||
}
|
}
|
||||||
|
if contextCanceled(ctx) {
|
||||||
|
return res, ctx.Err()
|
||||||
|
}
|
||||||
if err = renameAll(srcDataPath, dstDataPath, skipParent); err != nil {
|
if err = renameAll(srcDataPath, dstDataPath, skipParent); err != nil {
|
||||||
if legacyPreserved {
|
if legacyPreserved {
|
||||||
// Any failed rename calls un-roll previous transaction.
|
// Any failed rename calls un-roll previous transaction.
|
||||||
@ -2758,11 +2767,16 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
s.deleteFile(dstVolumeDir, dstDataPath, false, false)
|
s.deleteFile(dstVolumeDir, dstDataPath, false, false)
|
||||||
return res, osErrToFileErr(err)
|
return res, osErrToFileErr(err)
|
||||||
}
|
}
|
||||||
|
diskHealthCheckOK(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have oldDataDir then we must preserve current xl.meta
|
// If we have oldDataDir then we must preserve current xl.meta
|
||||||
// as backup, in-case needing renames().
|
// as backup, in-case needing renames().
|
||||||
if res.OldDataDir != "" {
|
if res.OldDataDir != "" {
|
||||||
|
if contextCanceled(ctx) {
|
||||||
|
return res, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// preserve current xl.meta inside the oldDataDir.
|
// preserve current xl.meta inside the oldDataDir.
|
||||||
if err = s.writeAll(ctx, dstVolume, pathJoin(dstPath, res.OldDataDir, xlStorageFormatFileBackup), dstBuf, true, skipParent); err != nil {
|
if err = s.writeAll(ctx, dstVolume, pathJoin(dstPath, res.OldDataDir, xlStorageFormatFileBackup), dstBuf, true, skipParent); err != nil {
|
||||||
if legacyPreserved {
|
if legacyPreserved {
|
||||||
@ -2773,6 +2787,10 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
diskHealthCheckOK(ctx, err)
|
diskHealthCheckOK(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contextCanceled(ctx) {
|
||||||
|
return res, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Commit meta-file
|
// Commit meta-file
|
||||||
if err = renameAll(srcFilePath, dstFilePath, skipParent); err != nil {
|
if err = renameAll(srcFilePath, dstFilePath, skipParent); err != nil {
|
||||||
if legacyPreserved {
|
if legacyPreserved {
|
||||||
|
@ -286,12 +286,23 @@ func (c *Client) dumpHTTP(req *http.Request, resp *http.Response) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrClientClosed returned when *Client is closed.
|
||||||
|
var ErrClientClosed = errors.New("rest client is closed")
|
||||||
|
|
||||||
// Call - make a REST call with context.
|
// Call - make a REST call with context.
|
||||||
func (c *Client) Call(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
func (c *Client) Call(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (reply io.ReadCloser, err error) {
|
||||||
if !c.IsOnline() {
|
switch atomic.LoadInt32(&c.connected) {
|
||||||
|
case closed:
|
||||||
|
// client closed, this is usually a manual process
|
||||||
|
// so return a local error as client is closed
|
||||||
|
return nil, &NetworkError{Err: ErrClientClosed}
|
||||||
|
case offline:
|
||||||
|
// client offline, return last error captured.
|
||||||
return nil, &NetworkError{Err: c.LastError()}
|
return nil, &NetworkError{Err: c.LastError()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// client is still connected, attempt the request.
|
||||||
|
|
||||||
// Shallow copy. We don't modify the *UserInfo, if set.
|
// Shallow copy. We don't modify the *UserInfo, if set.
|
||||||
// All other fields are copied.
|
// All other fields are copied.
|
||||||
u := *c.url
|
u := *c.url
|
||||||
@ -393,8 +404,6 @@ func NewClient(uu *url.URL, tr http.RoundTripper, newAuthToken func(aud string)
|
|||||||
clnt := &Client{
|
clnt := &Client{
|
||||||
httpClient: &http.Client{Transport: tr},
|
httpClient: &http.Client{Transport: tr},
|
||||||
url: u,
|
url: u,
|
||||||
lastErr: err,
|
|
||||||
lastErrTime: time.Now(),
|
|
||||||
newAuthToken: newAuthToken,
|
newAuthToken: newAuthToken,
|
||||||
connected: connected,
|
connected: connected,
|
||||||
lastConn: time.Now().UnixNano(),
|
lastConn: time.Now().UnixNano(),
|
||||||
@ -402,6 +411,11 @@ func NewClient(uu *url.URL, tr http.RoundTripper, newAuthToken func(aud string)
|
|||||||
HealthCheckReconnectUnit: 200 * time.Millisecond,
|
HealthCheckReconnectUnit: 200 * time.Millisecond,
|
||||||
HealthCheckTimeout: time.Second,
|
HealthCheckTimeout: time.Second,
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
clnt.lastErr = err
|
||||||
|
clnt.lastErrTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
if clnt.HealthCheckFn != nil {
|
if clnt.HealthCheckFn != nil {
|
||||||
// make connection pre-emptively.
|
// make connection pre-emptively.
|
||||||
go clnt.HealthCheckFn()
|
go clnt.HealthCheckFn()
|
||||||
@ -468,7 +482,7 @@ func (c *Client) runHealthCheck() bool {
|
|||||||
if atomic.CompareAndSwapInt32(&c.connected, offline, online) {
|
if atomic.CompareAndSwapInt32(&c.connected, offline, online) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
disconnected := now.Sub(c.LastConn())
|
disconnected := now.Sub(c.LastConn())
|
||||||
logger.Event(context.Background(), "Client '%s' re-connected in %s", c.url.String(), disconnected)
|
logger.Event(context.Background(), "healthcheck", "Client '%s' re-connected in %s", c.url.String(), disconnected)
|
||||||
atomic.StoreInt64(&c.lastConn, now.UnixNano())
|
atomic.StoreInt64(&c.lastConn, now.UnixNano())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user