From 4378947dd651a2a24c4f87c03aa00ac5f79126ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Van=C4=9Bk?= Date: Sat, 4 Jul 2026 11:39:20 +0200 Subject: [PATCH] fix(envd): stop misleading CA install cancel errors on rapid /init The orchestrator's /init retry loop uses short client timeouts. Since 83ee89f9b303 ("fix(envd): make CA install lock ctx-aware (#2690)") the foreground CA lock acquire respects the caller's ctx while a previous install's background cleanup goroutine holds the lock doing slow I/O. A rapid retry carrying the same cert blocked on that lock, had its request ctx canceled, and surfaced Failed to set data: failed to install CA bundle: acquire CA install lock: context canceled logged at Error, even though the install's background routine was still progressing. Wrap that canceled acquire in a known sentinel (ErrCAInstallInProgress) so writeInitError can recognise it and log at Warn instead of Error, and return 503 (retryable) instead of 400. --- packages/envd/internal/api/init.go | 4 ++++ packages/envd/internal/host/cacerts.go | 7 ++++++- packages/envd/internal/host/cacerts_test.go | 20 ++++++++++++++++++++ packages/envd/pkg/version.go | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/envd/internal/api/init.go b/packages/envd/internal/api/init.go index 0fde762a8f..620204b0a4 100644 --- a/packages/envd/internal/api/init.go +++ b/packages/envd/internal/api/init.go @@ -334,6 +334,10 @@ func writeInitError(w http.ResponseWriter, logger zerolog.Logger, err error) { switch { case errors.Is(err, ErrAccessTokenMismatch), errors.Is(err, ErrAccessTokenResetNotAuthorized): w.WriteHeader(http.StatusUnauthorized) + case errors.Is(err, host.ErrCAInstallInProgress): + // Not a failure, a concurrent install still holds the CA lock. + logger.Warn().Err(err).Msg("CA initialization still in progress, retrying") + w.WriteHeader(http.StatusServiceUnavailable) default: logger.Error().Msgf("Failed to set data: %v", err) w.WriteHeader(http.StatusBadRequest) diff --git a/packages/envd/internal/host/cacerts.go b/packages/envd/internal/host/cacerts.go index 90988753a0..87ed2274ba 100644 --- a/packages/envd/internal/host/cacerts.go +++ b/packages/envd/internal/host/cacerts.go @@ -2,6 +2,7 @@ package host import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -12,6 +13,9 @@ import ( "golang.org/x/sync/semaphore" ) +// ErrCAInstallInProgress means the lock is held by a prior install's cleanup. +var ErrCAInstallInProgress = errors.New("CA install already in progress") + const ( CaBundlePath = "/etc/ssl/certs/ca-certificates.crt" @@ -81,8 +85,9 @@ func (c *CACertInstaller) install(ctx context.Context, certPEM, bundlePath, extr // consistent regardless of how the caller formatted the PEM. normalized := strings.TrimRight(certPEM, "\n") + "\n" + // Cancellation here means a prior install's cleanup still holds mu (83ee89f9b). if err := c.mu.Acquire(ctx, 1); err != nil { - return fmt.Errorf("acquire CA install lock: %w", err) + return fmt.Errorf("%w: %w", ErrCAInstallInProgress, err) } defer c.mu.Release(1) diff --git a/packages/envd/internal/host/cacerts_test.go b/packages/envd/internal/host/cacerts_test.go index d13267b48e..277543caca 100644 --- a/packages/envd/internal/host/cacerts_test.go +++ b/packages/envd/internal/host/cacerts_test.go @@ -1,6 +1,7 @@ package host import ( + "context" "os" "path/filepath" "strings" @@ -235,6 +236,25 @@ func TestInstallCACert_ConcurrentResume(t *testing.T) { assert.Equal(t, 1, strings.Count(string(bundle), normalized), "cert should appear exactly once") } +// A canceled acquire while a previous install's cleanup still holds mu must +// return ErrCAInstallInProgress, not a raw context error. +func TestInstallCACert_CanceledCtxUnderContention(t *testing.T) { + t.Parallel() + bundlePath, extraPath := testPaths(t) + c := newTestInstaller(t) + + // Simulate the background cleanup goroutine still holding mu. + require.NoError(t, c.mu.Acquire(t.Context(), 1)) + defer c.mu.Release(1) + + canceledCtx, cancel := context.WithCancel(t.Context()) + cancel() + + err := c.install(canceledCtx, certA, bundlePath, extraPath) + require.ErrorIs(t, err, ErrCAInstallInProgress) + require.ErrorIs(t, err, context.Canceled) +} + func TestRemoveCertFromBundle(t *testing.T) { t.Parallel() dir := t.TempDir() diff --git a/packages/envd/pkg/version.go b/packages/envd/pkg/version.go index 69b8d3cf0d..b0b46d20a9 100644 --- a/packages/envd/pkg/version.go +++ b/packages/envd/pkg/version.go @@ -1,3 +1,3 @@ package pkg -const Version = "0.6.7" +const Version = "0.6.8"