Fix some race conditions in WorkerWrapper.
- check for incorrect status should happen in same
transaction as fetching the workspec
- shortcut code in Schedulers where there are no
eligible workspecs
This was discovered as part of the change that disabled
logging (ag/4407764) causing the Sherlock Holmes
integration app test to fail or become flaky.
Test: Updated and ran tests.
Change-Id: I178cf9051f98f4fd301d5a6d5325aa0a958abb12
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index b389e19..c9a8e64 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -576,16 +576,34 @@
@Test
@SmallTest
public void testScheduler() {
- OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
- insertWork(work);
- Scheduler mockScheduler = mock(Scheduler.class);
+ OneTimeWorkRequest prerequisiteWork =
+ new OneTimeWorkRequest.Builder(TestWorker.class).build();
+ OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
+ .setInitialState(BLOCKED).build();
+ Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
- new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
- .withSchedulers(Collections.singletonList(mockScheduler))
+ mDatabase.beginTransaction();
+ try {
+ insertWork(prerequisiteWork);
+ insertWork(work);
+ mDependencyDao.insertDependency(dependency);
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+
+ new WorkerWrapper.Builder(
+ mContext,
+ mConfiguration,
+ mDatabase,
+ prerequisiteWork.getStringId())
+ .withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
- verify(mockScheduler).schedule();
+ ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+ verify(mMockScheduler).schedule(captor.capture());
+ assertThat(captor.getValue().id, is(work.getStringId()));
}
@Test
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
index a31bf46..041f788 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -65,43 +65,38 @@
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers) {
-
- WorkSpecDao workSpecDao = workDatabase.workSpecDao();
- List<WorkSpec> eligibleWorkSpecs =
- workSpecDao.getEligibleWorkForScheduling(
- configuration.getMaxSchedulerLimit());
- scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
- }
-
-
-
- private static void scheduleInternal(
- @NonNull WorkDatabase workDatabase,
- List<Scheduler> schedulers,
- List<WorkSpec> workSpecs) {
-
- if (workSpecs == null || schedulers == null) {
+ if (schedulers == null || schedulers.size() == 0) {
return;
}
- long now = System.currentTimeMillis();
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
- // Mark all the WorkSpecs as scheduled.
- // Calls to Scheduler#schedule() could potentially result in more schedules
- // on a separate thread. Therefore, this needs to be done first.
+ List<WorkSpec> eligibleWorkSpecs;
+
workDatabase.beginTransaction();
try {
- for (WorkSpec workSpec : workSpecs) {
- workSpecDao.markWorkSpecScheduled(workSpec.id, now);
+ eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(
+ configuration.getMaxSchedulerLimit());
+ if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
+ long now = System.currentTimeMillis();
+
+ // Mark all the WorkSpecs as scheduled.
+ // Calls to Scheduler#schedule() could potentially result in more schedules
+ // on a separate thread. Therefore, this needs to be done first.
+ for (WorkSpec workSpec : eligibleWorkSpecs) {
+ workSpecDao.markWorkSpecScheduled(workSpec.id, now);
+ }
}
workDatabase.setTransactionSuccessful();
} finally {
workDatabase.endTransaction();
}
- WorkSpec[] eligibleWorkSpecsArray = workSpecs.toArray(new WorkSpec[0]);
- // Delegate to the underlying scheduler.
- for (Scheduler scheduler : schedulers) {
- scheduler.schedule(eligibleWorkSpecsArray);
+
+ if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
+ WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
+ // Delegate to the underlying scheduler.
+ for (Scheduler scheduler : schedulers) {
+ scheduler.schedule(eligibleWorkSpecsArray);
+ }
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index eed0ca4..d510e30 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -97,18 +97,28 @@
return;
}
- mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
- if (mWorkSpec == null) {
- Log.e(TAG, String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
- notifyListener(false, false);
- return;
- }
+ mWorkDatabase.beginTransaction();
+ try {
+ mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
+ if (mWorkSpec == null) {
+ Log.e(TAG, String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
+ notifyListener(false, false);
+ return;
+ }
- // Do a quick check to make sure we don't need to bail out in case this work is already
- // running, finished, or is blocked.
- if (mWorkSpec.state != ENQUEUED) {
- notifyIncorrectStatus();
- return;
+ // Do a quick check to make sure we don't need to bail out in case this work is already
+ // running, finished, or is blocked.
+ if (mWorkSpec.state != ENQUEUED) {
+ notifyIncorrectStatus();
+ mWorkDatabase.setTransactionSuccessful();
+ return;
+ }
+
+ // Needed for nested transactions, such as when we're in a dependent work request when
+ // using a SynchronousExecutor.
+ mWorkDatabase.setTransactionSuccessful();
+ } finally {
+ mWorkDatabase.endTransaction();
}
// Merge inputs. This can be potentially expensive code, so this should not be done inside
@@ -283,9 +293,9 @@
if (currentState == ENQUEUED) {
mWorkSpecDao.setState(RUNNING, mWorkSpecId);
mWorkSpecDao.incrementWorkSpecRunAttemptCount(mWorkSpecId);
- mWorkDatabase.setTransactionSuccessful();
setToRunning = true;
}
+ mWorkDatabase.setTransactionSuccessful();
} finally {
mWorkDatabase.endTransaction();
}