Automatic retry - Xenoage/RpcLib GitHub Wiki

Options for automatic retry

Normally, when an RPC command is called, it has a defined time period to execute. But when the timeout is hit (by default 30 seconds, but you can configure it individually), the function call throws an exception with a timeout failure and is not tried automatically again.

This can be changed for important commands, which should reach the other side as soon as it is online again, maybe even after restarting the computer. For this, the RpcRetryStrategy enum can be used, which has the value None by default. There are two other strategies:

  • RetryWhenOnline: Runs the command as soon as the other peer is online again. When 10 commands with this flag are called, all 10 commands will be executed later in exactly this order. Example use case: A method to add credit to a bank account (when adding 5, 10 and 30 ct, at the very end the other peer should have received all 45 ct). See the IBankAccountRpc.AddMoney function of the RetryOnClientTest demo project for a working example.

  • RetryNewestWhenOnline: Runs the command as soon as the other peer is online again, but only the newest call with the same method name is retried. Example use case: A method to change a specific configuration (when first setting the name to "MyFirstName", then to "MySecondName" and finally to "MyThirdName", only "MyThirdName" will be sent to the other peer as soon it is online again). See the IBankAccountRpc.ChangeOwnerName function of the RetryOnClientTest demo project for a working example.

When the other peer is online again, first the commands in this queue will be sent, and after that the recently called "normal" commands will be sent (if their timeout was not already hit). This will ensure that the commands will arrive on the remote peer in the exact order in which they were originally called on the local peer.

Usage

Using these retry strategies is very simple. Just annotate the function in your interface with the RpcOptionsAttribute, using the RetryStrategy parameter. Here is an example, using a service for saving log messages on the remote peer:

interface IRemoteLogger : IRpcFunctions {
   [RpcOptions(RetryStrategy = RpcRetryStrategy.RetryWhenOnline)]
   Task SaveMessage(string message);
}

Return values

Retryable commands may also return values, but of course this works only when the call works at the first attempt. Imagine a retryable command which fails for an hour because of missing internet connection, and then finally works when the internet is back. There is no calling context any more, to which the return value could be delivered, so it is just ignored.

Persisting the command queue

The RPC engine needs some way to persist the remembered commands, but does not include an own database for this. So, when using one of the above strategies, an implementation of the IRpcCommandBacklog interface has to be provided when initializing the RPC peer, i.e. when calling RpcMain.InitRpcServer or RpcMain.InitRpcClient. See DemoRpcCommandBacklog for a very simple implementation using JSON files for persisting the queue.

In real world projects, it is recommended to save the commands in a database. The ascending ID property and the TargetPeerID property of the RpcCommand class can be used as the composite primary key for each command.