// Copyright (c) 2006 Dustin Sallings <dustin@spy.net> package net.spy.concurrent; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; /** * Test out the ol' rescheduler. */ public class ReschedulerTest extends TestCase { private Rescheduler schedInline=null; private Rescheduler schedPooled=null; @Override protected void setUp() throws Exception { super.setUp(); schedInline=new Rescheduler(Executors.newSingleThreadScheduledExecutor()); schedPooled=new Rescheduler(new ScheduledThreadPoolExecutor(3)); } @Override protected void tearDown() throws Exception { super.tearDown(); schedInline.shutdown(); assertTrue(schedInline.awaitTermination( Long.MAX_VALUE, TimeUnit.MILLISECONDS)); schedPooled.shutdown(); assertTrue(schedPooled.awaitTermination( Long.MAX_VALUE, TimeUnit.MILLISECONDS)); // Note: This is always true because the inline scheduler never says no assertTrue(schedInline.isShutdown()); assertTrue(schedPooled.isShutdown()); } // Plain runnable (no special handling) public void testBasicRunnable() { TestRunnable tr=new TestRunnable(); assertEquals(0, tr.runs); schedInline.execute(tr); while(tr.runs < 1) { Thread.yield(); } assertEquals(1, tr.runs); } // Plain runnable with value (no special handling) public void testBasicRunnableWithValue() throws Exception { TestRunnable tr=new TestRunnable(); assertEquals(0, tr.runs); Future<String> f=schedInline.submit(tr, "X"); assertEquals("X", f.get()); assertEquals(1, tr.runs); } // Plain callable (no special handling) public void testBasicCallable() throws Exception { TestCallable<String> tc=new TestCallable<String>("X"); assertEquals(0, tc.runs); Future<String> f=schedInline.submit(tc); assertEquals("X", f.get()); assertEquals(1, tc.runs); } // Plain callable (no special handling) public void testBasicScheduledCallable() throws Exception { TestCallable<String> tc=new TestCallable<String>("X"); assertEquals(0, tc.runs); Future<String> f=schedInline.schedule(tc, 10, TimeUnit.MILLISECONDS); assertEquals("X", f.get()); assertEquals(1, tc.runs); } // A few retries and then success public void testRetryableCallable() throws Exception { TestRtryCallable<String> tc=new TestRtryCallable<String>("X", 2, 3); assertEquals(0, tc.runs); assertEquals(0, tc.retries); Future<String> f=schedPooled.submit(tc); assertEquals("X", f.get()); assertTrue(f.isDone()); assertFalse(f.isCancelled()); assertFalse(tc.gaveUp); assertEquals("X", tc.result); assertEquals(3, tc.runs); assertEquals(2, tc.retries); } public void testScheduledFutureSorting() throws Exception { TestRtryCallable<String> tc=new TestRtryCallable<String>("X", 2, 3); ScheduledFuture<String> f1=schedPooled.schedule( tc, 10, TimeUnit.MILLISECONDS); ScheduledFuture<String> f2=schedPooled.schedule( tc, 100, TimeUnit.MILLISECONDS); assertTrue(f1.compareTo(f2) < 0); } public void testShutdownNow() throws Exception { TestRtryCallable<String> tc=new TestRtryCallable<String>("X", 2, 3); schedPooled.schedule(tc, 1, TimeUnit.SECONDS); schedPooled.schedule(tc, 2, TimeUnit.SECONDS); List<Runnable> l=schedPooled.shutdownNow(); assertEquals(2, l.size()); Thread.sleep(100); assertTrue(schedPooled.isTerminated()); } public void testFixedRateRunnable() throws Exception { TestRunnable r=new TestRunnable(); ScheduledFuture<?> f=schedPooled.scheduleAtFixedRate(r, 10, 10, TimeUnit.MILLISECONDS); assertFalse(f.isDone()); assertEquals(0, r.runs); Thread.sleep(20); assertTrue(r.runs > 0); assertFalse(f.isCancelled()); f.cancel(true); assertTrue(f.isCancelled()); } public void testFixedDelayRunnable() throws Exception { TestRunnable r=new TestRunnable(); ScheduledFuture<?> f=schedPooled.scheduleWithFixedDelay(r, 10, 10, TimeUnit.MILLISECONDS); assertFalse(f.isDone()); assertEquals(0, r.runs); Thread.sleep(20); assertTrue(r.runs > 0); assertFalse(f.isCancelled()); f.cancel(true); assertTrue(f.isCancelled()); } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAll() throws Exception { Collection callables=Arrays.asList( new TestCallable<String>("A"), new TestCallable<String>("B"), new TestCallable<String>("C")); List<String> expected=new ArrayList<String>(); expected.addAll(Arrays.asList("A", "B", "C")); List<String> got=new ArrayList<String>(); List<Future<String>> res=schedInline.invokeAll(callables); for(Future<String> f : res) { got.add(f.get()); } assertEquals(expected, got); } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAllWithRetry() throws Exception { Collection callables=Arrays.asList( new TestRtryCallable<String>("A", 2, 3), new TestRtryCallable<String>("B", 2, 3), new TestRtryCallable<String>("C", 2, 3)); List<String> expected=new ArrayList<String>(); expected.addAll(Arrays.asList("A", "B", "C")); List<String> got=new ArrayList<String>(); List<Future<String>> res=schedPooled.invokeAll(callables); for(Future<String> f : res) { got.add(f.get()); } assertEquals(expected, got); } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAllWithRetryTimeout() throws Exception { Collection callables=Arrays.asList( new TestRtryCallable<String>("A", 1, 9, 10), new TestRtryCallable<String>("B", 5, 9, 100), new TestRtryCallable<String>("C", 7, 9, 1000)); // Only one of these should finish. List<String> expected=new ArrayList<String>(); expected.addAll(Arrays.asList("A")); List<String> got=new ArrayList<String>(); List<Future<String>> res=schedPooled.invokeAll(callables, 20, TimeUnit.MILLISECONDS); for(Future<String> f : res) { if(f.isDone()) { got.add(f.get()); } } assertEquals(expected, got); } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAllTimeout() throws Exception { Collection callables=Arrays.asList( new TestCallable<String>("A"), new TestCallable<String>("B"), new TestCallable<String>("C")); Set<String> expected=new HashSet<String>(); expected.addAll(Arrays.asList("A", "B", "C")); Set<String> got=new HashSet<String>(); List<Future<String>> res=schedInline.invokeAll(callables, 10, TimeUnit.MILLISECONDS); for(Future<String> f : res) { got.add(f.get()); } assertEquals(expected, got); } public void testInvokeAny() throws Exception { // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection c=Arrays.asList( new TestCallable<String>("A"), new TestCallable<String>("B"), new TestCallable<String>("C")); // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection<Callable<String>> callables=c; Set<String> expected=new HashSet<String>(); expected.addAll(Arrays.asList("A", "B", "C")); String res=schedInline.invokeAny(callables); assertTrue(expected.contains(res)); } public void testInvokeAnyTimeout() throws Exception { // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection<Callable<String>> callables=new ArrayList<Callable<String>>( Arrays.asList( new TestCallable<String>("A"), new TestCallable<String>("B", 30), new TestCallable<String>("C", 30))); String res=schedPooled.invokeAny(callables, 10, TimeUnit.MILLISECONDS); assertEquals("A", res); } public void testInvokeAnyWithRetryTimeoutTimedOut() throws Exception { // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection<Callable<String>> callables=new ArrayList<Callable<String>>( Arrays.asList( new TestRtryCallable<String>("A", 5, 9, 1000), new TestRtryCallable<String>("B", 5, 9, 1000), new TestRtryCallable<String>("C", 5, 9, 1000))); try { String res=schedPooled.invokeAny( callables, 10, TimeUnit.MILLISECONDS); fail("Expected timeout, got " + res); } catch(TimeoutException e) { // OK } } public void testInvokeAnyException() throws Exception { // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection<Callable<String>> callables=new ArrayList<Callable<String>>( Arrays.asList( new TestRtryCallable<String>("A", 4, 3), new TestRtryCallable<String>("B", 4, 3), new TestRtryCallable<String>("C", 4, 3))); try { String res=schedPooled.invokeAny(callables); fail("Expected exception, got " + res); } catch(ExecutionException e) { // OK } } public void testInvokeAnyExceptionTimeout() throws Exception { // java 1.5 screwed up invokeAny's definition @SuppressWarnings("unchecked") Collection<Callable<String>> callables=new ArrayList<Callable<String>>(); for(int i=0; i<3; i++) { callables.add(new Callable<String>(){ public String call() throws Exception { throw new Exception("Nope"); }}); } try { String res=schedPooled.invokeAny( callables, 10, TimeUnit.MILLISECONDS); fail("Expected exception, got " + res); } catch(ExecutionException e) { // OK } } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAnyWithRetry() throws Exception { Collection<Callable<String>> callables=new ArrayList<Callable<String>>( Arrays.asList( new TestRtryCallable<String>("A", 4, 5), new TestRtryCallable<String>("B", 6, 5), new TestRtryCallable<String>("C", 3, 5))); String res=schedPooled.invokeAny(callables); assertEquals("Got " + res + ", expected C", "C", res); } @SuppressWarnings("unchecked") // java 1.5 screwed up invokeAll's definition public void testInvokeAnyWithRetryTimeout() throws Exception { Collection<Callable<String>> callables=new ArrayList<Callable<String>>( Arrays.asList( new TestRtryCallable<String>("A", 4, 3), new TestRtryCallable<String>("B", 1, 3), new TestRtryCallable<String>("C", 3, 5))); String res=schedPooled.invokeAny(callables, 10, TimeUnit.SECONDS); assertEquals("B", res); } // A few retries and then success public void testCancellingRetryableCallable() throws Exception { TestRtryCallable<String> tc=new TestRtryCallable<String>("X", 2, 3); assertEquals(0, tc.runs); assertEquals(0, tc.retries); Future<String> f=schedPooled.submit(tc); f.cancel(true); try { Object o=f.get(); fail("expected cancellation, got " + o); } catch(CancellationException e) { // ok } } // A few retries and a failure public void testFailingRetryableCallable() throws Exception { TestRtryCallable<String> tc=new TestRtryCallable<String>("X", 4, 3); assertEquals(0, tc.runs); assertEquals(0, tc.retries); Future<String> f=schedPooled.submit(tc); try { String s=f.get(); fail("Expected failure, got " + s); } catch(ExecutionException e) { assertEquals("Too many failures", e.getMessage()); assertEquals(4, ((CompositeExecutorException)e).getExceptions().size()); StringWriter sw=new StringWriter(); PrintWriter pw=new PrintWriter(sw); e.printStackTrace(pw); assertTrue("Hey, no also caused by", sw.toString().indexOf("Also caused by") > 0); assertSame(e, tc.result); } assertTrue(tc.gaveUp); assertEquals(4, tc.runs); assertEquals(3, tc.retries); } // Verify you can still return an exception from your callable public void testRetryableCallableReturningExc() throws Exception { ExecutionException e=new ExecutionException(new Exception()); TestRtryCallable<ExecutionException> tc= new TestRtryCallable<ExecutionException>(e, 0, 1); assertEquals(0, tc.runs); assertEquals(0, tc.retries); Future<ExecutionException> f=schedPooled.submit(tc); assertSame(e, f.get()); assertFalse(tc.gaveUp); assertEquals(1, tc.runs); assertEquals(0, tc.retries); } // Verify you can return null from your callable public void testRetryableCallableReturningNull() throws Exception { TestRtryCallable<Object> tc=new TestRtryCallable<Object>(null, 1, 1); assertEquals(0, tc.runs); assertEquals(0, tc.retries); Future<Object> f=schedPooled.submit(tc); assertNull(f.get()); assertFalse(tc.gaveUp); assertEquals(2, tc.runs); assertEquals(1, tc.retries); } // // Support classes // static class TestRunnable implements Runnable { volatile int runs=0; public void run() { runs++; } } public static class TestCallable<T> implements Callable<T> { public int runs=0; private T val=null; private long sleeptime=0; public TestCallable(T v) { this(v, 0); } public TestCallable(T v, long st) { super(); val=v; sleeptime=st; } public T call() throws Exception { if(sleeptime > 0) { Thread.sleep(sleeptime); } runs++; return val; } } public static class TestRtryCallable<T> extends TestCallable<T> implements RetryableCallable<T> { int retries=0; int maxRetries=0; int failures=0; Object result=null; private long baseDelay=10; public boolean gaveUp=false; public TestRtryCallable(T v, int fail, int nretries, long delay) { super(v); maxRetries=nretries; failures=fail; } public TestRtryCallable(T v, int fail, int nretries) { this(v, fail, nretries, 10); } public long getRetryDelay() { long rv=baseDelay; if(retries >= maxRetries) { rv=-1; } return rv; } public void onComplete(boolean success, Object res) { gaveUp=!success; result=res; } public void onExecutionException(ExecutionException exception) { retries++; } @Override public T call() throws Exception { T rv=super.call(); if(--failures >= 0) { throw new Exception("Failed!"); } return rv; } } }