Skip to content

internal/rest/resources/control: Use independent context for join faiure cleanup#777

Draft
claudiubelu wants to merge 1 commit into
canonical:v4from
claudiubelu:v4-new-cleanup-context
Draft

internal/rest/resources/control: Use independent context for join faiure cleanup#777
claudiubelu wants to merge 1 commit into
canonical:v4from
claudiubelu:v4-new-cleanup-context

Conversation

@claudiubelu

Copy link
Copy Markdown
Contributor

The controlPost reverter used r.Context() for all cleanup operations (PreRemove hook, certificate fetch, client creation).

When a join fails due to context deadline exceeded, r.Context() may already be expired, causing the entire cleanup to silently fail and leaving a ghost node in the dqlite cluster. In particular, shared.GetRemoteCertificate was silently returning due an error returned (due to r.Context() being expired), which means that internalClient.DeleteClusterMember was never called.

Use an independent context with a 2-minute timeout for cleanup operations so they can complete regardless of the original request context state.

Also add error logging to GetRemoteCertificate and client creation failures which previously returned silently.

…lure cleanup

The controlPost reverter used r.Context() for all cleanup operations
(PreRemove hook, certificate fetch, client creation).

When a join fails due to context deadline exceeded, r.Context() may already
be expired, causing the entire cleanup to silently fail and leaving a ghost node
in the dqlite cluster. In particular, shared.GetRemoteCertificate was
silently returning due an error returned (due to r.Context() being expired),
which means that internalClient.DeleteClusterMember was never called.

Use an independent context with a 2-minute timeout for cleanup
operations so they can complete regardless of the original request
context state.

Also add error logging to GetRemoteCertificate and client creation
failures which previously returned silently.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts controlPost’s failure cleanup logic so that cleanup work isn’t blocked by an already-expired request context (e.g., join timeouts), and adds missing error logs for previously silent cleanup failures.

Changes:

  • Introduces a dedicated cleanup context with a 2-minute timeout for failure reverter operations.
  • Uses that cleanup context for pre-remove hook execution, certificate retrieval, and client creation during join cleanup.
  • Adds error logging when remote certificate fetch or client creation fails during cleanup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +99
// Use an independent context for cleanup so that it is not tied
// to the request context, which may already be expired (e.g. the
// join timed out).
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cleanupCancel()
Comment on lines 110 to 114
if joinInfo == nil || req.JoinToken == "" {
reExec, err := resetClusterMember(r.Context(), state, true)
reExec, err := resetClusterMember(cleanupCtx, state, true)
if err != nil {
logger.Error("Failed to reset cluster member on bootstrap error", slog.String("error", err.Error()))
return

@roosterfish roosterfish left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, could you please also add a test that triggers this?

// Use an independent context for cleanup so that it is not tied
// to the request context, which may already be expired (e.g. the
// join timed out).
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 2*time.Minute)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of deriving a new context, should we rather use the daemon's shutdownCtx here to be independent of the join request but still account for the health of the daemon when running the hooks?

@roosterfish

Copy link
Copy Markdown
Contributor

Marking as draft until you get a chance to get back to it @claudiubelu.

@roosterfish roosterfish marked this pull request as draft July 8, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants