@Override public JobHandle schedule( Group group, final Runnable runnable, long initialDelay, TimeUnit unit ) { return scheduler.submit( group, runnable, unit.toNanos( initialDelay ), 0 ); }
@Override public JobHandle scheduleRecurring( Group group, Runnable runnable, long initialDelay, long period, TimeUnit unit ) { return scheduler.submit( group, runnable, unit.toNanos( initialDelay ), unit.toNanos( period ) ); }
@Test public void mustOnlyScheduleTasksThatAreDue() throws Exception { JobHandle handle1 = scheduler.submit( Group.STORAGE_MAINTENANCE, () -> counter.addAndGet( 10 ), 100, 0 ); JobHandle handle2 = scheduler.submit( Group.STORAGE_MAINTENANCE, () -> counter.addAndGet( 100 ), 200, 0 ); scheduler.tick(); assertThat( counter.get(), is( 0 ) ); clock.forward( 199, TimeUnit.NANOSECONDS ); scheduler.tick(); handle1.waitTermination(); assertThat( counter.get(), is( 10 ) ); clock.forward( 1, TimeUnit.NANOSECONDS ); scheduler.tick(); handle2.waitTermination(); assertThat( counter.get(), is( 110 ) ); }
@Test public void mustNotStartRecurringTasksWherePriorExecutionHasNotYetFinished() { Runnable runnable = () -> { counter.incrementAndGet(); semaphore.acquireUninterruptibly(); }; scheduler.submit( Group.STORAGE_MAINTENANCE, runnable, 100, 100 ); for ( int i = 0; i < 4; i++ ) { scheduler.tick(); clock.forward( 100, TimeUnit.NANOSECONDS ); } semaphore.release( Integer.MAX_VALUE ); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 1 ) ); }
@Test public void longRunningTasksMustNotDelayExecutionOfOtherTasks() throws Exception { BinaryLatch latch = new BinaryLatch(); Runnable longRunning = latch::await; Runnable shortRunning = semaphore::release; scheduler.submit( Group.STORAGE_MAINTENANCE, longRunning, 100, 100 ); scheduler.submit( Group.STORAGE_MAINTENANCE, shortRunning, 100, 100 ); for ( int i = 0; i < 4; i++ ) { clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); } latch.release(); }
@Test public void mustDelayExecution() throws Exception { JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, counter::incrementAndGet, 100, 0 ); scheduler.tick(); assertThat( counter.get(), is( 0 ) ); clock.forward( 99, TimeUnit.NANOSECONDS ); scheduler.tick(); assertThat( counter.get(), is( 0 ) ); clock.forward( 1, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.waitTermination(); assertThat( counter.get(), is( 1 ) ); }
semaphore.acquireUninterruptibly(); }; JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, recurring, 100, 100 ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick();
@Test public void mustNotRescheduleDelayedTasks() throws Exception { JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, counter::incrementAndGet, 100, 0 ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.waitTermination(); assertThat( counter.get(), is( 1 ) ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.waitTermination(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 1 ) ); }
@Test public void mustNotRescheduleRecurringTasksThatThrows() throws Exception { Runnable runnable = () -> { semaphore.release(); throw new RuntimeException( "boom" ); }; JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, runnable, 100, 100 ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); try { handle.waitTermination(); fail( "waitTermination should have thrown because the task should have failed." ); } catch ( ExecutionException e ) { assertThat( e.getCause().getMessage(), is( "boom" ) ); } assertThat( semaphore.drainPermits(), is( 0 ) ); }
@Test public void delayedTasksMustNotRunIfCancelledFirst() throws Exception { List<Boolean> cancelListener = new ArrayList<>(); JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, counter::incrementAndGet, 100, 0 ); handle.registerCancelListener( cancelListener::add ); clock.forward( 90, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.cancel( false ); clock.forward( 10, TimeUnit.NANOSECONDS ); scheduler.tick(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 0 ) ); assertThat( cancelListener, contains( Boolean.FALSE ) ); try { handle.waitTermination(); fail( "waitTermination should have thrown a CancellationException." ); } catch ( CancellationException ignore ) { // Good stuff. } }
@Test public void mustRescheduleRecurringTasks() throws Exception { scheduler.submit( Group.STORAGE_MAINTENANCE, semaphore::release, 100, 100 ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); }
@Test public void recurringTasksMustStopWhenCancelled() throws InterruptedException { List<Boolean> cancelListener = new ArrayList<>(); Runnable recurring = () -> { counter.incrementAndGet(); semaphore.release(); }; JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, recurring, 100, 100 ); handle.registerCancelListener( cancelListener::add ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); handle.cancel( true ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 2 ) ); assertThat( cancelListener, contains( Boolean.TRUE ) ); }
@Override public JobHandle schedule( Group group, final Runnable runnable, long initialDelay, TimeUnit unit ) { return scheduler.submit( group, runnable, unit.toNanos( initialDelay ), 0 ); }
@Override public JobHandle scheduleRecurring( Group group, Runnable runnable, long initialDelay, long period, TimeUnit unit ) { return scheduler.submit( group, runnable, unit.toNanos( initialDelay ), unit.toNanos( period ) ); }