blob: 0f858b4f297a33822b30efbdd1cc3a844d100d13 [file] [log] [blame]
package org.robolectric.android.util.concurrent;
import android.support.annotation.NonNull;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AbstractFuture;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.LooperMode;
import org.robolectric.util.Logger;
/**
* Executor service that queues any posted tasks.
*
* Users must explicitly call {@link runAll()} to execute all pending tasks.
*
* Intended to be a replacement for {@link RoboExecutorService} when using
* {@link LooperMode.Mode#PAUSED}.
* Unlike {@link RoboExecutorService}, will execute tasks on a background thread. This is useful
* to test Android code that enforces it runs off the main thread.
*
* NOTE: Beta API, subject to change.
*/
@Beta
public class PausedExecutorService extends AbstractExecutorService {
private final ExecutorService realService;
private final Queue<Runnable> deferredTasks = new ConcurrentLinkedQueue<>();
private Thread executorThread;
private static class DeferredTask<V> extends AbstractFuture<V> implements RunnableFuture<V> {
private final Callable<V> callable;
private final ExecutorService executor;
DeferredTask(Callable<V> callable, ExecutorService executor) {
this.callable = callable;
this.executor = executor;
}
@Override
public void run() {
Future<V> result = executor.submit(callable);
try {
set(result.get());
} catch (ExecutionException e) {
rethrowOriginalException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public PausedExecutorService() {
this.realService =
Executors.newSingleThreadExecutor(
r -> {
executorThread = new Thread(r);
return executorThread;
});
}
private static void rethrowOriginalException(ExecutionException e) throws RuntimeException {
// try to preserve original exception if possible
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else if (e.getCause() != null) {
throw new RuntimeException(e.getCause());
}
throw new RuntimeException(e);
}
/**
* Execute all posted tasks and block until they are complete.
*
* @return the number of tasks executed
*/
public int runAll() {
int numTasksRun = 0;
if (Thread.currentThread().equals(executorThread)) {
Logger.info("ignoring request to execute task - called from executor's own thread");
return numTasksRun;
}
while (hasQueuedTasks()) {
runNext();
numTasksRun++;
}
return numTasksRun;
}
/**
* Executes the next queued task.
*
* Will be ignored if called from the executor service thread to prevent deadlocks.
*
* @return true if task was run, false if queue was empty
*/
public boolean runNext() {
if (!hasQueuedTasks()) {
return false;
}
if (Thread.currentThread().equals(executorThread)) {
Logger.info("ignoring request to execute task - called from executor's own thread");
return false;
}
Runnable task = deferredTasks.poll();
task.run();
return true;
}
/**
* @return true if there are queued pending tasks
*/
public boolean hasQueuedTasks() {
return !deferredTasks.isEmpty();
}
@Override
public void shutdown() {
realService.shutdown();
deferredTasks.clear();
}
@Override
public List<Runnable> shutdownNow() {
realService.shutdownNow();
List<Runnable> copy = ImmutableList.copyOf(deferredTasks);
deferredTasks.clear();
return copy;
}
@Override
public boolean isShutdown() {
return realService.isShutdown();
}
@Override
public boolean isTerminated() {
return realService.isTerminated();
}
@Override
public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
// If not shut down first, timeout would occur with normal behavior.
return realService.awaitTermination(l, timeUnit);
}
@Override
public void execute(@NonNull Runnable command) {
if (command instanceof DeferredTask) {
deferredTasks.add(command);
} else {
deferredTasks.add(new DeferredTask<>(Executors.callable(command), realService));
}
}
@Override
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return newTaskFor(Executors.callable(runnable, value));
}
@Override
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new DeferredTask<T>(callable, realService);
}
}