Reputation: 4840
I've encountered behavior of WorkManager
(version 2.0.1
) that I cannot understand. Unfortunately this behavior leads to issues in my application. To illustrate my problem, I will use a simpler example.
Let's assume with have three Worker
implementations - UniqueWorker1
, UniqueWorker2
and FinishingWorker
.
class UniqueWorker1(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
if (runAttemptCount == 0) {
Log.d("UniqueWorker1", "First try.")
return Result.retry()
}
Log.d("UniqueWorker1", "Second try")
return Result.success()
}
}
class UniqueWorker2(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
Log.d("UniqueWorker2", "doWork")
return Result.success()
}
}
class FinishingWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
Log.d("FinishingWorker", "doWork")
return Result.success()
}
}
As you can see, the first worker succeeds after second run attempt. Others just log the message and return successful result.
Now I'm enqueueing these workers in two ways. Firstly I start UniqueWorker1
as the unique work and tell WorkManager
to run FinishingWorker
when UniqueWorker1
succeeds.
val uniqueWorker1 = OneTimeWorkRequest.Builder(UniqueWorker1::class.java).build()
val finishingWorker = OneTimeWorkRequest.Builder(FinishingWorker::class.java).build()
val uniqueWorkContinuation = WorkManager.getInstance()
.beginUniqueWork("UniqueWorker", ExistingWorkPolicy.KEEP, uniqueWorker1)
val continuations = listOf(uniqueWorkContinuation)
WorkContinuation.combine(continuations)
.then(finishingWorker)
.enqueue()
The second way looks like that: I combine unique works of UniqueWork1
and UniqueWork2
. Then I tell WorkManager
to run FinishingWorker
when both works complete.
val uniqueWorker1 = OneTimeWorkRequest.Builder(UniqueWorker1::class.java).build()
val uniqueWorker2 = OneTimeWorkRequest.Builder(UniqueWorker2::class.java).build()
val finishingWorker = OneTimeWorkRequest.Builder(FinishingWorker::class.java).build()
val uniqueWorkContinuation1 = WorkManager.getInstance()
.beginUniqueWork("UniqueWorker1", ExistingWorkPolicy.KEEP, uniqueWorker1)
val uniqueWorkContinuation2 = WorkManager.getInstance()
.beginUniqueWork("UniqueWorker2", ExistingWorkPolicy.KEEP, uniqueWorker2)
val continuations = listOf(uniqueWorkContinuation1, uniqueWorkContinuation2)
WorkContinuation.combine(continuations)
.then(finishingWorker)
.enqueue()
Now imagine such case. I start workers in a first way. The UniqueWorker1
retries because it's his first run attempt. We have 30 seconds to wait (with the default BackoffPolicy
values). Before it retries, I start workers in a second way. The UniqueWorker1
is not enqueued (because it has been already started) but UniqueWorker2
starts its work. Now after 30 seconds, UniqueWorker1
succeeds, the WorkManager
starts FinishingWorker
, because of the first way work combination. The problem is that WorkManager
doesn't start FinishingWorker
for the second time. Why it should start FinishingWorker
for the second time? Because work combination in a second way tells to start FinishingWorker
when UniqueWorker1
succeeds and UniqueWorker2
succeeds. UniqueWorker2
succeeded immediately and UniqueWorker1
succeeded after 30s.
At the beginning I thought that when WorkerManager
sees that when one of the works in work combination is already enqueued, it won't finish and won't run request from then
method. But I checked this in a simpler example and it worked.
So the output of the situation I described looks like that:
// Run workers in a first way
D/UniqueWorker1: First try.
I/WM-WorkerWrapper: Worker result RETRY for Work [ id=7e2fe6b4-4c8e-42af-8a13-244c0cc30059, tags={ UniqueWorker1 } ]
// Run workers in a second way before 30s will pass
E/WM-EnqueueRunnable: Prerequisite b98a6246-28d4-4b25-ae50-ec3dda6cd3ac doesn't exist; not enqueuing
E/WM-EnqueueRunnable: Prerequisite 02d017e7-30b0-4038-9b44-a6217da3979c doesn't exist; not enqueuing
D/UniqueWorker2: doWork
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=ce9810cd-9565-4cad-b7d1-9556a01eae67, tags={ UniqueWorker2 } ]
// 30s passed
D/UniqueWorker1: Second try
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=7e2fe6b4-4c8e-42af-8a13-244c0cc30059, tags={ UniqueWorker1 } ]
I/WM-WorkerWrapper: Setting status to enqueued for c2ac89de-3a67-496f-93e6-037d85d11646
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=c2ac89de-3a67-496f-93e6-037d85d11646, tags={ androidx.work.impl.workers.CombineContinuationsWorker } ]
I/WM-WorkerWrapper: Setting status to enqueued for 3287bbec-b1c4-488a-b64b-35e0e6b58137
D/FinishingWorker: doWork
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=3287bbec-b1c4-488a-b64b-35e0e6b58137, tags={ FinishingWorker } ]
As you can see FinishingWorker
was enqueued only once. Sorry for the long explanation but this example shows exactly my problem. It's a serious issue for me because some of the important workers are not enqueued.
Question
Can someone explain the reason of such behavior? Is it intended behavior of WorkManager
or is it a bug?
Upvotes: 4
Views: 1077
Reputation: 563
I think there is some confusion around how you are approaching unique work so it's a little hard to reason about what you're doing.
You've got the following sequences:
First of all, sequence 1 has nothing to do with the rest of your code. Its got its own name; nothing about it has anything to do with the rest of it. It will try to execute independently and will succeed on its second try when it returns a success and then executes FinishingWorker.
So let's move on to the rest of it. This code will display the error message "Prerequisite [something] does not exist; not enqueuing" if you run your second method twice in a row (without even enqueuing the first sequence). That's because you are try to enqueue the UniqueWorkers twice with an ExistingWorkPolicy.KEEP. The second time, the policy will kick in and not enqueue anything new. At that point, when you decide to enqueue the FinishingWorker, it will have no parents. SO this behavior is working as intended.
It looks like you have some confusion about how unique works are supposed to work. I think the short answer for you is that each logical grouping of work should all have the same unique name. Otherwise you will run into strange issues like this. You can, for example, rewrite the second method to be something like this:
WorkManager.getInstance(context)
.beginUniqueWork("second_method_name", KEEP, listOf(uniqueWork1, uniqueWork2))
.then(finishingWork)
.enqueue()
I'd suggest further reading here: https://developer.android.com/topic/libraries/architecture/workmanager/how-to/unique-work and also the API documentation for:
public abstract WorkContinuation beginUniqueWork (String uniqueWorkName,
ExistingWorkPolicy existingWorkPolicy,
List<OneTimeWorkRequest> work)
Upvotes: 5