fightBoxing opened a new pull request, #4196:
URL: https://github.com/apache/amoro/pull/4196

   Fixes #4172.
   
   Supersedes #4195 (closed — that branch was stale against the recent 
scheduler-framework refactor).
   
   ## Problem
   
   When a table's optimizing process reaches the post-execution phase — all 
tasks are \`SUCCESS\` and the code is about to transition the table to 
\`COMMITTING\` — a transient failure of 
\`DefaultTableRuntime#beginCommitting()\` (e.g. DB lock wait timeout in the 
underlying \`UPDATE table_runtime\`) leaves the process \`RUNNING\` but the 
table status stuck in \`*_OPTIMIZING\`, **forever, until AMS is restarted**. 
The issue reporter's stack trace confirms exactly this symptom.
   
   ### Why existing code paths do not recover the table
   
   - \`OptimizingQueue.acceptResult()\` (L677–L683) calls 
\`tableRuntime.beginCommitting()\` **once** the moment a task transitions to 
\`SUCCESS\`. It has **no retry**: a single transient DB failure is 
unrecoverable at runtime.
   - \`OptimizingQueue#initTableRuntime\` (L144) does re-drive 
\`beginCommitting()\` when \`allTasksPrepared()\`, but only at AMS startup.
   - \`OptimizingCommitExecutor\` only schedules tables already in 
\`COMMITTING\`, so it cannot see the stuck state.
   
   Result: while AMS is alive, there is no self-healing; the operator must 
restart AMS.
   
   ## Approach
   
   Leverage the existing \`TableRuntimeRefreshExecutor\`, which already scans 
every table periodically. At the end of \`execute()\`, detect the stuck pattern 
and re-drive the transition:
   
   \`\`\`
   process != null
     && process.getStatus() == ProcessStatus.RUNNING
     && tableRuntime.getOptimizingStatus() != OptimizingStatus.COMMITTING
     && tableRuntime.getOptimizingStatus().isProcessing()
     && process.allTasksPrepared()
   \`\`\`
   
   When all conditions hold, call \`tableRuntime.beginCommitting()\`. On 
success, the normal \`handleTableChanged\` → \`OptimizingCommitExecutor\` 
pipeline resumes. On failure, log at \`WARN\` and retry on the next refresh 
cycle (≤ 1 minute by default).
   
   ## Why this location
   
   - \`TableRuntimeRefreshExecutor\` already runs \`enabled() == true\` for 
every table — no new thread pool, no new scheduling mechanism, and it's exactly 
the right granularity (slow poll, low cost per table).
   - \`OptimizingCommitExecutor\` is intentionally kept as-is; it gets notified 
via the existing \`handleTableChanged\` event once the self-heal succeeds.
   - \`OptimizingQueue.acceptResult()\` hot path (concurrent task completion) 
is **not** changed — we only expose an existing read method. The lock added 
around \`allTasksPrepared()\` is the same \`ReentrantLock\` that already 
serializes \`acceptResult\`.
   
   ## Changes
   
   | File | Change |
   |---|---|
   | \`OptimizingProcess.java\` | Add \`boolean allTasksPrepared()\` to the 
interface. |
   | \`OptimizingQueue.java\` | \`TableOptimizingProcess.allTasksPrepared()\` 
becomes \`@Override public\` and wraps \`lock.lock()\` / \`unlock()\` for safe 
cross-thread \`taskMap\` observation. |
   | \`scheduler/inline/TableRuntimeRefreshExecutor.java\` | New private 
\`tryHealStuckCommitting()\` invoked at the end of \`execute()\`. |
   | \`TestDefaultOptimizingService.java\` | New regression test 
\`testRefreshExecutorHealsStuckRunningProcess\` that uses 
\`TableRuntimeStore#begin().updateStatusCode\` to simulate the stuck state 
**without a reload**, then verifies a single \`execute()\` call transitions the 
table back to \`COMMITTING\`. |
   
   \`\`\`
   4 files changed, 133 insertions(+), 6 deletions(-)
   \`\`\`
   
   ## Verification
   
   All targeted test suites pass locally on JDK 11:
   
   \`\`\`
   mvn -pl amoro-ams -am test \
     
-Dtest='TestDefaultOptimizingService#testRefreshExecutorHealsStuckRunningProcess'
 \
     -Dsurefire.failIfNoSpecifiedTests=false -DskipITs
   → Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS
   
   mvn -pl amoro-ams -am test -Dtest='TestDefaultOptimizingService'
   → Tests run: 22, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS
   
   mvn -pl amoro-ams -am test -Dtest='TestOptimizingQueue'
   → Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS
   
   mvn -pl amoro-ams -am test -Dtest='TestTableRuntimeRefreshExecutor'
   → Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS
   \`\`\`
   
   \`spotless:check\` clean. The self-heal path is observable during the new 
test:
   
   \`\`\`
   WARN TableRuntimeRefresher: test_catalog.test_db.test_table detected stuck 
RUNNING
   optimizing process (processId=..., status=MINOR_OPTIMIZING): all tasks have
   succeeded but the table never transitioned to COMMITTING. Self-healing by
   re-driving beginCommitting() (issue #4172).
   \`\`\`
   
   ## Risk & behavioural notes
   
   - No change to concurrent hot paths. Only one additional idempotent read 
(\`allTasksPrepared\`, lock-protected) per refresh cycle, short-circuited early 
for tables that are not processing.
   - Worst case if the underlying DB stays unhealthy: self-heal keeps logging 
at \`WARN\` and retrying every refresh cycle — identical cost to today's plus 
one cheap read, but strictly better than "stuck until restart".
   - This is complementary to the existing restart-recovery in 
\`OptimizingQueue#initTableRuntime\`, which remains unchanged. A companion test 
\`testReloadAllTasksCompletedNotYetCommitting\` already guards that path.


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