kotlinx.coroutines: NullPointerException when setting StateFlow value
Describe the bug
After upgrading from version 1.6.4 to 1.7.1 (we have since bumped to 1.7.2) we started seeing NullPointerException crashes when updating a StateFlow value. I am not able to reproduce this locally, we are only seeing this through Firebase reports.
This is happening on Android with org.jetbrains.kotlinx:kotlinx-coroutines-android. It’s happening on many different devices and Android versions, so it isn’t specific to one manufacturer/version.
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isInterface()' on a null object reference
at java.lang.Class.isAssignableFrom(Class.java:589)
at java.lang.Class.isInstance(Class.java:542)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.accessCheck(AtomicReferenceFieldUpdater.java:389)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.get(AtomicReferenceFieldUpdater.java:447)
at kotlinx.coroutines.flow.StateFlowSlot.makePending(StateFlowSlot.java:59)
at kotlinx.coroutines.flow.StateFlowImpl.updateState(StateFlow.kt:349)
at kotlinx.coroutines.flow.StateFlowImpl.setValue(StateFlow.kt:316)
Provide a Reproducer
We have a lot of flows, and only one specific one is crashing. The only unique thing about the flow that is causing crashes is that it holds an Enum, but I don’t know if that’s causing it.
The StateFlow is nullable, but the value set on it after initialization is never null. The code below isn’t our production code, but is how our code looks like
enum class SomeEnum {
Apple,
Orange
}
class Class {
val flow: StateFlow<SomeEnum?> get() = _mutableFlow
private val _mutableFlow: MutableStateFlow<SomeEnum?> = MutableStateFlow(null)
fun updateValue(value: SomeEnum) {
_mutableFlow.value = value
}
}
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 45
- Comments: 35 (3 by maintainers)
Commits related to this issue
- Potential workaround for #3820 Replace the specific place where ARFU gets misexecuted by specific Android toolchain Fixes #3820 — committed to Kotlin/kotlinx.coroutines by qwwdfsad 4 months ago
- Potential workaround for #3820 (#4054) Replace the specific place where ARFU gets misexecuted by a specific Android toolchain Fixes #3820 — committed to Kotlin/kotlinx.coroutines by qwwdfsad 4 months ago
- Potential workaround for #3820 (#4054) Replace the specific place where ARFU gets misexecuted by a specific Android toolchain Fixes #3820 — committed to JetBrains/intellij-deps-kotlinx.coroutines by qwwdfsad 4 months ago
@yangwuan55 , @Monabr , the answer is literally on the same screen, you just need to scroll up a bit to see it: no, there are no updates, as it’s an Android problem, not the problem with our library; we don’t know what’s causing this. If you can provide a reliable reproducer (a project that consistently crashes in the emulator or at least on some specific device), please do, and we’ll try to introduce a workaround.
Specifically: https://github.com/Kotlin/kotlinx.coroutines/issues/3820#issuecomment-1714285034
The problem continues to reproduce. Android 12/13.
It is necessary to change
synthetic private static finalback tostatic finalso that “aggressive optimizations” stop working. There was no such problem in version 1.6.4.Issue in Google issue tracker: https://issuetracker.google.com/issues/325123736
We tried this solution but sadly the issue still occurs 😞
We just started getting this problem after updating our coroutines dependency from 1.6.4 to 1.8.0-RC2. We currently only have this happening in our beta release but so far it looks like it is limited to Android 13 and 14. I did a bit of digging and wanted to share it here, I will also file a ticket with Google as this seems to be an issue with the VM or with GC maybe? 🤷
The crash originates in
StateFlowSlot.makePending()which before theatomicfuupdate looked like this (this is decompiled Android byte code to Java:after the update to
atomicfuin 1.7.0 it looks like this:with the crash originating in this line:
Object obj = atomicReferenceFieldUpdater.get(this);Note: Even though
StateFlow.kthad no meaningful code change between 1.6.4 and 1.7.0 because of theatomicfuupdate the byte code changed. This is also the reason why the forced dependency downgrade ofatomicfuwill not “fix” this issue, as the impacted code is already in the coroutines-core.jarfile. So the downgrade during app build does not really do anything anymore.This is where it gets funky: This code calls
getonAtomicReferenceFieldUpdaterImplwiththis(StateFlowSlot) instance as parameter.with
cclassisStateFlowSlot.classbecause it is the first parameter inand the constructor of
AtomicReferenceFieldUpdaterImplassigns it to thetclass, which is the fist parameter:so this
accessCheckis checking if our instance of typeStateFlowSlotis an instance ofStateFlowSlot.classon Android 13 the
isInstancecall looks like this:so this will get the
classfrom the object (which itself is notnull) that we called this with (which would be the existing instance ofStateFlowSlow.into
And this is where the crash is in line
if (!cls.isInterface()) {.Which means that the
obj.getClass()call inisInstancereturned anullclass even though the instance is not null.The
Objectimplementation on Android seems to use some kind of backing field here:which I guess can be null, maybe a GC issue? Anyway, I will also file this with Google
Android standard lib code examples from: https://android.googlesource.com/platform/libcore/+/refs/heads/android13-d1-release/ojluni/src/main/java/java/lang
Any updates? Is there a temporary solution to avoid collapse?
Thanks you so much for the invitation to me
On Sat, 9 Mar 2024, 10:07 am mengrong.yang, @.***> wrote:
So glad hear that,thank you so much!
The original crash is happening on the main thread, and shouldn’t be very close to process instantiation. The second is called from the IO dispatcher, but I don’t think this should be close to process instantation either. The second is also a lot rarer, only happening 3 times compared to 80 for the original
Seeing the same crash occasionally after an upgrade to
1.7.1.In our case the flow receives a kotlin
objectimplementing a sealed class. The flow is updated from a dedicated dispatcher created out ofExecutors.newSingleThreadExecutor().The same stateflow is updated with
WithSomeDatain another code-path, but from the main thread, and that code-path has not been seen in the stacktraces.