Skip to content

Commit c990f59

Browse files
authored
Potential workaround for #3820 (#4054)
Replace the specific place where ARFU gets misexecuted by a specific Android toolchain Fixes #3820
1 parent 48178b3 commit c990f59

File tree

5 files changed

+66
-3
lines changed

5 files changed

+66
-3
lines changed

kotlinx-coroutines-core/common/src/flow/StateFlow.kt

+7-3
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,14 @@ private class StateFlowSlot : AbstractSharedFlowSlot<StateFlowImpl<*>>() {
250250
*
251251
* It is important that default `null` value is used, because there can be a race between allocation
252252
* of a new slot and trying to do [makePending] on this slot.
253+
*
254+
* ===
255+
* This should be `atomic<Any?>(null)` instead of the atomic reference, but because of #3820
256+
* it is used as a **temporary** solution starting from 1.8.1 version.
257+
* Depending on the fix rollout on Android, it will be removed in 1.9.0 or 2.0.0.
258+
* See https://issuetracker.google.com/issues/325123736
253259
*/
254-
private val _state = atomic<Any?>(null)
260+
private val _state = WorkaroundAtomicReference<Any?>(null)
255261

256262
override fun allocateLocked(flow: StateFlowImpl<*>): Boolean {
257263
// No need for atomic check & update here, since allocated happens under StateFlow lock
@@ -290,7 +296,6 @@ private class StateFlowSlot : AbstractSharedFlowSlot<StateFlowImpl<*>>() {
290296
return state === PENDING
291297
}
292298

293-
@Suppress("UNCHECKED_CAST")
294299
suspend fun awaitPending(): Unit = suspendCancellableCoroutine sc@ { cont ->
295300
assert { _state.value !is CancellableContinuationImpl<*> } // can be NONE or PENDING
296301
if (_state.compareAndSet(NONE, cont)) return@sc // installed continuation, waiting for pending
@@ -306,7 +311,6 @@ private class StateFlowImpl<T>(
306311
private val _state = atomic(initialState) // T | NULL
307312
private var sequence = 0 // serializes updates, value update is in process when sequence is odd
308313

309-
@Suppress("UNCHECKED_CAST")
310314
public override var value: T
311315
get() = NULL.unbox(_state.value)
312316
set(value) { updateState(null, value ?: NULL) }

kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt

+19
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,22 @@ internal expect fun <E> identitySet(expectedSize: Int): MutableSet<E>
1919
@OptionalExpectation
2020
@Target(AnnotationTarget.FIELD)
2121
internal expect annotation class BenignDataRace()
22+
23+
// Used **only** as a workaround for #3820 in StateFlow. Do not use anywhere else
24+
internal expect class WorkaroundAtomicReference<T>(value: T) {
25+
public fun get(): T
26+
public fun set(value: T)
27+
public fun getAndSet(value: T): T
28+
public fun compareAndSet(expected: T, value: T): Boolean
29+
}
30+
31+
@Suppress("UNUSED_PARAMETER", "EXTENSION_SHADOWED_BY_MEMBER")
32+
internal var <T> WorkaroundAtomicReference<T>.value: T
33+
get() = this.get()
34+
set(value) = this.set(value)
35+
36+
internal inline fun <T> WorkaroundAtomicReference<T>.loop(action: WorkaroundAtomicReference<T>.(value: T) -> Unit) {
37+
while (true) {
38+
action(value)
39+
}
40+
}

kotlinx-coroutines-core/jsAndWasmShared/src/internal/Concurrent.kt

+22
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,25 @@ internal class NoOpLock {
1111

1212
internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = HashSet(expectedSize)
1313

14+
internal actual class WorkaroundAtomicReference<T> actual constructor(private var value: T) {
15+
16+
public actual fun get(): T = value
17+
18+
public actual fun set(value: T) {
19+
this.value = value
20+
}
21+
22+
public actual fun getAndSet(value: T): T {
23+
val prev = this.value
24+
this.value = value
25+
return prev
26+
}
27+
28+
public actual fun compareAndSet(expected: T, value: T): Boolean {
29+
if (this.value === expected) {
30+
this.value = value
31+
return true
32+
}
33+
return false
34+
}
35+
}

kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLo
1010

1111
internal actual inline fun <T> ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action)
1212

13+
@Suppress("ACTUAL_WITHOUT_EXPECT") // Visibility
14+
internal actual typealias WorkaroundAtomicReference<T> = java.util.concurrent.atomic.AtomicReference<T>
15+
1316
@Suppress("NOTHING_TO_INLINE") // So that R8 can completely remove ConcurrentKt class
1417
internal actual inline fun <E> identitySet(expectedSize: Int): MutableSet<E> =
1518
Collections.newSetFromMap(IdentityHashMap(expectedSize))

kotlinx-coroutines-core/native/src/internal/Concurrent.kt

+15
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,18 @@ internal actual fun <E> identitySet(expectedSize: Int): MutableSet<E> = HashSet(
1313

1414
@Suppress("ACTUAL_WITHOUT_EXPECT") // This suppress can be removed in 2.0: KT-59355
1515
internal actual typealias BenignDataRace = kotlin.concurrent.Volatile
16+
17+
internal actual class WorkaroundAtomicReference<T> actual constructor(value: T) {
18+
19+
private val nativeAtomic = kotlin.concurrent.AtomicReference<T>(value)
20+
21+
public actual fun get(): T = nativeAtomic.value
22+
23+
public actual fun set(value: T) {
24+
nativeAtomic.value = value
25+
}
26+
27+
public actual fun getAndSet(value: T): T = nativeAtomic.getAndSet(value)
28+
29+
public actual fun compareAndSet(expected: T, value: T): Boolean = nativeAtomic.compareAndSet(expected, value)
30+
}

0 commit comments

Comments
 (0)