mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Add support for passing data between jobs.
This commit is contained in:
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -59,6 +60,14 @@ public abstract class Job {
|
||||
return nextRunAttemptTime;
|
||||
}
|
||||
|
||||
public final @Nullable Data getInputData() {
|
||||
return parameters.getInputData();
|
||||
}
|
||||
|
||||
public final @NonNull Data requireInputData() {
|
||||
return Objects.requireNonNull(parameters.getInputData());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is already called by {@link JobController} during job submission, but if you ever run a
|
||||
* job without submitting it to the {@link JobManager}, then you'll need to invoke this yourself.
|
||||
@@ -140,21 +149,28 @@ public abstract class Job {
|
||||
|
||||
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 static final Result SUCCESS_NO_DATA = new Result(ResultType.SUCCESS, null, null);
|
||||
private static final Result RETRY = new Result(ResultType.RETRY, null, null);
|
||||
private static final Result FAILURE = new Result(ResultType.FAILURE, null, null);
|
||||
|
||||
private final ResultType resultType;
|
||||
private final RuntimeException runtimeException;
|
||||
private final Data outputData;
|
||||
|
||||
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException) {
|
||||
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException, @Nullable Data outputData) {
|
||||
this.resultType = resultType;
|
||||
this.runtimeException = runtimeException;
|
||||
this.outputData = outputData;
|
||||
}
|
||||
|
||||
/** Job completed successfully. */
|
||||
public static Result success() {
|
||||
return SUCCESS;
|
||||
return SUCCESS_NO_DATA;
|
||||
}
|
||||
|
||||
/** Job completed successfully and wants to provide some output data. */
|
||||
public static Result success(@Nullable Data outputData) {
|
||||
return new Result(ResultType.SUCCESS, null, outputData);
|
||||
}
|
||||
|
||||
/** Job did not complete successfully, but it can be retried later. */
|
||||
@@ -169,7 +185,7 @@ public abstract class Job {
|
||||
|
||||
/** 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);
|
||||
return new Result(ResultType.FAILURE, runtimeException, null);
|
||||
}
|
||||
|
||||
boolean isSuccess() {
|
||||
@@ -188,6 +204,10 @@ public abstract class Job {
|
||||
return runtimeException;
|
||||
}
|
||||
|
||||
@Nullable Data getOutputData() {
|
||||
return outputData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
switch (resultType) {
|
||||
@@ -224,6 +244,7 @@ public abstract class Job {
|
||||
private final int maxInstances;
|
||||
private final String queue;
|
||||
private final List<String> constraintKeys;
|
||||
private final Data inputData;
|
||||
|
||||
private Parameters(@NonNull String id,
|
||||
long createTime,
|
||||
@@ -232,7 +253,8 @@ public abstract class Job {
|
||||
long maxBackoff,
|
||||
int maxInstances,
|
||||
@Nullable String queue,
|
||||
@NonNull List<String> constraintKeys)
|
||||
@NonNull List<String> constraintKeys,
|
||||
@Nullable Data inputData)
|
||||
{
|
||||
this.id = id;
|
||||
this.createTime = createTime;
|
||||
@@ -242,6 +264,7 @@ public abstract class Job {
|
||||
this.maxInstances = maxInstances;
|
||||
this.queue = queue;
|
||||
this.constraintKeys = constraintKeys;
|
||||
this.inputData = inputData;
|
||||
}
|
||||
|
||||
@NonNull String getId() {
|
||||
@@ -276,8 +299,12 @@ public abstract class Job {
|
||||
return constraintKeys;
|
||||
}
|
||||
|
||||
@Nullable Data getInputData() {
|
||||
return inputData;
|
||||
}
|
||||
|
||||
public Builder toBuilder() {
|
||||
return new Builder(id, createTime, maxBackoff, lifespan, maxAttempts, maxInstances, queue, constraintKeys);
|
||||
return new Builder(id, createTime, maxBackoff, lifespan, maxAttempts, maxInstances, queue, constraintKeys, inputData);
|
||||
}
|
||||
|
||||
|
||||
@@ -291,13 +318,14 @@ public abstract class Job {
|
||||
private int maxInstances;
|
||||
private String queue;
|
||||
private List<String> constraintKeys;
|
||||
private Data inputData;
|
||||
|
||||
public Builder() {
|
||||
this(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
Builder(@NonNull String id) {
|
||||
this(id, System.currentTimeMillis(), TimeUnit.SECONDS.toMillis(30), IMMORTAL, 1, UNLIMITED, null, new LinkedList<>());
|
||||
this(id, System.currentTimeMillis(), TimeUnit.SECONDS.toMillis(30), IMMORTAL, 1, UNLIMITED, null, new LinkedList<>(), null);
|
||||
}
|
||||
|
||||
private Builder(@NonNull String id,
|
||||
@@ -307,7 +335,8 @@ public abstract class Job {
|
||||
int maxAttempts,
|
||||
int maxInstances,
|
||||
@Nullable String queue,
|
||||
@NonNull List<String> constraintKeys)
|
||||
@NonNull List<String> constraintKeys,
|
||||
@Nullable Data inputData)
|
||||
{
|
||||
this.id = id;
|
||||
this.createTime = createTime;
|
||||
@@ -317,6 +346,7 @@ public abstract class Job {
|
||||
this.maxInstances = maxInstances;
|
||||
this.queue = queue;
|
||||
this.constraintKeys = constraintKeys;
|
||||
this.inputData = inputData;
|
||||
}
|
||||
|
||||
/** Should only be invoked by {@link JobController} */
|
||||
@@ -394,8 +424,17 @@ public abstract class Job {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input data that will be made availabe to the job when it is run.
|
||||
* Should only be set by {@link JobController}.
|
||||
*/
|
||||
@NonNull Builder setInputData(@Nullable Data inputData) {
|
||||
this.inputData = inputData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Parameters build() {
|
||||
return new Parameters(id, createTime, lifespan, maxAttempts, maxBackoff, maxInstances, queue, constraintKeys);
|
||||
return new Parameters(id, createTime, lifespan, maxAttempts, maxBackoff, maxInstances, queue, constraintKeys, inputData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,17 @@ class JobController {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
synchronized void onSuccess(@NonNull Job job) {
|
||||
synchronized void onSuccess(@NonNull Job job, @Nullable Data outputData) {
|
||||
if (outputData != null) {
|
||||
List<JobSpec> updates = Stream.of(jobStorage.getDependencySpecsThatDependOnJob(job.getId()))
|
||||
.map(DependencySpec::getJobId)
|
||||
.map(jobStorage::getJobSpec)
|
||||
.map(jobSpec -> mapToJobWithInputData(jobSpec, outputData))
|
||||
.toList();
|
||||
|
||||
jobStorage.updateJobs(updates);
|
||||
}
|
||||
|
||||
jobStorage.deleteJob(job.getId());
|
||||
jobTracker.onStateChange(job, JobTracker.JobState.SUCCESS);
|
||||
notifyAll();
|
||||
@@ -321,6 +331,7 @@ class JobController {
|
||||
job.getParameters().getLifespan(),
|
||||
job.getParameters().getMaxInstances(),
|
||||
dataSerializer.serialize(job.serialize()),
|
||||
null,
|
||||
false);
|
||||
|
||||
List<ConstraintSpec> constraintSpecs = Stream.of(job.getParameters().getConstraintKeys())
|
||||
@@ -402,6 +413,7 @@ class JobController {
|
||||
.setQueue(jobSpec.getQueueKey())
|
||||
.setConstraints(Stream.of(constraintSpecs).map(ConstraintSpec::getFactoryKey).toList())
|
||||
.setMaxBackoff(jobSpec.getMaxBackoff())
|
||||
.setInputData(jobSpec.getSerializedInputData() != null ? dataSerializer.deserialize(jobSpec.getSerializedInputData()) : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -413,6 +425,22 @@ class JobController {
|
||||
return currentTime + actualBackoff;
|
||||
}
|
||||
|
||||
private @NonNull JobSpec mapToJobWithInputData(@NonNull JobSpec jobSpec, @NonNull Data inputData) {
|
||||
return new JobSpec(jobSpec.getId(),
|
||||
jobSpec.getFactoryKey(),
|
||||
jobSpec.getQueueKey(),
|
||||
jobSpec.getCreateTime(),
|
||||
jobSpec.getNextRunAttemptTime(),
|
||||
jobSpec.getRunAttempt(),
|
||||
jobSpec.getMaxAttempts(),
|
||||
jobSpec.getMaxBackoff(),
|
||||
jobSpec.getLifespan(),
|
||||
jobSpec.getMaxInstances(),
|
||||
jobSpec.getSerializedData(),
|
||||
dataSerializer.serialize(inputData),
|
||||
jobSpec.isRunning());
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
void onEmpty();
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public class JobMigrator {
|
||||
jobSpec.getLifespan(),
|
||||
jobSpec.getMaxInstances(),
|
||||
dataSerializer.serialize(updatedJobData.getData()),
|
||||
jobSpec.getSerializedInputData(),
|
||||
jobSpec.isRunning());
|
||||
|
||||
iter.set(updatedJobSpec);
|
||||
|
||||
@@ -48,7 +48,7 @@ class JobRunner extends Thread {
|
||||
jobController.onJobFinished(job);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
jobController.onSuccess(job);
|
||||
jobController.onSuccess(job, result.getOutputData());
|
||||
} else if (result.isRetry()) {
|
||||
jobController.onRetry(job);
|
||||
job.onRetry();
|
||||
|
||||
@@ -19,6 +19,7 @@ public final class JobSpec {
|
||||
private final long lifespan;
|
||||
private final int maxInstances;
|
||||
private final String serializedData;
|
||||
private final String serializedInputData;
|
||||
private final boolean isRunning;
|
||||
|
||||
public JobSpec(@NonNull String id,
|
||||
@@ -32,20 +33,22 @@ public final class JobSpec {
|
||||
long lifespan,
|
||||
int maxInstances,
|
||||
@NonNull String serializedData,
|
||||
@Nullable String serializedInputData,
|
||||
boolean isRunning)
|
||||
{
|
||||
this.id = id;
|
||||
this.factoryKey = factoryKey;
|
||||
this.queueKey = queueKey;
|
||||
this.createTime = createTime;
|
||||
this.nextRunAttemptTime = nextRunAttemptTime;
|
||||
this.maxBackoff = maxBackoff;
|
||||
this.runAttempt = runAttempt;
|
||||
this.maxAttempts = maxAttempts;
|
||||
this.lifespan = lifespan;
|
||||
this.maxInstances = maxInstances;
|
||||
this.serializedData = serializedData;
|
||||
this.isRunning = isRunning;
|
||||
this.id = id;
|
||||
this.factoryKey = factoryKey;
|
||||
this.queueKey = queueKey;
|
||||
this.createTime = createTime;
|
||||
this.nextRunAttemptTime = nextRunAttemptTime;
|
||||
this.maxBackoff = maxBackoff;
|
||||
this.runAttempt = runAttempt;
|
||||
this.maxAttempts = maxAttempts;
|
||||
this.lifespan = lifespan;
|
||||
this.maxInstances = maxInstances;
|
||||
this.serializedData = serializedData;
|
||||
this.serializedInputData = serializedInputData;
|
||||
this.isRunning = isRunning;
|
||||
}
|
||||
|
||||
public @NonNull String getId() {
|
||||
@@ -92,6 +95,10 @@ public final class JobSpec {
|
||||
return serializedData;
|
||||
}
|
||||
|
||||
public @Nullable String getSerializedInputData() {
|
||||
return serializedInputData;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
@@ -112,18 +119,19 @@ public final class JobSpec {
|
||||
Objects.equals(id, jobSpec.id) &&
|
||||
Objects.equals(factoryKey, jobSpec.factoryKey) &&
|
||||
Objects.equals(queueKey, jobSpec.queueKey) &&
|
||||
Objects.equals(serializedData, jobSpec.serializedData);
|
||||
Objects.equals(serializedData, jobSpec.serializedData) &&
|
||||
Objects.equals(serializedInputData, jobSpec.serializedInputData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, lifespan, maxInstances, serializedData, isRunning);
|
||||
return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, lifespan, maxInstances, serializedData, serializedInputData, isRunning);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return String.format("id: JOB::%s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | maxBackoff: %d | maxInstances: %d | lifespan: %d | isRunning: %b | data: %s",
|
||||
id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, maxInstances, lifespan, isRunning, serializedData);
|
||||
return String.format("id: JOB::%s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | maxBackoff: %d | maxInstances: %d | lifespan: %d | isRunning: %b | data: %s | inputData: %s",
|
||||
id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, maxInstances, lifespan, isRunning, serializedData, serializedInputData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ final class WorkManagerDatabase extends SQLiteOpenHelper {
|
||||
TimeUnit.DAYS.toMillis(1),
|
||||
Job.Parameters.UNLIMITED,
|
||||
dataSerializer.serialize(DataMigrator.convert(data)),
|
||||
null,
|
||||
false);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user