Getting started
gollback runs Go functions concurrently and hands back what they return. It has three functions:
- Race runs them all and returns the first result without an error, cancelling the rest.
- All runs them all and returns every result and every error, in order.
- Retry calls one until it succeeds, runs out of attempts, or its context is done.
Install
Section titled “Install”gollback needs Go 1.26 or newer.
go get github.com/vardius/gollback/v2Your first race
Section titled “Your first race”This program asks several mirrors for the same file and prints whichever answers first. Each function makes one request, with the context Race gives it, so the slower requests are cancelled as soon as one succeeds:
// Command race fetches the same file from several mirrors and prints whichever answers first.//// go run ./examples/race 'https://go.dev/VERSION?m=text' 'https://golang.google.cn/VERSION?m=text'package main
import ( "context" "fmt" "io" "log" "net/http" "os" "time"
"github.com/vardius/gollback/v2")
// fetchFirst asks every mirror at once. The first good answer wins and the other requests are cancelled.func fetchFirst(ctx context.Context, client *http.Client, urls ...string) (string, error) { fetches := make([]gollback.AsyncFunc[string], len(urls)) for i, url := range urls { fetches[i] = func(ctx context.Context) (string, error) { return get(ctx, client, url) } }
return gollback.Race(ctx, fetches...)}
func get(ctx context.Context, client *http.Client, url string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", err } resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("%s: %s", url, resp.Status) } body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { return "", fmt.Errorf("%s: %w", url, err) }
return string(body), nil}
func main() { log.SetFlags(0) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel()
body, err := fetchFirst(ctx, http.DefaultClient, os.Args[1:]...) if err != nil { log.Fatal(err) } fmt.Print(body)}Run it
Section titled “Run it”-
From a clone of the repository, race two URLs:
Terminal window go run ./examples/race 'https://go.dev/VERSION?m=text' 'https://golang.google.cn/VERSION?m=text'Both serve the same file, the latest Go version; it prints whichever answers first.
-
Give it a URL that fails and one that works. The failure doesn’t end the race:
Terminal window go run ./examples/race https://go.dev/no-such-page 'https://go.dev/VERSION?m=text' -
Give it only failing URLs. Race returns every error, one per line, in the order of the URLs:
Terminal window go run ./examples/race https://go.dev/no-such-page https://go.dev/nor-this-onehttps://go.dev/no-such-page: 404 Not Foundhttps://go.dev/nor-this-one: 404 Not Found
What just happened
Section titled “What just happened”- Each URL became an
AsyncFunc[string]: a function that takes a context and returns astringand an error. The type parameter is the result’s type, soRacereturns astring, not aninterface{}. Racestarted every function in its own goroutine. The first to return a nil error won.- When one won, Race cancelled the context the others had, so their requests stopped.
- When every function failed, Race returned all their errors, joined with
errors.Join.
Next steps
Section titled “Next steps”RaceThe first success wins: losers, errors and cancellation in detail.
AllRun every function and get each result and error in order.
RetryCall a function until it succeeds, with backoff if you want it.
BenchmarksWhat each call costs, and how v2 compares with v1.