package db import ( "context" "errors" "time" "github.com/mattn/go-sqlite3" ) const defaultSQLiteRetryBudget = 20 * time.Second func isSQLiteBusy(err error) bool { var sqliteErr sqlite3.Error if !errors.As(err, &sqliteErr) { return false } return sqliteErr.Code == sqlite3.ErrBusy || sqliteErr.Code == sqlite3.ErrLocked } func retrySQLite(fn func() error) error { return retrySQLiteCtx(context.Background(), defaultSQLiteRetryBudget, fn) } func retrySQLiteCtx(ctx context.Context, budget time.Duration, fn func() error) error { if budget <= 0 { budget = defaultSQLiteRetryBudget } deadline := time.Now().Add(budget) backoff := 10 * time.Millisecond const maxBackoff = 100 * time.Millisecond for { if err := ctx.Err(); err != nil { return err } err := fn() if err == nil || !isSQLiteBusy(err) { return err } if time.Now().After(deadline) { return err } select { case <-ctx.Done(): return ctx.Err() case <-time.After(backoff): } backoff = min(backoff*2, maxBackoff) } }