diff --git a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
index b47151995b..5097696e94 100644
--- a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
+++ b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
@@ -52,7 +52,12 @@ import com.google.inject.Provides;
/**
* Configures {@link ExecutorService}.
*
- * Note that this uses threads
+ * Note that this uses threads.
+ *
+ *
+ * This extends the underlying Future to expose a description (the task's toString) and the submission context (stack trace).
+ * The submission stack trace is appended to relevant stack traces on exceptions that are returned,
+ * so the user can see the logical chain of execution (in the executor, and where it was passed to the executor).
*
* @author Adrian Cole
*/
@@ -92,7 +97,7 @@ public class ExecutorServiceModule extends AbstractModule {
static ExecutorService addToStringOnSubmit(ExecutorService executor) {
if (executor != null) {
- return new AddToStringOnSubmitExecutorService(executor);
+ return new DescribingExecutorService(executor);
}
return executor;
}
@@ -117,11 +122,11 @@ public class ExecutorServiceModule extends AbstractModule {
protected void configure() {
}
- static class AddToStringOnSubmitExecutorService implements ExecutorService {
+ static class DescribingExecutorService implements ExecutorService {
private final ExecutorService delegate;
- public AddToStringOnSubmitExecutorService(ExecutorService delegate) {
+ public DescribingExecutorService(ExecutorService delegate) {
this.delegate = checkNotNull(delegate, "delegate");
}
@@ -174,18 +179,18 @@ public class ExecutorServiceModule extends AbstractModule {
@Override
public Future submit(Callable task) {
- return new AddToStringFuture(delegate.submit(task), task.toString());
+ return new DescribedFuture(delegate.submit(task), task.toString(), getStackTraceHere());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Future> submit(Runnable task) {
- return new AddToStringFuture(delegate.submit(task), task.toString());
+ return new DescribedFuture(delegate.submit(task), task.toString(), getStackTraceHere());
}
@Override
public Future submit(Runnable task, T result) {
- return new AddToStringFuture(delegate.submit(task, result), task.toString());
+ return new DescribedFuture(delegate.submit(task, result), task.toString(), getStackTraceHere());
}
@Override
@@ -210,13 +215,15 @@ public class ExecutorServiceModule extends AbstractModule {
}
- static class AddToStringFuture implements Future {
+ static class DescribedFuture implements Future {
private final Future delegate;
- private final String toString;
+ private final String description;
+ private StackTraceElement[] submissionTrace;
- public AddToStringFuture(Future delegate, String toString) {
+ public DescribedFuture(Future delegate, String description, StackTraceElement[] submissionTrace) {
this.delegate = delegate;
- this.toString = toString;
+ this.description = description;
+ this.submissionTrace = submissionTrace;
}
@Override
@@ -226,12 +233,65 @@ public class ExecutorServiceModule extends AbstractModule {
@Override
public T get() throws InterruptedException, ExecutionException {
- return delegate.get();
+ try {
+ return delegate.get();
+ } catch (ExecutionException e) {
+ throw ensureCauseHasSubmissionTrace(e);
+ } catch (InterruptedException e) {
+ throw ensureCauseHasSubmissionTrace(e);
+ }
}
@Override
public T get(long arg0, TimeUnit arg1) throws InterruptedException, ExecutionException, TimeoutException {
- return delegate.get(arg0, arg1);
+ try {
+ return delegate.get(arg0, arg1);
+ } catch (ExecutionException e) {
+ throw ensureCauseHasSubmissionTrace(e);
+ } catch (InterruptedException e) {
+ throw ensureCauseHasSubmissionTrace(e);
+ } catch (TimeoutException e) {
+ throw ensureCauseHasSubmissionTrace(e);
+ }
+ }
+
+ /** This method does the work to ensure _if_ a submission stack trace was provided,
+ * it is included in the exception. most errors are thrown from the frame of the
+ * Future.get call, with a cause that took place in the executor's thread.
+ * We extend the stack trace of that cause with the submission stack trace.
+ * (An alternative would be to put the stack trace as a root cause,
+ * at the bottom of the stack, or appended to all traces, or inserted
+ * after the second cause, etc ... but since we can't change the "Caused by:"
+ * method in Throwable the compromise made here seems best.)
+ */
+ private ET ensureCauseHasSubmissionTrace(ET e) {
+ if (submissionTrace==null) return e;
+ if (e.getCause()==null) {
+ ExecutionException ee = new ExecutionException("task submitted from the following trace", null);
+ e.initCause(ee);
+ return e;
+ }
+ Throwable cause = e.getCause();
+ StackTraceElement[] causeTrace = cause.getStackTrace();
+ boolean causeIncludesSubmissionTrace = submissionTrace.length >= causeTrace.length;
+ for (int i=0; causeIncludesSubmissionTrace && i euc = performSubmissionInSeparateMethod1(user, t1);
+ assert euc.toString().indexOf("ConfigurableRunner") >= 0;
+ assert euc.get().equals("okay");
+
+ Future