From 81fc99724dbbdea8f8972678a18864000e9b2b7c Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 6 Mar 2023 10:48:18 -0500 Subject: [PATCH] Move the Job#onSubmit call to be outside of the JobController lock. --- .../securesms/jobmanager/JobController.java | 110 ++++++++++-------- 1 file changed, 62 insertions(+), 48 deletions(-) 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 dede570bf..934dd00ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -80,66 +80,80 @@ class JobController { } @WorkerThread - synchronized void submitNewJobChain(@NonNull List> chain) { - chain = Stream.of(chain).filterNot(List::isEmpty).toList(); + void submitNewJobChain(@NonNull List> chain) { + synchronized (this) { + chain = Stream.of(chain).filterNot(List::isEmpty).toList(); - if (chain.isEmpty()) { - Log.w(TAG, "Tried to submit an empty job chain. Skipping."); - return; + if (chain.isEmpty()) { + Log.w(TAG, "Tried to submit an empty job chain. Skipping."); + return; + } + + if (chainExceedsMaximumInstances(chain)) { + Job solo = chain.get(0).get(0); + jobTracker.onStateChange(solo, JobTracker.JobState.IGNORED); + Log.w(TAG, JobLogger.format(solo, "Already at the max instance count. Factory limit: " + solo.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + solo.getParameters().getMaxInstancesForQueue() + ". Skipping.")); + return; + } + + insertJobChain(chain); + scheduleJobs(chain.get(0)); } - if (chainExceedsMaximumInstances(chain)) { - Job solo = chain.get(0).get(0); - jobTracker.onStateChange(solo, JobTracker.JobState.IGNORED); - Log.w(TAG, JobLogger.format(solo, "Already at the max instance count. Factory limit: " + solo.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + solo.getParameters().getMaxInstancesForQueue() + ". Skipping.")); - return; - } - - insertJobChain(chain); + // We have no control over what happens in jobs' onSubmit method, so we drop our lock to reduce the possibility of a deadlock triggerOnSubmit(chain); - notifyAll(); - scheduleJobs(chain.get(0)); + + synchronized (this) { + notifyAll(); + } } @WorkerThread - synchronized void submitJobWithExistingDependencies(@NonNull Job job, @NonNull Collection dependsOn, @Nullable String dependsOnQueue) { + void submitJobWithExistingDependencies(@NonNull Job job, @NonNull Collection dependsOn, @Nullable String dependsOnQueue) { List> chain = Collections.singletonList(Collections.singletonList(job)); - if (chainExceedsMaximumInstances(chain)) { - jobTracker.onStateChange(job, JobTracker.JobState.IGNORED); - Log.w(TAG, JobLogger.format(job, "Already at the max instance count. Factory limit: " + job.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + job.getParameters().getMaxInstancesForQueue() + ". Skipping.")); - return; + synchronized (this) { + if (chainExceedsMaximumInstances(chain)) { + jobTracker.onStateChange(job, JobTracker.JobState.IGNORED); + Log.w(TAG, JobLogger.format(job, "Already at the max instance count. Factory limit: " + job.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + job.getParameters().getMaxInstancesForQueue() + ". Skipping.")); + return; + } + + Set allDependsOn = new HashSet<>(dependsOn); + Set aliveDependsOn = Stream.of(dependsOn) + .filter(id -> jobStorage.getJobSpec(id) != null) + .collect(Collectors.toSet()); + + if (dependsOnQueue != null) { + List inQueue = Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) + .map(JobSpec::getId) + .toList(); + + allDependsOn.addAll(inQueue); + aliveDependsOn.addAll(inQueue); + } + + if (jobTracker.haveAnyFailed(allDependsOn)) { + Log.w(TAG, "This job depends on a job that failed! Failing this job immediately."); + List dependents = onFailure(job); + job.setContext(application); + job.onFailure(); + Stream.of(dependents).forEach(Job::onFailure); + return; + } + + FullSpec fullSpec = buildFullSpec(job, aliveDependsOn); + jobStorage.insertJobs(Collections.singletonList(fullSpec)); + + scheduleJobs(Collections.singletonList(job)); } - Set allDependsOn = new HashSet<>(dependsOn); - Set aliveDependsOn = Stream.of(dependsOn) - .filter(id -> jobStorage.getJobSpec(id) != null) - .collect(Collectors.toSet()); - - if (dependsOnQueue != null) { - List inQueue = Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) - .map(JobSpec::getId) - .toList(); - - allDependsOn.addAll(inQueue); - aliveDependsOn.addAll(inQueue); - } - - if (jobTracker.haveAnyFailed(allDependsOn)) { - Log.w(TAG, "This job depends on a job that failed! Failing this job immediately."); - List dependents = onFailure(job); - job.setContext(application); - job.onFailure(); - Stream.of(dependents).forEach(Job::onFailure); - return; - } - - FullSpec fullSpec = buildFullSpec(job, aliveDependsOn); - jobStorage.insertJobs(Collections.singletonList(fullSpec)); - - scheduleJobs(Collections.singletonList(job)); + // We have no control over what happens in jobs' onSubmit method, so we drop our lock to reduce the possibility of a deadlock triggerOnSubmit(chain); - notifyAll(); + + synchronized (this) { + notifyAll(); + } } @WorkerThread