// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.util.thread; import java.util.Arrays; import java.util.Collection; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.toolchain.perf.PlatformMonitor; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.statistic.SampleStatistic; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(value = Parameterized.class) public class SchedulerTest { @Parameterized.Parameters public static Collection<Object[]> data() { Object[][] data = new Object[][]{ {new TimerScheduler()}, {new ScheduledExecutorScheduler()}/*, {new ConcurrentScheduler(0)}, {new ConcurrentScheduler(1500)}, {new ConcurrentScheduler(executor,1500)}*/ }; return Arrays.asList(data); } private Scheduler _scheduler; public SchedulerTest(Scheduler scheduler) { _scheduler=scheduler; } @Before public void before() throws Exception { _scheduler.start(); } @After public void after() throws Exception { _scheduler.stop(); } @Test public void testExecution() throws Exception { final AtomicLong executed = new AtomicLong(); long expected=System.currentTimeMillis()+1000; Scheduler.Task task=_scheduler.schedule(new Runnable() { @Override public void run() { executed.set(System.currentTimeMillis()); } },1000,TimeUnit.MILLISECONDS); Thread.sleep(1500); Assert.assertFalse(task.cancel()); Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected)); Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L)); } @Test public void testTwoExecution() throws Exception { final AtomicLong executed = new AtomicLong(); long expected=System.currentTimeMillis()+1000; Scheduler.Task task=_scheduler.schedule(new Runnable() { @Override public void run() { executed.set(System.currentTimeMillis()); } },1000,TimeUnit.MILLISECONDS); Thread.sleep(1500); Assert.assertFalse(task.cancel()); Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected)); Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L)); final AtomicLong executed1 = new AtomicLong(); long expected1=System.currentTimeMillis()+1000; Scheduler.Task task1=_scheduler.schedule(new Runnable() { @Override public void run() { executed1.set(System.currentTimeMillis()); } },1000,TimeUnit.MILLISECONDS); Thread.sleep(1500); Assert.assertFalse(task1.cancel()); Assert.assertThat(executed1.get(),Matchers.greaterThanOrEqualTo(expected1)); Assert.assertThat(expected1-executed1.get(),Matchers.lessThan(1000L)); } @Test public void testQuickCancel() throws Exception { final AtomicLong executed = new AtomicLong(); Scheduler.Task task=_scheduler.schedule(new Runnable() { @Override public void run() { executed.set(System.currentTimeMillis()); } },2000,TimeUnit.MILLISECONDS); Thread.sleep(100); Assert.assertTrue(task.cancel()); Thread.sleep(2500); Assert.assertEquals(0,executed.get()); } @Test public void testLongCancel() throws Exception { final AtomicLong executed = new AtomicLong(); Scheduler.Task task=_scheduler.schedule(new Runnable() { @Override public void run() { executed.set(System.currentTimeMillis()); } },2000,TimeUnit.MILLISECONDS); Thread.sleep(1600); Assert.assertTrue(task.cancel()); Thread.sleep(1000); Assert.assertEquals(0,executed.get()); } @Test public void testTaskThrowsException() throws Exception { try (StacklessLogging stackless = new StacklessLogging(TimerScheduler.class)) { long delay = 500; _scheduler.schedule(new Runnable() { @Override public void run() { throw new RuntimeException("Thrown by testTaskThrowsException"); } }, delay, TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS.sleep(2 * delay); // Check whether after a task throwing an exception, the scheduler is still working final CountDownLatch latch = new CountDownLatch(1); _scheduler.schedule(new Runnable() { @Override public void run() { latch.countDown(); } }, delay, TimeUnit.MILLISECONDS); Assert.assertTrue(latch.await(2 * delay, TimeUnit.MILLISECONDS)); } } @Test @Slow @Ignore public void testManySchedulesAndCancels() throws Exception { schedule(100,5000,3800,200); } @Test public void testFewSchedulesAndCancels() throws Exception { schedule(10,500,380,20); } @Test @Slow @Ignore public void testBenchmark() throws Exception { schedule(2000,10000,2000,50); PlatformMonitor benchmark = new PlatformMonitor(); PlatformMonitor.Start start = benchmark.start(); System.err.println(start); System.err.println(_scheduler); schedule(2000,30000,2000,50); PlatformMonitor.Stop stop = benchmark.stop(); System.err.println(stop); } private void schedule(int threads,final int duration, final int delay, final int interval) throws Exception { Thread[] test = new Thread[threads]; final AtomicInteger schedules = new AtomicInteger(); final SampleStatistic executions = new SampleStatistic(); final SampleStatistic cancellations = new SampleStatistic(); for (int i=test.length;i-->0;) { test[i]=new Thread() { @Override public void run() { try { Random random = new Random(); long now = System.currentTimeMillis(); long start=now; long end=start+duration; boolean last=false; while (!last) { final long expected=now+delay; int cancel=random.nextInt(interval); final boolean expected_to_execute; last=now+2*interval>end; if (cancel==0 || last) { expected_to_execute=true; cancel=delay+1000; } else expected_to_execute=false; schedules.incrementAndGet(); Scheduler.Task task=_scheduler.schedule(new Runnable() { @Override public void run() { long lateness=System.currentTimeMillis()-expected; if (expected_to_execute) executions.set(lateness); else executions.set(6666); } },delay,TimeUnit.MILLISECONDS); Thread.sleep(cancel); now = System.currentTimeMillis(); if (task.cancel()) { long lateness=now-expected; if (expected_to_execute) cancellations.set(lateness); else cancellations.set(0); } else { if (!expected_to_execute) { cancellations.set(9999); } } Thread.yield(); } } catch (InterruptedException e) { e.printStackTrace(); } } }; } for (Thread thread : test) thread.start(); for (Thread thread : test) thread.join(); // there were some executions and cancellations Assert.assertThat(executions.getCount(),Matchers.greaterThan(0L)); Assert.assertThat(cancellations.getCount(),Matchers.greaterThan(0L)); // All executed or cancelled // Not that SimpleScheduler can execute and cancel an event! Assert.assertThat(0L+schedules.get(),Matchers.lessThanOrEqualTo(executions.getCount()+cancellations.getCount())); // No really late executions Assert.assertThat(executions.getMax(),Matchers.lessThan(500L)); // Executions on average are close to the expected time Assert.assertThat(executions.getMean(),Matchers.lessThan(500.0)); // No cancellations long after expected executions Assert.assertThat(cancellations.getMax(),Matchers.lessThan(500L)); } }