Source file
src/context/context_test.go
Documentation: context
1
2
3
4
5 package context
6
7 import (
8 "fmt"
9 "math/rand"
10 "runtime"
11 "strings"
12 "sync"
13 "sync/atomic"
14 "time"
15 )
16
17 type testingT interface {
18 Deadline() (time.Time, bool)
19 Error(args ...interface{})
20 Errorf(format string, args ...interface{})
21 Fail()
22 FailNow()
23 Failed() bool
24 Fatal(args ...interface{})
25 Fatalf(format string, args ...interface{})
26 Helper()
27 Log(args ...interface{})
28 Logf(format string, args ...interface{})
29 Name() string
30 Parallel()
31 Skip(args ...interface{})
32 SkipNow()
33 Skipf(format string, args ...interface{})
34 Skipped() bool
35 }
36
37
38
39
40 type otherContext struct {
41 Context
42 }
43
44 const (
45 shortDuration = 1 * time.Millisecond
46 veryLongDuration = 1000 * time.Hour
47 )
48
49
50
51 func quiescent(t testingT) time.Duration {
52 deadline, ok := t.Deadline()
53 if !ok {
54 return 5 * time.Second
55 }
56
57 const arbitraryCleanupMargin = 1 * time.Second
58 return time.Until(deadline) - arbitraryCleanupMargin
59 }
60
61 func XTestBackground(t testingT) {
62 c := Background()
63 if c == nil {
64 t.Fatalf("Background returned nil")
65 }
66 select {
67 case x := <-c.Done():
68 t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
69 default:
70 }
71 if got, want := fmt.Sprint(c), "context.Background"; got != want {
72 t.Errorf("Background().String() = %q want %q", got, want)
73 }
74 }
75
76 func XTestTODO(t testingT) {
77 c := TODO()
78 if c == nil {
79 t.Fatalf("TODO returned nil")
80 }
81 select {
82 case x := <-c.Done():
83 t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
84 default:
85 }
86 if got, want := fmt.Sprint(c), "context.TODO"; got != want {
87 t.Errorf("TODO().String() = %q want %q", got, want)
88 }
89 }
90
91 func XTestWithCancel(t testingT) {
92 c1, cancel := WithCancel(Background())
93
94 if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
95 t.Errorf("c1.String() = %q want %q", got, want)
96 }
97
98 o := otherContext{c1}
99 c2, _ := WithCancel(o)
100 contexts := []Context{c1, o, c2}
101
102 for i, c := range contexts {
103 if d := c.Done(); d == nil {
104 t.Errorf("c[%d].Done() == %v want non-nil", i, d)
105 }
106 if e := c.Err(); e != nil {
107 t.Errorf("c[%d].Err() == %v want nil", i, e)
108 }
109
110 select {
111 case x := <-c.Done():
112 t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
113 default:
114 }
115 }
116
117 cancel()
118 for i, c := range contexts {
119 select {
120 case <-c.Done():
121 default:
122 t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
123 }
124 if e := c.Err(); e != Canceled {
125 t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
126 }
127 }
128 }
129
130 func contains(m map[canceler]struct{}, key canceler) bool {
131 _, ret := m[key]
132 return ret
133 }
134
135 func XTestParentFinishesChild(t testingT) {
136
137
138
139 parent, cancel := WithCancel(Background())
140 cancelChild, stop := WithCancel(parent)
141 defer stop()
142 valueChild := WithValue(parent, "key", "value")
143 timerChild, stop := WithTimeout(valueChild, veryLongDuration)
144 defer stop()
145
146 select {
147 case x := <-parent.Done():
148 t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
149 case x := <-cancelChild.Done():
150 t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
151 case x := <-timerChild.Done():
152 t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
153 case x := <-valueChild.Done():
154 t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
155 default:
156 }
157
158
159 pc := parent.(*cancelCtx)
160 cc := cancelChild.(*cancelCtx)
161 tc := timerChild.(*timerCtx)
162 pc.mu.Lock()
163 if len(pc.children) != 2 || !contains(pc.children, cc) || !contains(pc.children, tc) {
164 t.Errorf("bad linkage: pc.children = %v, want %v and %v",
165 pc.children, cc, tc)
166 }
167 pc.mu.Unlock()
168
169 if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
170 t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
171 }
172 if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
173 t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
174 }
175
176 cancel()
177
178 pc.mu.Lock()
179 if len(pc.children) != 0 {
180 t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
181 }
182 pc.mu.Unlock()
183
184
185 check := func(ctx Context, name string) {
186 select {
187 case <-ctx.Done():
188 default:
189 t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
190 }
191 if e := ctx.Err(); e != Canceled {
192 t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
193 }
194 }
195 check(parent, "parent")
196 check(cancelChild, "cancelChild")
197 check(valueChild, "valueChild")
198 check(timerChild, "timerChild")
199
200
201 precanceledChild := WithValue(parent, "key", "value")
202 select {
203 case <-precanceledChild.Done():
204 default:
205 t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
206 }
207 if e := precanceledChild.Err(); e != Canceled {
208 t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
209 }
210 }
211
212 func XTestChildFinishesFirst(t testingT) {
213 cancelable, stop := WithCancel(Background())
214 defer stop()
215 for _, parent := range []Context{Background(), cancelable} {
216 child, cancel := WithCancel(parent)
217
218 select {
219 case x := <-parent.Done():
220 t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
221 case x := <-child.Done():
222 t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
223 default:
224 }
225
226 cc := child.(*cancelCtx)
227 pc, pcok := parent.(*cancelCtx)
228 if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
229 t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
230 }
231
232 if pcok {
233 pc.mu.Lock()
234 if len(pc.children) != 1 || !contains(pc.children, cc) {
235 t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
236 }
237 pc.mu.Unlock()
238 }
239
240 cancel()
241
242 if pcok {
243 pc.mu.Lock()
244 if len(pc.children) != 0 {
245 t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
246 }
247 pc.mu.Unlock()
248 }
249
250
251 select {
252 case <-child.Done():
253 default:
254 t.Errorf("<-child.Done() blocked, but shouldn't have")
255 }
256 if e := child.Err(); e != Canceled {
257 t.Errorf("child.Err() == %v want %v", e, Canceled)
258 }
259
260
261 select {
262 case x := <-parent.Done():
263 t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
264 default:
265 }
266 if e := parent.Err(); e != nil {
267 t.Errorf("parent.Err() == %v want nil", e)
268 }
269 }
270 }
271
272 func testDeadline(c Context, name string, t testingT) {
273 t.Helper()
274 d := quiescent(t)
275 timer := time.NewTimer(d)
276 defer timer.Stop()
277 select {
278 case <-timer.C:
279 t.Fatalf("%s: context not timed out after %v", name, d)
280 case <-c.Done():
281 }
282 if e := c.Err(); e != DeadlineExceeded {
283 t.Errorf("%s: c.Err() == %v; want %v", name, e, DeadlineExceeded)
284 }
285 }
286
287 func XTestDeadline(t testingT) {
288 t.Parallel()
289
290 c, _ := WithDeadline(Background(), time.Now().Add(shortDuration))
291 if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
292 t.Errorf("c.String() = %q want prefix %q", got, prefix)
293 }
294 testDeadline(c, "WithDeadline", t)
295
296 c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
297 o := otherContext{c}
298 testDeadline(o, "WithDeadline+otherContext", t)
299
300 c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
301 o = otherContext{c}
302 c, _ = WithDeadline(o, time.Now().Add(veryLongDuration))
303 testDeadline(c, "WithDeadline+otherContext+WithDeadline", t)
304
305 c, _ = WithDeadline(Background(), time.Now().Add(-shortDuration))
306 testDeadline(c, "WithDeadline+inthepast", t)
307
308 c, _ = WithDeadline(Background(), time.Now())
309 testDeadline(c, "WithDeadline+now", t)
310 }
311
312 func XTestTimeout(t testingT) {
313 t.Parallel()
314
315 c, _ := WithTimeout(Background(), shortDuration)
316 if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
317 t.Errorf("c.String() = %q want prefix %q", got, prefix)
318 }
319 testDeadline(c, "WithTimeout", t)
320
321 c, _ = WithTimeout(Background(), shortDuration)
322 o := otherContext{c}
323 testDeadline(o, "WithTimeout+otherContext", t)
324
325 c, _ = WithTimeout(Background(), shortDuration)
326 o = otherContext{c}
327 c, _ = WithTimeout(o, veryLongDuration)
328 testDeadline(c, "WithTimeout+otherContext+WithTimeout", t)
329 }
330
331 func XTestCanceledTimeout(t testingT) {
332 c, _ := WithTimeout(Background(), time.Second)
333 o := otherContext{c}
334 c, cancel := WithTimeout(o, veryLongDuration)
335 cancel()
336 select {
337 case <-c.Done():
338 default:
339 t.Errorf("<-c.Done() blocked, but shouldn't have")
340 }
341 if e := c.Err(); e != Canceled {
342 t.Errorf("c.Err() == %v want %v", e, Canceled)
343 }
344 }
345
346 type key1 int
347 type key2 int
348
349 var k1 = key1(1)
350 var k2 = key2(1)
351 var k3 = key2(3)
352
353 func XTestValues(t testingT) {
354 check := func(c Context, nm, v1, v2, v3 string) {
355 if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
356 t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
357 }
358 if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
359 t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
360 }
361 if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
362 t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
363 }
364 }
365
366 c0 := Background()
367 check(c0, "c0", "", "", "")
368
369 c1 := WithValue(Background(), k1, "c1k1")
370 check(c1, "c1", "c1k1", "", "")
371
372 if got, want := fmt.Sprint(c1), `context.Background.WithValue(type context.key1, val c1k1)`; got != want {
373 t.Errorf("c.String() = %q want %q", got, want)
374 }
375
376 c2 := WithValue(c1, k2, "c2k2")
377 check(c2, "c2", "c1k1", "c2k2", "")
378
379 c3 := WithValue(c2, k3, "c3k3")
380 check(c3, "c2", "c1k1", "c2k2", "c3k3")
381
382 c4 := WithValue(c3, k1, nil)
383 check(c4, "c4", "", "c2k2", "c3k3")
384
385 o0 := otherContext{Background()}
386 check(o0, "o0", "", "", "")
387
388 o1 := otherContext{WithValue(Background(), k1, "c1k1")}
389 check(o1, "o1", "c1k1", "", "")
390
391 o2 := WithValue(o1, k2, "o2k2")
392 check(o2, "o2", "c1k1", "o2k2", "")
393
394 o3 := otherContext{c4}
395 check(o3, "o3", "", "c2k2", "c3k3")
396
397 o4 := WithValue(o3, k3, nil)
398 check(o4, "o4", "", "c2k2", "")
399 }
400
401 func XTestAllocs(t testingT, testingShort func() bool, testingAllocsPerRun func(int, func()) float64) {
402 bg := Background()
403 for _, test := range []struct {
404 desc string
405 f func()
406 limit float64
407 gccgoLimit float64
408 }{
409 {
410 desc: "Background()",
411 f: func() { Background() },
412 limit: 0,
413 gccgoLimit: 0,
414 },
415 {
416 desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
417 f: func() {
418 c := WithValue(bg, k1, nil)
419 c.Value(k1)
420 },
421 limit: 3,
422 gccgoLimit: 3,
423 },
424 {
425 desc: "WithTimeout(bg, 1*time.Nanosecond)",
426 f: func() {
427 c, _ := WithTimeout(bg, 1*time.Nanosecond)
428 <-c.Done()
429 },
430 limit: 12,
431 gccgoLimit: 15,
432 },
433 {
434 desc: "WithCancel(bg)",
435 f: func() {
436 c, cancel := WithCancel(bg)
437 cancel()
438 <-c.Done()
439 },
440 limit: 5,
441 gccgoLimit: 8,
442 },
443 {
444 desc: "WithTimeout(bg, 5*time.Millisecond)",
445 f: func() {
446 c, cancel := WithTimeout(bg, 5*time.Millisecond)
447 cancel()
448 <-c.Done()
449 },
450 limit: 8,
451 gccgoLimit: 25,
452 },
453 } {
454 limit := test.limit
455 if runtime.Compiler == "gccgo" {
456
457
458 limit = test.gccgoLimit
459 }
460 numRuns := 100
461 if testingShort() {
462 numRuns = 10
463 }
464 if n := testingAllocsPerRun(numRuns, test.f); n > limit {
465 t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
466 }
467 }
468 }
469
470 func XTestSimultaneousCancels(t testingT) {
471 root, cancel := WithCancel(Background())
472 m := map[Context]CancelFunc{root: cancel}
473 q := []Context{root}
474
475 for len(q) != 0 && len(m) < 100 {
476 parent := q[0]
477 q = q[1:]
478 for i := 0; i < 4; i++ {
479 ctx, cancel := WithCancel(parent)
480 m[ctx] = cancel
481 q = append(q, ctx)
482 }
483 }
484
485 var wg sync.WaitGroup
486 wg.Add(len(m))
487 for _, cancel := range m {
488 go func(cancel CancelFunc) {
489 cancel()
490 wg.Done()
491 }(cancel)
492 }
493
494 d := quiescent(t)
495 stuck := make(chan struct{})
496 timer := time.AfterFunc(d, func() { close(stuck) })
497 defer timer.Stop()
498
499
500 for ctx := range m {
501 select {
502 case <-ctx.Done():
503 case <-stuck:
504 buf := make([]byte, 10<<10)
505 n := runtime.Stack(buf, true)
506 t.Fatalf("timed out after %v waiting for <-ctx.Done(); stacks:\n%s", d, buf[:n])
507 }
508 }
509
510 done := make(chan struct{})
511 go func() {
512 wg.Wait()
513 close(done)
514 }()
515 select {
516 case <-done:
517 case <-stuck:
518 buf := make([]byte, 10<<10)
519 n := runtime.Stack(buf, true)
520 t.Fatalf("timed out after %v waiting for cancel functions; stacks:\n%s", d, buf[:n])
521 }
522 }
523
524 func XTestInterlockedCancels(t testingT) {
525 parent, cancelParent := WithCancel(Background())
526 child, cancelChild := WithCancel(parent)
527 go func() {
528 <-parent.Done()
529 cancelChild()
530 }()
531 cancelParent()
532 d := quiescent(t)
533 timer := time.NewTimer(d)
534 defer timer.Stop()
535 select {
536 case <-child.Done():
537 case <-timer.C:
538 buf := make([]byte, 10<<10)
539 n := runtime.Stack(buf, true)
540 t.Fatalf("timed out after %v waiting for child.Done(); stacks:\n%s", d, buf[:n])
541 }
542 }
543
544 func XTestLayersCancel(t testingT) {
545 testLayers(t, time.Now().UnixNano(), false)
546 }
547
548 func XTestLayersTimeout(t testingT) {
549 testLayers(t, time.Now().UnixNano(), true)
550 }
551
552 func testLayers(t testingT, seed int64, testTimeout bool) {
553 t.Parallel()
554
555 r := rand.New(rand.NewSource(seed))
556 errorf := func(format string, a ...interface{}) {
557 t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
558 }
559 const (
560 minLayers = 30
561 )
562 type value int
563 var (
564 vals []*value
565 cancels []CancelFunc
566 numTimers int
567 ctx = Background()
568 )
569 for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
570 switch r.Intn(3) {
571 case 0:
572 v := new(value)
573 ctx = WithValue(ctx, v, v)
574 vals = append(vals, v)
575 case 1:
576 var cancel CancelFunc
577 ctx, cancel = WithCancel(ctx)
578 cancels = append(cancels, cancel)
579 case 2:
580 var cancel CancelFunc
581 d := veryLongDuration
582 if testTimeout {
583 d = shortDuration
584 }
585 ctx, cancel = WithTimeout(ctx, d)
586 cancels = append(cancels, cancel)
587 numTimers++
588 }
589 }
590 checkValues := func(when string) {
591 for _, key := range vals {
592 if val := ctx.Value(key).(*value); key != val {
593 errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
594 }
595 }
596 }
597 if !testTimeout {
598 select {
599 case <-ctx.Done():
600 errorf("ctx should not be canceled yet")
601 default:
602 }
603 }
604 if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
605 t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
606 }
607 t.Log(ctx)
608 checkValues("before cancel")
609 if testTimeout {
610 d := quiescent(t)
611 timer := time.NewTimer(d)
612 defer timer.Stop()
613 select {
614 case <-ctx.Done():
615 case <-timer.C:
616 errorf("ctx should have timed out after %v", d)
617 }
618 checkValues("after timeout")
619 } else {
620 cancel := cancels[r.Intn(len(cancels))]
621 cancel()
622 select {
623 case <-ctx.Done():
624 default:
625 errorf("ctx should be canceled")
626 }
627 checkValues("after cancel")
628 }
629 }
630
631 func XTestCancelRemoves(t testingT) {
632 checkChildren := func(when string, ctx Context, want int) {
633 if got := len(ctx.(*cancelCtx).children); got != want {
634 t.Errorf("%s: context has %d children, want %d", when, got, want)
635 }
636 }
637
638 ctx, _ := WithCancel(Background())
639 checkChildren("after creation", ctx, 0)
640 _, cancel := WithCancel(ctx)
641 checkChildren("with WithCancel child ", ctx, 1)
642 cancel()
643 checkChildren("after canceling WithCancel child", ctx, 0)
644
645 ctx, _ = WithCancel(Background())
646 checkChildren("after creation", ctx, 0)
647 _, cancel = WithTimeout(ctx, 60*time.Minute)
648 checkChildren("with WithTimeout child ", ctx, 1)
649 cancel()
650 checkChildren("after canceling WithTimeout child", ctx, 0)
651 }
652
653 func XTestWithCancelCanceledParent(t testingT) {
654 parent, pcancel := WithCancel(Background())
655 pcancel()
656
657 c, _ := WithCancel(parent)
658 select {
659 case <-c.Done():
660 default:
661 t.Errorf("child not done immediately upon construction")
662 }
663 if got, want := c.Err(), Canceled; got != want {
664 t.Errorf("child not canceled; got = %v, want = %v", got, want)
665 }
666 }
667
668 func XTestWithValueChecksKey(t testingT) {
669 panicVal := recoveredValue(func() { WithValue(Background(), []byte("foo"), "bar") })
670 if panicVal == nil {
671 t.Error("expected panic")
672 }
673 panicVal = recoveredValue(func() { WithValue(Background(), nil, "bar") })
674 if got, want := fmt.Sprint(panicVal), "nil key"; got != want {
675 t.Errorf("panic = %q; want %q", got, want)
676 }
677 }
678
679 func XTestInvalidDerivedFail(t testingT) {
680 panicVal := recoveredValue(func() { WithCancel(nil) })
681 if panicVal == nil {
682 t.Error("expected panic")
683 }
684 panicVal = recoveredValue(func() { WithDeadline(nil, time.Now().Add(shortDuration)) })
685 if panicVal == nil {
686 t.Error("expected panic")
687 }
688 panicVal = recoveredValue(func() { WithValue(nil, "foo", "bar") })
689 if panicVal == nil {
690 t.Error("expected panic")
691 }
692 }
693
694 func recoveredValue(fn func()) (v interface{}) {
695 defer func() { v = recover() }()
696 fn()
697 return
698 }
699
700 func XTestDeadlineExceededSupportsTimeout(t testingT) {
701 i, ok := DeadlineExceeded.(interface {
702 Timeout() bool
703 })
704 if !ok {
705 t.Fatal("DeadlineExceeded does not support Timeout interface")
706 }
707 if !i.Timeout() {
708 t.Fatal("wrong value for timeout")
709 }
710 }
711
712 type myCtx struct {
713 Context
714 }
715
716 type myDoneCtx struct {
717 Context
718 }
719
720 func (d *myDoneCtx) Done() <-chan struct{} {
721 c := make(chan struct{})
722 return c
723 }
724
725 func XTestCustomContextGoroutines(t testingT) {
726 g := atomic.LoadInt32(&goroutines)
727 checkNoGoroutine := func() {
728 t.Helper()
729 now := atomic.LoadInt32(&goroutines)
730 if now != g {
731 t.Fatalf("%d goroutines created", now-g)
732 }
733 }
734 checkCreatedGoroutine := func() {
735 t.Helper()
736 now := atomic.LoadInt32(&goroutines)
737 if now != g+1 {
738 t.Fatalf("%d goroutines created, want 1", now-g)
739 }
740 g = now
741 }
742
743 _, cancel0 := WithCancel(&myDoneCtx{Background()})
744 cancel0()
745 checkCreatedGoroutine()
746
747 _, cancel0 = WithTimeout(&myDoneCtx{Background()}, veryLongDuration)
748 cancel0()
749 checkCreatedGoroutine()
750
751 checkNoGoroutine()
752 defer checkNoGoroutine()
753
754 ctx1, cancel1 := WithCancel(Background())
755 defer cancel1()
756 checkNoGoroutine()
757
758 ctx2 := &myCtx{ctx1}
759 ctx3, cancel3 := WithCancel(ctx2)
760 defer cancel3()
761 checkNoGoroutine()
762
763 _, cancel3b := WithCancel(&myDoneCtx{ctx2})
764 defer cancel3b()
765 checkCreatedGoroutine()
766
767 ctx4, cancel4 := WithTimeout(ctx3, veryLongDuration)
768 defer cancel4()
769 checkNoGoroutine()
770
771 ctx5, cancel5 := WithCancel(ctx4)
772 defer cancel5()
773 checkNoGoroutine()
774
775 cancel5()
776 checkNoGoroutine()
777
778 _, cancel6 := WithTimeout(ctx5, veryLongDuration)
779 defer cancel6()
780 checkNoGoroutine()
781
782
783 cancel6()
784 cancel1()
785 _, cancel7 := WithCancel(ctx5)
786 defer cancel7()
787 checkNoGoroutine()
788 }
789
View as plain text