[GH] Multiplatformize concurrent API usage using Stately
Test: ./gradlew test connectedCheck
Bug: 270612487
This is an imported pull request from https://github.com/androidx/androidx/pull/577.
Resolves #577
Github-Pr-Head-Sha: 62541e8f3e58143fce2dcb15c6216b3e287cf87a
GitOrigin-RevId: 4918bcfb7d5f267f3a2de764b2813b375404b6f8
Change-Id: Ie1d1b4ce9aabfeb78a75b09c932bbad5dae95d49
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c7f7ee3..d57b8e5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -57,6 +57,7 @@
skiko = "0.7.7"
spdxGradlePlugin = "0.1.0"
sqldelight = "1.3.0"
+stately = "2.0.0-rc3"
retrofit = "2.7.2"
wire = "4.7.0"
@@ -262,6 +263,8 @@
sqldelightAndroid = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelightCoroutinesExt = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version = "3.36.0" }
+statelyConcurrency = { module = "co.touchlab:stately-concurrency", version.ref = "stately" }
+statelyConcurrentCollections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "stately" }
testCore = { module = "androidx.test:core", version.ref = "androidxTestCore" }
testCoreKtx = { module = "androidx.test:core-ktx", version.ref = "androidxTestCore" }
testExtJunit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index 179d9b0..c1636dc 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -12230,6 +12230,42 @@
=CmMl
-----END PGP PUBLIC KEY BLOCK-----
+pub DE453E55DC86FC9B
+uid Touchlab <[email protected]>
+
+sub A947A3FCB1697B4F
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBF7H/6gBCACbEuIbxWAfHEYViPqdpwxDYauxsYwk6FgA9sSO1nS95KRwx+Cs
+X6F8nRGnfLtbo6Ffcp6r58fNi9RvY7ueRGiL0kQd6c5GYx6dH1b91Q1qrdVOeEdj
+vjHNVVXAlk1TN2oxFB81cz737cv2CTX1ibEO+qn8oxwOssgNO8ic6szJGorFur/K
+pCin+E1orZiL52+aSNtOsmzLW7qmL2VuDmoQ5guPfX7l6fioCwnUB9VA2LhD2Bm3
+oV4IhhH246CZ1iXWRG+vzCFDQjjPG5oPJfPvXtTmSuD7/65vNlrRk9sAh2p0BG3I
+i0k8304elsm+HnVIUDyroBjud464qc+iY2bLABEBAAG0HFRvdWNobGFiIDxidWls
+ZEB0b3VjaGxhYi5jbz6JAVQEEwEIAD4WIQSy+We2fa3B8HFy29reRT5V3Ib8mwUC
+Xsf/qAIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDeRT5V3Ib8
+m8QCB/97mcLfMXI5MuP+dLVx0ZltfAKb/2BRfgfGHn+xt+o7jeRG5B8qkKrl8+fZ
+3kS/aoW7VZu3XpHPLCiEkL5MLB45J8+AuSYKhQBrugJSmaXifgeAR1To5qoynLMV
+ZY/UgnkRnL1fBZ/gjMfnJzZxn2BCtcKXwmTT5rGraaapc4Cska+ZjRvT1NIGdMmH
+yHDxqsFzVVhbWXWO1xJxajP//umDC3j+gyQUI3nTWql76u/BxMaU6Oba5y4zzRQd
+1xV+yCm4mcfXcvXi5rf6xYd05JAlbfbZ0xCNFGgBZhyMoTbc2SANZIvG1/v0+p8k
+eZUS+HVdfWc0/7eUfru3vzkUvIphuQENBF7H/6gBCADbGrkDp9Jk+FPaV//G6Sy5
+oWoK1+3Hhw0aa4sKCStrmi+g4yPQ7l6M4hLj8cSE4u6UlRwB8s/FGB6CNNqoCJPh
+qdTHrYoKDnZKUOLL+8eYCnVcBoFdrJOmbx6gyaBfRoKj+EzPIgYpwnhA+evdIIXr
+Fcs036YLpAZEMKrhTAPTiF3MaOhjT9JcT1LSsyABi9e/r9zBQgzr718YgvMmce72
+nKt72vp1tijOHu0q3axi9I5LYt7OzBsSOmCgUndIb+JPIkd/axE77f/tznexTKEU
+Aed/xtYqAOg+fffGu9gRpkZFbFNNTH3iAvLPPg4SQAF3dQ5fSwT0NEbaXq6FT+aT
+ABEBAAGJATwEGAEIACYWIQSy+We2fa3B8HFy29reRT5V3Ib8mwUCXsf/qAIbDAUJ
+A8JnAAAKCRDeRT5V3Ib8m2JDB/4hb/taMn+1776Dd4DRzJVwiXY6zpwUmhgMlRAm
+H5qivj1vYK2CvACCf44VH03hKouUIj9ZAAuHHJjqKqBHMW+AMIn3CL+kl/2jsj8+
++CMziEtBDskrNFKYOHkQi0o+aBOv2MvOt881890JpcCIHaCmLInt29k9r7PKgHSi
+FBF53/CQB2yCzwiiutA1qE+9HFUyNCVvKsIACzOpLCwDU19+8LVxbrND/ns+Tah7
+3WHQrxtKeTt6aYYOuhjqIxjPPTJngBzSjNDUmOxo9F95mbffQ2h1FugmKI1xBZku
+ClYd7CwAcljpFxHI6Rol9sRlTbDJvAX4aQvrFqhlD+i1X12O
+=FrYN
+-----END PGP PUBLIC KEY BLOCK-----
+
pub 0729A0AFF8999A87
sub 6005789E24E5AD1E
sub 6A0975F8B1127B83
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 66d5536..5591e63 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -341,6 +341,7 @@
<trusting group="io.grpc"/>
</trusted-key>
<trusted-key id="b252e5789636134a311e4463971b04f56669b805" group="com.google.jsilver"/>
+ <trusted-key id="b2f967b67dadc1f07172dbdade453e55dc86fc9b" group="co.touchlab"/>
<trusted-key id="b41089a2da79b0fa5810252872385ff0af338d52" group="org.threeten"/>
<trusted-key id="b46dc71e03feeb7f89d1f2491f7a8f87b9d8f501" group="org.jetbrains.trove4j"/>
<trusted-key id="b47034c19c9b1f3dc3702f8d476634a4694e716a" group="com.googlecode.java-diff-utils"/>
@@ -982,6 +983,7 @@
<component group="org.sonatype.oss" name="oss-parent" version="9">
<artifact name="oss-parent-9.pom">
<pgp value="44fbdbbc1a00fe414f1c1873586654072ead6677"/>
+ <sha256 value="fb40265f982548212ff82e362e59732b2187ec6f0d80182885c14ef1f982827a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.tensorflow" name="tensorflow-lite-metadata" version="0.1.0-rc2">
diff --git a/paging/paging-common/build.gradle b/paging/paging-common/build.gradle
index 9786750..b949eff 100644
--- a/paging/paging-common/build.gradle
+++ b/paging/paging-common/build.gradle
@@ -41,6 +41,8 @@
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
api("androidx.annotation:annotation:1.7.0-alpha02")
+ implementation(libs.statelyConcurrency)
+ implementation(libs.statelyConcurrentCollections)
}
}
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/FlowExt.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/FlowExt.kt
index d6b55b8..bede1e6 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/FlowExt.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/FlowExt.kt
@@ -22,7 +22,7 @@
import androidx.paging.CombineSource.INITIAL
import androidx.paging.CombineSource.OTHER
import androidx.paging.CombineSource.RECEIVER
-import java.util.concurrent.atomic.AtomicInteger
+import co.touchlab.stately.concurrency.AtomicInt
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.SendChannel
@@ -137,7 +137,7 @@
crossinline transform: suspend (T1, T2, updateFrom: CombineSource) -> R,
): Flow<R> {
return simpleChannelFlow {
- val incompleteFlows = AtomicInteger(2)
+ val incompleteFlows = AtomicInt(2)
val unbatchedFlowCombiner = UnbatchedFlowCombiner<T1, T2> { t1, t2, updateFrom ->
send(transform(t1, t2, updateFrom))
}
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/HintHandler.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/HintHandler.kt
index 8c81e2e..536688d 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/HintHandler.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/HintHandler.kt
@@ -21,7 +21,7 @@
import androidx.annotation.RestrictTo
import androidx.paging.LoadType.APPEND
import androidx.paging.LoadType.PREPEND
-import java.util.concurrent.locks.ReentrantLock
+import co.touchlab.stately.concurrency.Lock
import kotlin.concurrent.withLock
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
@@ -106,7 +106,7 @@
get() = prepend.flow
val appendFlow
get() = append.flow
- private val lock = ReentrantLock()
+ private val lock = Lock()
/**
* Modifies the state inside a lock where it gets access to the mutable values.
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
index 2d6393b..fdeb52f 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
@@ -17,7 +17,7 @@
package androidx.paging
import androidx.annotation.VisibleForTesting
-import java.util.concurrent.locks.ReentrantLock
+import co.touchlab.stately.concurrency.Lock
import kotlin.concurrent.withLock
/**
@@ -30,7 +30,7 @@
*/
private val invalidGetter: (() -> Boolean)? = null,
) {
- private val lock = ReentrantLock()
+ private val lock = Lock()
private val callbacks = mutableListOf<T>()
internal var invalid = false
private set
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
index 3f74d54..c288fb3 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
@@ -17,7 +17,7 @@
package androidx.paging
import androidx.annotation.VisibleForTesting
-import java.util.concurrent.CopyOnWriteArrayList
+import co.touchlab.stately.collections.ConcurrentMutableList
/**
* Wrapper class for a [PagingSource] factory intended for usage in [Pager] construction.
@@ -25,7 +25,7 @@
* Calling [invalidate] on this [InvalidatingPagingSourceFactory] will forward invalidate signals
* to all active [PagingSource]s that were produced by calling [invoke].
*
- * This class is backed by a [CopyOnWriteArrayList], which is thread-safe for concurrent calls to
+ * This class is backed by a [ConcurrentMutableList], which is thread-safe for concurrent calls to
* any mutative operations including both [invoke] and [invalidate].
*
* @param pagingSourceFactory The [PagingSource] factory that returns a PagingSource when called
@@ -35,7 +35,7 @@
) : PagingSourceFactory<Key, Value> {
@VisibleForTesting
- internal val pagingSources = CopyOnWriteArrayList<PagingSource<Key, Value>>()
+ internal val pagingSources = ConcurrentMutableList<PagingSource<Key, Value>>()
/**
* @return [PagingSource] which will be invalidated when this factory's [invalidate] method
@@ -50,11 +50,9 @@
* [InvalidatingPagingSourceFactory]
*/
public fun invalidate() {
- for (pagingSource in pagingSources) {
- if (!pagingSource.invalid) {
- pagingSource.invalidate()
- }
- }
+ pagingSources
+ .filterNot { it.invalid }
+ .forEach { it.invalidate() }
pagingSources.removeAll { it.invalid }
}
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/LegacyPageFetcher.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/LegacyPageFetcher.kt
index d923015..9717bdd 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/LegacyPageFetcher.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/LegacyPageFetcher.kt
@@ -19,7 +19,7 @@
import androidx.paging.LoadState.Loading
import androidx.paging.LoadState.NotLoading
import androidx.paging.PagingSource.LoadParams
-import java.util.concurrent.atomic.AtomicBoolean
+import co.touchlab.stately.concurrency.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -45,7 +45,7 @@
}
val isDetached
- get() = detached.get()
+ get() = detached.value
private fun scheduleLoad(type: LoadType, params: LoadParams<K>) {
// Listen on the BG thread if the paged source is invalid, since it can be expensive.
@@ -154,7 +154,9 @@
}
}
- fun detach() = detached.set(true)
+ fun detach() {
+ detached.value = true
+ }
internal interface PageConsumer<V : Any> {
/**
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
index 0b12735..b622a16 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MutableCombinedLoadStateCollection.kt
@@ -19,7 +19,7 @@
import androidx.paging.LoadState.Error
import androidx.paging.LoadState.Loading
import androidx.paging.LoadState.NotLoading
-import java.util.concurrent.CopyOnWriteArrayList
+import co.touchlab.stately.collections.ConcurrentMutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -35,7 +35,7 @@
*/
internal class MutableCombinedLoadStateCollection {
- private val listeners = CopyOnWriteArrayList<(CombinedLoadStates) -> Unit>()
+ private val listeners = ConcurrentMutableList<(CombinedLoadStates) -> Unit>()
private val _stateFlow = MutableStateFlow<CombinedLoadStates?>(null)
public val stateFlow = _stateFlow.asStateFlow()
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 929da61..05d09ef 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -31,7 +31,7 @@
import androidx.paging.PagingSource.LoadResult
import androidx.paging.PagingSource.LoadResult.Page
import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
-import java.util.concurrent.atomic.AtomicBoolean
+import co.touchlab.stately.concurrency.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PagingDataDiffer.kt
index a9d2583..0635cfa 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -28,7 +28,7 @@
import androidx.paging.PagePresenter.ProcessPageEventCallback
import androidx.paging.internal.BUGANIZER_URL
import androidx.paging.internal.appendMediatorStatesIfNotNull
-import java.util.concurrent.CopyOnWriteArrayList
+import co.touchlab.stately.collections.ConcurrentMutableList
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
@@ -52,7 +52,7 @@
private val combinedLoadStatesCollection = MutableCombinedLoadStateCollection().apply {
cachedPagingData?.cachedEvent()?.let { set(it.sourceLoadStates, it.mediatorLoadStates) }
}
- private val onPagesUpdatedListeners = CopyOnWriteArrayList<() -> Unit>()
+ private val onPagesUpdatedListeners = ConcurrentMutableList<() -> Unit>()
private val collectFromRunner = SingleRunner()
diff --git a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
index 17b923c..b6f5b1a 100644
--- a/paging/paging-common/src/jvmMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
@@ -20,7 +20,7 @@
import androidx.paging.AccessorState.BlockState.REQUIRES_REFRESH
import androidx.paging.AccessorState.BlockState.UNBLOCKED
import androidx.paging.RemoteMediator.MediatorResult
-import java.util.concurrent.locks.ReentrantLock
+import co.touchlab.stately.concurrency.Lock
import kotlin.concurrent.withLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -67,7 +67,7 @@
* Simple wrapper around the local state of accessor to ensure we don't concurrently change it.
*/
private class AccessorStateHolder<Key : Any, Value : Any> {
- private val lock = ReentrantLock()
+ private val lock = Lock()
private val _loadStates = MutableStateFlow(LoadStates.IDLE)
val loadStates
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/CachingTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/CachingTest.kt
index 9f83856..91f0b34 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/CachingTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/CachingTest.kt
@@ -19,7 +19,7 @@
import androidx.paging.ActiveFlowTracker.FlowType
import androidx.paging.ActiveFlowTracker.FlowType.PAGED_DATA_FLOW
import androidx.paging.ActiveFlowTracker.FlowType.PAGE_EVENT_FLOW
-import java.util.concurrent.atomic.AtomicInteger
+import co.touchlab.stately.concurrency.AtomicInt
import kotlin.test.Test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -802,8 +802,8 @@
private class ActiveFlowTrackerImpl : ActiveFlowTracker {
private val counters = mapOf(
- PAGED_DATA_FLOW to AtomicInteger(0),
- PAGE_EVENT_FLOW to AtomicInteger(0)
+ PAGED_DATA_FLOW to AtomicInt(0),
+ PAGE_EVENT_FLOW to AtomicInt(0)
)
override fun onNewCachedEventFlow(cachedPageEventFlow: CachedPageEventFlow<*>) {
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
index 2199e884..f7ae9c3 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
@@ -17,9 +17,9 @@
package androidx.paging
import androidx.kruth.assertWithMessage
+import co.touchlab.stately.concurrency.AtomicBoolean
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
-import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread
import kotlin.random.Random
import kotlin.reflect.KClass
@@ -45,7 +45,7 @@
val arraySize = Random.nextInt(1000)
leak.add(ByteArray(arraySize))
System.gc()
- } while (continueTriggeringGc.get())
+ } while (continueTriggeringGc.value)
}
var collectedItemCount = 0
val expectedItemCount = size - expected.sumOf { it.second }
@@ -54,7 +54,7 @@
) {
collectedItemCount++
}
- continueTriggeringGc.set(false)
+ continueTriggeringGc.value = false
val leakedObjects = countLiveObjects()
val leakedObjectToStrings = references.mapNotNull {
it.get()
diff --git a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
index c847fb3..7cc69b1 100644
--- a/paging/paging-common/src/jvmTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
+++ b/paging/paging-common/src/jvmTest/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
@@ -25,7 +25,7 @@
import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
import androidx.paging.RemoteMediatorMock.LoadEvent
import androidx.paging.TestPagingSource.Companion.LOAD_ERROR
-import java.util.concurrent.atomic.AtomicBoolean
+import co.touchlab.stately.concurrency.AtomicBoolean
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail
@@ -500,7 +500,7 @@
return try {
super.load(loadType, state)
} finally {
- loading.set(false)
+ loading.value = false
}
}
}
@@ -583,7 +583,7 @@
return try {
super.load(loadType, state)
} finally {
- loading.set(false)
+ loading.value = false
}
}
}