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
313 _, err := os.Stat("/usr/bin/nohup")
314 if err != nil {
315 t.Skip("cannot find nohup; skipping second half of test")
316 }
317 Ignore(syscall.SIGHUP)
318 os.Remove("nohup.out")
319 out, err := testenv.Command(t, "/usr/bin/nohup", os.Args[0], "-test.run=^TestDetectNohup$", "-check_sighup_ignored").CombinedOutput()
320
321 data, _ := os.ReadFile("nohup.out")
322 os.Remove("nohup.out")
323 if err != nil {
324
325
326
327 if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
328 t.Skip("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
329 }
330
331 t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data)
332 }
333 }
334 }
335
336 var (
337 sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
338 dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP")
339 )
340
341
342 func TestStop(t *testing.T) {
343 sigs := []syscall.Signal{
344 syscall.SIGWINCH,
345 syscall.SIGHUP,
346 syscall.SIGUSR1,
347 }
348
349 for _, sig := range sigs {
350 sig := sig
351 t.Run(fmt.Sprint(sig), func(t *testing.T) {
352
353
354
355
356 t.Parallel()
357
358
359
360
361
362 mayHaveBlockedSignal := false
363 if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) {
364 syscall.Kill(syscall.Getpid(), sig)
365 quiesce()
366
367
368
369 mayHaveBlockedSignal = true
370 }
371
372
373 c := make(chan os.Signal, 1)
374 Notify(c, sig)
375
376
377 syscall.Kill(syscall.Getpid(), sig)
378 waitSig(t, c, sig)
379
380 if mayHaveBlockedSignal {
381
382
383
384
385 quiesce()
386 select {
387 case <-c:
388 default:
389 }
390 }
391
392
393
394 Stop(c)
395 if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
396 syscall.Kill(syscall.Getpid(), sig)
397 quiesce()
398
399 select {
400 case s := <-c:
401 t.Errorf("unexpected signal %v", s)
402 default:
403
404 }
405
406
407
408
409 Notify(c, sig)
410 quiesce()
411 Stop(c)
412 }
413 })
414 }
415 }
416
417
418 func TestNohup(t *testing.T) {
419
420
421
422
423
424
425
426
427
428
429
430 t.Run("uncaught", func(t *testing.T) {
431
432
433
434 c := make(chan os.Signal, 1)
435 Notify(c, syscall.SIGHUP)
436 t.Cleanup(func() { Stop(c) })
437
438 var subTimeout time.Duration
439 if deadline, ok := t.Deadline(); ok {
440 subTimeout = time.Until(deadline)
441 subTimeout -= subTimeout / 10
442 }
443 for i := 1; i <= 2; i++ {
444 i := i
445 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
446 t.Parallel()
447
448 args := []string{
449 "-test.v",
450 "-test.run=^TestStop$",
451 "-send_uncaught_sighup=" + strconv.Itoa(i),
452 "-die_from_sighup",
453 }
454 if subTimeout != 0 {
455 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
456 }
457 out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
458
459 if err == nil {
460 t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
461 } else {
462 t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out)
463 }
464 })
465 }
466 })
467
468 t.Run("nohup", func(t *testing.T) {
469
470
471 if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" {
472 t.Skip("Skipping nohup test due to running in tmux on darwin")
473 }
474
475
476 _, err := exec.LookPath("nohup")
477 if err != nil {
478 t.Skip("cannot find nohup; skipping second half of test")
479 }
480
481 var subTimeout time.Duration
482 if deadline, ok := t.Deadline(); ok {
483 subTimeout = time.Until(deadline)
484 subTimeout -= subTimeout / 10
485 }
486 for i := 1; i <= 2; i++ {
487 i := i
488 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
489 t.Parallel()
490
491
492
493
494
495
496
497 args := []string{
498 os.Args[0],
499 "-test.v",
500 "-test.run=^TestStop$",
501 "-send_uncaught_sighup=" + strconv.Itoa(i),
502 }
503 if subTimeout != 0 {
504 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
505 }
506 out, err := testenv.Command(t, "nohup", args...).CombinedOutput()
507
508 if err != nil {
509
510
511
512 if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
513
514
515 t.Logf("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
516 return
517 }
518
519 t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out)
520 } else {
521 t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out)
522 }
523 })
524 }
525 })
526 }
527
528
529 func TestSIGCONT(t *testing.T) {
530 c := make(chan os.Signal, 1)
531 Notify(c, syscall.SIGCONT)
532 defer Stop(c)
533 syscall.Kill(syscall.Getpid(), syscall.SIGCONT)
534 waitSig(t, c, syscall.SIGCONT)
535 }
536
537
538 func TestAtomicStop(t *testing.T) {
539 if os.Getenv("GO_TEST_ATOMIC_STOP") != "" {
540 atomicStopTestProgram(t)
541 t.Fatal("atomicStopTestProgram returned")
542 }
543
544 testenv.MustHaveExec(t)
545
546
547
548
549
550
551
552
553
554
555 cs := make(chan os.Signal, 1)
556 Notify(cs, syscall.SIGINT)
557 defer Stop(cs)
558
559 const execs = 10
560 for i := 0; i < execs; i++ {
561 timeout := "0"
562 if deadline, ok := t.Deadline(); ok {
563 timeout = time.Until(deadline).String()
564 }
565 cmd := testenv.Command(t, os.Args[0], "-test.run=^TestAtomicStop$", "-test.timeout="+timeout)
566 cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1")
567 out, err := cmd.CombinedOutput()
568 if err == nil {
569 if len(out) > 0 {
570 t.Logf("iteration %d: output %s", i, out)
571 }
572 } else {
573 t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
574 }
575
576 lost := bytes.Contains(out, []byte("lost signal"))
577 if lost {
578 t.Errorf("iteration %d: lost signal", i)
579 }
580
581
582
583 if err == nil {
584 if len(out) > 0 && !lost {
585 t.Errorf("iteration %d: unexpected output", i)
586 }
587 } else {
588 if ee, ok := err.(*exec.ExitError); !ok {
589 t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err)
590 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
591 t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys())
592 } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT {
593 t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee)
594 }
595 }
596 }
597 }
598
599
600
601
602 func atomicStopTestProgram(t *testing.T) {
603
604 if Ignored(syscall.SIGINT) {
605 fmt.Println("SIGINT is ignored")
606 os.Exit(1)
607 }
608
609 const tries = 10
610
611 timeout := 2 * time.Second
612 if deadline, ok := t.Deadline(); ok {
613
614
615 timeout = time.Until(deadline) / (tries + 1)
616 }
617
618 pid := syscall.Getpid()
619 printed := false
620 for i := 0; i < tries; i++ {
621 cs := make(chan os.Signal, 1)
622 Notify(cs, syscall.SIGINT)
623
624 var wg sync.WaitGroup
625 wg.Add(1)
626 go func() {
627 defer wg.Done()
628 Stop(cs)
629 }()
630
631 syscall.Kill(pid, syscall.SIGINT)
632
633
634
635
636
637
638 select {
639 case <-cs:
640 case <-time.After(timeout):
641 if !printed {
642 fmt.Print("lost signal on tries:")
643 printed = true
644 }
645 fmt.Printf(" %d", i)
646 }
647
648 wg.Wait()
649 }
650 if printed {
651 fmt.Print("\n")
652 }
653
654 os.Exit(0)
655 }
656
657 func TestTime(t *testing.T) {
658
659
660 dur := 3 * time.Second
661 if testing.Short() {
662 dur = 100 * time.Millisecond
663 }
664 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
665
666 sig := make(chan os.Signal, 1)
667 Notify(sig, syscall.SIGUSR1)
668
669 stop := make(chan struct{})
670 go func() {
671 for {
672 select {
673 case <-stop:
674
675
676 quiesce()
677 Stop(sig)
678
679
680
681
682 close(sig)
683 return
684
685 default:
686 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
687 runtime.Gosched()
688 }
689 }
690 }()
691
692 done := make(chan struct{})
693 go func() {
694 for range sig {
695
696 }
697 close(done)
698 }()
699
700 t0 := time.Now()
701 for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() {
702 }
703
704 close(stop)
705 <-done
706 }
707
708 var (
709 checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.")
710 ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received")
711 )
712
713 func TestNotifyContextNotifications(t *testing.T) {
714 if *checkNotifyContext {
715 ctx, _ := NotifyContext(context.Background(), syscall.SIGINT)
716
717
718 var wg sync.WaitGroup
719 n := *ctxNotifyTimes
720 wg.Add(n)
721 for i := 0; i < n; i++ {
722 go func() {
723 syscall.Kill(syscall.Getpid(), syscall.SIGINT)
724 wg.Done()
725 }()
726 }
727 wg.Wait()
728 <-ctx.Done()
729 fmt.Println("received SIGINT")
730
731
732
733 time.Sleep(settleTime)
734 return
735 }
736
737 t.Parallel()
738 testCases := []struct {
739 name string
740 n int
741 }{
742 {"once", 1},
743 {"multiple", 10},
744 }
745 for _, tc := range testCases {
746 tc := tc
747 t.Run(tc.name, func(t *testing.T) {
748 t.Parallel()
749
750 var subTimeout time.Duration
751 if deadline, ok := t.Deadline(); ok {
752 timeout := time.Until(deadline)
753 if timeout < 2*settleTime {
754 t.Fatalf("starting test with less than %v remaining", 2*settleTime)
755 }
756 subTimeout = timeout - (timeout / 10)
757 }
758
759 args := []string{
760 "-test.v",
761 "-test.run=^TestNotifyContextNotifications$",
762 "-check_notify_ctx",
763 fmt.Sprintf("-ctx_notify_times=%d", tc.n),
764 }
765 if subTimeout != 0 {
766 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
767 }
768 out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
769 if err != nil {
770 t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out)
771 }
772 if want := []byte("received SIGINT\n"); !bytes.Contains(out, want) {
773 t.Errorf("got %q, wanted %q", out, want)
774 }
775 })
776 }
777 }
778
779 func TestNotifyContextStop(t *testing.T) {
780 Ignore(syscall.SIGHUP)
781 if !Ignored(syscall.SIGHUP) {
782 t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.")
783 }
784
785 parent, cancelParent := context.WithCancel(context.Background())
786 defer cancelParent()
787 c, stop := NotifyContext(parent, syscall.SIGHUP)
788 defer stop()
789
790
791 if Ignored(syscall.SIGHUP) {
792 t.Errorf("expected SIGHUP to not be ignored.")
793 }
794
795 if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got {
796 t.Errorf("c.String() = %q, wanted %q", got, want)
797 }
798
799 stop()
800 <-c.Done()
801 if got := c.Err(); got != context.Canceled {
802 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
803 }
804 }
805
806 func TestNotifyContextCancelParent(t *testing.T) {
807 parent, cancelParent := context.WithCancel(context.Background())
808 defer cancelParent()
809 c, stop := NotifyContext(parent, syscall.SIGINT)
810 defer stop()
811
812 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
813 t.Errorf("c.String() = %q, want %q", got, want)
814 }
815
816 cancelParent()
817 <-c.Done()
818 if got := c.Err(); got != context.Canceled {
819 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
820 }
821 }
822
823 func TestNotifyContextPrematureCancelParent(t *testing.T) {
824 parent, cancelParent := context.WithCancel(context.Background())
825 defer cancelParent()
826
827 cancelParent()
828 c, stop := NotifyContext(parent, syscall.SIGINT)
829 defer stop()
830
831 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
832 t.Errorf("c.String() = %q, want %q", got, want)
833 }
834
835 <-c.Done()
836 if got := c.Err(); got != context.Canceled {
837 t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
838 }
839 }
840
841 func TestNotifyContextSimultaneousStop(t *testing.T) {
842 c, stop := NotifyContext(context.Background(), syscall.SIGINT)
843 defer stop()
844
845 if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
846 t.Errorf("c.String() = %q, want %q", got, want)
847 }
848
849 var wg sync.WaitGroup
850 n := 10
851 wg.Add(n)
852 for i := 0; i < n; i++ {
853 go func() {
854 stop()
855 wg.Done()
856 }()
857 }
858 wg.Wait()
859 <-c.Done()
860 if got := c.Err(); got != context.Canceled {
861 t.Errorf("c.Err() = %q, want %q", got, context.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 trace.Stop()
908 size := buf.Len()
909 if size == 0 {
910 t.Fatalf("[%d] trace is empty", i)
911 }
912 }
913 close(quit)
914 <-done
915 }
916
View as plain text