Error: The Replica returned an error: code 5, message: "Canister trapped explicitly: could not perform self call"

I get this error when I’m trying to call an async class method from within a for loop of another async actor method.
Can anyone shed some light on this?

@claudio @diegop

Does it only happen if the for loop makes a larger number of iterations?

Can you share the code?

1 Like

I just tested this in the REPL and yes, you are correct. When you iterate 114 times, the Error pops up again.

1 Like

You are probably hitting the problem that the output queue of a canister is bounded. When it is full, the canister cannot issue more calls, and the system will synchronously fail ic0.call_perform. These synchronous errors were added at point when Motoko already supported message calling, and there was no obvious way to add user-controlled error handling, besides treating it like any other out-of-resource error (e.g. memory allocation failure), and trap.

Traps cannot be caught directly, but indirectly if you wrap it another call (e.g. try await async { … }). Or maybe you can avoid the limit by sending only one message per call (plus one self-message), by adding a await async (); line that effectively yields to the scheduler.

2 Likes

Thanks, this is very insightful!

Out of curiosity, what does synchronously fail mean? Are all the iterations up until the failure commited to state? So can this “brute force” method be used to get as many messages out there as possible, and on failure - by catching the error - I add the last iteration back to the queue? And then call the method again, hoping that the some of the messages in the queue were processed and there is room for more?

When I have a method like this, what is the limiting factor when I await on each iteration? Can the computation span multiple rounds?

@nomeata bump :innocent: and 20 chars

With the raw system api, yes, you could do that: Send out calls until ic0.perform_call returns “ok”, and then return.

From Motoko, though, a synchronously failing call invariable causes a trap, which rolls back all the work done so far in this thread.

When I have a method like this, what is the limiting factor when I await on each iteration? Can the computation span multiple rounds?

Every await is its own message, so can happen at a later round – is that what you mean?

1 Like

Yes, that’s what I meant. Thanks!