394215 - Scheduled tasks throwing exceptions kill java.util.Timer thread.
Wrapping the the task run() method into a try/catch to avoid that the Timer thread dies.
This commit is contained in:
parent
a37f9c813d
commit
c07bab8a05
|
@ -24,9 +24,13 @@ import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
public class TimerScheduler extends AbstractLifeCycle implements Scheduler
|
public class TimerScheduler extends AbstractLifeCycle implements Scheduler
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(TimerScheduler.class);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class uses the Timer class rather than an ScheduledExecutionService because
|
* This class uses the Timer class rather than an ScheduledExecutionService because
|
||||||
* it uses the same algorithm internally and the signature is cheaper to use as there are no
|
* it uses the same algorithm internally and the signature is cheaper to use as there are no
|
||||||
|
@ -85,7 +89,14 @@ public class TimerScheduler extends AbstractLifeCycle implements Scheduler
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
_task.run();
|
try
|
||||||
|
{
|
||||||
|
_task.run();
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
LOG.debug("Exception while executing task "+_task,x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.util.thread;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -45,7 +46,7 @@ public class SchedulerTest
|
||||||
{
|
{
|
||||||
private static final BenchmarkHelper benchmark = new BenchmarkHelper();
|
private static final BenchmarkHelper benchmark = new BenchmarkHelper();
|
||||||
private static final Executor executor = Executors.newFixedThreadPool(256);
|
private static final Executor executor = Executors.newFixedThreadPool(256);
|
||||||
|
|
||||||
@Parameterized.Parameters
|
@Parameterized.Parameters
|
||||||
public static Collection<Object[]> data()
|
public static Collection<Object[]> data()
|
||||||
{
|
{
|
||||||
|
@ -58,27 +59,27 @@ public class SchedulerTest
|
||||||
};
|
};
|
||||||
return Arrays.asList(data);
|
return Arrays.asList(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheduler _scheduler=new SimpleScheduler();
|
// Scheduler _scheduler=new SimpleScheduler();
|
||||||
Scheduler _scheduler;
|
Scheduler _scheduler;
|
||||||
|
|
||||||
public SchedulerTest(Scheduler scheduler)
|
public SchedulerTest(Scheduler scheduler)
|
||||||
{
|
{
|
||||||
_scheduler=scheduler;
|
_scheduler=scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception
|
public void before() throws Exception
|
||||||
{
|
{
|
||||||
_scheduler.start();
|
_scheduler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() throws Exception
|
public void after() throws Exception
|
||||||
{
|
{
|
||||||
_scheduler.stop();
|
_scheduler.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExecution() throws Exception
|
public void testExecution() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -92,14 +93,14 @@ public class SchedulerTest
|
||||||
executed.set(System.currentTimeMillis());
|
executed.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
},3000,TimeUnit.MILLISECONDS);
|
},3000,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(4000);
|
Thread.sleep(4000);
|
||||||
Assert.assertFalse(task.cancel());
|
Assert.assertFalse(task.cancel());
|
||||||
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
||||||
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTwoExecution() throws Exception
|
public void testTwoExecution() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -113,12 +114,12 @@ public class SchedulerTest
|
||||||
executed.set(System.currentTimeMillis());
|
executed.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
},3000,TimeUnit.MILLISECONDS);
|
},3000,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(4000);
|
Thread.sleep(4000);
|
||||||
Assert.assertFalse(task.cancel());
|
Assert.assertFalse(task.cancel());
|
||||||
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
||||||
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
||||||
|
|
||||||
final AtomicLong executed1 = new AtomicLong();
|
final AtomicLong executed1 = new AtomicLong();
|
||||||
long expected1=System.currentTimeMillis()+3000;
|
long expected1=System.currentTimeMillis()+3000;
|
||||||
Scheduler.Task task1=_scheduler.schedule(new Runnable()
|
Scheduler.Task task1=_scheduler.schedule(new Runnable()
|
||||||
|
@ -129,13 +130,13 @@ public class SchedulerTest
|
||||||
executed1.set(System.currentTimeMillis());
|
executed1.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
},3000,TimeUnit.MILLISECONDS);
|
},3000,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(4000);
|
Thread.sleep(4000);
|
||||||
Assert.assertFalse(task1.cancel());
|
Assert.assertFalse(task1.cancel());
|
||||||
Assert.assertThat(executed1.get(),Matchers.greaterThanOrEqualTo(expected1));
|
Assert.assertThat(executed1.get(),Matchers.greaterThanOrEqualTo(expected1));
|
||||||
Assert.assertThat(expected1-executed1.get(),Matchers.lessThan(1000L));
|
Assert.assertThat(expected1-executed1.get(),Matchers.lessThan(1000L));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQuickCancel() throws Exception
|
public void testQuickCancel() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -148,13 +149,13 @@ public class SchedulerTest
|
||||||
executed.set(System.currentTimeMillis());
|
executed.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
},2000,TimeUnit.MILLISECONDS);
|
},2000,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
Assert.assertTrue(task.cancel());
|
Assert.assertTrue(task.cancel());
|
||||||
Thread.sleep(2500);
|
Thread.sleep(2500);
|
||||||
Assert.assertEquals(0,executed.get());
|
Assert.assertEquals(0,executed.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLongCancel() throws Exception
|
public void testLongCancel() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -167,20 +168,50 @@ public class SchedulerTest
|
||||||
executed.set(System.currentTimeMillis());
|
executed.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
},2000,TimeUnit.MILLISECONDS);
|
},2000,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(1600);
|
Thread.sleep(1600);
|
||||||
Assert.assertTrue(task.cancel());
|
Assert.assertTrue(task.cancel());
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
Assert.assertEquals(0,executed.get());
|
Assert.assertEquals(0,executed.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTaskThrowsException() throws Exception
|
||||||
|
{
|
||||||
|
long delay = 500;
|
||||||
|
Scheduler.Task task=_scheduler.schedule(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}, 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
|
@Test
|
||||||
@Slow
|
@Slow
|
||||||
public void testManySchedulesAndCancels() throws Exception
|
public void testManySchedulesAndCancels() throws Exception
|
||||||
{
|
{
|
||||||
schedule(100,5000,3800,200);
|
schedule(100,5000,3800,200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Slow
|
@Slow
|
||||||
@Ignore
|
@Ignore
|
||||||
|
@ -192,16 +223,16 @@ public class SchedulerTest
|
||||||
schedule(2000,30000,2000,50);
|
schedule(2000,30000,2000,50);
|
||||||
benchmark.stopStatistics();
|
benchmark.stopStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void schedule(int threads,final int duration, final int delay, final int interval) throws Exception
|
private void schedule(int threads,final int duration, final int delay, final int interval) throws Exception
|
||||||
{
|
{
|
||||||
final Random random = new Random(1);
|
final Random random = new Random(1);
|
||||||
Thread[] test = new Thread[threads];
|
Thread[] test = new Thread[threads];
|
||||||
|
|
||||||
final AtomicInteger schedules = new AtomicInteger();
|
final AtomicInteger schedules = new AtomicInteger();
|
||||||
final SampleStatistic executions = new SampleStatistic();
|
final SampleStatistic executions = new SampleStatistic();
|
||||||
final SampleStatistic cancellations = new SampleStatistic();
|
final SampleStatistic cancellations = new SampleStatistic();
|
||||||
|
|
||||||
for (int i=test.length;i-->0;)
|
for (int i=test.length;i-->0;)
|
||||||
{
|
{
|
||||||
test[i]=new Thread()
|
test[i]=new Thread()
|
||||||
|
@ -220,7 +251,7 @@ public class SchedulerTest
|
||||||
final long expected=now+delay;
|
final long expected=now+delay;
|
||||||
int cancel=random.nextInt(interval);
|
int cancel=random.nextInt(interval);
|
||||||
final boolean expected_to_execute;
|
final boolean expected_to_execute;
|
||||||
|
|
||||||
last=now+2*interval>end;
|
last=now+2*interval>end;
|
||||||
if (cancel==0 || last)
|
if (cancel==0 || last)
|
||||||
{
|
{
|
||||||
|
@ -236,15 +267,15 @@ public class SchedulerTest
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
long lateness=System.currentTimeMillis()-expected;
|
long lateness=System.currentTimeMillis()-expected;
|
||||||
if (expected_to_execute)
|
if (expected_to_execute)
|
||||||
executions.set(lateness);
|
executions.set(lateness);
|
||||||
else
|
else
|
||||||
executions.set(6666);
|
executions.set(6666);
|
||||||
|
|
||||||
}
|
}
|
||||||
},delay,TimeUnit.MILLISECONDS);
|
},delay,TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
Thread.sleep(cancel);
|
Thread.sleep(cancel);
|
||||||
now = System.currentTimeMillis();
|
now = System.currentTimeMillis();
|
||||||
if (task.cancel())
|
if (task.cancel())
|
||||||
|
@ -255,7 +286,7 @@ public class SchedulerTest
|
||||||
else
|
else
|
||||||
cancellations.set(0);
|
cancellations.set(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!expected_to_execute)
|
if (!expected_to_execute)
|
||||||
{
|
{
|
||||||
|
@ -270,17 +301,17 @@ public class SchedulerTest
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Thread thread : test)
|
for (Thread thread : test)
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
for (Thread thread : test)
|
for (Thread thread : test)
|
||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
// System.err.println(schedules);
|
// System.err.println(schedules);
|
||||||
// System.err.println(executions);
|
// System.err.println(executions);
|
||||||
// System.err.println(cancellations);
|
// System.err.println(cancellations);
|
||||||
|
@ -292,17 +323,17 @@ public class SchedulerTest
|
||||||
// All executed or cancelled
|
// All executed or cancelled
|
||||||
// Not that SimpleScheduler can execute and cancel an event!
|
// Not that SimpleScheduler can execute and cancel an event!
|
||||||
Assert.assertThat(0L+schedules.get(),Matchers.lessThanOrEqualTo(executions.getCount()+cancellations.getCount()));
|
Assert.assertThat(0L+schedules.get(),Matchers.lessThanOrEqualTo(executions.getCount()+cancellations.getCount()));
|
||||||
|
|
||||||
// No really late executions
|
// No really late executions
|
||||||
Assert.assertThat(executions.getMax(),Matchers.lessThan(500L));
|
Assert.assertThat(executions.getMax(),Matchers.lessThan(500L));
|
||||||
|
|
||||||
// Executions on average are close to the expected time
|
// Executions on average are close to the expected time
|
||||||
Assert.assertThat(executions.getMean(),Matchers.lessThan(500.0));
|
Assert.assertThat(executions.getMean(),Matchers.lessThan(500.0));
|
||||||
|
|
||||||
// No cancellations long after expected executions
|
// No cancellations long after expected executions
|
||||||
Assert.assertThat(cancellations.getMax(),Matchers.lessThan(500L));
|
Assert.assertThat(cancellations.getMax(),Matchers.lessThan(500L));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue