Adding cycles to motoko futures

A bit of a technical question here.

If I’m using futures and making a bunch of call before awaiting them later, do I do Cycles.add(xxxxxxx) before each future call? or do I wait and do it when I actually await the future later? I’m assuming it is before the future and that motoko will take care of adding each set of cycles to the correct call, but I thought it was worth asking.

I’ve tested the approach you describe in production and it seems to work predictably, at least with where the follow-up call is to deposit_cycles on the management canister

At the call site when you send the message.
Not at the await.

1 Like

Just a heads-up. I am in the process of re-working Cycles.add. The final solution might look like

actor {
  func bar(a : actor { foo : () -> async () }) : async () {
    await (with cycles = 999) a.foo();
    await (with cycles = 4242) async ();
  }
}

Here the with-note (parenthetical) applies to the message send to its right.

3 Likes

Would this also work without the await? So we can create a future with reserved cycle allocation to be waited later? And would it work with async*(this seems hard as there may be many awaits in an async*.)

Yes, the await is irrelevant, as it (the note) augments the send itself. Yes, it will result in a future that has already sent out the message (with the cycles included as indicated). And, no, it doesn’t apply to async*, as that is not a send per se (but can expand to sends, which need to be annotated there).

1 Like

Would it make sense to put this in <> like the system annotations? Or does that confuse the issue too much?

‘await a.foo<withCycles(1_000_000)>()’

I think that is a different concept. <system, ...> passes a capability to a library, while parentheticals modify the AST node that they are affixed to. Those syntax nodes can be calls or async expressions and (later, possibly) operator expressions too.

How will this work for multiple inter-canister call settings, like those with the new scalable messaging model or extend to other message settings as the protocol adopts them? In this case, maybe some sort of record syntax might be more familiar?

I might imagine the call looking something like this

  await with({
    cycles = 999;
    // message type
    messageType = #best_effort;;
    // the call being made
    call = a.foo();
  });

And then not breaking current semantics

  await a.foo()

or

  ignore a.foo()

My point here is that parentheses aren’t as intuitive to use if there are multiple settings associated with the call.

What I have implemented so far indeed borrows elements of the record syntax:

    let defaults = { timeout = #seconds 3; retries = 4 };
    ...
    await (defaults with cycles = 495; sizeLimit = 1024) canister.call(<args>);

Note that all attributes other than cycles are speculative at this point.

Also the whole syntax thing is not cast in stone yet. I find it cute, but there might be better solutions. Keep the ideas coming, but remember that it must be unambiguously parseable and intuitive too in order to make sense at all.