Serge Bazanski | 4166a71 | 2021-06-07 21:58:54 +0200 | [diff] [blame] | 1 | package combinectx_test |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "time" |
| 8 | |
| 9 | "source.monogon.dev/metropolis/pkg/combinectx" |
| 10 | ) |
| 11 | |
| 12 | // ExampleCombine shows how to combine two contexts for use with a contextful |
| 13 | // method. |
| 14 | func ExampleCombine() { |
| 15 | // Let's say you're trying to combine two different contexts: the first one is |
| 16 | // some long-term local worker context, while the second is a context from some |
| 17 | // incoming request. |
| 18 | ctxA, cancelA := context.WithCancel(context.Background()) |
Serge Bazanski | 2098b98 | 2021-07-07 15:13:46 +0200 | [diff] [blame^] | 19 | ctxB, cancelB := context.WithTimeout(context.Background(), time.Millisecond*100) |
Serge Bazanski | 4166a71 | 2021-06-07 21:58:54 +0200 | [diff] [blame] | 20 | defer cancelA() |
| 21 | defer cancelB() |
| 22 | |
| 23 | // doIO is some contextful, black box IO-heavy function. You want it to return |
| 24 | // early when either the long-term context or the short-term request context |
| 25 | // are Done(). |
| 26 | doIO := func(ctx context.Context) (string, error) { |
| 27 | t := time.NewTicker(time.Second) |
| 28 | defer t.Stop() |
| 29 | |
| 30 | select { |
| 31 | case <-ctx.Done(): |
| 32 | return "", ctx.Err() |
| 33 | case <-t.C: |
| 34 | return "successfully reticulated splines", nil |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | // Combine the two given contexts into one... |
| 39 | ctx := combinectx.Combine(ctxA, ctxB) |
| 40 | // ... and call doIO with it. |
| 41 | v, err := doIO(ctx) |
| 42 | if err == nil { |
| 43 | fmt.Printf("doIO success: %v\n", v) |
| 44 | return |
| 45 | } |
| 46 | |
| 47 | fmt.Printf("doIO failed: %v\n", err) |
| 48 | |
| 49 | // The returned error will always be equal to the combined context's Err() call |
| 50 | // if the error is due to the combined context being Done(). |
| 51 | if err == ctx.Err() { |
| 52 | fmt.Printf("doIO err == ctx.Err()\n") |
| 53 | } |
| 54 | |
| 55 | // The returned error will pass any errors.Is(err, context....) checks. This |
| 56 | // ensures compatibility with blackbox code that performs special actions on |
| 57 | // the given context. |
| 58 | if errors.Is(err, context.DeadlineExceeded) { |
| 59 | fmt.Printf("doIO err is context.DeadlineExceeded\n") |
| 60 | } |
| 61 | |
| 62 | // The returned error can be inspected by converting it to a *Error and calling |
| 63 | // .First()/.Second()/.Unwrap(). This lets the caller figure out which of the |
| 64 | // parent contexts caused the combined context to expires. |
| 65 | cerr := &combinectx.Error{} |
| 66 | if errors.As(err, &cerr) { |
| 67 | fmt.Printf("doIO err is *combinectx.Error\n") |
| 68 | fmt.Printf("doIO first failed: %v, second failed: %v\n", cerr.First(), cerr.Second()) |
| 69 | } |
| 70 | |
| 71 | // Output: |
| 72 | // doIO failed: context deadline exceeded |
| 73 | // doIO err == ctx.Err() |
| 74 | // doIO err is context.DeadlineExceeded |
| 75 | // doIO err is *combinectx.Error |
| 76 | // doIO first failed: false, second failed: true |
| 77 | } |