Skip to content

fix: do not NPE when adding a finalizer to a resource deleted mid-retry - #3530

Merged
csviri merged 2 commits into
operator-framework:mainfrom
csviri:fix/add-finalizer-null-resource
Aug 3, 2026
Merged

fix: do not NPE when adding a finalizer to a resource deleted mid-retry#3530
csviri merged 2 commits into
operator-framework:mainfrom
csviri:fix/add-finalizer-null-resource

Conversation

@csviri

@csviri csviri commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

conflictRetryingPatchPrimary / conflictRetryingPatch re-read the
resource from the API server after a 409 or 422 and then re-evaluate the
precondition:

resource = operation.inNamespace(ns).withName(name).get();

get() returns null if the resource was deleted in the meantime, so the
next iteration calls the precondition with null. removeFinalizer
anticipates this:

r -> {
  if (r == null) {
    log.warn("Cannot remove finalizer since resource not exists.");
    return false;
  }
  return r.hasFinalizer(finalizerName);
}

but addFinalizer passes r -> !r.hasFinalizer(finalizerName), which
throws a NullPointerException instead of exiting cleanly.

Gives addFinalizer the same null guard, in both ResourceOperations and
the deprecated PrimaryUpdateAndCacheUtils.

No test is added: reaching the retry path requires stubbing the client to
answer 409/422 and then 404 through the whole fabric8 DSL chain, which the
existing unit tests are not set up for.

Part of #3517

Copilot AI review requested due to automatic review settings July 30, 2026 09:05
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 30, 2026

Copilot AI 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.

Pull request overview

Prevents a NullPointerException when addFinalizer is executed through the conflict-retry patch path and the primary resource is deleted between retries (i.e., the re-read get() returns null). This makes addFinalizer behave consistently with existing removeFinalizer handling in ResourceOperations.

Changes:

  • Add a null-guard to the addFinalizer precondition in ResourceOperations to avoid dereferencing a deleted resource during retry.
  • Add the same null-guard to the deprecated PrimaryUpdateAndCacheUtils.addFinalizer retry precondition (and log when the resource is gone).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java Adds null-guarded precondition for addFinalizer during conflict-retry patch.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java Adds null-guarded precondition for deprecated addFinalizer during conflict-retry patch.

Copilot AI review requested due to automatic review settings August 3, 2026 08:33
@csviri
csviri marked this pull request as ready for review August 3, 2026 08:34
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 3, 2026
@openshift-ci
openshift-ci Bot requested review from metacosm and xstefank August 3, 2026 08:34
csviri and others added 2 commits August 3, 2026 10:34
`conflictRetryingPatchPrimary` / `conflictRetryingPatch` re-read the
resource from the API server after a 409 or 422 and then re-evaluate the
precondition:

    resource = operation.inNamespace(ns).withName(name).get();

`get()` returns null if the resource was deleted in the meantime, so the
next iteration calls the precondition with null. `removeFinalizer`
anticipates this:

    r -> {
      if (r == null) {
        log.warn("Cannot remove finalizer since resource not exists.");
        return false;
      }
      return r.hasFinalizer(finalizerName);
    }

but `addFinalizer` passes `r -> !r.hasFinalizer(finalizerName)`, which
throws a NullPointerException instead of exiting cleanly.

Gives `addFinalizer` the same null guard, in both `ResourceOperations` and
the deprecated `PrimaryUpdateAndCacheUtils`.

No test is added: reaching the retry path requires stubbing the client to
answer 409/422 and then 404 through the whole fabric8 DSL chain, which the
existing unit tests are not set up for.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@csviri
csviri force-pushed the fix/add-finalizer-null-resource branch from e92fb53 to 3b0a833 Compare August 3, 2026 08:34

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceOperations.java:1094

  • addFinalizer can now return null when the resource is deleted mid-retry (because the retry loop re-fetches with .get() which may return null, and the precondition returns false in that case). The Javadoc currently says it returns the updated resource, which no longer fully describes the behavior; please document the possible null return so callers know they must handle it.
        r -> {
          if (r == null) {
            log.warn("Cannot add finalizer since resource no longer exists.");
            return false;
          }
          return !r.hasFinalizer(finalizerName);
        },

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java:298

  • addFinalizer can now return null when the resource is deleted mid-retry (the retry loop re-fetches with .get() which may return null, and the precondition returns false). The Javadoc currently states it returns the updated resource, which is no longer complete; please document the possible null return value for callers.
        r -> {
          if (r == null) {
            log.warn("Cannot add finalizer since resource no longer exists.");
            return false;
          }
          return !r.hasFinalizer(finalizerName);
        });

@csviri csviri left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

LGTM

@csviri
csviri merged commit 96e59cd into operator-framework:main Aug 3, 2026
28 checks passed
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.

2 participants