Search code examples
androidkotlinandroid-workmanager

Android WorkManager sometimes retries after a success


I have a SyncWorker class I am using to capture offline actions and relay them to the server. It works, but I am currently trying to improve the success rate by investigating cases of failed sync operations captured in logs. A substantial number of these failures were reported as missing the data provided for sync (stored in a local database). I initially assumed these to be indications of local database failures, but upon investigating specific cases of this failure I found that every single one (about a dozen investigated so far) was immediately preceded by the same operation succeeding. I am at a loss as to what may be causing these extra work instances, but the order of operations seems to be something like this:

  1. Action performed in-app, work enqueued
  2. Work runs when the app has an internet connection
  3. Work completes successfully (HTTP 200-300 from server)
  4. App performs "cleanup" by deleting the data that was synced
  5. Extra work instance runs, finding no data (as it was deleted) and reporting a failure

The success case is currently set to return Result.success(). The corresponding doWork contents (located near the end of the method) are here:

    if (response.isSuccessful) {
        performCleanup(recordId, databaseResult.data)

        Log.i(TAG, "$logInfo: Sync request succeeded")
        return Result.success()
    }

And the subsequent triggered failure, near the beginning of doWork:

    if (databaseResult.data == null) {
        Log.e(TAG, "$logInfo: Model data not found, stopping worker")
        return Result.failure()
    }

Here is an example of the issue as it presents in logs, for ID 62bfd14d776c5e7f458ccb2a (filtered somewhat):

Logs for item 62bfd14d776c5e7f458ccb2a

And here is an example of enqueuing the work:

    val constraints = Constraints.Builder().apply {
        setRequiredNetworkType(NetworkType.CONNECTED)
    }.build()

    val request = OneTimeWorkRequestBuilder<SyncWorker>().apply {
        setInputData(inputData)
        setConstraints(constraints)
        addTag(SyncWorker.syncWorkTag)
    }.build()

    val manager = WorkManager.getInstance(applicationContext)
    manager.enqueue(request)

The issue is occasional (about 0.33% of all sync instances), but I'd love to root out these false negatives. I've been combing logs for the past few days looking for some kind of pattern to no avail (various app versions, Android versions, devices, etc.) and the developer pages for WorkManager have not gotten me anywhere either. I've been finding it hard to search for any issues related to this as well, getting mostly discussions of users that want their work to requeue itself. Any advice that could point me towards the why of this behavior would be greatly appreciated. Thanks.


Solution

  • Tested with work manager 2.7.0.

    After overriding onStopped() with log, I noticed that onStopped() is called while worker is active.

    After worker returns Result.success(), the same worker is rescheduled again.

    Assuming you're not the one that cancels the worker & reschedule, I went to documentation and after eliminating other possible reasons, only one left:

    Your work's constraints are no longer met.

    So this is what I figured as network can be unstable for some users:

    1. Your scheduled a worker with the constraint of internet connection.
    2. Your worker starts.
    3. Internet is momentarily inturropted.
    4. Work Manager stopping your worker since the constraints are no longer met and it's notifying you by calling onStopped().
    5. Your worker finishes successfully.
    6. Work Manager is "doing you a favor" by rescheduling your worker when constraints are met again.