|  | package combinectx_test | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "errors" | 
|  | "fmt" | 
|  | "time" | 
|  |  | 
|  | "source.monogon.dev/metropolis/pkg/combinectx" | 
|  | ) | 
|  |  | 
|  | // ExampleCombine shows how to combine two contexts for use with a contextful | 
|  | // method. | 
|  | func ExampleCombine() { | 
|  | // Let's say you're trying to combine two different contexts: the first one is | 
|  | // some long-term local worker context, while the second is a context from some | 
|  | // incoming request. | 
|  | ctxA, cancelA := context.WithCancel(context.Background()) | 
|  | ctxB, cancelB := context.WithTimeout(context.Background(), time.Millisecond*100) | 
|  | defer cancelA() | 
|  | defer cancelB() | 
|  |  | 
|  | // doIO is some contextful, black box IO-heavy function. You want it to return | 
|  | // early when either the long-term context or the short-term request context | 
|  | // are Done(). | 
|  | doIO := func(ctx context.Context) (string, error) { | 
|  | t := time.NewTicker(time.Second) | 
|  | defer t.Stop() | 
|  |  | 
|  | select { | 
|  | case <-ctx.Done(): | 
|  | return "", ctx.Err() | 
|  | case <-t.C: | 
|  | return "successfully reticulated splines", nil | 
|  | } | 
|  | } | 
|  |  | 
|  | // Combine the two given contexts into one... | 
|  | ctx := combinectx.Combine(ctxA, ctxB) | 
|  | // ... and call doIO with it. | 
|  | v, err := doIO(ctx) | 
|  | if err == nil { | 
|  | fmt.Printf("doIO success: %v\n", v) | 
|  | return | 
|  | } | 
|  |  | 
|  | fmt.Printf("doIO failed: %v\n", err) | 
|  |  | 
|  | // The returned error will always be equal to the combined context's Err() call | 
|  | // if the error is due to the combined context being Done(). | 
|  | if err == ctx.Err() { | 
|  | fmt.Printf("doIO err == ctx.Err()\n") | 
|  | } | 
|  |  | 
|  | // The returned error will pass any errors.Is(err, context....) checks. This | 
|  | // ensures compatibility with blackbox code that performs special actions on | 
|  | // the given context. | 
|  | if errors.Is(err, context.DeadlineExceeded) { | 
|  | fmt.Printf("doIO err is context.DeadlineExceeded\n") | 
|  | } | 
|  |  | 
|  | // The returned error can be inspected by converting it to a *Error and calling | 
|  | // .First()/.Second()/.Unwrap(). This lets the caller figure out which of the | 
|  | // parent contexts caused the combined context to expires. | 
|  | cerr := &combinectx.Error{} | 
|  | if errors.As(err, &cerr) { | 
|  | fmt.Printf("doIO err is *combinectx.Error\n") | 
|  | fmt.Printf("doIO first failed: %v, second failed: %v\n", cerr.First(), cerr.Second()) | 
|  | } | 
|  |  | 
|  | // Output: | 
|  | // doIO failed: context deadline exceeded | 
|  | // doIO err == ctx.Err() | 
|  | // doIO err is context.DeadlineExceeded | 
|  | // doIO err is *combinectx.Error | 
|  | // doIO first failed: false, second failed: true | 
|  | } |