Djuro
03/06/2024, 12:13 PMMutableStateFlow<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
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?wasyl
03/06/2024, 12:27 PMupdate
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 placeDjuro
03/06/2024, 12:33 PMDjuro
03/06/2024, 12:36 PMDjuro
03/06/2024, 12:38 PMLoading
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?wasyl
03/06/2024, 12:44 PMfunction: (T) -> T
look like? I think types solve this issue:
update { currentState ->
when (currentState) {
Loading -> Complete
Complete -> Complete
SomethingElse -> currentState
else -> ???
}
}
basically you will handle it one way or anotherDjuro
03/06/2024, 2:18 PMupdate { 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 -> ???
}
}
wasyl
03/06/2024, 2:35 PMupdate
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
afterwardsDjuro
03/06/2024, 8:47 PMStateFlow
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?