Source file
src/runtime/proc_test.go
1
2
3
4
5 package runtime_test
6
7 import (
8 "bytes"
9 "fmt"
10 "internal/race"
11 "internal/testenv"
12 "internal/trace"
13 "internal/trace/testtrace"
14 "io"
15 "math"
16 "net"
17 "runtime"
18 "runtime/debug"
19 "slices"
20 "strings"
21 "sync"
22 "sync/atomic"
23 "syscall"
24 "testing"
25 "time"
26 )
27
28 var stop = make(chan bool, 1)
29
30 func perpetuumMobile() {
31 select {
32 case <-stop:
33 default:
34 go perpetuumMobile()
35 }
36 }
37
38 func TestStopTheWorldDeadlock(t *testing.T) {
39 if runtime.GOARCH == "wasm" {
40 t.Skip("no preemption on wasm yet")
41 }
42 if testing.Short() {
43 t.Skip("skipping during short test")
44 }
45 maxprocs := runtime.GOMAXPROCS(3)
46 compl := make(chan bool, 2)
47 go func() {
48 for i := 0; i != 1000; i += 1 {
49 runtime.GC()
50 }
51 compl <- true
52 }()
53 go func() {
54 for i := 0; i != 1000; i += 1 {
55 runtime.GOMAXPROCS(3)
56 }
57 compl <- true
58 }()
59 go perpetuumMobile()
60 <-compl
61 <-compl
62 stop <- true
63 runtime.GOMAXPROCS(maxprocs)
64 }
65
66 func TestYieldProgress(t *testing.T) {
67 testYieldProgress(false)
68 }
69
70 func TestYieldLockedProgress(t *testing.T) {
71 testYieldProgress(true)
72 }
73
74 func testYieldProgress(locked bool) {
75 c := make(chan bool)
76 cack := make(chan bool)
77 go func() {
78 if locked {
79 runtime.LockOSThread()
80 }
81 for {
82 select {
83 case <-c:
84 cack <- true
85 return
86 default:
87 runtime.Gosched()
88 }
89 }
90 }()
91 time.Sleep(10 * time.Millisecond)
92 c <- true
93 <-cack
94 }
95
96 func TestYieldLocked(t *testing.T) {
97 const N = 10
98 c := make(chan bool)
99 go func() {
100 runtime.LockOSThread()
101 for i := 0; i < N; i++ {
102 runtime.Gosched()
103 time.Sleep(time.Millisecond)
104 }
105 c <- true
106
107 }()
108 <-c
109 }
110
111 func TestGoroutineParallelism(t *testing.T) {
112 if runtime.NumCPU() == 1 {
113
114 t.Skip("skipping on uniprocessor")
115 }
116 P := 4
117 N := 10
118 if testing.Short() {
119 P = 3
120 N = 3
121 }
122 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
123
124
125
126 defer debug.SetGCPercent(debug.SetGCPercent(-1))
127
128
129
130 runtime.GC()
131 for try := 0; try < N; try++ {
132 done := make(chan bool)
133 x := uint32(0)
134 for p := 0; p < P; p++ {
135
136 go func(p int) {
137 for i := 0; i < 3; i++ {
138 expected := uint32(P*i + p)
139 for atomic.LoadUint32(&x) != expected {
140 }
141 atomic.StoreUint32(&x, expected+1)
142 }
143 done <- true
144 }(p)
145 }
146 for p := 0; p < P; p++ {
147 <-done
148 }
149 }
150 }
151
152
153 func TestGoroutineParallelism2(t *testing.T) {
154
155 testGoroutineParallelism2(t, true, false)
156 testGoroutineParallelism2(t, false, true)
157 testGoroutineParallelism2(t, true, true)
158 }
159
160 func testGoroutineParallelism2(t *testing.T, load, netpoll bool) {
161 if runtime.NumCPU() == 1 {
162
163 t.Skip("skipping on uniprocessor")
164 }
165 P := 4
166 N := 10
167 if testing.Short() {
168 N = 3
169 }
170 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
171
172
173
174 defer debug.SetGCPercent(debug.SetGCPercent(-1))
175
176
177
178 runtime.GC()
179 for try := 0; try < N; try++ {
180 if load {
181
182
183
184 done := make(chan bool)
185 x := uint32(0)
186 for p := 0; p < P; p++ {
187 go func() {
188 if atomic.AddUint32(&x, 1) == uint32(P) {
189 done <- true
190 return
191 }
192 for atomic.LoadUint32(&x) != uint32(P) {
193 }
194 }()
195 }
196 <-done
197 }
198 if netpoll {
199
200 laddr := "localhost:0"
201 if runtime.GOOS == "android" {
202
203
204
205 laddr = "127.0.0.1:0"
206 }
207 ln, err := net.Listen("tcp", laddr)
208 if err == nil {
209 defer ln.Close()
210 }
211 }
212 done := make(chan bool)
213 x := uint32(0)
214
215 for p := 0; p < P/2; p++ {
216 go func(p int) {
217 for p2 := 0; p2 < 2; p2++ {
218 go func(p2 int) {
219 for i := 0; i < 3; i++ {
220 expected := uint32(P*i + p*2 + p2)
221 for atomic.LoadUint32(&x) != expected {
222 }
223 atomic.StoreUint32(&x, expected+1)
224 }
225 done <- true
226 }(p2)
227 }
228 }(p)
229 }
230 for p := 0; p < P; p++ {
231 <-done
232 }
233 }
234 }
235
236 func TestBlockLocked(t *testing.T) {
237 const N = 10
238 c := make(chan bool)
239 go func() {
240 runtime.LockOSThread()
241 for i := 0; i < N; i++ {
242 c <- true
243 }
244 runtime.UnlockOSThread()
245 }()
246 for i := 0; i < N; i++ {
247 <-c
248 }
249 }
250
251 func TestTimerFairness(t *testing.T) {
252 if runtime.GOARCH == "wasm" {
253 t.Skip("no preemption on wasm yet")
254 }
255
256 done := make(chan bool)
257 c := make(chan bool)
258 for i := 0; i < 2; i++ {
259 go func() {
260 for {
261 select {
262 case c <- true:
263 case <-done:
264 return
265 }
266 }
267 }()
268 }
269
270 timer := time.After(20 * time.Millisecond)
271 for {
272 select {
273 case <-c:
274 case <-timer:
275 close(done)
276 return
277 }
278 }
279 }
280
281 func TestTimerFairness2(t *testing.T) {
282 if runtime.GOARCH == "wasm" {
283 t.Skip("no preemption on wasm yet")
284 }
285
286 done := make(chan bool)
287 c := make(chan bool)
288 for i := 0; i < 2; i++ {
289 go func() {
290 timer := time.After(20 * time.Millisecond)
291 var buf [1]byte
292 for {
293 syscall.Read(0, buf[0:0])
294 select {
295 case c <- true:
296 case <-c:
297 case <-timer:
298 done <- true
299 return
300 }
301 }
302 }()
303 }
304 <-done
305 <-done
306 }
307
308
309
310 var preempt = func() int {
311 var a [128]int
312 sum := 0
313 for _, v := range a {
314 sum += v
315 }
316 return sum
317 }
318
319 func TestPreemption(t *testing.T) {
320 if runtime.GOARCH == "wasm" {
321 t.Skip("no preemption on wasm yet")
322 }
323
324
325 N := 5
326 if testing.Short() {
327 N = 2
328 }
329 c := make(chan bool)
330 var x uint32
331 for g := 0; g < 2; g++ {
332 go func(g int) {
333 for i := 0; i < N; i++ {
334 for atomic.LoadUint32(&x) != uint32(g) {
335 preempt()
336 }
337 atomic.StoreUint32(&x, uint32(1-g))
338 }
339 c <- true
340 }(g)
341 }
342 <-c
343 <-c
344 }
345
346 func TestPreemptionGC(t *testing.T) {
347 if runtime.GOARCH == "wasm" {
348 t.Skip("no preemption on wasm yet")
349 }
350
351
352 P := 5
353 N := 10
354 if testing.Short() {
355 P = 3
356 N = 2
357 }
358 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P + 1))
359 var stop uint32
360 for i := 0; i < P; i++ {
361 go func() {
362 for atomic.LoadUint32(&stop) == 0 {
363 preempt()
364 }
365 }()
366 }
367 for i := 0; i < N; i++ {
368 runtime.Gosched()
369 runtime.GC()
370 }
371 atomic.StoreUint32(&stop, 1)
372 }
373
374 func TestAsyncPreempt(t *testing.T) {
375 if !runtime.PreemptMSupported {
376 t.Skip("asynchronous preemption not supported on this platform")
377 }
378 output := runTestProg(t, "testprog", "AsyncPreempt")
379 want := "OK\n"
380 if output != want {
381 t.Fatalf("want %s, got %s\n", want, output)
382 }
383 }
384
385 func TestGCFairness(t *testing.T) {
386 output := runTestProg(t, "testprog", "GCFairness")
387 want := "OK\n"
388 if output != want {
389 t.Fatalf("want %s, got %s\n", want, output)
390 }
391 }
392
393 func TestGCFairness2(t *testing.T) {
394 output := runTestProg(t, "testprog", "GCFairness2")
395 want := "OK\n"
396 if output != want {
397 t.Fatalf("want %s, got %s\n", want, output)
398 }
399 }
400
401 func TestNumGoroutine(t *testing.T) {
402 output := runTestProg(t, "testprog", "NumGoroutine")
403 want := "1\n"
404 if output != want {
405 t.Fatalf("want %q, got %q", want, output)
406 }
407
408 buf := make([]byte, 1<<20)
409
410
411
412
413 for i := 0; ; i++ {
414
415
416
417
418 runtime.Gosched()
419
420 n := runtime.NumGoroutine()
421 buf = buf[:runtime.Stack(buf, true)]
422
423
424
425 output := strings.ReplaceAll(string(buf), "in goroutine", "")
426 nstk := strings.Count(output, "goroutine ")
427 if n == nstk {
428 break
429 }
430 if i >= 10 {
431 t.Fatalf("NumGoroutine=%d, but found %d goroutines in stack dump: %s", n, nstk, buf)
432 }
433 }
434 }
435
436 func TestPingPongHog(t *testing.T) {
437 if runtime.GOARCH == "wasm" {
438 t.Skip("no preemption on wasm yet")
439 }
440 if testing.Short() {
441 t.Skip("skipping in -short mode")
442 }
443 if race.Enabled {
444
445
446 t.Skip("skipping in -race mode")
447 }
448
449 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
450 done := make(chan bool)
451 hogChan, lightChan := make(chan bool), make(chan bool)
452 hogCount, lightCount := 0, 0
453
454 run := func(limit int, counter *int, wake chan bool) {
455 for {
456 select {
457 case <-done:
458 return
459
460 case <-wake:
461 for i := 0; i < limit; i++ {
462 *counter++
463 }
464 wake <- true
465 }
466 }
467 }
468
469
470 for i := 0; i < 2; i++ {
471 go run(1e6, &hogCount, hogChan)
472 }
473
474
475 for i := 0; i < 2; i++ {
476 go run(1e3, &lightCount, lightChan)
477 }
478
479
480 hogChan <- true
481 lightChan <- true
482 time.Sleep(100 * time.Millisecond)
483 close(done)
484 <-hogChan
485 <-lightChan
486
487
488
489
490
491
492
493 const factor = 20
494 if hogCount/factor > lightCount || lightCount/factor > hogCount {
495 t.Fatalf("want hogCount/lightCount in [%v, %v]; got %d/%d = %g", 1.0/factor, factor, hogCount, lightCount, float64(hogCount)/float64(lightCount))
496 }
497 }
498
499 func BenchmarkPingPongHog(b *testing.B) {
500 if b.N == 0 {
501 return
502 }
503 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
504
505
506 stop, done := make(chan bool), make(chan bool)
507 go func() {
508 for {
509 select {
510 case <-stop:
511 done <- true
512 return
513 default:
514 }
515 }
516 }()
517
518
519 ping, pong := make(chan bool), make(chan bool)
520 go func() {
521 for j := 0; j < b.N; j++ {
522 pong <- <-ping
523 }
524 close(stop)
525 done <- true
526 }()
527 go func() {
528 for i := 0; i < b.N; i++ {
529 ping <- <-pong
530 }
531 done <- true
532 }()
533 b.ResetTimer()
534 ping <- true
535 <-stop
536 b.StopTimer()
537 <-ping
538 <-done
539 <-done
540 <-done
541 }
542
543 var padData [128]uint64
544
545 func stackGrowthRecursive(i int) {
546 var pad [128]uint64
547 pad = padData
548 for j := range pad {
549 if pad[j] != 0 {
550 return
551 }
552 }
553 if i != 0 {
554 stackGrowthRecursive(i - 1)
555 }
556 }
557
558 func TestPreemptSplitBig(t *testing.T) {
559 if testing.Short() {
560 t.Skip("skipping in -short mode")
561 }
562 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
563 stop := make(chan int)
564 go big(stop)
565 for i := 0; i < 3; i++ {
566 time.Sleep(10 * time.Microsecond)
567 runtime.GC()
568 }
569 close(stop)
570 }
571
572 func big(stop chan int) int {
573 n := 0
574 for {
575
576 for i := 0; i < 1e9; i++ {
577 n++
578 }
579
580
581 bigframe(stop)
582
583
584 select {
585 case <-stop:
586 return n
587 }
588 }
589 }
590
591 func bigframe(stop chan int) int {
592
593
594
595 var x [8192]byte
596 return small(stop, &x)
597 }
598
599 func small(stop chan int, x *[8192]byte) int {
600 for i := range x {
601 x[i] = byte(i)
602 }
603 sum := 0
604 for i := range x {
605 sum += int(x[i])
606 }
607
608
609
610 nonleaf(stop)
611
612 return sum
613 }
614
615 func nonleaf(stop chan int) bool {
616
617 select {
618 case <-stop:
619 return true
620 default:
621 return false
622 }
623 }
624
625 func TestSchedLocalQueue(t *testing.T) {
626 runtime.RunSchedLocalQueueTest()
627 }
628
629 func TestSchedLocalQueueSteal(t *testing.T) {
630 runtime.RunSchedLocalQueueStealTest()
631 }
632
633 func TestSchedLocalQueueEmpty(t *testing.T) {
634 if runtime.NumCPU() == 1 {
635
636 t.Skip("skipping on uniprocessor")
637 }
638 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
639
640
641
642 defer debug.SetGCPercent(debug.SetGCPercent(-1))
643
644
645
646 runtime.GC()
647
648 iters := int(1e5)
649 if testing.Short() {
650 iters = 1e2
651 }
652 runtime.RunSchedLocalQueueEmptyTest(iters)
653 }
654
655 func benchmarkStackGrowth(b *testing.B, rec int) {
656 b.RunParallel(func(pb *testing.PB) {
657 for pb.Next() {
658 stackGrowthRecursive(rec)
659 }
660 })
661 }
662
663 func BenchmarkStackGrowth(b *testing.B) {
664 benchmarkStackGrowth(b, 10)
665 }
666
667 func BenchmarkStackGrowthDeep(b *testing.B) {
668 benchmarkStackGrowth(b, 1024)
669 }
670
671 func BenchmarkCreateGoroutines(b *testing.B) {
672 benchmarkCreateGoroutines(b, 1)
673 }
674
675 func BenchmarkCreateGoroutinesParallel(b *testing.B) {
676 benchmarkCreateGoroutines(b, runtime.GOMAXPROCS(-1))
677 }
678
679 func benchmarkCreateGoroutines(b *testing.B, procs int) {
680 c := make(chan bool)
681 var f func(n int)
682 f = func(n int) {
683 if n == 0 {
684 c <- true
685 return
686 }
687 go f(n - 1)
688 }
689 for i := 0; i < procs; i++ {
690 go f(b.N / procs)
691 }
692 for i := 0; i < procs; i++ {
693 <-c
694 }
695 }
696
697 func BenchmarkCreateGoroutinesCapture(b *testing.B) {
698 b.ReportAllocs()
699 for i := 0; i < b.N; i++ {
700 const N = 4
701 var wg sync.WaitGroup
702 wg.Add(N)
703 for i := 0; i < N; i++ {
704 go func() {
705 if i >= N {
706 b.Logf("bad")
707 }
708 wg.Done()
709 }()
710 }
711 wg.Wait()
712 }
713 }
714
715
716
717 func warmupScheduler(targetThreadCount int) {
718 var wg sync.WaitGroup
719 var count int32
720 for i := 0; i < targetThreadCount; i++ {
721 wg.Add(1)
722 go func() {
723 atomic.AddInt32(&count, 1)
724 for atomic.LoadInt32(&count) < int32(targetThreadCount) {
725
726 }
727
728
729 doWork(time.Millisecond)
730 wg.Done()
731 }()
732 }
733 wg.Wait()
734 }
735
736 func doWork(dur time.Duration) {
737 start := time.Now()
738 for time.Since(start) < dur {
739 }
740 }
741
742
743
744
745
746
747
748 func BenchmarkCreateGoroutinesSingle(b *testing.B) {
749
750
751 warmupScheduler(runtime.GOMAXPROCS(0))
752 b.ResetTimer()
753
754 var wg sync.WaitGroup
755 wg.Add(b.N)
756 for i := 0; i < b.N; i++ {
757 go func() {
758 wg.Done()
759 }()
760 }
761 wg.Wait()
762 }
763
764 func BenchmarkClosureCall(b *testing.B) {
765 sum := 0
766 off1 := 1
767 for i := 0; i < b.N; i++ {
768 off2 := 2
769 func() {
770 sum += i + off1 + off2
771 }()
772 }
773 _ = sum
774 }
775
776 func benchmarkWakeupParallel(b *testing.B, spin func(time.Duration)) {
777 if runtime.GOMAXPROCS(0) == 1 {
778 b.Skip("skipping: GOMAXPROCS=1")
779 }
780
781 wakeDelay := 5 * time.Microsecond
782 for _, delay := range []time.Duration{
783 0,
784 1 * time.Microsecond,
785 2 * time.Microsecond,
786 5 * time.Microsecond,
787 10 * time.Microsecond,
788 20 * time.Microsecond,
789 50 * time.Microsecond,
790 100 * time.Microsecond,
791 } {
792 b.Run(delay.String(), func(b *testing.B) {
793 if b.N == 0 {
794 return
795 }
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828 ping, pong := make(chan struct{}), make(chan struct{})
829 start := make(chan struct{})
830 done := make(chan struct{})
831 go func() {
832 <-start
833 for i := 0; i < b.N; i++ {
834
835 spin(delay + wakeDelay)
836 ping <- struct{}{}
837
838 spin(delay)
839 <-pong
840 }
841 done <- struct{}{}
842 }()
843 go func() {
844 for i := 0; i < b.N; i++ {
845
846 spin(delay)
847 <-ping
848
849 spin(delay + wakeDelay)
850 pong <- struct{}{}
851 }
852 done <- struct{}{}
853 }()
854 b.ResetTimer()
855 start <- struct{}{}
856 <-done
857 <-done
858 })
859 }
860 }
861
862 func BenchmarkWakeupParallelSpinning(b *testing.B) {
863 benchmarkWakeupParallel(b, func(d time.Duration) {
864 end := time.Now().Add(d)
865 for time.Now().Before(end) {
866
867 }
868 })
869 }
870
871
872
873
874
875 var sysNanosleep func(d time.Duration)
876
877 func BenchmarkWakeupParallelSyscall(b *testing.B) {
878 if sysNanosleep == nil {
879 b.Skipf("skipping on %v; sysNanosleep not defined", runtime.GOOS)
880 }
881 benchmarkWakeupParallel(b, func(d time.Duration) {
882 sysNanosleep(d)
883 })
884 }
885
886 type Matrix [][]float64
887
888 func BenchmarkMatmult(b *testing.B) {
889 b.StopTimer()
890
891
892 n := int(math.Cbrt(float64(b.N))) + 1
893 A := makeMatrix(n)
894 B := makeMatrix(n)
895 C := makeMatrix(n)
896 b.StartTimer()
897 matmult(nil, A, B, C, 0, n, 0, n, 0, n, 8)
898 }
899
900 func makeMatrix(n int) Matrix {
901 m := make(Matrix, n)
902 for i := 0; i < n; i++ {
903 m[i] = make([]float64, n)
904 for j := 0; j < n; j++ {
905 m[i][j] = float64(i*n + j)
906 }
907 }
908 return m
909 }
910
911 func matmult(done chan<- struct{}, A, B, C Matrix, i0, i1, j0, j1, k0, k1, threshold int) {
912 di := i1 - i0
913 dj := j1 - j0
914 dk := k1 - k0
915 if di >= dj && di >= dk && di >= threshold {
916
917 mi := i0 + di/2
918 done1 := make(chan struct{}, 1)
919 go matmult(done1, A, B, C, i0, mi, j0, j1, k0, k1, threshold)
920 matmult(nil, A, B, C, mi, i1, j0, j1, k0, k1, threshold)
921 <-done1
922 } else if dj >= dk && dj >= threshold {
923
924 mj := j0 + dj/2
925 done1 := make(chan struct{}, 1)
926 go matmult(done1, A, B, C, i0, i1, j0, mj, k0, k1, threshold)
927 matmult(nil, A, B, C, i0, i1, mj, j1, k0, k1, threshold)
928 <-done1
929 } else if dk >= threshold {
930
931
932 mk := k0 + dk/2
933 matmult(nil, A, B, C, i0, i1, j0, j1, k0, mk, threshold)
934 matmult(nil, A, B, C, i0, i1, j0, j1, mk, k1, threshold)
935 } else {
936
937 for i := i0; i < i1; i++ {
938 for j := j0; j < j1; j++ {
939 for k := k0; k < k1; k++ {
940 C[i][j] += A[i][k] * B[k][j]
941 }
942 }
943 }
944 }
945 if done != nil {
946 done <- struct{}{}
947 }
948 }
949
950 func TestStealOrder(t *testing.T) {
951 runtime.RunStealOrderTest()
952 }
953
954 func TestLockOSThreadNesting(t *testing.T) {
955 if runtime.GOARCH == "wasm" {
956 t.Skip("no threads on wasm yet")
957 }
958
959 go func() {
960 e, i := runtime.LockOSCounts()
961 if e != 0 || i != 0 {
962 t.Errorf("want locked counts 0, 0; got %d, %d", e, i)
963 return
964 }
965 runtime.LockOSThread()
966 runtime.LockOSThread()
967 runtime.UnlockOSThread()
968 e, i = runtime.LockOSCounts()
969 if e != 1 || i != 0 {
970 t.Errorf("want locked counts 1, 0; got %d, %d", e, i)
971 return
972 }
973 runtime.UnlockOSThread()
974 e, i = runtime.LockOSCounts()
975 if e != 0 || i != 0 {
976 t.Errorf("want locked counts 0, 0; got %d, %d", e, i)
977 return
978 }
979 }()
980 }
981
982 func TestLockOSThreadExit(t *testing.T) {
983 testLockOSThreadExit(t, "testprog")
984 }
985
986 func testLockOSThreadExit(t *testing.T, prog string) {
987 output := runTestProg(t, prog, "LockOSThreadMain", "GOMAXPROCS=1")
988 want := "OK\n"
989 if output != want {
990 t.Errorf("want %q, got %q", want, output)
991 }
992
993 output = runTestProg(t, prog, "LockOSThreadAlt")
994 if output != want {
995 t.Errorf("want %q, got %q", want, output)
996 }
997 }
998
999 func TestLockOSThreadAvoidsStatePropagation(t *testing.T) {
1000 want := "OK\n"
1001 skip := "unshare not permitted\n"
1002 output := runTestProg(t, "testprog", "LockOSThreadAvoidsStatePropagation", "GOMAXPROCS=1")
1003 if output == skip {
1004 t.Skip("unshare syscall not permitted on this system")
1005 } else if output != want {
1006 t.Errorf("want %q, got %q", want, output)
1007 }
1008 }
1009
1010 func TestLockOSThreadTemplateThreadRace(t *testing.T) {
1011 testenv.MustHaveGoRun(t)
1012
1013 exe, err := buildTestProg(t, "testprog")
1014 if err != nil {
1015 t.Fatal(err)
1016 }
1017
1018 iterations := 100
1019 if testing.Short() {
1020
1021
1022 iterations = 5
1023 }
1024 for i := 0; i < iterations; i++ {
1025 want := "OK\n"
1026 output := runBuiltTestProg(t, exe, "LockOSThreadTemplateThreadRace")
1027 if output != want {
1028 t.Fatalf("run %d: want %q, got %q", i, want, output)
1029 }
1030 }
1031 }
1032
1033 func TestLockOSThreadVgetrandom(t *testing.T) {
1034 if runtime.GOOS != "linux" {
1035 t.Skipf("vgetrandom only relevant on Linux")
1036 }
1037 output := runTestProg(t, "testprog", "LockOSThreadVgetrandom")
1038 want := "OK\n"
1039 if output != want {
1040 t.Errorf("want %q, got %q", want, output)
1041 }
1042 }
1043
1044
1045
1046
1047 func fakeSyscall(duration time.Duration) {
1048 runtime.Entersyscall()
1049 for start := runtime.Nanotime(); runtime.Nanotime()-start < int64(duration); {
1050 }
1051 runtime.Exitsyscall()
1052 }
1053
1054
1055 func testPreemptionAfterSyscall(t *testing.T, syscallDuration time.Duration) {
1056 if runtime.GOARCH == "wasm" {
1057 t.Skip("no preemption on wasm yet")
1058 }
1059
1060 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
1061
1062 iterations := 10
1063 if testing.Short() {
1064 iterations = 1
1065 }
1066 const (
1067 maxDuration = 5 * time.Second
1068 nroutines = 8
1069 )
1070
1071 for i := 0; i < iterations; i++ {
1072 c := make(chan bool, nroutines)
1073 stop := uint32(0)
1074
1075 start := time.Now()
1076 for g := 0; g < nroutines; g++ {
1077 go func(stop *uint32) {
1078 c <- true
1079 for atomic.LoadUint32(stop) == 0 {
1080 fakeSyscall(syscallDuration)
1081 }
1082 c <- true
1083 }(&stop)
1084 }
1085
1086 for g := 0; g < nroutines; g++ {
1087 <-c
1088 }
1089 atomic.StoreUint32(&stop, 1)
1090
1091 for g := 0; g < nroutines; g++ {
1092 <-c
1093 }
1094 duration := time.Since(start)
1095
1096 if duration > maxDuration {
1097 t.Errorf("timeout exceeded: %v (%v)", duration, maxDuration)
1098 }
1099 }
1100 }
1101
1102 func TestPreemptionAfterSyscall(t *testing.T) {
1103 if runtime.GOOS == "plan9" {
1104 testenv.SkipFlaky(t, 41015)
1105 }
1106
1107 for _, i := range []time.Duration{10, 100, 1000} {
1108 d := i * time.Microsecond
1109 t.Run(fmt.Sprint(d), func(t *testing.T) {
1110 testPreemptionAfterSyscall(t, d)
1111 })
1112 }
1113 }
1114
1115 func TestGetgThreadSwitch(t *testing.T) {
1116 runtime.RunGetgThreadSwitchTest()
1117 }
1118
1119
1120
1121
1122
1123 func TestNetpollBreak(t *testing.T) {
1124 if runtime.GOMAXPROCS(0) == 1 {
1125 t.Skip("skipping: GOMAXPROCS=1")
1126 }
1127
1128
1129 runtime.NetpollGenericInit()
1130
1131 start := time.Now()
1132 c := make(chan bool, 2)
1133 go func() {
1134 c <- true
1135 runtime.Netpoll(10 * time.Second.Nanoseconds())
1136 c <- true
1137 }()
1138 <-c
1139
1140
1141
1142 loop:
1143 for {
1144 runtime.Usleep(100)
1145 runtime.NetpollBreak()
1146 runtime.NetpollBreak()
1147 select {
1148 case <-c:
1149 break loop
1150 default:
1151 }
1152 }
1153 if dur := time.Since(start); dur > 5*time.Second {
1154 t.Errorf("netpollBreak did not interrupt netpoll: slept for: %v", dur)
1155 }
1156 }
1157
1158
1159
1160 func TestBigGOMAXPROCS(t *testing.T) {
1161 t.Parallel()
1162 output := runTestProg(t, "testprog", "NonexistentTest", "GOMAXPROCS=1024")
1163
1164 for _, errstr := range []string{
1165 "failed to create new OS thread",
1166 "cannot allocate memory",
1167 } {
1168 if strings.Contains(output, errstr) {
1169 t.Skipf("failed to create 1024 threads")
1170 }
1171 }
1172 if !strings.Contains(output, "unknown function: NonexistentTest") {
1173 t.Errorf("output:\n%s\nwanted:\nunknown function: NonexistentTest", output)
1174 }
1175 }
1176
1177 type goroutineState struct {
1178 G trace.GoID
1179 P trace.ProcID
1180 M trace.ThreadID
1181 }
1182
1183 func newGoroutineState(g trace.GoID) *goroutineState {
1184 return &goroutineState{
1185 G: g,
1186 P: trace.NoProc,
1187 M: trace.NoThread,
1188 }
1189 }
1190
1191
1192
1193 func TestTraceSTW(t *testing.T) {
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212 if testing.Short() {
1213 t.Skip("skipping in -short mode")
1214 }
1215
1216 if runtime.NumCPU() < 4 {
1217 t.Skip("This test sets GOMAXPROCS=4 and wants to avoid thread descheduling as much as possible. Skip on machines with less than 4 CPUs")
1218 }
1219
1220 const runs = 50
1221
1222 var errors int
1223 for i := range runs {
1224 err := runTestTracesSTW(t, i, "TraceSTW", "stop-the-world (read mem stats)")
1225 if err != nil {
1226 t.Logf("Run %d failed: %v", i, err)
1227 errors++
1228 }
1229 }
1230
1231 pct := float64(errors) / float64(runs)
1232 t.Logf("Errors: %d/%d = %f%%", errors, runs, 100*pct)
1233 if pct > 0.25 {
1234 t.Errorf("Error rate too high")
1235 }
1236 }
1237
1238
1239
1240 func TestTraceGCSTW(t *testing.T) {
1241
1242
1243
1244
1245
1246
1247
1248 if testing.Short() {
1249 t.Skip("skipping in -short mode")
1250 }
1251
1252 if runtime.NumCPU() < 8 {
1253 t.Skip("This test sets GOMAXPROCS=8 and wants to avoid thread descheduling as much as possible. Skip on machines with less than 8 CPUs")
1254 }
1255
1256 const runs = 50
1257
1258 var errors int
1259 for i := range runs {
1260 err := runTestTracesSTW(t, i, "TraceGCSTW", "stop-the-world (GC sweep termination)")
1261 if err != nil {
1262 t.Logf("Run %d failed: %v", i, err)
1263 errors++
1264 }
1265 }
1266
1267 pct := float64(errors) / float64(runs)
1268 t.Logf("Errors: %d/%d = %f%%", errors, runs, 100*pct)
1269 if pct > 0.25 {
1270 t.Errorf("Error rate too high")
1271 }
1272 }
1273
1274 func runTestTracesSTW(t *testing.T, run int, name, stwType string) (err error) {
1275 t.Logf("Run %d", run)
1276
1277
1278
1279
1280
1281
1282 buf := []byte(runTestProg(t, "testprog", name, "GORACE=atexit_sleep_ms=0"))
1283
1284
1285
1286
1287
1288
1289
1290 defer func() {
1291 if err != nil || t.Failed() {
1292 testtrace.Dump(t, fmt.Sprintf("Test%s-run%d", name, run), []byte(buf), false)
1293 }
1294 }()
1295
1296 br, err := trace.NewReader(bytes.NewReader(buf))
1297 if err != nil {
1298 t.Fatalf("NewReader got err %v want nil", err)
1299 }
1300
1301 var targetGoroutines []*goroutineState
1302 findGoroutine := func(goid trace.GoID) *goroutineState {
1303 for _, gs := range targetGoroutines {
1304 if gs.G == goid {
1305 return gs
1306 }
1307 }
1308 return nil
1309 }
1310 findProc := func(pid trace.ProcID) *goroutineState {
1311 for _, gs := range targetGoroutines {
1312 if gs.P == pid {
1313 return gs
1314 }
1315 }
1316 return nil
1317 }
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329 var startLogSeen bool
1330 var stwSeen bool
1331 findStart:
1332 for {
1333 ev, err := br.ReadEvent()
1334 if err == io.EOF {
1335
1336 t.Fatalf("Trace missing start log message")
1337 }
1338 if err != nil {
1339 t.Fatalf("ReadEvent got err %v want nil", err)
1340 }
1341 t.Logf("Event: %s", ev.String())
1342
1343 switch ev.Kind() {
1344 case trace.EventStateTransition:
1345 st := ev.StateTransition()
1346 if st.Resource.Kind != trace.ResourceGoroutine {
1347 continue
1348 }
1349
1350 goid := st.Resource.Goroutine()
1351 from, to := st.Goroutine()
1352
1353
1354 if from == trace.GoNotExist {
1355 for sf := range st.Stack.Frames() {
1356 if sf.Func == "main.traceSTWTarget" {
1357 targetGoroutines = append(targetGoroutines, newGoroutineState(goid))
1358 t.Logf("Identified target goroutine id %d", goid)
1359 }
1360
1361
1362
1363 break
1364 }
1365 }
1366
1367
1368 if to == trace.GoRunning {
1369 gs := findGoroutine(goid)
1370 if gs == nil {
1371 continue
1372 }
1373 gs.P = ev.Proc()
1374 gs.M = ev.Thread()
1375 t.Logf("G %d running on P %d M %d", gs.G, gs.P, gs.M)
1376 }
1377 case trace.EventLog:
1378
1379 log := ev.Log()
1380 if log.Category != "TraceSTW" {
1381 continue
1382 }
1383 if log.Message != "start" {
1384 t.Fatalf("Log message got %s want start", log.Message)
1385 }
1386
1387
1388 t.Logf("Found start message")
1389 startLogSeen = true
1390 case trace.EventRangeBegin:
1391 if !startLogSeen {
1392
1393 continue
1394 }
1395
1396 r := ev.Range()
1397 if r.Name == stwType {
1398 t.Logf("Found STW")
1399 stwSeen = true
1400 break findStart
1401 }
1402 }
1403 }
1404
1405 if !stwSeen {
1406 t.Fatal("Can't find STW in the test trace")
1407 }
1408
1409 t.Log("Target goroutines:")
1410 for _, gs := range targetGoroutines {
1411 t.Logf("%+v", gs)
1412 }
1413
1414 if len(targetGoroutines) != 2 {
1415 t.Fatalf("len(targetGoroutines) got %d want 2", len(targetGoroutines))
1416 }
1417
1418 for _, gs := range targetGoroutines {
1419 if gs.P == trace.NoProc {
1420 t.Fatalf("Goroutine %+v not running on a P", gs)
1421 }
1422 if gs.M == trace.NoThread {
1423 t.Fatalf("Goroutine %+v not running on an M", gs)
1424 }
1425 }
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461 var pRunning []trace.ProcID
1462 var gRunning []trace.GoID
1463 findEnd:
1464 for {
1465 ev, err := br.ReadEvent()
1466 if err == io.EOF {
1467 break
1468 }
1469 if err != nil {
1470 t.Fatalf("ReadEvent got err %v want nil", err)
1471 }
1472 t.Logf("Event: %s", ev.String())
1473
1474 switch ev.Kind() {
1475 case trace.EventStateTransition:
1476 st := ev.StateTransition()
1477 switch st.Resource.Kind {
1478 case trace.ResourceProc:
1479 p := st.Resource.Proc()
1480 _, to := st.Proc()
1481
1482
1483 if to == trace.ProcRunning {
1484 gs := findProc(p)
1485 if gs == nil {
1486 continue
1487 }
1488
1489 if slices.Contains(pRunning, p) {
1490
1491
1492
1493
1494 continue
1495 }
1496 pRunning = append(pRunning, p)
1497
1498 m := ev.Thread()
1499 if m != gs.M {
1500 t.Logf("Proc %d running on M %d want M %d", p, m, gs.M)
1501 return fmt.Errorf("P did not remain on M")
1502 }
1503 }
1504 case trace.ResourceGoroutine:
1505 goid := st.Resource.Goroutine()
1506 _, to := st.Goroutine()
1507
1508
1509 if to == trace.GoRunning {
1510 p := ev.Proc()
1511 m := ev.Thread()
1512
1513 gs := findGoroutine(goid)
1514 if gs == nil {
1515
1516
1517
1518
1519 gs = findProc(p)
1520 if gs == nil {
1521 continue
1522 }
1523
1524 if slices.Contains(gRunning, gs.G) {
1525
1526
1527
1528
1529 continue
1530 }
1531
1532 t.Logf("Goroutine %d running on P %d M %d want this P to run G %d", goid, p, m, gs.G)
1533 return fmt.Errorf("P ran incorrect goroutine")
1534 }
1535
1536 if !slices.Contains(gRunning, goid) {
1537 gRunning = append(gRunning, goid)
1538 }
1539
1540 if p != gs.P || m != gs.M {
1541 t.Logf("Goroutine %d running on P %d M %d want P %d M %d", goid, p, m, gs.P, gs.M)
1542
1543
1544
1545
1546
1547 }
1548 }
1549 }
1550 case trace.EventLog:
1551
1552 log := ev.Log()
1553 if log.Category != "TraceSTW" {
1554 continue
1555 }
1556 if log.Message != "end" {
1557 t.Fatalf("Log message got %s want end", log.Message)
1558 }
1559
1560
1561 t.Logf("Found end message")
1562 break findEnd
1563 }
1564 }
1565
1566 return nil
1567 }
1568
1569 func TestMexitSTW(t *testing.T) {
1570 got := runTestProg(t, "testprog", "mexitSTW")
1571 want := "OK\n"
1572 if got != want {
1573 t.Fatalf("expected %q, but got:\n%s", want, got)
1574 }
1575 }
1576
View as plain text