Skip to content

Retry

Automatic retries with exponential backoff for unreliable operations.

What to notice:

  • WithStepMaxRetries(5) retries the step up to 5 times before failing
  • Backoff grows exponentially: 500ms, 1s, 2s, 4s, 8s (capped by WithMaxInterval)
  • If all retries are exhausted, the workflow fails with ErrMaxRetries
go
package main

import (
	"context"
	"fmt"
	"log"
	"math/rand"
	"time"

	"github.com/YakirOren/turbine"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/core"
)

func CallUnreliableAPI(ctx context.Context) (string, error) {
	if rand.Intn(3) == 0 { // fails ~66% of the time
		return "", fmt.Errorf("service unavailable")
	}
	return "ok", nil
}

// FetchWorkflow calls an unreliable API with automatic retries and exponential backoff.
func FetchWorkflow(ctx turbine.Context, url string) (string, error) {
	result, err := turbine.Do(ctx, CallUnreliableAPI,
		turbine.WithStepName("fetch"),
		turbine.WithStepMaxRetries(5),
		turbine.WithBackoffFactor(2.0),
		turbine.WithBaseInterval(500*time.Millisecond),
		turbine.WithMaxInterval(10*time.Second),
	)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("fetched %s: %s", url, result), nil
}

func main() {
	app := pocketbase.New()

	rt := turbine.Setup(app, turbine.Config{})

	turbine.Register(rt, FetchWorkflow)

	app.OnServe().BindFunc(func(e *core.ServeEvent) error {
		e.Router.POST("/fetch/{url}", func(re *core.RequestEvent) error {
			url := re.Request.PathValue("url")

			handle, err := turbine.Run(rt, FetchWorkflow, url)
			if err != nil {
				return re.JSON(500, map[string]string{"error": err.Error()})
			}

			result, err := handle.GetResult()
			if err != nil {
				return re.JSON(500, map[string]string{"error": err.Error()})
			}

			return re.JSON(200, map[string]string{"result": result})
		})
		return e.Next()
	})

	if err := app.Start(); err != nil {
		log.Fatal(err)
	}
}