From dc2db2de5e292cfe474e0594b000bba6c5367354 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 28 Aug 2022 14:51:20 +0200 Subject: [PATCH 1/2] b2: cancel connection setup after a minute If the connection to B2 fails, the library enters an endless loop. --- internal/backend/b2/b2.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/backend/b2/b2.go b/internal/backend/b2/b2.go index 19328706d..43a7857c9 100644 --- a/internal/backend/b2/b2.go +++ b/internal/backend/b2/b2.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "path" + "time" "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/backend/sema" @@ -36,8 +37,14 @@ var _ restic.Backend = &b2Backend{} func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Client, error) { opts := []b2.ClientOption{b2.Transport(rt)} + // if the connection B2 fails, this can cause the client to hang + // cancel the connection after a minute to at least provide some feedback to the user + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() c, err := b2.NewClient(ctx, cfg.AccountID, cfg.Key.Unwrap(), opts...) - if err != nil { + if err == context.DeadlineExceeded { + return nil, errors.New("connection to B2 failed") + } else if err != nil { return nil, errors.Wrap(err, "b2.NewClient") } return c, nil From e5b2c4d57184481c6a100fb66bde93720ec2508e Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 28 Aug 2022 15:00:30 +0200 Subject: [PATCH 2/2] b2: sniff the error that caused init retry loops --- internal/backend/b2/b2.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/internal/backend/b2/b2.go b/internal/backend/b2/b2.go index 43a7857c9..f81d954bf 100644 --- a/internal/backend/b2/b2.go +++ b/internal/backend/b2/b2.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "path" + "sync" "time" "github.com/restic/restic/internal/backend" @@ -34,8 +35,25 @@ const defaultListMaxItems = 10 * 1000 // ensure statically that *b2Backend implements restic.Backend. var _ restic.Backend = &b2Backend{} +type sniffingRoundTripper struct { + sync.Mutex + lastErr error + http.RoundTripper +} + +func (s *sniffingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + res, err := s.RoundTripper.RoundTrip(req) + if err != nil { + s.Lock() + s.lastErr = err + s.Unlock() + } + return res, err +} + func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Client, error) { - opts := []b2.ClientOption{b2.Transport(rt)} + sniffer := &sniffingRoundTripper{RoundTripper: rt} + opts := []b2.ClientOption{b2.Transport(sniffer)} // if the connection B2 fails, this can cause the client to hang // cancel the connection after a minute to at least provide some feedback to the user @@ -43,6 +61,9 @@ func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Clien defer cancel() c, err := b2.NewClient(ctx, cfg.AccountID, cfg.Key.Unwrap(), opts...) if err == context.DeadlineExceeded { + if sniffer.lastErr != nil { + return nil, sniffer.lastErr + } return nil, errors.New("connection to B2 failed") } else if err != nil { return nil, errors.Wrap(err, "b2.NewClient")