UnfinishedWorkListener should not take down the entire process when multiple processes race to initialize and enqueue work in WorkManager.
* Only register the listener in the designated process.
Test: Existing unit tests.
Bug: 333913329
Change-Id: If3121bcf8595fe64b9827839ca21a7db553067ab
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
index 014b025..35720c0 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
@@ -17,20 +17,53 @@
package androidx.work.impl
import android.content.Context
+import androidx.work.Configuration
+import androidx.work.Logger
import androidx.work.impl.background.systemalarm.RescheduleReceiver
import androidx.work.impl.utils.PackageManagerHelper
+import androidx.work.impl.utils.isDefaultProcess
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.retryWhen
-internal fun CoroutineScope.launchUnfinishedWorkListener(appContext: Context, db: WorkDatabase) =
- db.workSpecDao().hasUnfinishedWorkFlow()
- .conflate()
- .distinctUntilChanged()
- .onEach { hasUnfinishedWork ->
- PackageManagerHelper.setComponentEnabled(
- appContext, RescheduleReceiver::class.java, hasUnfinishedWork
- )
- }.launchIn(this)
+private val TAG = Logger.tagWithPrefix("UnfinishedWorkListener")
+
+// Backoff policies.
+private const val DELAY_MS = 30_000
+private val MAX_DELAY_MS = TimeUnit.HOURS.toMillis(1)
+
+/**
+ * Keeps track of unfinished work and enables [RescheduleReceiver] when applicable.
+ *
+ * Note: The listener is only ever registered in the designated process. This avoids interference
+ * from other processes given subsequent registrations are redundant.
+ */
+internal fun CoroutineScope.maybeLaunchUnfinishedWorkListener(
+ appContext: Context,
+ configuration: Configuration,
+ db: WorkDatabase
+) {
+ // Only register this in the designated process.
+ if (isDefaultProcess(appContext, configuration)) {
+ db.workSpecDao().hasUnfinishedWorkFlow()
+ .retryWhen { throwable, attempt ->
+ Logger.get().error(TAG, "Cannot check for unfinished work", throwable)
+ // Linear backoff is good enough here.
+ val delayBy = minOf(attempt * DELAY_MS, MAX_DELAY_MS)
+ delay(delayBy)
+ true
+ }
+ .conflate()
+ .distinctUntilChanged()
+ .onEach { hasUnfinishedWork ->
+ PackageManagerHelper.setComponentEnabled(
+ appContext, RescheduleReceiver::class.java, hasUnfinishedWork
+ )
+ }.launchIn(this)
+ }
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 27248a9..a8267c4 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -21,7 +21,7 @@
import static android.text.TextUtils.isEmpty;
import static androidx.work.ListenableFutureKt.executeAsync;
-import static androidx.work.impl.UnfinishedWorkListenerKt.launchUnfinishedWorkListener;
+import static androidx.work.impl.UnfinishedWorkListenerKt.maybeLaunchUnfinishedWorkListener;
import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
import static androidx.work.impl.WorkManagerImplExtKt.createWorkManagerScope;
import static androidx.work.impl.WorkerUpdater.enqueueUniquelyNamedPeriodic;
@@ -256,7 +256,7 @@
workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration);
// Checks for app force stops.
mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
- launchUnfinishedWorkListener(mWorkManagerScope, mContext, workDatabase);
+ maybeLaunchUnfinishedWorkListener(mWorkManagerScope, mContext, configuration, workDatabase);
}
/**