Hello everyone, I have a question I've been discus...
# flow
d
Hello everyone, I have a question I've been discussing with a couple of colleagues. Regarding
MutableStateFlow<T>.update/getAndUpdate/updateAndGet
, is this real atomicity (only one coroutine can read the state at a time when used and update it, meaning any other coroutine using
.update
for example won't be able to get/update the value until the one accessing the resource finishes. Which statement is the correct one? 1. While 1 coroutine is using
.update
on a given state the next one that tries to do so will be waiting 2. It is possible for state to be updated while
.update
is being executed meaning a race condition This is implementation of the state flow
Copy code
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
    while (true) {
        val prevValue = value
        val nextValue = function(prevValue)
        if (compareAndSet(prevValue, nextValue)) {
            return
        }
    }
}
It is obvious from its implementation that while
function(prevValue)
is being executed, value can change in another coroutine and it is also written in docs of StateFlow:
function may be evaluated multiple times, if value is being concurrently updated.
Question is, why would this be desired behaviour? Why not lock the resource(state) from the very begining until the update is finished? This can cause an infinite loop also if race condition occurs. Update is then not a classical critical section, but
compareAndSet
is instead. What am I missing?
w
> Why not lock the resource(state) from the very begining until the update is finished? Can't find anything to back this up, but I recall something about the typical, expected scenario for there not to be multiple concurrent updates, and the function is optimized for that > This can cause an infinite loop also if race condition occurs. Can it? 🤔 If you have N simultaneous
update
calls, then on every loop exactly one will update the current value. So some update calls will loop more, but eventually should be done when the contention is over. Unless there's never-ending stream of concurrent updates then I suppose one
update
could always be unlucky and not update the state, but that'd be a suspicious setup in the first place
d
Yeah yeah, I was rushing there, infinite loop can't really happen
but in a scenario where you new state depends on a specific previous state this can cause issues
Imagine a scenario where you automatically update your state after it has changed. For example
Loading
and then
Complete
, let's say that calculating Complete takes 5seconds and you call
.update
after loading is emitted. In the meantime some UI events happens updating from
Complete
to some completely other state. What is the best practice for solving these scenarios?
w
If that happened, what would your
function: (T) -> T
look like? I think types solve this issue:
Copy code
update { currentState ->
  when (currentState) {
      Loading -> Complete
      Complete -> Complete
      SomethingElse -> currentState
      else -> ???
   }
}
basically you will handle it one way or another
d
Copy code
update { currentState ->
  when (currentState) {
      Loading -> {
         val data = getData() // suspending lasting for 10 sec for example
         Complete (currentState.someData.merge(data)) // so, here you see that if currentState is not Loading we got a situation, it would be skipped
      }
      Complete -> Complete
      SomethingElse -> currentState
      else -> ???
   }
}
w
That's very theoretical though — I don't think loading data in
update
function is how the update should be done. But even then I don't see a problem with the snippet — if you're loading the data and before setting the state to
completed
the state is not loading, then something else must've set that state, and the update function will be called again. What I mean is that if you have some other place which changes the state from Loading to something else, then why would the snippet above set
Completed
afterwards
👍 1
d
yeah, implementing finite state machine with
StateFlow
can work as a solution. Then for the next time it is updated it just wont change for example but would return the same state. Do you have some good example/article that explains how to manage state for more complex scenarios and avoind race conditions alltogether?