From e490b9a7f394e268fa1946ca51aa998c78c3d46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Fri, 9 May 2025 16:15:07 +0200 Subject: [PATCH] fix: timeout while waiting for SSH to become available (#92) In #68 I reduced the general limits for the back off, thinking that it would speed up the upload on average because it was retrying faster. But because it was retrying faster, the 10 available retries were used up before SSH became available. The new 100 retries match the 3 minutes of total timeout that the previous solution had, and should fix all issues. In addition, I discovered that my implementation in `hcloudimages/backoff.ExponentialBackoffWithLimit` has a bug where the calculated offset could overflow before the limit was applied, resulting in negative durations. I did not fix the issue because `hcloud-go` provides such a method natively nowadays. Instead, I marked the method as deprecated, to be removed in a later release. --- cmd/root.go | 3 +-- hcloudimages/backoff/backoff.go | 4 ++++ hcloudimages/client.go | 2 +- hcloudimages/internal/control/retry.go | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 86cfe89..98a25d3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" "github.com/apricote/hcloud-upload-image/hcloudimages" - "github.com/apricote/hcloud-upload-image/hcloudimages/backoff" "github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger" "github.com/apricote/hcloud-upload-image/internal/ui" "github.com/apricote/hcloud-upload-image/internal/version" @@ -89,7 +88,7 @@ func initClient(cmd *cobra.Command, _ []string) { opts := []hcloud.ClientOption{ hcloud.WithToken(os.Getenv("HCLOUD_TOKEN")), hcloud.WithApplication("hcloud-upload-image", version.Version), - hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: backoff.ExponentialBackoffWithLimit(2, 1*time.Second, 30*time.Second)}), + hcloud.WithPollOpts(hcloud.PollOpts{BackoffFunc: hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 1 * time.Second, Cap: 30 * time.Second})}), } if os.Getenv("HCLOUD_DEBUG") != "" || verbose >= 2 { diff --git a/hcloudimages/backoff/backoff.go b/hcloudimages/backoff/backoff.go index 310312a..346fb8f 100644 --- a/hcloudimages/backoff/backoff.go +++ b/hcloudimages/backoff/backoff.go @@ -16,6 +16,10 @@ import ( // It uses the formula: // // min(b^retries * d, limit) +// +// This function has a known overflow issue and should not be used anymore. +// +// Deprecated: Use BackoffFuncWithOpts from github.com/hetznercloud/hcloud-go/v2/hcloud instead. func ExponentialBackoffWithLimit(b float64, d time.Duration, limit time.Duration) hcloud.BackoffFunc { return func(retries int) time.Duration { current := time.Duration(math.Pow(b, float64(retries))) * d diff --git a/hcloudimages/client.go b/hcloudimages/client.go index 62e218a..c30cef9 100644 --- a/hcloudimages/client.go +++ b/hcloudimages/client.go @@ -316,7 +316,7 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima err = control.Retry( contextlogger.New(ctx, logger.With("operation", "ssh")), - 10, + 100, // ~ 3 minutes func() error { var err error logger.DebugContext(ctx, "trying to connect to server", "ip", server.PublicNet.IPv4.IP) diff --git a/hcloudimages/internal/control/retry.go b/hcloudimages/internal/control/retry.go index eb56721..44cfe55 100644 --- a/hcloudimages/internal/control/retry.go +++ b/hcloudimages/internal/control/retry.go @@ -8,7 +8,8 @@ import ( "context" "time" - "github.com/apricote/hcloud-upload-image/hcloudimages/backoff" + "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger" ) @@ -18,7 +19,7 @@ func Retry(ctx context.Context, maxTries int, f func() error) error { var err error - backoffFunc := backoff.ExponentialBackoffWithLimit(2, 200*time.Millisecond, 2*time.Second) + backoffFunc := hcloud.ExponentialBackoffWithOpts(hcloud.ExponentialBackoffOpts{Multiplier: 2, Base: 200 * time.Millisecond, Cap: 2 * time.Second}) for try := 0; try < maxTries; try++ { if ctx.Err() != nil {