Source file
src/os/signal/signal_test.go
1
2
3
4
5
6
7 package signal
8
9 import (
10 "bytes"
11 "context"
12 "flag"
13 "fmt"
14 "internal/testenv"
15 "os"
16 "os/exec"
17 "runtime"
18 "runtime/trace"
19 "strconv"
20 "strings"
21 "sync"
22 "syscall"
23 "testing"
24 "time"
25 )
26
27
28
29
30
31
32 var settleTime = 100 * time.Millisecond
33
34
35
36
37 var fatalWaitingTime = 30 * time.Second
38
39 func init() {
40 if testenv.Builder() == "solaris-amd64-oraclerel" {
41
42
43
44
45
46
47
48
49
50
51 settleTime = 5 * time.Second
52 } else if runtime.GOOS == "linux" && strings.HasPrefix(runtime.GOARCH, "ppc64") {
53
54
55
56
57
58 settleTime = 5 * time.Second
59 } else if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
60 if scale, err := strconv.Atoi(s); err == nil {
61 settleTime *= time.Duration(scale)
62 }
63 }
64 }
65
66 func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
67 t.Helper()
68 waitSig1(t, c, sig, false)
69 }
70 func waitSigAll(t *testing.T, c <-chan os.Signal, sig os.Signal) {
71 t.Helper()
72 waitSig1(t, c, sig, true)
73 }
74
75 func waitSig1(t *testing.T, c <-chan os.Signal, sig os.Signal, all bool) {
76 t.Helper()
77
78
79
80 start := time.Now()
81 timer := time.NewTimer(settleTime / 10)
82 defer timer.Stop()
83
84
85
86
87
88 for time.Since(start) < fatalWaitingTime {
89 select {
90 case s := <-c:
91 if s == sig {
92 return
93 }
94 if !all || s != syscall.SIGURG {
95 t.Fatalf("signal was %v, want %v", s, sig)
96 }
97 case <-timer.C:
98 timer.Reset(settleTime / 10)
99 }
100 }
101 t.Fatalf("timeout after %v waiting for %v", fatalWaitingTime, sig)
102 }
103
104
105
106 func quiesce() {
107
108
109
110
111
112
113 start := time.Now()
114 for time.Since(start) < settleTime {
115 time.Sleep(settleTime / 10)
116 }
117 }
118
119
120 func TestSignal(t *testing.T) {
121
122 c := make(chan os.Signal, 1)
123 Notify(c, syscall.SIGHUP)
124 defer Stop(c)
125
126
127 t.Logf("sighup...")
128 syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
129 waitSig(t, c, syscall.SIGHUP)
130
131
132
133
134 c1 := make(chan os.Signal, 10)
135 Notify(c1)
136
137 Reset(syscall.SIGURG)
138 defer Stop(c1)
139
140
141 t.Logf("sigwinch...")
142 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
143 waitSigAll(t, c1, syscall.SIGWINCH)
144
145
146
147
148 t.Logf("sighup...")
149 syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
150 waitSigAll(t, c1, syscall.SIGHUP)
151 t.Logf("sighup...")
152 syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
153 waitSigAll(t, c1, syscall.SIGHUP)
154
155
156 waitSig(t, c, syscall.SIGHUP)
157 }
158
159 func TestStress(t *testing.T) {
160 dur := 3 * time.Second
161 if testing.Short() {
162 dur = 100 * time.Millisecond
163 }
164 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
165
166 sig := make(chan os.Signal, 1)
167 Notify(sig, syscall.SIGUSR1)
168
169 go func() {
170 stop := time.After(dur)
171 for {
172 select {
173 case <-stop:
174
175
176 quiesce()
177 Stop(sig)
178
179
180
181
182 close(sig)
183 return
184
185 default:
186 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
187 runtime.Gosched()
188 }
189 }
190 }()
191
192 for range sig {
193
194 }
195 }
196
197 func testCancel(t *testing.T, ignore bool) {
198
199 c1 := make(chan os.Signal, 1)
200 Notify(c1, syscall.SIGWINCH)
201 defer Stop(c1)
202
203
204 c2 := make(chan os.Signal, 1)
205 Notify(c2, syscall.SIGHUP)
206 defer Stop(c2)
207
208
209 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
210 waitSig(t, c1, syscall.SIGWINCH)
211
212
213 syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
214 waitSig(t, c2, syscall.SIGHUP)
215
216
217
218 if ignore {
219 Ignore(syscall.SIGWINCH, syscall.SIGHUP)
220
221
222
223 } else {
224 Reset(syscall.SIGWINCH, syscall.SIGHUP)
225 }
226
227
228 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
229
230
231 if ignore {
232 syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
233 }
234
235 quiesce()
236
237 select {
238 case s := <-c1:
239 t.Errorf("unexpected signal %v", s)
240 default:
241
242 }
243
244 select {
245 case s := <-c2:
246 t.Errorf("unexpected signal %v", s)
247 default:
248
249 }
250
251
252
253
254 Notify(c1, syscall.SIGWINCH)
255 Notify(c2, syscall.SIGHUP)
256 quiesce()
257 }
258
259
260 func TestReset(t *testing.T) {
261 testCancel(t, false)
262 }
263
264
265 func TestIgnore(t *testing.T) {
266 testCancel(t, true)
267 }
268
269
270 func TestIgnored(t *testing.T) {
271
272 c := make(chan os.Signal, 1)
273 Notify(c, syscall.SIGWINCH)
274
275
276 if Ignored(syscall.SIGWINCH) {
277 t.Errorf("expected SIGWINCH to not be ignored.")
278 }
279 Stop(c)
280 Ignore(syscall.SIGWINCH)
281
282
283 if !Ignored(syscall.SIGWINCH) {
284 t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.")
285 }
286
287 Reset()
288 }
289
290 var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.")
291
292
293 func TestDetectNohup(t *testing.T) {
294 if *checkSighupIgnored {
295 if !Ignored(syscall.SIGHUP) {
296 t.Fatal("SIGHUP is not ignored.")
297 } else {
298 t.Log("SIGHUP is ignored.")
299 }
300 } else {
301 defer Reset()
302
303
304
305 c := make(chan os.Signal, 1)
306 Notify(c, syscall.SIGHUP)
307 if out, err := testenv.Command(t, os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil {
308 t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out)
309 }
310 Stop(c)
311
312 _, err := os.Stat("/usr/bin/nohup")
313 if err != nil {
314 t.Skip("cannot find nohup; skipping second half of test")
315 }
316 Ignore(syscall.SIGHUP)
317 os.Remove("nohup.out")
318 out, err := testenv.Command(t, "/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput()
319
320 data, _ := os.ReadFile("nohup.out")
321 os.Remove("nohup.out")
322 if err != nil {
323 t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data)
324 }
325 }
326 }
327
328 var (
329 sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
330 dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP")
331 )
332
333
334 func TestStop(t *testing.T) {
335 sigs := []syscall.Signal{
336 syscall.SIGWINCH,
337 syscall.SIGHUP,
338 syscall.SIGUSR1,
339 }
340
341 for _, sig := range sigs {
342 sig := sig
343 t.Run(fmt.Sprint(sig), func(t *testing.T) {
344
345
346
347
348 t.Parallel()
349
350
351
352
353
354 mayHaveBlockedSignal := false
355 if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) {
356 syscall.Kill(syscall.Getpid(), sig)
357 quiesce()
358
359
360
361 mayHaveBlockedSignal = true
362 }
363
364
365 c := make(chan os.Signal, 1)
366 Notify(c, sig)
367
368
369 syscall.Kill(syscall.Getpid(), sig)
370 waitSig(t, c, sig)
371
372 if mayHaveBlockedSignal {
373
374
375
376
377 quiesce()
378 select {
379 case <-c:
380 default:
381 }
382 }
383
384
385
386 Stop(c)
387 if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
388 syscall.Kill(syscall.Getpid(), sig)
389 quiesce()
390
391 select {
392 case s := <-c:
393 t.Errorf("unexpected signal %v", s)
394 default:
395
396 }
397
398
399
400
401 Notify(c, sig)
402 quiesce()
403 Stop(c)
404 }
405 })
406 }
407 }
408
409
410 func TestNohup(t *testing.T) {
411
412
413
414 c := make(chan os.Signal, 1)
415 Notify(c, syscall.SIGHUP)
416
417
418
419
420
421
422
423
424
425
426
427
428 var subTimeout time.Duration
429
430 var wg sync.WaitGroup
431 wg.Add(2)
432 if deadline, ok := t.Deadline(); ok {
433 subTimeout = time.Until(deadline)
434 subTimeout -= subTimeout / 10
435 }
436 for i := 1; i <= 2; i++ {
437 i := i
438 go t.Run(fmt.Sprintf("uncaught-%d", i), func(t *testing.T) {
439 defer wg.Done()
440
441 args := []string{
442 "-test.v",
443 "-test.run=TestStop",
444 "-send_uncaught_sighup=" + strconv.Itoa(i),
445 "-die_from_sighup",
446 }
447 if subTimeout != 0 {
448 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
449 }
450 out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
451
452 if err == nil {
453 t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
454 } else {
455 t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out)
456 }
457 })
458 }
459 wg.Wait()
460
461 Stop(c)
462
463
464
465 if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" {
466 t.Skip("Skipping nohup test due to running in tmux on darwin")
467 }
468
469
470 _, err := exec.LookPath("nohup")
471 if err != nil {
472 t.Skip("cannot find nohup; skipping second half of test")
473 }
474
475 wg.Add(2)
476 if deadline, ok := t.Deadline(); ok {
477 subTimeout = time.Until(deadline)
478 subTimeout -= subTimeout / 10
479 }
480 for i := 1; i <= 2; i++ {
481 i := i
482 go t.Run(fmt.Sprintf("nohup-%d", i), func(t *testing.T) {
483 defer wg.Done()
484
485
486
487
488
489
490
491 args := []string{
492 os.Args[0],
493 "-test.v",
494 "-test.run=TestStop",
495 "-send_uncaught_sighup=" + strconv.Itoa(i),
496 }
497 if subTimeout != 0 {
498 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
499 }
500 out, err := testenv.Command(t, "nohup", args...).CombinedOutput()
501
502 if err != nil {
503 t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out)
504 } else {
505 t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out)
506 }
507 })
508 }
509 wg.Wait()
510 }
511
512
513 func TestSIGCONT(t *testing.T) {
514 c := make(chan os.Signal, 1)
515 Notify(c, syscall.SIGCONT)
516 defer Stop(c)
517 syscall.Kill(syscall.Getpid(), syscall.SIGCONT)
518 waitSig(t, c, syscall.SIGCONT)
519 }
520
521
522 func TestAtomicStop(t *testing.T) {
523 if os.Getenv("GO_TEST_ATOMIC_STOP") != "" {
524 atomicStopTestProgram(t)
525 t.Fatal("atomicStopTestProgram returned")
526 }
527
528 testenv.MustHaveExec(t)
529
530
531
532
533
534
535
536
537
538
539 cs := make(chan os.Signal, 1)
540 Notify(cs, syscall.SIGINT)
541 defer Stop(cs)
542
543 const execs = 10
544 for i := 0; i < execs; i++ {
545 timeout := "0"
546 if deadline, ok := t.Deadline(); ok {
547 timeout = time.Until(deadline).String()
548 }
549 cmd := testenv.Command(t, os.Args[0], "-test.run=TestAtomicStop", "-test.timeout="+timeout)
550 cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1")
551 out, err := cmd.CombinedOutput()
552 if err == nil {
553 if len(out) > 0 {
554 t.Logf("iteration %d: output %s", i, out)
555 }
556 } else {
557 t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
558 }
559
560 lost := bytes.Contains(out, []byte("lost signal"))
561 if lost {
562 t.Errorf("iteration %d: lost signal", i)
563 }
564
565
566
567 if err == nil {
568 if len(out) > 0 && !lost {
569 t.Errorf("iteration %d: unexpected output", i)
570 }
571 } else {
572 if ee, ok := err.(*exec.ExitError); !ok {
573 t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err)
574 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
575 t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys())
576 } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT {
577 t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee)
578 }
579 }
580 }
581 }
582
583
584
585
586 func atomicStopTestProgram(t *testing.T) {
587
588 if Ignored(syscall.SIGINT) {
589 fmt.Println("SIGINT is ignored")
590 os.Exit(1)
591 }
592
593 const tries = 10
594
595 timeout := 2 * time.Second
596 if deadline, ok := t.Deadline(); ok {
597
598
599 timeout = time.Until(deadline) / (tries + 1)
600 }
601
602 pid := syscall.Getpid()
603 printed := false
604 for i := 0; i < tries; i++ {
605 cs := make(chan os.Signal, 1)
606 Notify(cs, syscall.SIGINT)
607
608 var wg sync.WaitGroup
609 wg.Add(1)
610 go func() {
611 defer wg.Done()
612 Stop(cs)
613 }()
614
615 syscall.Kill(pid, syscall.SIGINT)
616
617
618
619
620
621
622 select {
623 case <-cs:
624 case <-time.After(timeout):
625 if !printed {
626 fmt.Print("lost signal on tries:")
627 printed = true
628 }
629 fmt.Printf(" %d", i)
630 }
631
632 wg.Wait()
633 }
634 if printed {
635 fmt.Print("\n")
636 }
637
638 os.Exit(0)
639 }
640
641 func TestTime(t *testing.T) {
642
643
644 dur := 3 * time.Second
645 if testing.Short() {
646 dur = 100 * time.Millisecond
647 }
648 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
649
650 sig := make(chan os.Signal, 1)
651 Notify(sig, syscall.SIGUSR1)
652
653 stop := make(chan struct{})
654 go func() {
655 for {
656 select {
657 case <-stop:
658
659
660 quiesce()
661 Stop(sig)
662
663
664
665
666 close(sig)
667 return
668
669 default:
670 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
671 runtime.Gosched()
672 }
673 }
674 }()
675
676 done := make(chan struct{})
677 go func() {
678 for range sig {
679
680 }
681 close(done)
682 }()
683
684 t0 := time.Now()
685 for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() {
686 }
687
688 close(stop)
689 <-done
690 }
691
692 var (
693 checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.")
694 ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received")
695 )
696
697 func TestNotifyContextNotifications(t *testing.T) {
698 if *checkNotifyContext {
699 ctx, _ := NotifyContext(context.Background(), syscall.SIGINT)
700
701
702 var wg sync.WaitGroup
703 n := *ctxNotifyTimes
704 wg.Add(n)
705 for i := 0; i < n; i++ {
706 go func() {
707 syscall.Kill(syscall.Getpid(), syscall.SIGINT)
708 wg.Done()
709 }()
710 }
711 wg.Wait()
712 <-ctx.Done()
713 fmt.Println("received SIGINT")
714
715
716
717 time.Sleep(settleTime)
718 return
719 }
720
721 t.Parallel()
722 testCases := []struct {
723 name string
724 n int
725 }{
726 {"once", 1},
727 {"multiple", 10},
728 }
729 for _, tc := range testCases {
730 tc := tc
731 t.Run(tc.name, func(t *testing.T) {
732 t.Parallel()
733
734 var subTimeout time.Duration
735 if deadline, ok := t.Deadline(); ok {
736 timeout := time.Until(deadline)
737 if timeout < 2*settleTime {
738 t.Fatalf("starting test with less than %v remaining", 2*settleTime)
739 }
740 subTimeout = timeout - (timeout / 10)
741 }
742
743 args := []string{
744 "-test.v",
745 "-test.run=TestNotifyContextNotifications$",
746 "-check_notify_ctx",
747 fmt.Sprintf("-ctx_notify_times=%d", tc.n),
748 }
749 if subTimeout != 0 {
750 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
751 }
752 out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
753 if err != nil {
754 t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out)
755 }
756 if want := []byte("received SIGINT\n"); !bytes.Contains(out, want) {
757 t.Errorf("got %q, wanted %q", out, want)
758 }
759 })
760 }
761 }
762
763 func TestNotifyContextStop(t *testing.T) {
764 Ignore(syscall.SIGHUP)
765 if !Ignored(syscall.SIGHUP) {
766 t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.")
767 }
768
769 parent, cancelParent := context.WithCancel(context.Background())
770 defer cancelParent()
771 c, stop := NotifyContext(parent, syscall.SIGHUP)
772 defer stop()
773
774
775 if Ignored(syscall.SIGHUP) {
776 t.Errorf("expected SIGHUP to not be ignored.")
777 }
778
779 if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got {
780 t.Errorf("c.String() = %q, wanted %q", got, want)
781 }
782
783 stop()
784 select {
785 case <-c.Done():
786 if got := c.Err(); got != context.Canceled {
787 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
788 }
789 case <-time.After(time.Second):
790 t.Errorf("timed out waiting for context to be done after calling stop")
791 }
792 }
793
794 func TestNotifyContextCancelParent(t *testing.T) {
795 parent, cancelParent := context.WithCancel(context.Background())
796 defer cancelParent()
797 c, stop := NotifyContext(parent, syscall.SIGINT)
798 defer stop()
799
800 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
801 t.Errorf("c.String() = %q, want %q", got, want)
802 }
803
804 cancelParent()
805 select {
806 case <-c.Done():
807 if got := c.Err(); got != context.Canceled {
808 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
809 }
810 case <-time.After(time.Second):
811 t.Errorf("timed out waiting for parent context to be canceled")
812 }
813 }
814
815 func TestNotifyContextPrematureCancelParent(t *testing.T) {
816 parent, cancelParent := context.WithCancel(context.Background())
817 defer cancelParent()
818
819 cancelParent()
820 c, stop := NotifyContext(parent, syscall.SIGINT)
821 defer stop()
822
823 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
824 t.Errorf("c.String() = %q, want %q", got, want)
825 }
826
827 select {
828 case <-c.Done():
829 if got := c.Err(); got != context.Canceled {
830 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
831 }
832 case <-time.After(time.Second):
833 t.Errorf("timed out waiting for parent context to be canceled")
834 }
835 }
836
837 func TestNotifyContextSimultaneousStop(t *testing.T) {
838 c, stop := NotifyContext(context.Background(), syscall.SIGINT)
839 defer stop()
840
841 if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
842 t.Errorf("c.String() = %q, want %q", got, want)
843 }
844
845 var wg sync.WaitGroup
846 n := 10
847 wg.Add(n)
848 for i := 0; i < n; i++ {
849 go func() {
850 stop()
851 wg.Done()
852 }()
853 }
854 wg.Wait()
855 select {
856 case <-c.Done():
857 if got := c.Err(); got != context.Canceled {
858 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
859 }
860 case <-time.After(time.Second):
861 t.Errorf("expected context to be canceled")
862 }
863 }
864
865 func TestNotifyContextStringer(t *testing.T) {
866 parent, cancelParent := context.WithCancel(context.Background())
867 defer cancelParent()
868 c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
869 defer stop()
870
871 want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])`
872 if got := fmt.Sprint(c); got != want {
873 t.Errorf("c.String() = %q, want %q", got, want)
874 }
875 }
876
877
878 func TestSignalTrace(t *testing.T) {
879 done := make(chan struct{})
880 quit := make(chan struct{})
881 c := make(chan os.Signal, 1)
882 Notify(c, syscall.SIGHUP)
883
884
885
886
887 go func() {
888 defer close(done)
889 defer Stop(c)
890 pid := syscall.Getpid()
891 for {
892 select {
893 case <-quit:
894 return
895 default:
896 syscall.Kill(pid, syscall.SIGHUP)
897 }
898 waitSig(t, c, syscall.SIGHUP)
899 }
900 }()
901
902 for i := 0; i < 100; i++ {
903 buf := new(bytes.Buffer)
904 if err := trace.Start(buf); err != nil {
905 t.Fatalf("[%d] failed to start tracing: %v", i, err)
906 }
907 time.After(1 * time.Microsecond)
908 trace.Stop()
909 size := buf.Len()
910 if size == 0 {
911 t.Fatalf("[%d] trace is empty", i)
912 }
913 }
914 close(quit)
915 <-done
916 }
917
View as plain text