| // Copyright 2025 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. |
| |
| package main |
| |
| /* |
| // Defined in tracebackctxt_c.c. |
| extern void C1(void); |
| extern void C2(void); |
| extern void tcContext(void*); |
| extern void tcTraceback(void*); |
| extern void tcSymbolizer(void*); |
| */ |
| import "C" |
| |
| import ( |
| "fmt" |
| "runtime" |
| "sync" |
| "unsafe" |
| ) |
| |
| // Regression test for https://go.dev/issue/73949. TSAN should not report races |
| // on writes to the argument passed to the symbolizer function. |
| // |
| // Triggering this race requires calls to the symbolizer function with the same |
| // argument pointer on multiple threads. The runtime passes a stack variable to |
| // this function, so that means we need to get a single goroutine to execute on |
| // two threads, calling the symbolizer function on each. |
| // |
| // runtime.CallersFrames / Next will call the symbolizer function (if there are |
| // C frames). So the approach here is, with GOMAXPROCS=2, have 2 goroutines |
| // that use CallersFrames over and over, both frequently calling Gosched in an |
| // attempt to get picked up by the other P. |
| |
| var tracebackOK bool |
| |
| func main() { |
| runtime.GOMAXPROCS(2) |
| runtime.SetCgoTraceback(0, unsafe.Pointer(C.tcTraceback), unsafe.Pointer(C.tcContext), unsafe.Pointer(C.tcSymbolizer)) |
| C.C1() |
| if tracebackOK { |
| fmt.Println("OK") |
| } |
| } |
| |
| //export G1 |
| func G1() { |
| C.C2() |
| } |
| |
| //export G2 |
| func G2() { |
| pc := make([]uintptr, 32) |
| n := runtime.Callers(0, pc) |
| |
| var wg sync.WaitGroup |
| for range 2 { |
| wg.Go(func() { |
| for range 1000 { |
| cf := runtime.CallersFrames(pc[:n]) |
| var frames []runtime.Frame |
| for { |
| frame, more := cf.Next() |
| frames = append(frames, frame) |
| if !more { |
| break |
| } |
| } |
| runtime.Gosched() |
| } |
| }) |
| } |
| wg.Wait() |
| |
| tracebackOK = true |
| } |