Hi. I've been watching this video that probably many of you watched and noticed something strange during updates to the save functionality.
There is this snippet of code used in the result functionality (I cleaned it up and left only essentials for this question)
public func addDrink(mgCaffeine: Double, onDate date: Date) {
…
currentDrinks = drinks
Task {
await self.drinksUpdated()
}
}
private func drinksUpdated() async {
...
await store.save(currentDrinks)
}
addDrink is always called on the main actor so we are safe with updating instance variable currentDrinks. Everything is fine here. But then afterwards we schedule a task to perform saving of the current state. We await for the store because it is an actor and this means suspension point.
Now imagine that there are several calls happening to addDrink faster then store is able to save. This will mean that there will be several Tasks waiting for the access to the store. And there is no guarantee which task will run first so this means that we can't be sure which state of the currentDrinks will get written to the store last.
The same example in a bit different words.
- We have 0 values in currentDrinks
addDrinkis getting called- Now we have 1 value in
currentDrinks - We fire a Task (lets call it Task1) to save to the
storeand it starts running addDrinkis getting called again- Now we have 2 values in
currentDrinks - We schedule another Task (Task2) to save to the
storewhilecurrentDrinkshas 2 values and it suspends at theawait store.save(currentDrinks)(first task is still running) addDrinkis getting called again- Now we have 3 values in
currentDrinks - We schedule another Task (Task3) to save to the
storewhilecurrentDrinkshas 3 values and it suspends at theawait store.save(currentDrinks)(first task is still running) - Task1 finishes
- Now one of the tasks from Task2 and Task3 will get the chance to run but we don't control which one.
- Task3 runs first and saves 3 values into
store - Task2 runs and saves 2 values into
storeoverwriting what Task3 did - We end up with 3 values in
currentDrinksand 2 values instore
If we will relaunch app after this, we will basically loose 3rd drink that we added on the previous run.
Am I correct that this sample code contains a bug or am I missing something about Swift Concurrency?