DanielLeens commented on PR #11856:
URL: https://github.com/apache/seatunnel/pull/11856#issuecomment-5503126335

   Since this is my own PR, another self-check pass — this one scoped 
specifically to the commit I just pushed, `a215916e`. Nobody, including @SEZ9, 
has looked at it yet, so I'm not re-litigating the 8 issues from the earlier 
rounds; those still stand as resolved.
   
   # What `a215916e` changes
   
   On top of the already-reviewed marker/TTL/atomicity work, this commit:
   - Removes `SeaTunnelServer`'s `GracefulShutdownAwareService` implementation 
and the `onShutdown()` override added in `f953140e` — the fix @SEZ9 asked for 
in Issue 1 of the original review, which stood unchanged for 10 days.
   - Adds a hand-rolled `Runtime.getRuntime().addShutdownHook(...)` in 
`SeaTunnelServer.init()` (`registerShutdownHook`/`shutdownFromJvmHook`) that 
writes the marker and then calls `lifecycleService.shutdown()` directly.
   - Sets `hazelcast.shutdownhook.enabled: false` in all 15 shipped Hazelcast 
config/doc locations (`config/*.yaml`, `deploy/kubernetes/**`, `docs/en|zh/**`) 
to stop Hazelcast's own built-in shutdown hook from running alongside the new 
one.
   - Changes `seatunnel-cluster.sh`'s foreground path from `java ...` to `exec 
java ...`.
   - Adds `restoringRunningJobsFromMasterSwitch` / 
`canClearGracefulMemberRemovalMarker()` to keep the marker alive through 
master-failover job recovery.
   
   That's a much bigger footprint than a log-level fix, so I traced it against 
the real Hazelcast source rather than taking the new code comments at face 
value.
   
   # Issue 1 (Critical) — the stated reason for the rewrite doesn't match 
Hazelcast's actual source, and a one-line fix was already available
   
   **Location:** `SeaTunnelServer.java` 
(`registerShutdownHook`/`shutdownFromJvmHook`, new in this commit); 
`com.hazelcast.instance.impl.Node` and 
`com.hazelcast.spi.properties.ClusterProperty` in `hazelcast/hazelcast` tag 
`v5.1` (this project's pinned Hazelcast version — verified directly against 
that tag, not from memory).
   
   The new Javadoc says: *"Hazelcast's own hook cannot provide that ordering 
because it puts its services into the shutting-down state before 
graceful-service callbacks run."* Tracing `Node` at `v5.1` directly:
   
   ```text
   Node.shutdown(boolean terminate)                                    
[Node.java:502-552]
     if (!terminate) callGracefulShutdownAwareServices(maxWaitSeconds)  
[Node.java:518-520]
                        -> GracefulShutdownAwareService.onShutdown()    
[Node.java:554-587]
     // only afterward: resetJoinState(), removeShutdownHook(), 
shutdownServices() teardown
   
   NodeShutdownHookThread.run()                                        
[Node.java:759-778]
     switch (policy) {
       case TERMINATE: hazelcastInstance.getLifecycleService().terminate();  // 
-> Node.shutdown(true), SKIPS graceful callbacks entirely
       case GRACEFUL:  hazelcastInstance.getLifecycleService().shutdown();   // 
-> Node.shutdown(false), same graceful path as any other caller
     }
   ```
   
   Hazelcast's own hook calls the *identical* `Node.shutdown(false)` method as 
everything else — there is no separate, earlier-teardown code path, and 
`GracefulShutdownAwareService` callbacks run before teardown regardless of what 
triggers `.shutdown()`. So "ordering" isn't actually the problem.
   
   The real gap is one property: `ClusterProperty.SHUTDOWNHOOK_POLICY` defaults 
to `"TERMINATE"` (`ClusterProperty.java:1533-1534`), and this repo has never 
set it to `GRACEFUL` — not on `dev`, not anywhere in this PR before this commit 
(`git grep shutdownhook` on both only turns up `shutdownhook.enabled`, added 
today). So Hazelcast's own default hook, if it's what actually answers a 
container's SIGTERM, calls `.terminate()` — skipping 
`callGracefulShutdownAwareServices()` altogether. That is a real, 
previously-unnoticed gap in `f953140e`: all 8 prior self-review rounds and 
@SEZ9's review verified that `onShutdown()` runs before teardown *once 
invoked*, but never checked whether Hazelcast's default configuration would 
invoke the graceful path at all on a real signal.
   
   Given that, the minimal fix is:
   
   ```yaml
   hazelcast.shutdownhook.policy: GRACEFUL
   ```
   
   added next to the existing `properties:` blocks — zero Java changes — which 
makes Hazelcast's own already-mature, already-self-deregistering hook call 
exactly the `f953140e`/`94eaf4155b` mechanism that's already been reviewed and 
had its race condition fixed. That keeps this PR's Java diff where it was after 
`94eaf4155b`, with no new hook-vs-hook interaction to reason about.
   
   # Issue 2 (High) — the chosen fix creates a new dual-hook race for any 
deployment that doesn't carry the new config forward
   
   The docs added in this commit say: *"Custom Hazelcast configurations must 
retain that setting to preserve this behavior."* Nothing in the code enforces 
or checks that. If a deployment supplies its own Hazelcast properties — an 
ordinary scenario for a production system customizing vendor YAML over years — 
without `hazelcast.shutdownhook.enabled: false`, both hooks get registered:
   - Hazelcast's own `NodeShutdownHookThread` (default policy `TERMINATE`)
   - SeaTunnel's new `seatunnel-graceful-member-removal-shutdown` thread
   
   Per the JVM's own `Runtime.addShutdownHook` contract, registered hooks are 
started in unspecified order and run concurrently. `Node.shutdown()` has a 
re-entrancy guard (`setShuttingDown()`), so whichever thread's call wins that 
race decides graceful vs. forceful — which is scheduler-dependent, not 
deterministic. If Hazelcast's own hook wins, the instance tears down via 
`.terminate()` while SeaTunnel's hook may not have written the marker yet, 
reproducing the exact ERROR-instead-of-WARN symptom this PR exists to fix — 
silently, with no test able to catch a JVM-shutdown-hook race. That's a 
production-only failure mode, gated purely on whether an operator's Hazelcast 
config happens to carry a doc-only convention forward.
   
   # Non-blocking, but worth recording
   
   - `exec java ...` in `seatunnel-cluster.sh` (foreground path) is a good fix 
on its own merits and worth keeping regardless of how Issues 1-2 are resolved: 
container runtimes typically deliver SIGTERM to PID 1 only, and without `exec` 
the JVM here runs as a child of the launcher shell that may never see it 
directly.
   - `restoringRunningJobsFromMasterSwitch` / 
`canClearGracefulMemberRemovalMarker` (retaining the marker through 
master-failover job recovery) reads correctly to me on a source-level pass, but 
it's new, untested-by-anyone-else surface added the same day as Issues 1-2, on 
an already large PR.
   - CI on this exact head (`a215916e`) is still queued as of this review — no 
build/test signal either way yet.
   
   # Recommendation
   
   Not my call to make alone — that's the actual problem with this being round 
9 of what's mostly been me reviewing my own PR. @SEZ9, given this commit swaps 
out the mechanism you already signed off on for a new one with the race 
described in Issue 2, could you take a look specifically at `a215916e` when you 
have a chance? I'd lean toward reverting to the `94eaf4155b` shutdown mechanism 
plus the one-line `hazelcast.shutdownhook.policy: GRACEFUL` config addition 
(Issue 1) rather than keeping the new custom hook, but that's a real design 
call and I don't think I should be the one deciding it on my own PR.
   


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