| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | package retry | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 21:02:52 +02:00
										 |  |  | 	"github.com/cenkalti/backoff/v4" | 
					
						
							| 
									
										
										
										
											2017-11-30 22:05:14 +01:00
										 |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | // Backend retries operations on the backend in case of an error with a | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | // backoff. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | type Backend struct { | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 	restic.Backend | 
					
						
							|  |  |  | 	MaxTries int | 
					
						
							|  |  |  | 	Report   func(string, error, time.Duration) | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 	Success  func(string, int) | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // statically ensure that RetryBackend implements restic.Backend. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | var _ restic.Backend = &Backend{} | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | // New wraps be with a backend that retries operations after a | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | // backoff. report is called with a description and the error, if one occurred. | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | // success is called with the number of retries before a successful operation | 
					
						
							|  |  |  | // (it is not called if it succeeded on the first try) | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func New(be restic.Backend, maxTries int, report func(string, error, time.Duration), success func(string, int)) *Backend { | 
					
						
							|  |  |  | 	return &Backend{ | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 		Backend:  be, | 
					
						
							|  |  |  | 		MaxTries: maxTries, | 
					
						
							|  |  |  | 		Report:   report, | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 		Success:  success, | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | // retryNotifyErrorWithSuccess is an extension of backoff.RetryNotify with notification of success after an error. | 
					
						
							|  |  |  | // success is NOT notified on the first run of operation (only after an error). | 
					
						
							|  |  |  | func retryNotifyErrorWithSuccess(operation backoff.Operation, b backoff.BackOff, notify backoff.Notify, success func(retries int)) error { | 
					
						
							| 
									
										
										
										
											2020-03-21 19:05:26 +00:00
										 |  |  | 	if success == nil { | 
					
						
							|  |  |  | 		return backoff.RetryNotify(operation, b, notify) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 	retries := 0 | 
					
						
							| 
									
										
										
										
											2020-03-21 19:05:26 +00:00
										 |  |  | 	operationWrapper := func() error { | 
					
						
							|  |  |  | 		err := operation() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 			retries++ | 
					
						
							|  |  |  | 		} else if retries > 0 { | 
					
						
							|  |  |  | 			success(retries) | 
					
						
							| 
									
										
										
										
											2020-03-21 19:05:26 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return backoff.RetryNotify(operationWrapper, b, notify) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 22:29:58 +02:00
										 |  |  | var fastRetries = false | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) retry(ctx context.Context, msg string, f func() error) error { | 
					
						
							| 
									
										
										
										
											2020-12-13 20:11:01 +01:00
										 |  |  | 	// Don't do anything when called with an already cancelled context. There would be | 
					
						
							|  |  |  | 	// no retries in that case either, so be consistent and abort always. | 
					
						
							|  |  |  | 	// This enforces a strict contract for backend methods: Using a cancelled context | 
					
						
							|  |  |  | 	// will prevent any backup repository modifications. This simplifies ensuring that | 
					
						
							|  |  |  | 	// a backup repository is not modified any further after a context was cancelled. | 
					
						
							|  |  |  | 	// The 'local' backend for example does not provide this guarantee on its own. | 
					
						
							|  |  |  | 	if ctx.Err() != nil { | 
					
						
							|  |  |  | 		return ctx.Err() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-15 22:29:58 +02:00
										 |  |  | 	bo := backoff.NewExponentialBackOff() | 
					
						
							|  |  |  | 	if fastRetries { | 
					
						
							|  |  |  | 		// speed up integration tests | 
					
						
							|  |  |  | 		bo.InitialInterval = 1 * time.Millisecond | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 	err := retryNotifyErrorWithSuccess(f, | 
					
						
							| 
									
										
										
										
											2022-10-15 22:29:58 +02:00
										 |  |  | 		backoff.WithContext(backoff.WithMaxRetries(bo, uint64(be.MaxTries)), ctx), | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 		func(err error, d time.Duration) { | 
					
						
							|  |  |  | 			if be.Report != nil { | 
					
						
							|  |  |  | 				be.Report(msg, err, d) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-03-21 19:39:01 +00:00
										 |  |  | 		func(retries int) { | 
					
						
							|  |  |  | 			if be.Success != nil { | 
					
						
							|  |  |  | 				be.Success(msg, retries) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2017-12-01 07:46:20 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Save stores the data in the backend under the given handle. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error { | 
					
						
							| 
									
										
										
										
											2017-12-01 07:46:20 -05:00
										 |  |  | 	return be.retry(ctx, fmt.Sprintf("Save(%v)", h), func() error { | 
					
						
							| 
									
										
										
										
											2018-03-03 14:20:54 +01:00
										 |  |  | 		err := rd.Rewind() | 
					
						
							| 
									
										
										
										
											2017-10-17 21:46:38 +02:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-30 22:05:14 +01:00
										 |  |  | 		err = be.Backend.Save(ctx, h, rd) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-21 11:14:53 +02:00
										 |  |  | 		if be.Backend.HasAtomicReplace() { | 
					
						
							|  |  |  | 			debug.Log("Save(%v) failed with error: %v", h, err) | 
					
						
							|  |  |  | 			// there is no need to remove files from backends which can atomically replace files | 
					
						
							|  |  |  | 			// in fact if something goes wrong at the backend side the delete operation might delete the wrong instance of the file | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			debug.Log("Save(%v) failed with error, removing file: %v", h, err) | 
					
						
							|  |  |  | 			rerr := be.Backend.Remove(ctx, h) | 
					
						
							|  |  |  | 			if rerr != nil { | 
					
						
							|  |  |  | 				debug.Log("Remove(%v) returned error: %v", h, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-11-30 22:05:14 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// return original error | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Load returns a reader that yields the contents of the file at h at the | 
					
						
							|  |  |  | // given offset. If length is larger than zero, only a portion of the file | 
					
						
							|  |  |  | // is returned. rd must be closed after use. If an error is returned, the | 
					
						
							|  |  |  | // ReadCloser must be nil. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) (err error) { | 
					
						
							| 
									
										
										
										
											2018-01-16 23:59:16 -05:00
										 |  |  | 	return be.retry(ctx, fmt.Sprintf("Load(%v, %v, %v)", h, length, offset), | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 		func() error { | 
					
						
							| 
									
										
										
										
											2018-01-16 23:59:16 -05:00
										 |  |  | 			return be.Backend.Load(ctx, h, length, offset, consumer) | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 		}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Stat returns information about the File identified by h. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) Stat(ctx context.Context, h restic.Handle) (fi restic.FileInfo, err error) { | 
					
						
							| 
									
										
										
										
											2017-12-01 07:46:20 -05:00
										 |  |  | 	err = be.retry(ctx, fmt.Sprintf("Stat(%v)", h), | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 		func() error { | 
					
						
							|  |  |  | 			var innerError error | 
					
						
							|  |  |  | 			fi, innerError = be.Backend.Stat(ctx, h) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-03 11:42:48 +01:00
										 |  |  | 			if be.Backend.IsNotExist(innerError) { | 
					
						
							|  |  |  | 				// do not retry if file is not found, as stat is usually used  to check whether a file exists | 
					
						
							|  |  |  | 				return backoff.Permanent(innerError) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-10-14 14:51:00 +02:00
										 |  |  | 			return innerError | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	return fi, err | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-22 18:34:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Remove removes a File with type t and name. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) Remove(ctx context.Context, h restic.Handle) (err error) { | 
					
						
							| 
									
										
										
										
											2017-12-22 18:34:17 +01:00
										 |  |  | 	return be.retry(ctx, fmt.Sprintf("Remove(%v)", h), func() error { | 
					
						
							|  |  |  | 		return be.Backend.Remove(ctx, h) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-01-06 23:13:42 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-24 13:23:42 +01:00
										 |  |  | // List runs fn for each file in the backend which has the type t. When an | 
					
						
							|  |  |  | // error is returned by the underlying backend, the request is retried. When fn | 
					
						
							|  |  |  | // returns an error, the operation is aborted and the error is returned to the | 
					
						
							|  |  |  | // caller. | 
					
						
							| 
									
										
										
										
											2022-10-15 16:33:15 +02:00
										 |  |  | func (be *Backend) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error { | 
					
						
							| 
									
										
										
										
											2018-02-24 13:23:42 +01:00
										 |  |  | 	// create a new context that we can cancel when fn returns an error, so | 
					
						
							|  |  |  | 	// that listing is aborted | 
					
						
							|  |  |  | 	listCtx, cancel := context.WithCancel(ctx) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listed := make(map[string]struct{}) // remember for which files we already ran fn | 
					
						
							|  |  |  | 	var innerErr error                  // remember when fn returned an error, so we can return that to the caller | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := be.retry(listCtx, fmt.Sprintf("List(%v)", t), func() error { | 
					
						
							| 
									
										
										
										
											2018-01-24 10:25:40 -05:00
										 |  |  | 		return be.Backend.List(ctx, t, func(fi restic.FileInfo) error { | 
					
						
							|  |  |  | 			if _, ok := listed[fi.Name]; ok { | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			listed[fi.Name] = struct{}{} | 
					
						
							| 
									
										
										
										
											2018-02-24 13:23:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			innerErr = fn(fi) | 
					
						
							|  |  |  | 			if innerErr != nil { | 
					
						
							|  |  |  | 				// if fn returned an error, listing is aborted, so we cancel the context | 
					
						
							|  |  |  | 				cancel() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return innerErr | 
					
						
							| 
									
										
										
										
											2018-01-24 10:25:40 -05:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2018-02-24 13:23:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// the error fn returned takes precedence | 
					
						
							|  |  |  | 	if innerErr != nil { | 
					
						
							|  |  |  | 		return innerErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							| 
									
										
										
										
											2018-01-24 10:25:40 -05:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-04-08 12:53:43 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (be *Backend) Unwrap() restic.Backend { | 
					
						
							|  |  |  | 	return be.Backend | 
					
						
							|  |  |  | } |