这是indexloc提供的服务,不要输入任何密码
Skip to content
This repository was archived by the owner on Sep 2, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion bolts-tasks/src/main/java/bolts/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,54 @@ public class Task<TResult> {
*/
public static final Executor UI_THREAD_EXECUTOR = AndroidExecutors.uiThread();

/**
* Interface for handlers invoked when a failed <tt>Task</tt> is about to be
* finalized, but the exception has not been consumed.
*
* <p>The handler will execute in the GC thread, so if the handler needs to do
* anything time consuming or complex it is a good idea to fire off a Task
* to handle the exception.
*
* @see #getUnobservedExceptionHandler
* @see #setUnobservedExceptionHandler
*/
public interface UnobservedExceptionHandler {
/**
* Method invoked when the given task has an unobserved exception.
* <p>Any exception thrown by this method will be ignored.
* @param t the task
* @param e the exception
*/
void unobservedException(Task<?> t, UnobservedTaskException e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right. In Java isn't this usually in some form of onUnobservedException

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently matches Thread.UncaughtExceptionHandler's pattern: https://developer.android.com/reference/java/lang/Thread.UncaughtExceptionHandler.html

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm then

}

// null unless explicitly set
private static volatile UnobservedExceptionHandler unobservedExceptionHandler;

/**
* Returns the handler invoked when a task has an unobserved
* exception or null.
*/
public static UnobservedExceptionHandler getUnobservedExceptionHandler() {
return unobservedExceptionHandler;
}

/**
* Set the handler invoked when a task has an unobserved exception.
* @param eh the object to use as an unobserved exception handler. If
* <tt>null</tt> then unobserved exceptions will be ignored.
*/
public static void setUnobservedExceptionHandler(UnobservedExceptionHandler eh) {
unobservedExceptionHandler = eh;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will we need some kind of lock here? I can see possible race condition

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

volatile should protect us here, no?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right I didn't see that.

}

private final Object lock = new Object();
private boolean complete;
private boolean cancelled;
private TResult result;
private Exception error;
private boolean errorHasBeenObserved;
private UnobservedErrorNotifier unobservedErrorNotifier;
private List<Continuation<TResult, Void>> continuations = new ArrayList<>();

/* package */ Task() {
Expand Down Expand Up @@ -101,7 +144,7 @@ public boolean isCancelled() {
*/
public boolean isFaulted() {
synchronized (lock) {
return error != null;
return getError() != null;
}
}

Expand All @@ -119,6 +162,13 @@ public TResult getResult() {
*/
public Exception getError() {
synchronized (lock) {
if (error != null) {
errorHasBeenObserved = true;
if (unobservedErrorNotifier != null) {
unobservedErrorNotifier.setObserved();
unobservedErrorNotifier = null;
}
}
return error;
}
}
Expand Down Expand Up @@ -357,6 +407,8 @@ public static <TResult> Task<Task<TResult>> whenAnyResult(Collection<? extends T
public Void then(Task<TResult> task) {
if (isAnyTaskComplete.compareAndSet(false, true)) {
firstCompleted.setResult(task);
} else {
Throwable ensureObserved = task.getError();
}
return null;
}
Expand Down Expand Up @@ -392,6 +444,8 @@ public static Task<Task<?>> whenAny(Collection<? extends Task<?>> tasks) {
public Void then(Task<Object> task) {
if (isAnyTaskComplete.compareAndSet(false, true)) {
firstCompleted.setResult(task);
} else {
Throwable ensureObserved = task.getError();
}
return null;
}
Expand Down Expand Up @@ -939,8 +993,11 @@ private void runContinuations() {
}
complete = true;
Task.this.error = error;
errorHasBeenObserved = false;
lock.notifyAll();
runContinuations();
if (!errorHasBeenObserved && getUnobservedExceptionHandler() != null)
unobservedErrorNotifier = new UnobservedErrorNotifier(this);
return true;
}
}
Expand Down
33 changes: 33 additions & 0 deletions bolts-tasks/src/main/java/bolts/UnobservedErrorNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package bolts;

/**
* This class is used to retain a faulted task until either its error is observed or it is
* finalized. If it is finalized with a task, then the uncaught exception handler is exected
* with an UnobservedTaskException.
*/
class UnobservedErrorNotifier {
private Task<?> task;

public UnobservedErrorNotifier(Task<?> task) {
this.task = task;
}

@Override
protected void finalize() throws Throwable {
try {
Task faultedTask = this.task;
if (faultedTask != null) {
Task.UnobservedExceptionHandler ueh = Task.getUnobservedExceptionHandler();
if (ueh != null) {
ueh.unobservedException(faultedTask, new UnobservedTaskException(faultedTask.getError()));
}
}
} finally {
super.finalize();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one small edit: we should be using try { ... } finally { super.finalize() } here


public void setObserved() {
task = null;
}
}
10 changes: 10 additions & 0 deletions bolts-tasks/src/main/java/bolts/UnobservedTaskException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package bolts;

/**
* Used to signify that a Task's error went unobserved.
*/
public class UnobservedTaskException extends RuntimeException {
public UnobservedTaskException(Throwable cause) {
super(cause);
}
}
34 changes: 34 additions & 0 deletions bolts-tasks/src/test/java/bolts/TaskTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,40 @@ public Void then(Task<Void> task) {
});
}

@Test
public void testUnobservedError() throws InterruptedException {
try {
final Object sync = new Object();
Task.setUnobservedExceptionHandler(new Task.UnobservedExceptionHandler() {
@Override
public void unobservedException(Task<?> t, UnobservedTaskException e) {
synchronized (sync) {
sync.notify();
}
}
});

synchronized (sync) {
startFailedTask();
System.gc();
sync.wait();
}

} finally {
Task.setUnobservedExceptionHandler(null);
}
}

// runs in a separate method to ensure it is out of scope.
private void startFailedTask() throws InterruptedException {
Task.call(new Callable<Object>() {
@Override
public Object call() throws Exception {
throw new RuntimeException();
}
}).waitForCompletion();
}

@Test
public void testWhenAllNoTasks() {
Task<Void> task = Task.whenAll(new ArrayList<Task<Void>>());
Expand Down