DanielLeens opened a new pull request, #12211:
URL: https://github.com/apache/seatunnel/pull/12211

   ### Purpose of this pull request
   
   Adds one new unit test to `CheckpointCoordinatorTest`, as part of the ongoing
   extreme-case E2E/regression coverage initiative for the Zeta engine. This
   test closes a coverage gap in the existing regression tests for
   apache/seatunnel#10655 ("NPE in 
`CheckpointCoordinator.completePendingCheckpoint()`"),
   fixed by the merged apache/seatunnel#10705 ("Converge notifyCompleted failure
   handling in CheckpointCoordinator").
   
   ### Background: the bug and its fix
   
   **Issue #10655**: when `notifyCompleted()` hit an internal exception, it
   called `handleCoordinatorError()` -> `cleanPendingCheckpoint()`, which
   cleared `pendingCheckpoints`. The original call chain then continued and
   called the then-unconditional
   
`pendingCheckpoints.remove(checkpointId).abortCheckpointTimeoutFutureWhenIsCompleted()`
   on the now-already-cleared map, got `null` back, and NPE'd on the chained
   call.
   
   **Fix #10705** changed `completePendingCheckpoint()`
   (`CheckpointCoordinator.java`, current `dev` HEAD) in two ways, quoted from
   the current source:
   
   ```java
   if (!notifyCompleted(completedCheckpoint)) {
       return;
   }
   PendingCheckpoint pendingCheckpoint = 
pendingCheckpoints.remove(checkpointId);
   if (pendingCheckpoint != null) {
       pendingCheckpoint.abortCheckpointTimeoutFutureWhenIsCompleted();
   }
   pendingCounter.decrementAndGet();
   ```
   
   1. `notifyCompleted()` now returns `boolean` (`false` if it caught an
      internal exception, after already calling `handleCoordinatorError()`).
      All three call sites (`allTaskReady()`, `restoreCoordinator()`,
      `completePendingCheckpoint()`) now bail out immediately when it returns
      `false`.
   2. `pendingCheckpoints.remove(checkpointId)` is now null-checked before the
      previously-unconditional `.abortCheckpointTimeoutFutureWhenIsCompleted()`
      call is made on it -- defense in depth, independent of (1).
   
   I verified this fix is still intact, unmodified, on current `apache/dev`
   (`CheckpointCoordinator.java:1370-1377`; `notifyCompleted()` itself at
   `CheckpointCoordinator.java:466-491`; `lock` field at line 168).
   
   ### Why this is not a duplicate of apache/seatunnel#10705's own tests
   
   Fix PR #10705 already shipped three white-box unit tests in
   `CheckpointCoordinatorTest.java`, still present on current `dev`:
   
   - `testCompletePendingCheckpointShouldReturnEarlyWhenNotifyCompletedFails`
   - `testAllTaskReadyShouldNotScheduleCheckpointWhenNotifyCompletedFails`
   - `testRestoreCoordinatorShouldNotTriggerCheckpointWhenNotifyCompletedFails`
   
   All three mock `notifyCompleted()` to **return `false`**, which makes
   `completePendingCheckpoint()` take the early-return guard in (1)
   immediately -- execution never reaches the 
`pendingCheckpoints.remove(checkpointId)`
   call at all, so the null-check added in (2) is never exercised by any of
   them. I confirmed this by grepping the whole current test tree: the string
   `abortCheckpointTimeoutFutureWhenIsCompleted` appears nowhere except the two
   `src/main` call sites themselves -- no existing test reaches that line.
   
   This PR's new test instead has `notifyCompleted()` **return `true`** (i.e.
   it does *not* fail), so `completePendingCheckpoint()` proceeds past guard
   (1) into the `pendingCheckpoints.remove(checkpointId)` call -- and asserts
   that it still doesn't NPE when that map entry is already gone, exercising
   guard (2) specifically.
   
   ### Why this is not a duplicate of apache/seatunnel#12199's coverage either
   
   apache/seatunnel#12199 
(`CheckpointCoordinatorFailoverIT#testStreamJobFailsAfterCheckpointTriggerDispatchFailure`)
   covers a different bug (apache/seatunnel#10442, fixed by 
apache/seatunnel#10448) in a
   different method: `startTriggerPendingCheckpoint()`'s checkpoint **dispatch**
   path (an `IllegalArgumentException` from `queryTaskGroupAddress` when
   `ownedSlotProfilesIMap` is missing an entry). It never touches
   `notifyCompleted()` or `completePendingCheckpoint()`. This PR's mechanism is
   unrelated: the checkpoint **completion/notification** path.
   
   ### Trigger mechanism, and why it's reliable without having run it locally
   
   Per this project's Apache SeaTunnel local-verification policy, I have not
   run this test locally; GitHub CI on this PR's head is the first execution.
   
   The interleaving this test reproduces is realistic, not contrived:
   `completePendingCheckpoint()` is `synchronized` on `this`, but
   `cleanPendingCheckpoint()` (invoked from `handleCoordinatorError()`, which
   has call sites with no relation to this checkpoint's own `notifyCompleted()`
   -- e.g. `reportedTask()`'s async task-status-report handling at
   `CheckpointCoordinator.java:309-333`, and other call sites at lines 519,
   922, 930, 960, 966, 994 and 1020) synchronizes on the separate `lock` field
   (`CheckpointCoordinator.java:168`) instead. The two methods can therefore
   genuinely run concurrently on different threads in production: an unrelated
   failure elsewhere in the pipeline can clear `pendingCheckpoints` in the
   window between this checkpoint's own `notifyCompleted()` returning
   successfully and this call reaching its own `remove()`.
   
   Rather than trying to hit that narrow, timing-dependent window with a real
   two-thread race (which this coordinator-internal logic doesn't need, and
   which I could not have verified deterministically without running it), the
   test reproduces the resulting *state* directly: the `notifyCompleted()`
   mock clears `pendingCheckpoints` itself (mirroring exactly what a concurrent
   `cleanPendingCheckpoint()` does to that map) immediately before returning
   `true`. This is the same reflection-based, single-threaded technique
   already used (and already merged, already CI-green) by the sibling test
   `testCompletePendingCheckpointShouldReturnEarlyWhenNotifyCompletedFails` in
   this same file -- only the return value and the resulting code path differ.
   It is fully deterministic: no `Thread.sleep`, no `Awaitility` poll, no
   timing window to miss.
   
   I chose this unit-level, white-box approach over a full E2E cluster test
   (this initiative's usual style, e.g. #12199, #12111, #12110) because this
   bug lives entirely in `CheckpointCoordinator`'s own internal error-handling
   and map bookkeeping -- reproducing it through a live multi-node cluster
   would require forcing a real, precisely-timed race between an unrelated
   task failure and a checkpoint's own completion callback, which is far less
   reliable to pin down deterministically than driving the coordinator
   directly, exactly as PR #10705's own reviewers already accepted for the
   sibling tests in this file.
   
   ### Does this PR introduce any user-facing change?
   
   No. Test-only; no `src/main` changes.
   
   ### How was this patch tested?
   
   - `./mvnw spotless:apply -pl seatunnel-engine/seatunnel-engine-server -nsu 
-Dmaven.gitcommitid.skip=true` -- BUILD SUCCESS; spotless reflowed one Javadoc 
paragraph, no other changes.
   - Per this repo's Apache SeaTunnel local-verification policy, no local 
compile/test execution was performed; GitHub CI on this PR's head is the 
authoritative verification.
   
   ### Check list
   
   - [x] Test-only change, no `src/main` modifications.
   - [x] No new dependencies.
   - [x] No documentation changes needed (test-only).
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to