Motoko Sharable Generics

I’m trying to perform parallel execution of an async function on each of the elements of a collection (i.e. Buffer<T>) and then to collect the results.

This would behave something like Promise.allSettled() in Javascript.

The code looks something like this

  public func mapParallel<A, B>(as: StableBuffer<A>, f: shared (A) -> async B): async StableBuffer<B> {
    let executingFunctionsBuffer = StableBuffer.init<async B>();
    var i = 0;
    label execute loop {
      if (i >= as.count) break execute;
      StableBuffer.add<async B>(executingFunctionsBuffer, f(as[i]));
      i += 1;
    };
    let collectingResultsBuffer = Buffer.init<B>();
    i := 0;
    label collect loop {
      if (i >= as.count) break execute;
      Buffer.add<B>(collectingResultsBuffer, await executingFunctionsBuffer.elems[i]);
    };

    collectingResultsBuffer;
  };

Attempting to compile returns - type error [M0031], shared function has non-shared parameter type

Note: I’ve been able to get the above code to work and compile if it is not generic, and is specific in the types of A and B.

My specific use case is that I’d like to be able to spin down multiple canisters at the same time

This means for a list of canisters I’d like to do each of the following steps in parallel, then collect the results and move on to the next step.

  1. Transfer cycles from the canisters (execute in parallel, then collect results)
  2. Stop each canister (execute in parallel, then collect results)
  3. Delete each canister (execute in parallel, then collect results)

My current approach is to do this in a for loop awaiting the spin down of each canister one by one, but if one has a fleet of canisters this could take some time to do so.

The benefits of going canister by canister instead of all at once is that if a particular canister fails, it’s easier to troubleshoot and diagnose the issue.

However, I think there’s a use case here for performing parallel async operations and providing a generic interface for doing so, especially since each of the parallel operations for these steps (deposit_cycles, stop_canister, delete_canister would be hitting the management canister, and are therefore is not dependent on the fleet of canisters to be deleted. These deletion calls are then guaranteed to eventually succeed once the requests are sent to the management canister and reach processing status.

2 Likes