Rangsh commented on issue #12441:
URL: https://github.com/apache/seatunnel/issues/12441#issuecomment-5777719148

   @Vivek1106-04 @DanielLeens Here is my proposed solution. I would like to go 
over it with you and check whether there are any missing parts or unreasonable 
aspects.
   
   ## 1. Background
   
   Before creating a savepoint checkpoint, 
`CheckpointCoordinator.startSavepoint()` waits until any in-flight checkpoint 
has drained (`pendingCounter == 0`) or the coordinator has shut down. On 
current `dev`, that wait is a sleep-poll **inside** `synchronized (lock)`:
   
   ```java
   synchronized (lock) {
       while (pendingCounter.get() > 0 && !shutdown) {
           Thread.sleep(500);
       }
       // create + start SAVEPOINT_TYPE pending checkpoint
   }
   ```
   
   The periodic trigger path (`tryTriggerPendingCheckpoint`) also enters the 
same `lock` before it can skip, create a checkpoint, or re-arm the next timer. 
A trigger that fires during this window therefore blocks for as long as the 
in-flight checkpoint takes (often seconds under backpressure), instead of 
failing fast or re-arming.
   
   `CheckpointManager.triggerSavePoints()` fans out over every pipeline 
coordinator with `parallelStream`, so one job suspend can put all of that job's 
pipelines into this wait at the same time.
   
   Today the blocked trigger mainly occupies that pipeline's own small 
scheduled pool, so the damage is mostly self-contained. With the member-wide 
bounded dispatch pool from #12165 (STIP-40: #12339), the same lock-holding can 
occupy shared dispatch threads that unrelated pipelines need for their own 
triggers and timeout watchdogs. The issue reports the lock-holding defect 
(source-verified); full simultaneous occupancy of the dispatch pool is not 
required to justify fixing the lock scope.
   
   ## 2. Goal
   
   Keep savepoint's "wait for in-flight checkpoint, then create SAVEPOINT_TYPE" 
behavior, but **do not hold the coordinator lock while sleeping**. Concurrent 
work that needs the same lock (especially periodic triggers) must not be 
serialized behind that drain. Shutdown / already-completed / existing in-flight 
savepoint handling must stay correct. Checkpoint and savepoint completeness / 
recovery semantics must not change.
   
   ## 3. Design
   
   ### 3.1 Change the wait boundary in `startSavepoint()`
   
   1. Perform the `pendingCounter` drain wait **outside** `synchronized (lock)` 
(same 500 ms poll cadence, or equivalent), exiting when `pendingCounter == 0` 
or `shutdown`.
   2. Enter `synchronized (lock)` only for a short critical section that:
      - re-checks shutdown / completed (fail closed with the existing close 
reasons if the coordinator ended during the unlocked wait);
      - re-checks whether a savepoint is already in flight;
      - then `createPendingCheckpoint(..., SAVEPOINT_TYPE)` + 
`startTriggerPendingCheckpoint(...)`.
   3. Leave `CheckpointManager.triggerSavePoints()` fan-out and savepoint 
completion future wiring unchanged in this slice.
   
   This removes the long critical section at its source, so every caller of 
that lock benefits on current `dev` and under the shared dispatch pool—without 
relying on only the trigger path becoming non-blocking while savepoint still 
sleeps under the lock.
   
   ### 3.2 Correctness constraints for the unlocked window
   
   While waiting outside the lock, another thread may complete the pending 
checkpoint, shut the coordinator down, or start competing coordinator work. The 
post-wait locked section must therefore treat the unlocked drain as **stale 
until revalidated**, and must not create a savepoint against a terminal 
coordinator. Inclusive behavior for "already have an in-flight savepoint" 
should remain as today's early return of that savepoint's future.
   
   ### 3.3 Tests
   
   Add deterministic unit coverage in `CheckpointCoordinatorTest` (no 
full-cluster requirement for the first PR):
   
   1. Force `pendingCounter > 0`, start `startSavepoint()` on one thread so it 
enters the drain wait.
   2. Concurrently invoke `tryTriggerPendingCheckpoint` (or another path that 
needs the same `lock`) and assert it is **not** blocked for the full drain 
duration—i.e. sleep-poll no longer runs under the lock.
   3. After the pending checkpoint drains, assert the savepoint is still 
created successfully.
   4. Assert shutdown / already-completed during the unlocked wait still fails 
closed with the existing close reasons.
   
   I am not treating "fill the entire shared dispatch pool" as a merge gate for 
the first PR; scheduling-delay measurement can stay with #12418. The first PR 
should prove the lock-scope fix on current `dev` with the unit tests above.
   
   Please let me know if this direction looks right, or if anything above 
should be adjusted before I open the PR. Thanks!


-- 
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