| // |
| // ======================================================================== |
| // 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)); |
| } |
| } |