Discard state changes from update call without trap

Is there any way to discard memory from an update call without experiencing a trap? I would love to have that capability. My use case is within Sudograph. There are many possible errors that could occur within a GraphQL mutation (handled by an update call). I am using Rust, so I would love to propagate Results all the way to the exposed canister function from within my GraphQL/database code. I would still like to return a well-formatted GraphQL response detailing the errors, but I would also like to discard all of the memory. This is very important for transaction-like behavior (within a single canister at least).

I’m not sure if this is possible currently, and I think in Rust I have to experience a panic to experience a trap and thus to discard state changes. Panics and traps don’t let me give the user a nice JSON result. I imagine this would function very similarly to a query call, where state changes occur but are simply discarded.

2 Likes

@nomeata actually proposed such a mechanism in the past but it wasn’t adopted at the time.

FWIW, an ic0.trap can actually have some binary data attached, so you could pass back some json, but I’m not sure how much…

2 Likes

That would be a gross abuse of the API. The message passed to ic0.trap is meant to be text (not enforced, because it’s trapping already, but an implementation may just drop the data if it isn’t valid text), and definitely not used for programmatic consumption. Not that I can stop anyone, of course …

It seems like the system lacks this mechanism (unless you feel like abusing the API in a “gross” way, which could be fun, but probably not reliable for the future.)

I wonder what Rust as a language provides for doing a kind of speculative mutation to state? (“transactions”?)

In Motoko, I’d advise a developer to use applicative data structures for their updates, and get the rollback semantics of a transaction failure from the applicative (purely functional) nature of the “updates”. The final mutation would only happen in the #ok case. Perhaps something similar could be done in Rust?

This crate always seemed intriguing to me, though I haven’t used it myself, yet.

3 Likes

What is the programmatic way to revert state on the IC? Check any values before making state updates and throw a Result type?
It seems if we have made state updates and have to revert we have to panic out of it and use the string.

I’d say there is no way to revert the state on the system level while still responding in a useful manner (disregarding hacks involving ic0.trap), so I would say that any reversion of state needs to happen within the application.

I’m not saying that “reply but don’t commit the state changes” is completely out of the question, but it’d be a new feature, and there are some open questions around it. For example, while it may make sense when handling a call directly and without doing subsequent calls, it seems pretty much pointless as soon as you are sending off further calls during this message execution (as you’d be losing the callback continuation), or when handling callbacks (as then you’d likely leak the memory resources associated with that callback).

Very interesting to know…

Interesting to know, I’ve been doing some work on this recently.

For what its worth, after a conversation with @lastmjs , we raised this as an explicit feature in the internal IC roadmap. This could be prioritised based on community feedback. It would indeed be a useful feature to implement.

3 Likes