// Copyright (c) 2006 Dustin Sallings <dustin@spy.net>
package net.spy.concurrent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import net.spy.SpyObject;
import net.spy.concurrent.SynchronizationObject.Predicate;
/**
* ScheduledExecutorService wrapper that allows RetryableCallable objects to
* be retried upon failure.
*
* Note that while this does back an existing ScheduledExecutorService, any
* Callables sent directly into that service will not be eligible for retry.
*/
public class Rescheduler extends SpyObject implements ScheduledExecutorService {
private final ScheduledExecutorService executor;
/**
* Get an instance of Rescheduler with the given backing
* ScheduledExecutorService.
*
* @param x the ScheduledExecutorService that will be responsible execution
*/
public Rescheduler(ScheduledExecutorService x) {
super();
executor=x;
}
@SuppressWarnings("unchecked") // discarding future type
void examineCompletion(final FutureFuture future) {
try {
boolean set=false;
Object result=null;
while(!set) {
try {
result=future.getCurrentFuture().get();
set=true;
} catch(InterruptedException e) {
getLogger().info("Interrupted. Retrying", e);
}
}
future.setResult(result);
future.callable.onComplete(true, result);
} catch(CancellationException e) {
future.setCancelled();
} catch (ExecutionException e) {
assert future != null : "Lost the future";
future.addException(e);
long nextTime=future.callable.getRetryDelay();
if(!future.isCancelled()) {
if(nextTime >= 0) {
future.callable.onExecutionException(null);
future.clearCurrentFuture();
scheduleFutureFuture(future,
nextTime, TimeUnit.MILLISECONDS);
} else {
future.callable.onComplete(false, future.exceptions);
assert future.exceptions != null : "Exceptions is null";
future.setResult(future.exceptions);
}
}
}
}
public ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit) {
return executor.schedule(r, delay, unit);
}
private <T> void scheduleFutureFuture(final FutureFuture<T> ff,
long delay, TimeUnit unit) {
FutureTask<T> ft=new FutureTask<T>(ff.callable) {
@Override
protected void done() {
examineCompletion(ff);
}
};
executor.schedule(ft, delay, unit);
ff.setCurrentFuture(ft);
}
/**
* Process the given callable. If this is a RetryableCallable, it may be
* retried if execution fails.
*/
public <T> ScheduledFuture<T> schedule(Callable<T> c, long delay,
TimeUnit unit) {
ScheduledFuture<T> rv=null;
if(c instanceof RetryableCallable) {
RetryableCallable<T> rc=(RetryableCallable<T>)c;
final ScheduledFutureFuture<T> ff=new ScheduledFutureFuture<T>(rc,
TimeUnit.MILLISECONDS.convert(delay, unit));
scheduleFutureFuture(ff, delay, unit);
rv=ff;
} else {
rv=executor.schedule(c, delay, unit);
}
return rv;
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long arg1,
long arg2, TimeUnit arg3) {
return executor.scheduleAtFixedRate(r, arg1, arg2, arg3);
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable r, long arg1,
long arg2, TimeUnit arg3) {
return executor.scheduleWithFixedDelay(r, arg1, arg2, arg3);
}
public boolean awaitTermination(long arg0, TimeUnit arg1)
throws InterruptedException {
return executor.awaitTermination(arg0, arg1);
}
public <T> List<Future<T>> invokeAll(Collection<Callable<T>> callables)
throws InterruptedException {
List<Future<T>> rv=new ArrayList<Future<T>>(callables.size());
RetryableExecutorCompletionService<T> ecs=
new RetryableExecutorCompletionService<T>(this);
for(Callable<T> c : callables) {
rv.add(ecs.submit(c));
}
for(int i=0; i<callables.size(); i++) {
ecs.take();
}
return rv;
}
public <T> List<Future<T>> invokeAll(Collection<Callable<T>> callables,
long timeout, TimeUnit unit) throws InterruptedException {
long end=System.currentTimeMillis()+TimeUnit.MILLISECONDS.convert(
timeout, unit);
List<Future<T>> rv=new ArrayList<Future<T>>(callables.size());
RetryableExecutorCompletionService<T> ecs=
new RetryableExecutorCompletionService<T>(this);
for(Callable<T> c : callables) {
rv.add(ecs.submit(c));
}
for(int i=0; i<callables.size(); i++) {
long now=System.currentTimeMillis();
long towait=end-now;
if(towait > 0) {
ecs.poll(towait, TimeUnit.MILLISECONDS);
}
}
return rv;
}
public <T> T invokeAny(Collection<Callable<T>> callables)
throws InterruptedException, ExecutionException {
List<Future<T>> futures=new ArrayList<Future<T>>(callables.size());
RetryableExecutorCompletionService<T> ecs=
new RetryableExecutorCompletionService<T>(this);
for(Callable<T> c : callables) {
futures.add(ecs.submit(c));
}
Collection<ExecutionException> exceptions=
new ArrayList<ExecutionException>();
// Everything's submitted. Wait for stuff to finish.
boolean foundResult=false;
T rv=null;
while(!foundResult && !futures.isEmpty()) {
Future<T> f=ecs.take();
futures.remove(f);
try {
rv=f.get();
foundResult=true;
} catch(ExecutionException e) {
exceptions.add(e);
}
}
if(!foundResult) {
throw new CompositeExecutorException(exceptions);
}
for(Future<T> f : futures) {
f.cancel(true);
}
return rv;
}
public <T> T invokeAny(Collection<Callable<T>> callables, long timeout,
TimeUnit unit) throws InterruptedException, ExecutionException,
TimeoutException {
long end=System.currentTimeMillis() +
TimeUnit.MILLISECONDS.convert(timeout, unit);
List<Future<T>> futures=new ArrayList<Future<T>>(callables.size());
RetryableExecutorCompletionService<T> ecs=
new RetryableExecutorCompletionService<T>(this);
for(Callable<T> c : callables) {
futures.add(ecs.submit(c));
}
Collection<ExecutionException> exceptions=
new ArrayList<ExecutionException>();
// Everything's submitted. Wait for stuff to finish.
boolean foundResult=false;
T rv=null;
while(!foundResult && !futures.isEmpty()) {
long now=System.currentTimeMillis();
long towait=end-now;
Future<T> f=ecs.poll(towait, TimeUnit.MILLISECONDS);
if(f == null) {
throw new TimeoutException(
"Timed out waiting " + towait + "ms of "
+ timeout + unit.name());
} else {
futures.remove(f);
assert f.isDone() : "Future is not done";
try {
rv=f.get();
foundResult=true;
} catch(ExecutionException e) {
exceptions.add(e);
}
}
}
if(!foundResult) {
throw new CompositeExecutorException(exceptions);
}
for(Future<T> f : futures) {
f.cancel(true);
}
return rv;
}
public void shutdown() {
executor.shutdown();
}
public boolean isShutdown() {
return executor.isShutdown();
}
public boolean isTerminated() {
return executor.isTerminated();
}
public List<Runnable> shutdownNow() {
return executor.shutdownNow();
}
/**
* Process the given callable. If this is a RetryableCallable, it may be
* retried if execution fails.
*/
public <T> Future<T> submit(Callable<T> c) {
Future<T> rv=null;
if(c instanceof RetryableCallable) {
RetryableCallable<T> rc=(RetryableCallable<T>)c;
final FutureFuture<T> ff=new FutureFuture<T>(rc);
FutureTask<T> ft=new FutureTask<T>(rc) {
@Override
protected void done() {
examineCompletion(ff);
}
};
executor.submit(ft);
ff.setCurrentFuture(ft);
rv=ff;
} else {
rv=executor.submit(c);
}
return rv;
}
public Future<?> submit(Runnable arg0) {
return executor.submit(arg0);
}
public <T> Future<T> submit(Runnable arg0, T arg1) {
return executor.submit(arg0, arg1);
}
public void execute(Runnable arg0) {
executor.execute(arg0);
}
static class FutureFuture<T> implements Future<T> {
final RetryableCallable<T> callable;
final Object defaultObj=new Object();
final Object cancelObj=new Object();
CompositeExecutorException exceptions=null;
private final SynchronizationObject<Future<T>> futureSync=
new SynchronizationObject<Future<T>>(null);
private final SynchronizationObject<Object> sync=
new SynchronizationObject<Object>(defaultObj);
public FutureFuture(RetryableCallable<T> c) {
super();
callable=c;
}
public void addException(ExecutionException e) {
if(exceptions == null) {
exceptions=new CompositeExecutorException(e);
} else {
exceptions.addException(e);
}
}
public void setResult(Object t) {
sync.set(t);
}
public Future<T> getCurrentFuture() throws InterruptedException {
try {
futureSync.waitUntilNotNull(
Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
Future<T> f=futureSync.get();
assert f != null : "Current future fetch failed";
return f;
}
public void clearCurrentFuture() {
setCurrentFuture(null);
}
public void setCurrentFuture(Future<T> to) {
futureSync.set(to);
}
public void setCancelled() {
sync.set(cancelObj);
}
public boolean cancel(boolean mayInterruptIfRunning) {
boolean rv=false;
setCancelled();
Future<T> f=futureSync.get();
if(f != null) {
rv=f.cancel(mayInterruptIfRunning);
}
return rv;
}
public T get() throws InterruptedException, ExecutionException {
try {
return get(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new RuntimeException(
"Infinite sleep over. The end is near", e);
}
}
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
sync.waitUntilTrue(new Predicate<Object>(){
public boolean evaluate(Object o) {
return o != defaultObj;
}}, timeout, unit);
Object o=sync.get();
if(o == cancelObj) {
throw new CancellationException("Cancelled");
} else if(o instanceof CompositeExecutorException) {
throw (ExecutionException)o;
} else {
@SuppressWarnings("unchecked")
T rv=(T)o;
return rv;
}
}
public boolean isCancelled() {
return sync.get() == cancelObj;
}
public boolean isDone() {
return sync.get() != defaultObj;
}
}
static class ScheduledFutureFuture<T> extends FutureFuture<T>
implements ScheduledFuture<T> {
private final long when;
public ScheduledFutureFuture(RetryableCallable<T> c, long delay) {
super(c);
when=System.currentTimeMillis()+delay;
}
public long getDelay(TimeUnit unit) {
return TimeUnit.MILLISECONDS.convert(
when-System.currentTimeMillis(), unit);
}
public int compareTo(Delayed d) {
return new Long(getDelay(TimeUnit.MILLISECONDS))
.compareTo(d.getDelay(TimeUnit.MILLISECONDS));
}
}
}