How to ensure atomicity when calling a canister function?

Let’s say I have a simple (pointless) canister that keeps a simple hashmap <User, Nat>. I also have a global variable counter which starts at 0. I have a method called saveId which assigns a user an id based on the counter. This get’s stored in the hashmap. After each call to the method, the counter gets incremented. How do I make this method atomic, so that no user gets assigned the same id? I assume many users can call this function at once.

Execution is atomic by default, you don’t have to do anything. In Motoko, you merely have to be aware that executing await allows other methods to interleave. But other than during awaits, no other method call can interfere.

1 Like

Ah okay, so if I call an async function, but don’t specify await, then it will be atomic?

If the async function itself (the callee) does not use await, then it is atomic. If it uses await, it is only atomic between awaits.

4 Likes

Practically, I think the counter canister just needs to make sure it updates its value before calling an await or handing the answer back and it will guarantee that no one gets the same number. You’d get in trouble if you did something like:

user canister:

 a_func(){
    let number = await other.get_id_from_other_canister()
}

counter canister:

get_id_from_other_canister(){
  let answer = lastID + 1;
  //update last id here to maintain atomicity lastID:=answer
  await some_third_canister():
  lastID := answer; //bad
}

By the way, state modification before external calls, is the recommended approach for re-entrancy attack prevention.