Skip to content

Commit 9f09d75

Browse files
author
张文领
committed
Fix the issue of optimizing tasks get stuck when stale ack arrives after task reset by OptimizerKeeper
1 parent d8f3793 commit 9f09d75

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

amoro-ams/src/main/java/org/apache/amoro/server/optimizing/TaskRuntime.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@
3333
import org.apache.amoro.shade.guava32.com.google.common.base.MoreObjects;
3434
import org.apache.amoro.shade.guava32.com.google.common.collect.ImmutableMap;
3535
import org.apache.amoro.shade.guava32.com.google.common.collect.ImmutableSet;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
3638

3739
import java.util.Map;
3840
import java.util.Set;
3941

4042
public class TaskRuntime<T extends StagedTaskDescriptor<?, ?, ?>> extends StatedPersistentBase {
4143

44+
private static final Logger LOG = LoggerFactory.getLogger(TaskRuntime.class);
45+
4246
private final SimpleFuture future = new SimpleFuture();
4347
private final TaskStatusMachine statusMachine = new TaskStatusMachine();
4448
private OptimizingTaskId taskId;
@@ -135,7 +139,23 @@ void schedule(OptimizerThread thread) {
135139
void ack(OptimizerThread thread) {
136140
invokeConsistency(
137141
() -> {
138-
validThread(thread);
142+
// If task was reset (token cleared) due to optimizer expiry, the ack is stale.
143+
// Log a warning and skip this ack instead of throwing exception to avoid blocking
144+
// the optimizing process.
145+
if (token == null) {
146+
LOG.warn(
147+
"Ignoring stale ack for task {} because it has been reset (optimizer expired). "
148+
+ "The task should have been retried by OptimizerKeeper.",
149+
taskId);
150+
return;
151+
}
152+
153+
// For non-stale acks, validate thread match
154+
if (!thread.getToken().equals(getToken()) || thread.getThreadId() != threadId) {
155+
throw new TaskRuntimeException(
156+
"The optimizer thread does not match, the thread in the task is OptimizerThread(token=%s, threadId=%s), and the thread in the request is OptimizerThread(token=%s, threadId=%s).",
157+
getToken(), threadId, thread.getToken(), thread.getThreadId());
158+
}
139159
statusMachine.accept(Status.ACKED);
140160
persistTaskRuntime();
141161
});

amoro-ams/src/test/java/org/apache/amoro/server/TestDefaultOptimizingService.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.amoro.optimizing.TableOptimizing;
4343
import org.apache.amoro.process.ProcessStatus;
4444
import org.apache.amoro.resource.ResourceGroup;
45+
import org.apache.amoro.server.optimizing.OptimizingQueue;
4546
import org.apache.amoro.server.optimizing.OptimizingStatus;
4647
import org.apache.amoro.server.optimizing.TaskRuntime;
4748
import org.apache.amoro.server.persistence.SqlSessionFactoryProvider;
@@ -70,6 +71,7 @@
7071
import org.junit.runner.RunWith;
7172
import org.junit.runners.Parameterized;
7273

74+
import java.lang.reflect.Method;
7375
import java.util.ArrayList;
7476
import java.util.List;
7577
import java.util.Map;
@@ -830,4 +832,39 @@ public void run() {
830832
}
831833
}
832834
}
835+
836+
/**
837+
* Test that stale ack after task reset does not throw exception. Uses reflection to directly
838+
* reset the task via the queue, simulating the race condition where the task is reset
839+
* (token=null) but the optimizer is still registered.
840+
*/
841+
@Test
842+
public void testStaleAckAfterTaskReset() throws Exception {
843+
OptimizingTask task = optimizingService().pollTask(token, THREAD_ID);
844+
Assertions.assertNotNull(task);
845+
assertTaskStatus(TaskRuntime.Status.SCHEDULED);
846+
847+
// Reset the task via queue to simulate: task retried by OptimizerKeeper but optimizer still
848+
// alive
849+
Method getQueueByToken =
850+
DefaultOptimizingService.class.getDeclaredMethod("getQueueByToken", String.class);
851+
getQueueByToken.setAccessible(true);
852+
OptimizingQueue queue = (OptimizingQueue) getQueueByToken.invoke(optimizingService(), token);
853+
queue.collectTasks(t -> t.getTaskId().equals(task.getTaskId())).forEach(queue::retryTask);
854+
855+
assertTaskStatus(TaskRuntime.Status.PLANNED);
856+
857+
// Ack with still-valid old token → reaches TaskRuntime.ack() with token==null, gracefully
858+
// ignored
859+
optimizingService().ackTask(token, THREAD_ID, task.getTaskId());
860+
861+
assertTaskStatus(TaskRuntime.Status.PLANNED);
862+
863+
OptimizingTask task2 = optimizingService().pollTask(token, THREAD_ID);
864+
Assertions.assertEquals(task.getTaskId(), task2.getTaskId());
865+
866+
optimizingService().ackTask(token, THREAD_ID, task2.getTaskId());
867+
optimizingService().completeTask(token, buildOptimizingTaskResult(task2.getTaskId()));
868+
assertTaskCompleted(optimizingService().listTasks(defaultResourceGroup().getName()).get(0));
869+
}
833870
}

0 commit comments

Comments
 (0)