package com.netflix.discovery; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class TimedSupervisorTaskTest { private static final int EXP_BACK_OFF_BOUND = 10; private ScheduledExecutorService scheduler; private ListeningExecutorService helperExecutor; private ThreadPoolExecutor executor; private AtomicLong testTaskStartCounter; private AtomicLong testTaskSuccessfulCounter; private AtomicLong testTaskInterruptedCounter; private AtomicLong maxConcurrentTestTasks; private AtomicLong testTaskCounter; @Before public void setUp() { scheduler = Executors.newScheduledThreadPool(4, new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-%d") .setDaemon(true) .build()); helperExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); executor = new ThreadPoolExecutor( 1, // corePoolSize 3, // maxPoolSize 0, // keepAliveTime TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); // use direct handoff testTaskStartCounter = new AtomicLong(0); testTaskSuccessfulCounter = new AtomicLong(0); testTaskInterruptedCounter = new AtomicLong(0); maxConcurrentTestTasks = new AtomicLong(0); testTaskCounter = new AtomicLong(0); } @After public void tearDown() { if (executor != null) { executor.shutdownNow(); } if (helperExecutor != null) { helperExecutor.shutdownNow(); } if (scheduler != null) { scheduler.shutdownNow(); } } @Test public void testSupervisorTaskDefaultSingleTestTaskHappyCase() throws Exception { // testTask should never timeout TestTask testTask = new TestTask(1, false); TimedSupervisorTask supervisorTask = new TimedSupervisorTask("test", scheduler, executor, 5, TimeUnit.SECONDS, EXP_BACK_OFF_BOUND, testTask); helperExecutor.submit(supervisorTask).get(); Assert.assertEquals(1, maxConcurrentTestTasks.get()); Assert.assertEquals(0, testTaskCounter.get()); Assert.assertEquals(1, testTaskStartCounter.get()); Assert.assertEquals(1, testTaskSuccessfulCounter.get()); Assert.assertEquals(0, testTaskInterruptedCounter.get()); } @Test public void testSupervisorTaskCancelsTimedOutTask() throws Exception { // testTask will always timeout TestTask testTask = new TestTask(5, false); TimedSupervisorTask supervisorTask = new TimedSupervisorTask("test", scheduler, executor, 1, TimeUnit.SECONDS, EXP_BACK_OFF_BOUND, testTask); helperExecutor.submit(supervisorTask).get(); Thread.sleep(500); // wait a little bit for the subtask interrupt handler Assert.assertEquals(1, maxConcurrentTestTasks.get()); Assert.assertEquals(1, testTaskCounter.get()); Assert.assertEquals(1, testTaskStartCounter.get()); Assert.assertEquals(0, testTaskSuccessfulCounter.get()); Assert.assertEquals(1, testTaskInterruptedCounter.get()); } @Test public void testSupervisorRejectNewTasksIfThreadPoolIsFullForIncompleteTasks() throws Exception { // testTask should always timeout TestTask testTask = new TestTask(4, true); TimedSupervisorTask supervisorTask = new TimedSupervisorTask("test", scheduler, executor, 1, TimeUnit.MILLISECONDS, EXP_BACK_OFF_BOUND, testTask); scheduler.schedule(supervisorTask, 0, TimeUnit.SECONDS); Thread.sleep(500); // wait a little bit for the subtask interrupt handlers Assert.assertEquals(3, maxConcurrentTestTasks.get()); Assert.assertEquals(3, testTaskCounter.get()); Assert.assertEquals(3, testTaskStartCounter.get()); Assert.assertEquals(0, testTaskSuccessfulCounter.get()); Assert.assertEquals(0, testTaskInterruptedCounter.get()); } @Test public void testSupervisorTaskAsPeriodicScheduledJobHappyCase() throws Exception { // testTask should never timeout TestTask testTask = new TestTask(1, false); TimedSupervisorTask supervisorTask = new TimedSupervisorTask("test", scheduler, executor, 4, TimeUnit.SECONDS, EXP_BACK_OFF_BOUND, testTask); scheduler.schedule(supervisorTask, 0, TimeUnit.SECONDS); Thread.sleep(5000); // let the scheduler run for long enough for some results Assert.assertEquals(1, maxConcurrentTestTasks.get()); Assert.assertEquals(0, testTaskCounter.get()); Assert.assertEquals(0, testTaskInterruptedCounter.get()); } @Test public void testSupervisorTaskAsPeriodicScheduledJobTestTaskTimingOut() throws Exception { // testTask should always timeout TestTask testTask = new TestTask(5, false); TimedSupervisorTask supervisorTask = new TimedSupervisorTask("test", scheduler, executor, 2, TimeUnit.SECONDS, EXP_BACK_OFF_BOUND, testTask); scheduler.schedule(supervisorTask, 0, TimeUnit.SECONDS); Thread.sleep(5000); // let the scheduler run for long enough for some results Assert.assertEquals(1, maxConcurrentTestTasks.get()); Assert.assertTrue(0 != testTaskCounter.get()); // tasks are been cancelled Assert.assertEquals(0, testTaskSuccessfulCounter.get()); } private class TestTask implements Runnable { private final int runTimeSecs; private final boolean blockInterrupt; public TestTask(int runTimeSecs, boolean blockInterrupt) { this.runTimeSecs = runTimeSecs; this.blockInterrupt = blockInterrupt; } public void run() { testTaskStartCounter.incrementAndGet(); try { testTaskCounter.incrementAndGet(); synchronized (maxConcurrentTestTasks) { int activeCount = executor.getActiveCount(); if (maxConcurrentTestTasks.get() < activeCount) { maxConcurrentTestTasks.set(activeCount); } } long endTime = System.currentTimeMillis() + runTimeSecs * 1000; while (endTime >= System.currentTimeMillis()) { try { Thread.sleep(runTimeSecs * 1000); } catch (InterruptedException e) { if (!blockInterrupt) { throw e; } } } testTaskCounter.decrementAndGet(); testTaskSuccessfulCounter.incrementAndGet(); } catch (InterruptedException e) { testTaskInterruptedCounter.incrementAndGet(); } } } }