Skip to main content

Errors

When a Reboot RPC executes, the result will fall into one of three classes:

  1. All operations completed normally. The RPC should return a successful response, and its effects should be persisted.
  2. An expected normal-course-of-business error was encountered and the call aborted with a specified error. The method's effects should not happen, the caller/user should get a clear and helpful error message, and the application should continue processing requests.
  3. An internal error was encountered and some unexpected error was thrown. The application is in trouble and should stop processing requests, and developers should be made aware of the issue.

It's important that a Reboot system be able to identify and differentiate between these scenario types. Reboot must know when a call has failed so that the method's effects (and potentially the effects of its caller) can be canceled. It must also know what communication mechanism is most appropriate for reporting the error to RPC developers and clients.

For that reason, Reboot includes a mechanism for methods to enumerate the expected errors (class 2) that they may throw in normal-course-of-business. Throwing any unexpected error can then be treated as a bug to be reported (class 3), while returning effects indicates successful completion of the method (class 1).

Reporting Expected Errors in Reboot

Reboot methods should enumerate all errors that are expected as part of normal business in their proto definitions.

For example, a Withdraw method for a bank account might raise an OverdraftError if the account doesn't have sufficient funds for the requested withdrawal:

rpc Withdraw(WithdrawRequest) returns (WithdrawResponse) {
option (rbt.v1alpha1.method) = {
writer: {},
errors: [ "OverdraftError" ],
};
}

These errors are themselves defined by proto messages.

// Error returned when a withdrawal would overdraft the account.
message OverdraftError {
// Amount that we would have overdraft by.
uint32 amount = 1;
}
note

For a complete enumeration, a method must explicitly list all the errors it expects to return, including the errors from all methods it calls.

Reboot will use the proto-defined error messages to generate raisable types for use in code. Each method gets a wrapper type, MethodNameAborted, which can contain any of the error types listed in the RPC definition. This wrapper allows clients to catch all possible errors, even when the server's list of error types is incomplete or gets updated.

async def Withdraw(
self,
context: WriterContext,
state: Account.State,
request: WithdrawRequest,
) -> WithdrawResponse:
updated_balance = state.balance - request.amount
if updated_balance < 0:
raise Account.WithdrawAborted(
OverdraftError(amount=-updated_balance)
)
state.balance = updated_balance
return WithdrawResponse(updated_balance=updated_balance)