diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java index 014cecc10d..a8ad2ae04c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -17,11 +17,13 @@ import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; import org.thoughtcrime.securesms.util.Debouncer; import org.thoughtcrime.securesms.util.FeatureFlags; +import org.thoughtcrime.securesms.util.SetUtil; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -111,17 +113,29 @@ class JobController { return; } - Set dependsOnSet = Stream.of(dependsOn) - .filter(id -> jobStorage.getJobSpec(id) != null) - .collect(Collectors.toSet()); + Set allDependsOn = new HashSet<>(dependsOn); + Set aliveDependsOn = Stream.of(dependsOn) + .filter(id -> jobStorage.getJobSpec(id) != null) + .collect(Collectors.toSet()); if (dependsOnQueue != null) { - dependsOnSet.addAll(Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) - .map(JobSpec::getId) - .toList()); + List inQueue = Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) + .map(JobSpec::getId) + .toList(); + + allDependsOn.addAll(inQueue); + aliveDependsOn.addAll(inQueue); } - FullSpec fullSpec = buildFullSpec(job, dependsOnSet); + if (jobTracker.haveAnyFailed(allDependsOn)) { + Log.w(TAG, "This job depends on a job that failed! Failing this job immediately."); + List dependents = onFailure(job); + job.onFailure(); + Stream.of(dependents).forEach(Job::onFailure); + return; + } + + FullSpec fullSpec = buildFullSpec(job, aliveDependsOn); jobStorage.insertJobs(Collections.singletonList(fullSpec)); scheduleJobs(Collections.singletonList(job)); @@ -145,8 +159,9 @@ class JobController { Log.w(TAG, JobLogger.format(job, "Job failed.")); job.cancel(); + List dependents = onFailure(job); job.onFailure(); - onFailure(job); + Stream.of(dependents).forEach(Job::onFailure); } else { Log.w(TAG, "Tried to cancel JOB::" + id + ", but it could not be found."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java index d5ce30641d..4604a3176e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobTracker.java @@ -10,6 +10,8 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.LRUCache; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -66,6 +68,22 @@ public class JobTracker { }); } + /** + * Returns whether or not any jobs referenced by the IDs in the provided collection have failed. + * Keep in mind that this is not perfect -- our data is only kept in memory, and even then only up + * to a certain limit. + */ + synchronized boolean haveAnyFailed(@NonNull Collection jobIds) { + for (String jobId : jobIds) { + JobInfo jobInfo = jobInfos.get(jobId); + if (jobInfo != null && jobInfo.getJobState() == JobState.FAILURE) { + return true; + } + } + + return false; + } + private @NonNull JobInfo getOrCreateJobInfo(@NonNull Job job) { JobInfo jobInfo = jobInfos.get(job.getId());