639 lines
22 KiB
Java
639 lines
22 KiB
Java
/*
|
|
* Copyright (c) 2015-present, Parse, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
package com.parse;
|
|
|
|
|
|
import android.Manifest;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.net.ConnectivityManager;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
import bolts.Continuation;
|
|
import bolts.Task;
|
|
import bolts.TaskCompletionSource;
|
|
|
|
/**
|
|
* Manages all *Eventually calls when the local datastore is enabled.
|
|
*
|
|
* Constraints:
|
|
* - *Eventually calls must be executed in the same order they were queued.
|
|
* - *Eventually calls must only be executed when it's ParseOperationSet is ready in
|
|
* {@link ParseObject#taskQueue}.
|
|
* - All rules apply on start from reboot.
|
|
*/
|
|
/** package */ class ParsePinningEventuallyQueue extends ParseEventuallyQueue {
|
|
private static final String TAG = "ParsePinningEventuallyQueue";
|
|
|
|
/**
|
|
* TCS that is held until a {@link ParseOperationSet} is completed.
|
|
*/
|
|
private HashMap<String, TaskCompletionSource<JSONObject>> pendingOperationSetUUIDTasks =
|
|
new HashMap<>();
|
|
|
|
/**
|
|
* Queue for reading/writing eventually operations. Makes all reads/writes atomic operations.
|
|
*/
|
|
private TaskQueue taskQueue = new TaskQueue();
|
|
|
|
/**
|
|
* Queue for running *Eventually operations. It uses waitForOperationSetAndEventuallyPin to
|
|
* synchronize {@link ParseObject#taskQueue} until they are both ready to process the same
|
|
* ParseOperationSet.
|
|
*/
|
|
private TaskQueue operationSetTaskQueue = new TaskQueue();
|
|
|
|
/**
|
|
* List of {@link ParseOperationSet#uuid} that are currently queued in
|
|
* {@link ParsePinningEventuallyQueue#operationSetTaskQueue}.
|
|
*/
|
|
private ArrayList<String> eventuallyPinUUIDQueue = new ArrayList<>();
|
|
|
|
/**
|
|
* TCS that is created when there is no internet connection and isn't resolved until connectivity
|
|
* is achieved.
|
|
*
|
|
* If an error is set, it means that we are trying to clear out the taskQueues.
|
|
*/
|
|
private TaskCompletionSource<Void> connectionTaskCompletionSource = new TaskCompletionSource<>();
|
|
private final Object connectionLock = new Object();
|
|
private final ParseHttpClient httpClient;
|
|
|
|
private ConnectivityNotifier notifier;
|
|
private ConnectivityNotifier.ConnectivityListener listener = new ConnectivityNotifier.ConnectivityListener() {
|
|
@Override
|
|
public void networkConnectivityStatusChanged(Context context, Intent intent) {
|
|
boolean connectionLost =
|
|
intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
|
if (connectionLost) {
|
|
setConnected(false);
|
|
} else {
|
|
setConnected(ConnectivityNotifier.isConnected(context));
|
|
}
|
|
}
|
|
};
|
|
|
|
public ParsePinningEventuallyQueue(Context context, ParseHttpClient client) {
|
|
setConnected(ConnectivityNotifier.isConnected(context));
|
|
|
|
httpClient = client;
|
|
|
|
notifier = ConnectivityNotifier.getNotifier(context);
|
|
notifier.addListener(listener);
|
|
|
|
resume();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
//TODO (grantland): pause #6484855
|
|
|
|
notifier.removeListener(listener);
|
|
}
|
|
|
|
@Override
|
|
public void setConnected(boolean connected) {
|
|
synchronized (connectionLock) {
|
|
if (isConnected() != connected) {
|
|
super.setConnected(connected);
|
|
if (connected) {
|
|
connectionTaskCompletionSource.trySetResult(null);
|
|
connectionTaskCompletionSource = Task.create();
|
|
connectionTaskCompletionSource.trySetResult(null);
|
|
} else {
|
|
connectionTaskCompletionSource = Task.create();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int pendingCount() {
|
|
try {
|
|
return ParseTaskUtils.wait(pendingCountAsync());
|
|
} catch (ParseException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
|
|
public Task<Integer> pendingCountAsync() {
|
|
final TaskCompletionSource<Integer> tcs = new TaskCompletionSource<>();
|
|
|
|
taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
return pendingCountAsync(toAwait).continueWithTask(new Continuation<Integer, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Integer> task) throws Exception {
|
|
int count = task.getResult();
|
|
tcs.setResult(count);
|
|
return Task.forResult(null);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return tcs.getTask();
|
|
}
|
|
|
|
public Task<Integer> pendingCountAsync(Task<Void> toAwait) {
|
|
return toAwait.continueWithTask(new Continuation<Void, Task<Integer>>() {
|
|
@Override
|
|
public Task<Integer> then(Task<Void> task) throws Exception {
|
|
return EventuallyPin.findAllPinned().continueWithTask(new Continuation<List<EventuallyPin>, Task<Integer>>() {
|
|
@Override
|
|
public Task<Integer> then(Task<List<EventuallyPin>> task) throws Exception {
|
|
List<EventuallyPin> pins = task.getResult();
|
|
return Task.forResult(pins.size());
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void pause() {
|
|
synchronized (connectionLock) {
|
|
// Error out tasks waiting on waitForConnectionAsync.
|
|
connectionTaskCompletionSource.trySetError(new PauseException());
|
|
connectionTaskCompletionSource = Task.create();
|
|
connectionTaskCompletionSource.trySetError(new PauseException());
|
|
}
|
|
|
|
synchronized (taskQueueSyncLock) {
|
|
for (String key : pendingEventuallyTasks.keySet()) {
|
|
// Error out tasks waiting on waitForOperationSetAndEventuallyPin.
|
|
pendingEventuallyTasks.get(key).trySetError(new PauseException());
|
|
}
|
|
pendingEventuallyTasks.clear();
|
|
uuidToOperationSet.clear();
|
|
uuidToEventuallyPin.clear();
|
|
}
|
|
|
|
try {
|
|
ParseTaskUtils.wait(whenAll(Arrays.asList(taskQueue, operationSetTaskQueue)));
|
|
} catch (ParseException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void resume() {
|
|
// Reset waitForConnectionAsync.
|
|
if (isConnected()) {
|
|
connectionTaskCompletionSource.trySetResult(null);
|
|
connectionTaskCompletionSource = Task.create();
|
|
connectionTaskCompletionSource.trySetResult(null);
|
|
} else {
|
|
connectionTaskCompletionSource = Task.create();
|
|
}
|
|
|
|
populateQueueAsync();
|
|
}
|
|
|
|
private Task<Void> waitForConnectionAsync() {
|
|
synchronized (connectionLock) {
|
|
return connectionTaskCompletionSource.getTask();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pins the eventually operation on {@link ParsePinningEventuallyQueue#taskQueue}.
|
|
*
|
|
* @return Returns a Task that will be resolved when the command completes.
|
|
*/
|
|
@Override
|
|
public Task<JSONObject> enqueueEventuallyAsync(final ParseRESTCommand command,
|
|
final ParseObject object) {
|
|
Parse.requirePermission(Manifest.permission.ACCESS_NETWORK_STATE);
|
|
final TaskCompletionSource<JSONObject> tcs = new TaskCompletionSource<>();
|
|
|
|
taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
return enqueueEventuallyAsync(command, object, toAwait, tcs);
|
|
}
|
|
});
|
|
|
|
return tcs.getTask();
|
|
}
|
|
|
|
private Task<Void> enqueueEventuallyAsync(final ParseRESTCommand command,
|
|
final ParseObject object, Task<Void> toAwait, final TaskCompletionSource<JSONObject> tcs) {
|
|
return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
Task<EventuallyPin> pinTask = EventuallyPin.pinEventuallyCommand(object, command);
|
|
|
|
return pinTask.continueWithTask(new Continuation<EventuallyPin, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<EventuallyPin> task) throws Exception {
|
|
EventuallyPin pin = task.getResult();
|
|
Exception error = task.getError();
|
|
if (error != null) {
|
|
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
|
|
PLog.w(TAG, "Unable to save command for later.", error);
|
|
}
|
|
notifyTestHelper(TestHelper.COMMAND_NOT_ENQUEUED);
|
|
return Task.forResult(null);
|
|
}
|
|
|
|
pendingOperationSetUUIDTasks.put(pin.getUUID(), tcs);
|
|
|
|
// We don't need to wait for this.
|
|
populateQueueAsync().continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
/*
|
|
* We need to wait until after we populated the operationSetTaskQueue to notify
|
|
* that we've enqueued this command.
|
|
*/
|
|
notifyTestHelper(TestHelper.COMMAND_ENQUEUED);
|
|
return task;
|
|
}
|
|
});
|
|
|
|
return task.makeVoid();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Queries for pinned eventually operations on {@link ParsePinningEventuallyQueue#taskQueue}.
|
|
*
|
|
* @return Returns a Task that is resolved when all EventuallyPins are enqueued in the
|
|
* operationSetTaskQueue.
|
|
*/
|
|
private Task<Void> populateQueueAsync() {
|
|
return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
return populateQueueAsync(toAwait);
|
|
}
|
|
});
|
|
}
|
|
|
|
private Task<Void> populateQueueAsync(Task<Void> toAwait) {
|
|
return toAwait.continueWithTask(new Continuation<Void, Task<List<EventuallyPin>>>() {
|
|
@Override
|
|
public Task<List<EventuallyPin>> then(Task<Void> task) throws Exception {
|
|
// We don't want to enqueue any EventuallyPins that are already queued.
|
|
return EventuallyPin.findAllPinned(eventuallyPinUUIDQueue);
|
|
}
|
|
}).onSuccessTask(new Continuation<List<EventuallyPin>, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<List<EventuallyPin>> task) throws Exception {
|
|
List<EventuallyPin> pins = task.getResult();
|
|
|
|
for (final EventuallyPin pin : pins) {
|
|
// We don't need to wait for this.
|
|
runEventuallyAsync(pin);
|
|
}
|
|
|
|
return task.makeVoid();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Queues an eventually operation on {@link ParsePinningEventuallyQueue#operationSetTaskQueue}.
|
|
*
|
|
* Each eventually operation is run synchronously to maintain the order in which they were
|
|
* enqueued.
|
|
*/
|
|
private Task<Void> runEventuallyAsync(final EventuallyPin eventuallyPin) {
|
|
final String uuid = eventuallyPin.getUUID();
|
|
if (eventuallyPinUUIDQueue.contains(uuid)) {
|
|
// We don't want to enqueue the same operation more than once.
|
|
return Task.forResult(null);
|
|
}
|
|
eventuallyPinUUIDQueue.add(uuid);
|
|
|
|
operationSetTaskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(final Task<Void> toAwait) throws Exception {
|
|
return runEventuallyAsync(eventuallyPin, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
eventuallyPinUUIDQueue.remove(uuid);
|
|
return task;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return Task.forResult(null);
|
|
}
|
|
|
|
/**
|
|
* Runs the eventually operation. It first waits for a valid connection and if it's a save, it
|
|
* also waits for the ParseObject to be ready.
|
|
*
|
|
* @return A task that is resolved when the eventually operation completes.
|
|
*/
|
|
private Task<Void> runEventuallyAsync(final EventuallyPin eventuallyPin, final Task<Void> toAwait) {
|
|
return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
return waitForConnectionAsync();
|
|
}
|
|
}).onSuccessTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
return waitForOperationSetAndEventuallyPin(null, eventuallyPin).continueWithTask(new Continuation<JSONObject, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<JSONObject> task) throws Exception {
|
|
Exception error = task.getError();
|
|
if (error != null) {
|
|
if (error instanceof PauseException) {
|
|
// Bubble up the PauseException.
|
|
return task.makeVoid();
|
|
}
|
|
|
|
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
|
|
PLog.e(TAG, "Failed to run command.", error);
|
|
}
|
|
|
|
notifyTestHelper(TestHelper.COMMAND_FAILED, error);
|
|
} else {
|
|
notifyTestHelper(TestHelper.COMMAND_SUCCESSFUL);
|
|
}
|
|
|
|
TaskCompletionSource<JSONObject> tcs =
|
|
pendingOperationSetUUIDTasks.remove(eventuallyPin.getUUID());
|
|
if (tcs != null) {
|
|
if (error != null) {
|
|
tcs.setError(error);
|
|
} else {
|
|
tcs.setResult(task.getResult());
|
|
}
|
|
}
|
|
return task.makeVoid();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Lock to make sure all changes to the below parameters happen atomically.
|
|
*/
|
|
private final Object taskQueueSyncLock = new Object();
|
|
|
|
/**
|
|
* Map of eventually operation UUID to TCS that is resolved when the operation is complete.
|
|
*/
|
|
private HashMap<String, TaskCompletionSource<JSONObject>> pendingEventuallyTasks =
|
|
new HashMap<>();
|
|
|
|
/**
|
|
* Map of eventually operation UUID to matching ParseOperationSet.
|
|
*/
|
|
private HashMap<String, ParseOperationSet> uuidToOperationSet = new HashMap<>();
|
|
|
|
/**
|
|
* Map of eventually operation UUID to matching EventuallyPin.
|
|
*/
|
|
private HashMap<String, EventuallyPin> uuidToEventuallyPin = new HashMap<>();
|
|
|
|
/**
|
|
* Synchronizes ParseObject#taskQueue (Many) and ParseCommandCache#taskQueue (One). Each queue
|
|
* will be held until both are ready, matched on operationSetUUID. Once both are ready, the
|
|
* eventually task will be run.
|
|
*
|
|
* @param operationSet
|
|
* From {@link ParseObject}
|
|
* @param eventuallyPin
|
|
* From {@link ParsePinningEventuallyQueue}
|
|
*/
|
|
//TODO (grantland): We can probably generalize this to synchronize/join more than 2 taskQueues
|
|
@Override
|
|
/* package */ Task<JSONObject> waitForOperationSetAndEventuallyPin(ParseOperationSet operationSet,
|
|
EventuallyPin eventuallyPin) {
|
|
if (eventuallyPin != null && eventuallyPin.getType() != EventuallyPin.TYPE_SAVE) {
|
|
return process(eventuallyPin, null);
|
|
}
|
|
|
|
final String uuid; // The key we use to join the taskQueues
|
|
final TaskCompletionSource<JSONObject> tcs;
|
|
|
|
synchronized (taskQueueSyncLock) {
|
|
if (operationSet != null && eventuallyPin == null) {
|
|
uuid = operationSet.getUUID();
|
|
uuidToOperationSet.put(uuid, operationSet);
|
|
} else if (operationSet == null && eventuallyPin != null) {
|
|
uuid = eventuallyPin.getOperationSetUUID();
|
|
uuidToEventuallyPin.put(uuid, eventuallyPin);
|
|
} else {
|
|
throw new IllegalStateException("Either operationSet or eventuallyPin must be set.");
|
|
}
|
|
|
|
eventuallyPin = uuidToEventuallyPin.get(uuid);
|
|
operationSet = uuidToOperationSet.get(uuid);
|
|
|
|
if (eventuallyPin == null || operationSet == null) {
|
|
if (pendingEventuallyTasks.containsKey(uuid)) {
|
|
tcs = pendingEventuallyTasks.get(uuid);
|
|
} else {
|
|
tcs = Task.create();
|
|
pendingEventuallyTasks.put(uuid, tcs);
|
|
}
|
|
return tcs.getTask();
|
|
} else {
|
|
tcs = pendingEventuallyTasks.get(uuid);
|
|
}
|
|
}
|
|
|
|
return process(eventuallyPin, operationSet).continueWithTask(new Continuation<JSONObject, Task<JSONObject>>() {
|
|
@Override
|
|
public Task<JSONObject> then(Task<JSONObject> task) throws Exception {
|
|
synchronized (taskQueueSyncLock) {
|
|
pendingEventuallyTasks.remove(uuid);
|
|
uuidToOperationSet.remove(uuid);
|
|
uuidToEventuallyPin.remove(uuid);
|
|
}
|
|
|
|
Exception error = task.getError();
|
|
if (error != null) {
|
|
tcs.trySetError(error);
|
|
} else if (task.isCancelled()) {
|
|
tcs.trySetCancelled();
|
|
} else {
|
|
tcs.trySetResult(task.getResult());
|
|
}
|
|
return tcs.getTask();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Invokes the eventually operation.
|
|
*/
|
|
private Task<JSONObject> process(final EventuallyPin eventuallyPin,
|
|
final ParseOperationSet operationSet) {
|
|
|
|
return waitForConnectionAsync().onSuccessTask(new Continuation<Void, Task<JSONObject>>() {
|
|
@Override
|
|
public Task<JSONObject> then(Task<Void> task) throws Exception {
|
|
final int type = eventuallyPin.getType();
|
|
final ParseObject object = eventuallyPin.getObject();
|
|
String sessionToken = eventuallyPin.getSessionToken();
|
|
|
|
Task<JSONObject> executeTask;
|
|
if (type == EventuallyPin.TYPE_SAVE) {
|
|
executeTask = object.saveAsync(httpClient, operationSet, sessionToken);
|
|
} else if (type == EventuallyPin.TYPE_DELETE) {
|
|
executeTask = object.deleteAsync(sessionToken).cast();
|
|
} else { // else if (type == EventuallyPin.TYPE_COMMAND) {
|
|
ParseRESTCommand command = eventuallyPin.getCommand();
|
|
if (command == null) {
|
|
executeTask = Task.forResult(null);
|
|
notifyTestHelper(TestHelper.COMMAND_OLD_FORMAT_DISCARDED);
|
|
} else {
|
|
executeTask = command.executeAsync(httpClient);
|
|
}
|
|
}
|
|
|
|
return executeTask.continueWithTask(new Continuation<JSONObject, Task<JSONObject>>() {
|
|
@Override
|
|
public Task<JSONObject> then(final Task<JSONObject> executeTask) throws Exception {
|
|
Exception error = executeTask.getError();
|
|
if (error != null) {
|
|
if (error instanceof ParseException
|
|
&& ((ParseException) error).getCode() == ParseException.CONNECTION_FAILED) {
|
|
// We did our retry logic in ParseRequest, so just mark as not connected
|
|
// and move on.
|
|
setConnected(false);
|
|
|
|
notifyTestHelper(TestHelper.NETWORK_DOWN);
|
|
|
|
return process(eventuallyPin, operationSet);
|
|
}
|
|
}
|
|
|
|
// Delete the command regardless, even if it failed. Otherwise, we'll just keep
|
|
// trying it forever.
|
|
// We don't have to wait for taskQueue since it will not be enqueued again
|
|
// since this EventuallyPin is still in eventuallyPinUUIDQueue.
|
|
return eventuallyPin.unpinInBackground(EventuallyPin.PIN_NAME).continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
JSONObject result = executeTask.getResult();
|
|
if (type == EventuallyPin.TYPE_SAVE) {
|
|
return object.handleSaveEventuallyResultAsync(result, operationSet);
|
|
} else if (type == EventuallyPin.TYPE_DELETE) {
|
|
if (executeTask.isFaulted()) {
|
|
return task;
|
|
} else {
|
|
return object.handleDeleteEventuallyResultAsync();
|
|
}
|
|
} else { // else if (type == EventuallyPin.TYPE_COMMAND) {
|
|
return task;
|
|
}
|
|
}
|
|
}).continueWithTask(new Continuation<Void, Task<JSONObject>>() {
|
|
@Override
|
|
public Task<JSONObject> then(Task<Void> task) throws Exception {
|
|
return executeTask;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
/* package */ void simulateReboot() {
|
|
pause();
|
|
|
|
pendingOperationSetUUIDTasks.clear();
|
|
pendingEventuallyTasks.clear();
|
|
uuidToOperationSet.clear();
|
|
uuidToEventuallyPin.clear();
|
|
|
|
resume();
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
pause();
|
|
|
|
Task<Void> task = taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
return EventuallyPin.findAllPinned().onSuccessTask(new Continuation<List<EventuallyPin>, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<List<EventuallyPin>> task) throws Exception {
|
|
List<EventuallyPin> pins = task.getResult();
|
|
|
|
List<Task<Void>> tasks = new ArrayList<>();
|
|
for (EventuallyPin pin : pins) {
|
|
tasks.add(pin.unpinInBackground(EventuallyPin.PIN_NAME));
|
|
}
|
|
return Task.whenAll(tasks);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
try {
|
|
ParseTaskUtils.wait(task);
|
|
} catch (ParseException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
|
|
simulateReboot();
|
|
|
|
resume();
|
|
}
|
|
|
|
/**
|
|
* Creates a Task that is resolved when all the TaskQueues are "complete".
|
|
*
|
|
* "Complete" is when all the TaskQueues complete the queue of Tasks that were in it before
|
|
* whenAll was invoked. This will not keep track of tasks that are added on after whenAll
|
|
* was invoked.
|
|
*/
|
|
private Task<Void> whenAll(Collection<TaskQueue> taskQueues) {
|
|
List<Task<Void>> tasks = new ArrayList<>();
|
|
|
|
for (TaskQueue taskQueue : taskQueues) {
|
|
Task<Void> task = taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> toAwait) throws Exception {
|
|
return toAwait;
|
|
}
|
|
});
|
|
|
|
tasks.add(task);
|
|
}
|
|
|
|
return Task.whenAll(tasks);
|
|
}
|
|
|
|
private static class PauseException extends Exception {
|
|
// This class was intentionally left blank.
|
|
}
|
|
}
|