Exploring actors

From Data Race Safety:

“While actors do guarantee safety from data races, they do not ensure atomicity across suspension points. Concurrent code often needs to execute a sequence of operations together as an atomic unit, such that other threads can never see an intermediate state. Units of code that require this property are known as critical sections.
Because the current isolation domain is freed up to perform other work, actor-isolated state may change after an asynchronous call. As a consequence, you can think of explicitly marking potential suspension points as a way to indicate the end of a critical section.”

func deposit(pineapples: [Pineapple], onto island: Island) async {
   var food = await island.food
   food += pineapples
   await island.store(food)
}

“This code assumes, incorrectly, that the island actor’s food value will not change between asynchronous calls. Critical sections should always be structured to run synchronously.”

So far so good - but how is the example done correctly?

Instead of computing the final result yourself and reinitializing the actor's state with it you make the actor compute & store it.

func deposit(pineapples: [Pineapple], onto island: Island) async {
   await island.appendNewFruit(pineapples)
}
1 Like

Ah yes, right. That gives a completely different picture. Somehow I was misguided by "Critical sections should always be structured to run synchronously." Or I completely misunderstood. Thanks.

It could use some more wording but what the example is trying to say is that all of these steps (fetch current list of fruit, modify it, store it back) are considered "critical activity" .

If Island weren't an actor, with classical synchronization mechanisms it the examples would look like this:

// BAD
func deposit(pineapples: [Pineapple], onto island: Island) {
   island.mutex.lock()
   var food = island.food
   island.mutex.unlock()

   food += pineapples 

   island.mutex.lock() 
   island.store(food)
   island.mutex.unlock()
  
}

// GOOD
func deposit(pineapples: [Pineapple], onto island: Island) {
   island.mutex.lock() 
  
   var food = island.food
   food += pineapples
   island.store(food)
 
   island.mutex.unlock() 
  
}

Although they're both technically thread safe only one of them is "logically thread-safe"

2 Likes

Yep.