| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris |
| // +build darwin dragonfly freebsd linux netbsd openbsd solaris |
| |
| package main |
| |
| import ( |
| "bytes" |
| "cmd/internal/traceviewer" |
| traceparser "internal/trace" |
| "io" |
| "runtime" |
| "runtime/trace" |
| "sync" |
| "syscall" |
| "testing" |
| "time" |
| ) |
| |
| // TestGoroutineInSyscall tests threads for timer goroutines |
| // that preexisted when the tracing started were not counted |
| // as threads in syscall. See golang.org/issues/22574. |
| func TestGoroutineInSyscall(t *testing.T) { |
| // Start one goroutine blocked in syscall. |
| // |
| // TODO: syscall.Pipe used to cause the goroutine to |
| // remain blocked in syscall is not portable. Replace |
| // it with a more portable way so this test can run |
| // on non-unix architecture e.g. Windows. |
| var p [2]int |
| if err := syscall.Pipe(p[:]); err != nil { |
| t.Fatalf("failed to create pipe: %v", err) |
| } |
| |
| var wg sync.WaitGroup |
| defer func() { |
| syscall.Write(p[1], []byte("a")) |
| wg.Wait() |
| |
| syscall.Close(p[0]) |
| syscall.Close(p[1]) |
| }() |
| wg.Add(1) |
| go func() { |
| var tmp [1]byte |
| syscall.Read(p[0], tmp[:]) |
| wg.Done() |
| }() |
| |
| // Start multiple timer goroutines. |
| allTimers := make([]*time.Timer, 2*runtime.GOMAXPROCS(0)) |
| defer func() { |
| for _, timer := range allTimers { |
| timer.Stop() |
| } |
| }() |
| |
| var timerSetup sync.WaitGroup |
| for i := range allTimers { |
| timerSetup.Add(1) |
| go func(i int) { |
| defer timerSetup.Done() |
| allTimers[i] = time.AfterFunc(time.Hour, nil) |
| }(i) |
| } |
| timerSetup.Wait() |
| |
| // Collect and parse trace. |
| buf := new(bytes.Buffer) |
| if err := trace.Start(buf); err != nil { |
| t.Fatalf("failed to start tracing: %v", err) |
| } |
| trace.Stop() |
| |
| res, err := traceparser.Parse(buf, "") |
| if err == traceparser.ErrTimeOrder { |
| t.Skipf("skipping due to golang.org/issue/16755 (timestamps are unreliable): %v", err) |
| } else if err != nil { |
| t.Fatalf("failed to parse trace: %v", err) |
| } |
| |
| // Check only one thread for the pipe read goroutine is |
| // considered in-syscall. |
| c := viewerDataTraceConsumer(io.Discard, 0, 1<<63-1) |
| c.consumeViewerEvent = func(ev *traceviewer.Event, _ bool) { |
| if ev.Name == "Threads" { |
| arg := ev.Arg.(*threadCountersArg) |
| if arg.InSyscall > 1 { |
| t.Errorf("%d threads in syscall at time %v; want less than 1 thread in syscall", arg.InSyscall, ev.Time) |
| } |
| } |
| } |
| |
| param := &traceParams{ |
| parsed: res, |
| endTime: int64(1<<63 - 1), |
| } |
| if err := generateTrace(param, c); err != nil { |
| t.Fatalf("failed to generate ViewerData: %v", err) |
| } |
| } |