Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplatformize concurrent API usage using Stately #577

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ paparazziNative = "2022.1.1-canary-f5f9f71"
skiko = "0.7.7"
spdxGradlePlugin = "0.1.0"
sqldelight = "1.3.0"
stately = "2.0.0-rc3"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrofit = "2.7.2"
wire = "4.7.0"

Expand Down Expand Up @@ -262,6 +263,8 @@ spdxGradlePluginz = { module = "org.spdx:spdx-gradle-plugin", version.ref = "spd
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" }
Expand Down
36 changes: 36 additions & 0 deletions gradle/verification-keyring.keys
Original file line number Diff line number Diff line change
Expand Up @@ -12159,6 +12159,42 @@ VDzc34e+Nr/b2pN05MDHA0dXmb/irwPBl0mTOgAgC805qkR14xhd1GeL6MEA34k8
=CmMl
-----END PGP PUBLIC KEY BLOCK-----

pub DE453E55DC86FC9B
uid Touchlab <build@touchlab.co>

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
Expand Down
2 changes: 2 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,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"/>
Expand Down Expand Up @@ -985,6 +986,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">
Expand Down
2 changes: 2 additions & 0 deletions paging/paging-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ androidXMultiplatform {
dependencies {
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
implementation(libs.statelyConcurrency)
implementation(libs.statelyConcurrentCollections)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import androidx.annotation.RestrictTo
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
Expand Down Expand Up @@ -137,7 +137,7 @@ internal suspend inline fun <T1, T2, R> Flow<T1>.combineWithoutBatching(
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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package androidx.paging
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
Expand Down Expand Up @@ -106,7 +106,7 @@ internal class HintHandler {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -30,7 +30,7 @@ internal class InvalidateCallbackTracker<T>(
*/
private val invalidGetter: (() -> Boolean)? = null,
) {
private val lock = ReentrantLock()
private val lock = Lock()
private val callbacks = mutableListOf<T>()
internal var invalid = false
private set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
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.
*
* 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
Expand All @@ -35,7 +35,7 @@ public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
) : 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
Expand All @@ -50,11 +50,9 @@ public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
* [InvalidatingPagingSourceFactory]
*/
public fun invalidate() {
for (pagingSource in pagingSources) {
if (!pagingSource.invalid) {
pagingSource.invalidate()
}
}
pagingSources
.filterNot { it.invalid }
.forEach { it.invalidate() }

pagingSources.removeAll { it.invalid }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package androidx.paging
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
Expand All @@ -45,7 +45,7 @@ internal class LegacyPageFetcher<K : Any, V : Any>(
}

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.
Expand Down Expand Up @@ -154,7 +154,9 @@ internal class LegacyPageFetcher<K : Any, V : Any>(
}
}

fun detach() = detached.set(true)
fun detach() {
detached.value = true
}

internal interface PageConsumer<V : Any> {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package androidx.paging
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
Expand All @@ -35,7 +35,7 @@ import kotlinx.coroutines.flow.update
*/
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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import androidx.paging.PagingSource.LoadParams
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.paging.PageEvent.StaticList
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
Expand All @@ -52,7 +52,7 @@ public abstract class PagingDataDiffer<T : Any>(
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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import androidx.paging.AccessorState.BlockState.COMPLETED
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
Expand Down Expand Up @@ -67,7 +67,7 @@ internal fun <Key : Any, Value : Any> RemoteMediatorAccessor(
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import androidx.kruth.assertThat
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
Expand Down Expand Up @@ -802,8 +802,8 @@ class CachingTest {

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<*>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,7 +45,7 @@ internal class GarbageCollectionTestHelper {
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 }
Expand All @@ -54,7 +54,7 @@ internal class GarbageCollectionTestHelper {
) {
collectedItemCount++
}
continueTriggeringGc.set(false)
continueTriggeringGc.value = false
val leakedObjects = countLiveObjects()
val leakedObjectToStrings = references.mapNotNull {
it.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
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
Expand Down Expand Up @@ -500,7 +500,7 @@ class RemoteMediatorAccessorTest {
return try {
super.load(loadType, state)
} finally {
loading.set(false)
loading.value = false
}
}
}
Expand Down Expand Up @@ -583,7 +583,7 @@ class RemoteMediatorAccessorTest {
return try {
super.load(loadType, state)
} finally {
loading.set(false)
loading.value = false
}
}
}
Expand Down