Calling a counter multiple times asynchronously with Promise.all gives the same value each time

Here is some Motoko code:

stable var GLOBAL_ID_COUNT = 0;
public shared func generateId(): async Text {
   GLOBAL_ID_COUNT := GLOBAL_ID_COUNT + 1;
   return Int.toText(GLOBAL_ID_COUNT);
 };

Javascript code:

let ids = await Promise.all([
      generateId(),
      generateId(),
      generateId(),
    ]); 

    ids.forEach(element => {
      console.log(element);
    });

// Result is 1 1 1 

Calling the generateId function returns the same value for each promise. What’s interesting when I look at the network logs I see three requests to the backend but looking at the dfx logs I only see one execution of generateId.

If I change my Motoko code to accept an ignored value and change that value on each request I get the correct result.

// Note val is not used in this method
public shared func generateId(val: Text): async Text {
   GLOBAL_ID_COUNT := GLOBAL_ID_COUNT + 1;
   return Int.toText(GLOBAL_ID_COUNT);
 };

New javascript code

let ids = await Promise.all([
      generateId('a'),
      generateId('b'),
      generateId('c'),
    ]); 

    ids.forEach(element => {
      console.log(element);
    });

// Result is 1 2 3 

So if I’m going to call the same method in parallel multiple times do I need to always provide some unique value? Am I supposed to be calling this method in a different way?

2 Likes

Thanks for this report. I have reproduced the problem and will follow up.

I wonder if this is because icx-proxy (running on the boundary node) caches responses, so if you call a method with the same arguments (in your case, no argument), it will just look it up from the cache.

That may explain why dfx only executed the method once in your first example.

It turns out that the HttpAgent created by the default language binding isn’t configured to generate a Nonce. Update requests with the same arguments look like duplicate requests, unless the ingress_expiry fields happen to be different.

I’ve fixed this for an upcoming dfx version, but in the meantime, to work around this you’d have to set up your HttpAgent manually, something like this:

const agent = new HttpAgent({ ...options?.agentOptions });
agent.addTransform(makeNonceTransform(makeNonce));
return Actor.createActor(idlFactory, {
    agent,
    canisterId,
    ...options?.actorOptions,
  });
6 Likes

Thank you @ericswanson appreciates the quick follow-up, we will try that in the meantime.

1 Like