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

Non-conflating subscription count in MutableStateFlow and MutableSharedFlow #2871

Closed
qwwdfsad opened this issue Aug 11, 2021 · 1 comment
Closed

Comments

@qwwdfsad
Copy link
Contributor

qwwdfsad commented Aug 11, 2021

Problem statement

Currently, the infrastructure of Flow sharing is written with complete asynchrony and implementation unification in mind. While it benefits its usages in general, the implementation of sharing leaks StateFlow conflation into seemingly orthogonal concepts such as sharing strategies.

It can be demonstrated by the example from #2488:

val flow = flow {
    println("Flow is started") // Never printed
    emit(1)
}.stateIn(this, SharingStarted.Lazily, initialValue = 0)

assertEquals(expected = 0, actual = flow.first()) // Passes
yield()
assertNotEquals(illegal = 0, actual = flow.first()) // Fails

The root cause is sharing implementation -- any sharing strategy subscribes to the flow subscriptionCount and observes the subscriptions. When the collector subscribes and immediately (whether in the same dispatcher or in a different one, but the one that is lagging behind) unsubscribed, the original value is conflated to zero.

A more complex example that involves timings can be found in #2863.

Proposed solution

During the investigation, it was found that local changes cannot solve the asynchrony problem. While the targeted tweaks can address originally reported issues, the problem will be still unresolved and it will be possible to observe it in less trivial scenarios. It doesn't matter whether the sharing happens in the same thread (e.g. Dispatchers.Main) or in a separate one,
the timings and conflations can still be observed in a way that breaks the mental model of sharing.

Due to compatibility reasons, it's close to impossible to change the type of subscriptionCount and it's not the goal to replace trivially-readable (subscriptionCount.value) primitive with a brand new type.

Instead, we decided to make subscriptionCount non-conflating -- any subscriber will be able to observe all changes in subscribers number. While it is not aligned with how the rest of the state flows behave, it is the smallest potentially-breaking change that only affects implementors of sharing strategies and introduces small inconsistency, but provides clear mental model to the library users and does not introduce new concepts to the library

@qwwdfsad qwwdfsad self-assigned this Aug 11, 2021
qwwdfsad added a commit that referenced this issue Aug 11, 2021
Sharing strategies are too sensitive to conflation around extrema and may miss the necessity to start or not to stop the sharing. For more particular examples see #2863 and #2488

Fixes #2488
Fixes #2863
Fixes #2871
pablobaxter pushed a commit to pablobaxter/kotlinx.coroutines that referenced this issue Sep 14, 2022
…#2872)

* Non-conflating subscription count in SharedFlow and StateFlow

Sharing strategies are too sensitive to conflation around extrema and may miss the necessity to start or not to stop the sharing. For more particular examples see Kotlin#2863 and Kotlin#2488

Fixes Kotlin#2488
Fixes Kotlin#2863
Fixes Kotlin#2871
@bubenheimer
Copy link

bubenheimer commented Nov 7, 2023

@qwwdfsad: the kdoc documentation of this change currently appears misleading (kotlinx.coroutines release 1.7.3):

Implementation note: the resulting flow does not conflate subscription count.

This sounds as if the absence of conflation was not part of the contract for subscriptionCount, and just happened to be implemented this way at the present time and subject to change without notice.

Rather, my reading of this issue here suggests that non-conflation is intended to be part of the contract. Otherwise a consumer of this API would not validly be able to rely on this behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants