Mutating global canister state after an inter-canister call

Let’s say I have a Motoko canister that has state like this:

type UserProfile = {
  mut count : Nat;
};

actor {
  var users : Trie.Trie<Types.UserId, Types.UserProfile> = Trie.empty();

  public func foo(userId : Types.UserId) : async () {
    let ?user = Trie.find(users, Utils.key(userId), Text.equal);
    
    // make inter-canister call
    await AnotherCanister.bar();

    user.count += 1;
  };
};

If I immediately call foo twice with the same userId, will user.count be equal to 2 when both calls have fully run to completion? (Assume I call foo twice very quickly, before either AnotherCanister.bar() call has a chance to return a response.)

My guess is yes, but I wanted to confirm. Tagging @nomeata for visibility.

2 Likes

I would say yes provided there are no other concurrent clients and assuming the user account already exists with initial value 0 (your code doesn’t ensure that)

If you are worried about interference during the +=, those are guaranteed to be atomic as they are await-free.

1 Like

I’ve played around with a canister receiving multiple requests that are processed at the exact same time (with the exact same timestamp), and can attest that each record does get processed correctly and updates/mutates the global state in the order one would expect.

Funny enough, I did find that sometimes the responses coming back from the multiple update calls are received by client in a different order than they were sent out and processed. (Might be a client/server latency thing).

1 Like