7070import org .junit .runner .RunWith ;
7171import org .junit .runners .Parameterized ;
7272
73+ import java .time .Duration ;
7374import java .util .ArrayList ;
7475import java .util .List ;
7576import java .util .Map ;
7677
7778@ RunWith (Parameterized .class )
7879public class TestDefaultOptimizingService extends AMSTableTestBase {
7980
81+ private static final Duration EXPIRATION_TEST_HEARTBEAT_TIMEOUT = Duration .ofMillis (800 );
82+ private static final long OPTIMIZER_HEARTBEAT_INTERVAL_MS = 100 ;
83+ private static final long ASYNC_WAIT_TIMEOUT_MS = 10000 ;
84+
8085 private final int THREAD_ID = 0 ;
8186 private String token ;
8287 private Toucher toucher ;
88+ private boolean customHeartbeatTimeout ;
8389
8490 @ Parameterized .Parameters (name = "{0}, {1}" )
8591 public static Object [] parameters () {
@@ -121,6 +127,12 @@ public void clear() {
121127 dropDatabase ();
122128 } catch (Exception e ) {
123129 // ignore
130+ } finally {
131+ if (customHeartbeatTimeout ) {
132+ disposeTableService ();
133+ initTableService ();
134+ customHeartbeatTimeout = false ;
135+ }
124136 }
125137 }
126138
@@ -255,20 +267,22 @@ public void testTouch() throws InterruptedException {
255267
256268 @ Test
257269 public void testTouchTimeout () throws InterruptedException {
270+ rebootWithHeartbeatTimeout (EXPIRATION_TEST_HEARTBEAT_TIMEOUT );
258271 OptimizingTask task = optimizingService ().pollTask (token , THREAD_ID );
259272 Assertions .assertNotNull (task );
273+ String expiredToken = token ;
260274 toucher .stop ();
261275 toucher = null ;
262- Thread .sleep (1000 );
263- Assertions .assertThrows (PluginRetryAuthException .class , () -> optimizingService ().touch (token ));
276+ waitForOptimizerExpiration (expiredToken , ASYNC_WAIT_TIMEOUT_MS );
264277 Assertions .assertThrows (
265- PluginRetryAuthException .class , () -> optimizingService ().pollTask (token , THREAD_ID ));
278+ PluginRetryAuthException .class , () -> optimizingService ().touch (expiredToken ));
279+ Assertions .assertThrows (
280+ PluginRetryAuthException .class ,
281+ () -> optimizingService ().pollTask (expiredToken , THREAD_ID ));
266282 // After optimizer expires, its tasks are immediately reset to PLANNED
267283 // because unregister happens before task scan in OptimizerKeeper
268- assertTaskStatus (TaskRuntime .Status .PLANNED );
269- token = optimizingService ().authenticate (buildRegisterInfo ());
284+ waitForTaskStatus (TaskRuntime .Status .PLANNED , ASYNC_WAIT_TIMEOUT_MS );
270285 toucher = new Toucher ();
271- Thread .sleep (1000 );
272286 assertTaskStatus (TaskRuntime .Status .PLANNED );
273287 OptimizingTask task2 = optimizingService ().pollTask (token , THREAD_ID );
274288 Assertions .assertEquals (task2 .getTaskId (), task .getTaskId ());
@@ -283,11 +297,10 @@ public void testTouchTimeout() throws InterruptedException {
283297 public void testRebootAndPoll () throws InterruptedException {
284298 OptimizingTask task = optimizingService ().pollTask (token , THREAD_ID );
285299 Assertions .assertNotNull (task );
286- reboot ( );
300+ rebootWithHeartbeatTimeout ( EXPIRATION_TEST_HEARTBEAT_TIMEOUT );
287301
288302 // wait for last optimizer expiring
289- Thread .sleep (1000 );
290- assertTaskStatus (TaskRuntime .Status .PLANNED );
303+ waitForTaskStatus (TaskRuntime .Status .PLANNED , ASYNC_WAIT_TIMEOUT_MS );
291304 OptimizingTask task2 = optimizingService ().pollTask (token , THREAD_ID );
292305 Assertions .assertNotNull (task2 );
293306 Assertions .assertEquals (task2 .getTaskId (), task .getTaskId ());
@@ -329,10 +342,10 @@ public void testAckTimeoutResetThenLateAckRejected() throws InterruptedException
329342 Assertions .assertNotNull (task );
330343 assertTaskStatus (TaskRuntime .Status .SCHEDULED ); // polled but NOT acked
331344
332- // the optimizer stays alive (Toucher touches every 300ms) , so waiting past the ack timeout hits
345+ // the optimizer stays alive, so waiting past the ack timeout hits
333346 // the SCHEDULED + ackTimeout branch rather than the optimizer-expired branch: the keeper resets
334347 // the task out from under the live optimizer
335- waitForTaskStatus (TaskRuntime .Status .PLANNED , 10000 );
348+ waitForTaskStatus (TaskRuntime .Status .PLANNED , 20000 );
336349
337350 // the delayed ack arrives for the now-reset task -> rejected, exactly like the issue
338351 Assertions .assertThrows (
@@ -351,11 +364,8 @@ public void testExecuteTaskTimeOutAndRetry() throws InterruptedException {
351364 optimizingService ().listTasks (defaultResourceGroup ().getName ()).get (0 );
352365 assertTaskStatus (TaskRuntime .Status .ACKED );
353366
354- // In this test, OPTIMIZER_TASK_EXECUTE_TIMEOUT is set to 30 seconds, so after waiting 45
355- // seconds the task will be considered suspended and retried
356- Thread .sleep (45000 );
357-
358- assertTaskStatus (TaskRuntime .Status .PLANNED );
367+ // In this test, OPTIMIZER_TASK_EXECUTE_TIMEOUT is set to 30 seconds.
368+ waitForTaskStatus (TaskRuntime .Status .PLANNED , 60000 );
359369 OptimizingTask task2 = optimizingService ().pollTask (token , THREAD_ID );
360370 Assertions .assertNotNull (task2 );
361371 Assertions .assertEquals (task2 .getTaskId (), task .getTaskId ());
@@ -706,7 +716,9 @@ public void testHandleConfigChangedGroupNotExist() {
706716 private OptimizerRegisterInfo buildRegisterInfo () {
707717 OptimizerRegisterInfo registerInfo = new OptimizerRegisterInfo ();
708718 Map <String , String > registerProperties = Maps .newHashMap ();
709- registerProperties .put (OptimizerProperties .OPTIMIZER_HEART_BEAT_INTERVAL , "100" );
719+ registerProperties .put (
720+ OptimizerProperties .OPTIMIZER_HEART_BEAT_INTERVAL ,
721+ String .valueOf (OPTIMIZER_HEARTBEAT_INTERVAL_MS ));
710722 registerInfo .setProperties (registerProperties );
711723 registerInfo .setThreadCount (1 );
712724 registerInfo .setMemoryMb (1024 );
@@ -785,6 +797,23 @@ private void waitForTaskStatus(TaskRuntime.Status expectedStatus, long timeoutMs
785797 assertTaskStatus (expectedStatus );
786798 }
787799
800+ private void waitForOptimizerExpiration (String optimizerToken , long timeoutMs )
801+ throws InterruptedException {
802+ long deadline = System .currentTimeMillis () + timeoutMs ;
803+ while (System .currentTimeMillis () < deadline ) {
804+ boolean optimizerExists =
805+ optimizerManager ().listOptimizers ().stream ()
806+ .anyMatch (optimizer -> optimizerToken .equals (optimizer .getToken ()));
807+ boolean optimizerAuthenticated =
808+ optimizingService ().getTotalQuota (defaultResourceGroup ().getName ()) > 0 ;
809+ if (!optimizerExists && !optimizerAuthenticated ) {
810+ return ;
811+ }
812+ Thread .sleep (100 );
813+ }
814+ Assertions .fail ("Optimizer did not expire within " + timeoutMs + " ms" );
815+ }
816+
788817 private void assertTaskCompleted (TaskRuntime <?> taskRuntime ) {
789818 if (taskRuntime != null ) {
790819 Assertions .assertEquals (TaskRuntime .Status .SUCCESS , taskRuntime .getStatus ());
@@ -798,17 +827,18 @@ private void assertTaskCompleted(TaskRuntime<?> taskRuntime) {
798827 }
799828
800829 protected void reload () {
801- disposeTableService ();
802830 toucher .suspend ();
831+ disposeTableService ();
803832 initTableService ();
804833 toucher .goOn ();
805834 }
806835
807- protected void reboot () throws InterruptedException {
808- disposeTableService ();
836+ protected void rebootWithHeartbeatTimeout (Duration heartbeatTimeout ) throws InterruptedException {
809837 toucher .stop ();
810838 toucher = null ;
811- initTableService ();
839+ disposeTableService ();
840+ customHeartbeatTimeout = true ;
841+ initTableService (heartbeatTimeout );
812842 toucher = new Toucher ();
813843 }
814844
@@ -855,7 +885,7 @@ public synchronized void goOn() {
855885 public void run () {
856886 while (!stop ) {
857887 try {
858- Thread .sleep (300 );
888+ Thread .sleep (OPTIMIZER_HEARTBEAT_INTERVAL_MS );
859889 synchronized (this ) {
860890 if (!suspend ) {
861891 optimizingService ().touch (token );
0 commit comments