Allow RuntimeExceptions thrown by Jobs to crash.

This commit is contained in:
Greyson Parrelli 2019-07-25 19:55:26 -04:00 committed by Alan Evans
parent e8e80e5d05
commit 17400020b7
3 changed files with 91 additions and 23 deletions

View File

@ -125,9 +125,60 @@ public abstract class Job {
@NonNull T create(@NonNull Parameters parameters, @NonNull Data data);
}
public enum Result {
public static final class Result {
private static final Result SUCCESS = new Result(ResultType.SUCCESS, null);
private static final Result RETRY = new Result(ResultType.RETRY, null);
private static final Result FAILURE = new Result(ResultType.FAILURE, null);
private final ResultType resultType;
private final RuntimeException runtimeException;
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException) {
this.resultType = resultType;
this.runtimeException = runtimeException;
}
/** Job completed successfully. */
public static Result success() {
return SUCCESS;
}
/** Job did not complete successfully, but it can be retried later. */
public static Result retry() {
return RETRY;
}
/** Job did not complete successfully and should not be tried again. Dependent jobs will also be failed.*/
public static Result failure() {
return FAILURE;
}
/** Same as {@link #failure()}, except the app should also crash with the provided exception. */
public static Result fatalFailure(@NonNull RuntimeException runtimeException) {
return new Result(ResultType.FAILURE, runtimeException);
}
boolean isSuccess() {
return resultType == ResultType.SUCCESS;
}
boolean isRetry() {
return resultType == ResultType.RETRY;
}
boolean isFailure() {
return resultType == ResultType.FAILURE;
}
@Nullable RuntimeException getException() {
return runtimeException;
}
private enum ResultType {
SUCCESS, FAILURE, RETRY
}
}
public static final class Parameters {

View File

@ -12,6 +12,14 @@ import org.thoughtcrime.securesms.util.WakeLockUtil;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* A thread that constantly checks for available {@link Job}s owned by the {@link JobController}.
* When one is available, this class will execute it and call the appropriate methods on
* {@link JobController} based on the result.
*
* {@link JobRunner} and {@link JobController} were written such that you should be able to have
* N concurrent {@link JobRunner}s operating over the same {@link JobController}.
*/
class JobRunner extends Thread {
private static final String TAG = JobRunner.class.getSimpleName();
@ -32,25 +40,28 @@ class JobRunner extends Thread {
@Override
public synchronized void run() {
//noinspection InfiniteLoopStatement
while (true) {
Job job = jobController.pullNextEligibleJobForExecution();
Job.Result result = run(job);
jobController.onJobFinished(job);
switch (result) {
case SUCCESS:
if (result.isSuccess()) {
jobController.onSuccess(job);
break;
case RETRY:
} else if (result.isRetry()) {
jobController.onRetry(job);
job.onRetry();
break;
case FAILURE:
} else if (result.isFailure()) {
List<Job> dependents = jobController.onFailure(job);
job.onCanceled();
Stream.of(dependents).forEach(Job::onCanceled);
break;
if (result.getException() != null) {
throw result.getException();
}
} else {
throw new AssertionError("Invalid job result!");
}
}
}
@ -60,7 +71,7 @@ class JobRunner extends Thread {
if (isJobExpired(job)) {
Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing after surpassing its lifespan."));
return Job.Result.FAILURE;
return Job.Result.failure();
}
Job.Result result = null;
@ -71,7 +82,7 @@ class JobRunner extends Thread {
result = job.run();
} catch (Exception e) {
Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing due to an unexpected exception."), e);
return Job.Result.FAILURE;
return Job.Result.failure();
} finally {
if (wakeLock != null) {
WakeLockUtil.release(wakeLock, job.getId());
@ -80,11 +91,12 @@ class JobRunner extends Thread {
printResult(job, result);
if (result == Job.Result.RETRY && job.getRunAttempt() + 1 >= job.getParameters().getMaxAttempts() &&
if (result.isRetry() &&
job.getRunAttempt() + 1 >= job.getParameters().getMaxAttempts() &&
job.getParameters().getMaxAttempts() != Job.Parameters.UNLIMITED)
{
Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing after surpassing its max number of attempts."));
return Job.Result.FAILURE;
return Job.Result.failure();
}
return result;
@ -101,7 +113,9 @@ class JobRunner extends Thread {
}
private void printResult(@NonNull Job job, @NonNull Job.Result result) {
if (result == Job.Result.FAILURE) {
if (result.getException() != null) {
Log.e(TAG, JobLogger.format(job, String.valueOf(id), "Job failed with a fatal exception. Crash imminent."));
} else if (result.isFailure()) {
Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Job failed."));
} else {
Log.i(TAG, JobLogger.format(job, String.valueOf(id), "Job finished with result: " + result));

View File

@ -19,14 +19,17 @@ public abstract class BaseJob extends Job {
public @NonNull Result run() {
try {
onRun();
return Result.SUCCESS;
return Result.success();
} catch (RuntimeException e) {
Log.e(TAG, "Encountered a fatal exception. Crash imminent.", e);
return Result.fatalFailure(e);
} catch (Exception e) {
if (onShouldRetry(e)) {
Log.i(TAG, JobLogger.format(this, "Encountered a retryable exception."), e);
return Result.RETRY;
return Result.retry();
} else {
Log.w(TAG, JobLogger.format(this, "Encountered a failing exception."), e);
return Result.FAILURE;
return Result.failure();
}
}
}