Retries

Some FTL features allow specifying a retry policy via a Go comment directive. Retries back off exponentially until the maximum is reached.

The directive has the following syntax:

//ftl:retry [<attempts=10>] <min-backoff> [<max-backoff=1hr>] [catch <catchVerb>]

For example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc.

//ftl:retry 10 5s 1m
func Process(ctx context.Context, in Invoice) error {
  // ...
}

PubSub

Subscribers can have a retry policy. For example:

//ftl:subscribe exampleSubscription
//ftl:retry 5 1s catch recoverPaymentProcessing
func ProcessPayment(ctx context.Context, payment Payment) error {
    ...
}

FSM

Retries can be declared on the FSM or on individual transition verbs. Retries declared on a verb take precedence over ones declared on the FSM. For example:

//ftl:retry 10 1s 10s
var fsm = ftl.FSM("fsm",
	ftl.Start(Start),
	ftl.Transition(Start, End),
)

//ftl:verb
//ftl:retry 1 1s 1s
func Start(ctx context.Context, in Event) error {
	// Start uses its own retry policy
}


//ftl:verb
func End(ctx context.Context, in Event) error {
	// End inherits the default retry policy from the FSM
}

Catching

After all retries have failed, a catch verb can be used to safely recover.

These catch verbs have a request type of builtin.CatchRequest<Req> and no response type. If a catch verb returns an error, it will be retried until it succeeds so it is important to handle errors carefully.

//ftl:retry 5 1s catch recoverPaymentProcessing
func ProcessPayment(ctx context.Context, payment Payment) error {
    ...
}

//ftl:verb
func RecoverPaymentProcessing(ctx context.Context, request builtin.CatchRequest[Payment]) error {
    // safely handle final failure of the payment
}

For FSMs, after a catch verb has been successfully called the FSM will moved to the failed state.