Share a Context Deadline Across Steps
Derive one deadline for a multi-step operation, check the remaining budget before each call, and fail fast with a wrapped error.
Code
package main
import (
"context"
"fmt"
"time"
)
type Step struct {
Name string
Cost time.Duration
}
func runSteps(ctx context.Context, steps []Step) error {
for _, step := range steps {
if err := ctx.Err(); err != nil {
return fmt.Errorf("before step %q: %w", step.Name, err)
}
if deadline, ok := ctx.Deadline(); ok {
remaining := time.Until(deadline)
if remaining < step.Cost {
return fmt.Errorf(
"step %q needs %s but only %s remains: %w",
step.Name,
step.Cost,
remaining.Round(time.Millisecond),
context.DeadlineExceeded,
)
}
}
timer := time.NewTimer(step.Cost)
select {
case <-ctx.Done():
timer.Stop()
return fmt.Errorf("step %q interrupted: %w", step.Name, ctx.Err())
case <-timer.C:
fmt.Printf("completed %q in %s\n", step.Name, step.Cost)
}
}
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond)
defer cancel()
steps := []Step{
{Name: "load-config", Cost: 20 * time.Millisecond},
{Name: "fetch-profile", Cost: 25 * time.Millisecond},
{Name: "render-report", Cost: 60 * time.Millisecond},
}
if err := runSteps(ctx, steps); err != nil {
fmt.Println("stopped:", err)
}
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.