vamsikarnika opened a new pull request, #19867:
URL: https://github.com/apache/hudi/pull/19867

   ### Describe the issue this Pull Request addresses
   
   A commit that has already been made durable can be reported back to the 
caller as a **failed** write,
   purely because of heartbeat file cleanup.
   
   `stop()` cancels the heartbeat scheduler with `shutdownNow()` and 
immediately deletes the heartbeat
   file, without waiting for a refresh that is already in flight. The refresh 
recreates the file, and on
   storage that enforces preconditions it also changes the object generation, 
so the generation-matched
   delete is rejected. Observed in production on GCS as `412 conditionNotMet`, 
retried four times by the
   connector and then given up on:
   
   ```
   Precondition not met while deleting '.../.hoodie/.heartbeat/<instant>' at 
generation <gen>.
   Attempt 1. Retrying: {"code":412, ... "reason":"conditionNotMet"}
   ```
   
   `HoodieHadoopStorage.delete` reports a failed delete by throwing 
`HoodieIOException` when the object
   still exists afterwards, which is exactly the outcome here. That exception 
is unchecked, so it escapes
   the `IOException`-only catch in `WriterHeartbeatUtils.deleteHeartbeatFile`, 
propagates out of
   `BaseHoodieWriteClient.postCommit` and aborts `commitStats` — after 
`commit()` has already completed.
   The write is on storage, but the caller is told the round failed.
   
   ### Summary and Changelog
   
   Users no longer see a successful commit reported as a failure because of 
heartbeat cleanup.
   
   - `HoodieHeartbeatClient.stopHeartbeatScheduler`: `shutdown()` plus 
`awaitTermination()` before the
     caller deletes the heartbeat file, so a refresh in flight completes first 
and the delete then reads
     a settled object. `shutdownNow()` interrupts rather than waits, which is 
what left the window open.
     The await is deliberately **not** placed in `shutdownHeartbeatScheduler`, 
because the missed-refresh
     path in `updateHeartbeat` calls that from the scheduler thread and must 
not await itself. The bound
     is `heartbeatWriteTimeoutMs + heartbeatIntervalInMs`, since an in-flight 
tick can be parked on the
     bounded write.
   - `WriterHeartbeatUtils.deleteHeartbeatFile`: also catch 
`HoodieIOException`. By the time `postCommit`
     runs the cleanup the commit is durable, and no caller consumes the return 
value
     (`HoodieHeartbeatClient.stop`, `BaseHoodieTableServiceClient` x2 all 
discard it), so a failed cleanup
     of a transient lease marker must never be fatal. The existing `log.error` 
is kept, so a leaked file
     is still visible.
   
   The two changes are complementary rather than redundant: a write that 
exceeds `heartbeatWriteTimeoutMs`
   is deliberately abandoned by `writeHeartbeatFile`, and an abandoned write 
can still land after the
   delete. The await closes the ordering race for refreshes that complete 
normally; the widened catch is
   what keeps the abandoned-write tail non-fatal.
   
   Test: `TestHoodieHeartbeatClient#testStopWaitsForInFlightHeartbeatRefresh` 
gates a scheduled refresh
   mid-write, asserts `stop()` parks rather than deleting, and asserts no 
refresh is recorded after the
   delete. It fails on current master (the stopper never parks) and passes with 
this change.
   
   ### Impact
   
   No public API change. `deleteHeartbeatFile` keeps its signature and its 
documented "returns whether
   the file was deleted" contract; this makes the implementation honour that 
contract instead of sometimes
   throwing. `stop()` may now block briefly behind an in-flight heartbeat 
refresh, bounded as described
   above.
   
   ### Risk Level
   
   low
   
   Confined to the heartbeat classes. No new locks and nothing held across 
storage IO. The widened catch is
   on a path whose result every caller discards. Verified with JDK 11: module 
compiles, 0 checkstyle
   violations, and all 9 tests in `TestHoodieHeartbeatClient` pass, including 
the two existing
   scheduler-liveness tests (`testSlowHeartbeatWriteDoesNotBlockScheduler`,
   `testScheduledHeartbeatRetriesAfterWriteFailure`). The new test was 
confirmed to fail against
   unmodified master.
   
   ### Documentation Update
   
   none — no new config, no default changed, no user-facing behaviour beyond 
the removal of a spurious
   failure.
   
   ### Contributor's checklist
   
   - [x] Read through [contributor's 
guide](https://hudi.apache.org/contribute/how-to-contribute)
   - [x] Enough context is provided in the sections above
   - [x] Adequate tests were added if applicable
   


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