Update to go1.24.2
This commit is contained in:
@@ -355,7 +355,9 @@ func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
|
||||
gp.m.incgo = true
|
||||
unlockOSThread()
|
||||
|
||||
if gp.m.isextra {
|
||||
if gp.m.isextra && gp.m.ncgo == 0 {
|
||||
// There are no active cgocalls above this frame (ncgo == 0),
|
||||
// thus there can't be more Go frames above this frame.
|
||||
gp.m.isExtraInC = true
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,22 @@ func TestCgoCallbackGC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoCallbackPprof(t *testing.T) {
|
||||
t.Parallel()
|
||||
switch runtime.GOOS {
|
||||
case "plan9", "windows":
|
||||
t.Skipf("no pthreads on %s", runtime.GOOS)
|
||||
}
|
||||
if testenv.CPUProfilingBroken() {
|
||||
t.Skip("skipping on platform with broken profiling")
|
||||
}
|
||||
|
||||
got := runTestProg(t, "testprogcgo", "CgoCallbackPprof")
|
||||
if want := "OK\n"; got != want {
|
||||
t.Fatalf("expected %q, but got:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCgoExternalThreadPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
if runtime.GOOS == "plan9" {
|
||||
|
||||
@@ -6,6 +6,7 @@ package runtime_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
@@ -59,3 +60,36 @@ func ExampleFrames() {
|
||||
// - more:true | runtime_test.ExampleFrames.func3
|
||||
// - more:true | runtime_test.ExampleFrames
|
||||
}
|
||||
|
||||
func ExampleAddCleanup() {
|
||||
tempFile, err := os.CreateTemp(os.TempDir(), "file.*")
|
||||
if err != nil {
|
||||
fmt.Println("failed to create temp file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
ch := make(chan struct{})
|
||||
|
||||
// Attach a cleanup function to the file object.
|
||||
runtime.AddCleanup(&tempFile, func(fileName string) {
|
||||
if err := os.Remove(fileName); err == nil {
|
||||
fmt.Println("temp file has been removed")
|
||||
}
|
||||
ch <- struct{}{}
|
||||
}, tempFile.Name())
|
||||
|
||||
if err := tempFile.Close(); err != nil {
|
||||
fmt.Println("failed to close temp file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Run the garbage collector to reclaim unreachable objects
|
||||
// and enqueue their cleanup functions.
|
||||
runtime.GC()
|
||||
|
||||
// Wait until cleanup function is done.
|
||||
<-ch
|
||||
|
||||
// Output:
|
||||
// temp file has been removed
|
||||
}
|
||||
|
||||
@@ -416,27 +416,6 @@ func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Loca
|
||||
return p
|
||||
}
|
||||
|
||||
func cpuProfilingBroken() bool {
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
// Profiling unimplemented.
|
||||
return true
|
||||
case "aix":
|
||||
// See https://golang.org/issue/45170.
|
||||
return true
|
||||
case "ios", "dragonfly", "netbsd", "illumos", "solaris":
|
||||
// See https://golang.org/issue/13841.
|
||||
return true
|
||||
case "openbsd":
|
||||
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
|
||||
// See https://golang.org/issue/13841.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need,
|
||||
// as interpreted by matches, and returns the parsed profile.
|
||||
func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Duration)) *profile.Profile {
|
||||
@@ -454,7 +433,7 @@ func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Dura
|
||||
t.Skip("skipping on wasip1")
|
||||
}
|
||||
|
||||
broken := cpuProfilingBroken()
|
||||
broken := testenv.CPUProfilingBroken()
|
||||
|
||||
deadline, ok := t.Deadline()
|
||||
if broken || !ok {
|
||||
|
||||
@@ -419,14 +419,21 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) {
|
||||
name := u.srcFunc(uf).name()
|
||||
if stringslite.HasPrefix(name, "runtime.") ||
|
||||
stringslite.HasPrefix(name, "runtime/internal/") ||
|
||||
stringslite.HasPrefix(name, "internal/runtime/") ||
|
||||
stringslite.HasPrefix(name, "reflect.") {
|
||||
// For now we never async preempt the runtime or
|
||||
// anything closely tied to the runtime. Known issues
|
||||
// include: various points in the scheduler ("don't
|
||||
// preempt between here and here"), much of the defer
|
||||
// implementation (untyped info on stack), bulk write
|
||||
// barriers (write barrier check),
|
||||
// reflect.{makeFuncStub,methodValueCall}.
|
||||
// barriers (write barrier check), atomic functions in
|
||||
// internal/runtime/atomic, reflect.{makeFuncStub,methodValueCall}.
|
||||
//
|
||||
// Note that this is a subset of the runtimePkgs in pkgspecial.go
|
||||
// and these checks are theoretically redundant because the compiler
|
||||
// marks "all points" in runtime functions as unsafe for async preemption.
|
||||
// But for some reason, we can't eliminate these checks until https://go.dev/issue/72031
|
||||
// is resolved.
|
||||
//
|
||||
// TODO(austin): We should improve this, or opt things
|
||||
// in incrementally.
|
||||
|
||||
@@ -556,7 +556,7 @@ type m struct {
|
||||
printlock int8
|
||||
incgo bool // m is executing a cgo call
|
||||
isextra bool // m is an extra m
|
||||
isExtraInC bool // m is an extra m that is not executing Go code
|
||||
isExtraInC bool // m is an extra m that does not have any Go frames
|
||||
isExtraInSig bool // m is an extra m in a signal handler
|
||||
freeWait atomic.Uint32 // Whether it is safe to free g0 and delete m (one of freeMRef, freeMStack, freeMWait)
|
||||
needextram bool
|
||||
|
||||
138
src/runtime/testdata/testprogcgo/callback_pprof.go
vendored
Normal file
138
src/runtime/testdata/testprogcgo/callback_pprof.go
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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.
|
||||
|
||||
//go:build !plan9 && !windows
|
||||
|
||||
package main
|
||||
|
||||
// Regression test for https://go.dev/issue/72870. Go code called from C should
|
||||
// never be reported as external code.
|
||||
|
||||
/*
|
||||
#include <pthread.h>
|
||||
|
||||
void go_callback1();
|
||||
void go_callback2();
|
||||
|
||||
static void *callback_pprof_thread(void *arg) {
|
||||
go_callback1();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void c_callback(void) {
|
||||
go_callback2();
|
||||
}
|
||||
|
||||
static void start_callback_pprof_thread() {
|
||||
pthread_t th;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_create(&th, &attr, callback_pprof_thread, 0);
|
||||
// Don't join, caller will watch pprof.
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"internal/profile"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("CgoCallbackPprof", CgoCallbackPprof)
|
||||
}
|
||||
|
||||
func CgoCallbackPprof() {
|
||||
C.start_callback_pprof_thread()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pprof.StartCPUProfile(&buf); err != nil {
|
||||
fmt.Printf("Error starting CPU profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
p, err := profile.Parse(&buf)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
foundCallee := false
|
||||
for _, s := range p.Sample {
|
||||
funcs := flattenFrames(s)
|
||||
if len(funcs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
leaf := funcs[0]
|
||||
if leaf.Name != "main.go_callback1_callee" {
|
||||
continue
|
||||
}
|
||||
foundCallee = true
|
||||
|
||||
if len(funcs) < 2 {
|
||||
fmt.Printf("Profile: %s\n", p)
|
||||
frames := make([]string, len(funcs))
|
||||
for i := range funcs {
|
||||
frames[i] = funcs[i].Name
|
||||
}
|
||||
fmt.Printf("FAIL: main.go_callback1_callee sample missing caller in frames %v\n", frames)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if funcs[1].Name != "main.go_callback1" {
|
||||
// In https://go.dev/issue/72870, this will be runtime._ExternalCode.
|
||||
fmt.Printf("Profile: %s\n", p)
|
||||
frames := make([]string, len(funcs))
|
||||
for i := range funcs {
|
||||
frames[i] = funcs[i].Name
|
||||
}
|
||||
fmt.Printf("FAIL: main.go_callback1_callee sample caller got %s want main.go_callback1 in frames %v\n", funcs[1].Name, frames)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if !foundCallee {
|
||||
fmt.Printf("Missing main.go_callback1_callee sample in profile %s\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("OK\n")
|
||||
}
|
||||
|
||||
// Return the frame functions in s, regardless of inlining.
|
||||
func flattenFrames(s *profile.Sample) []*profile.Function {
|
||||
ret := make([]*profile.Function, 0, len(s.Location))
|
||||
for _, loc := range s.Location {
|
||||
for _, line := range loc.Line {
|
||||
ret = append(ret, line.Function)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
//export go_callback1
|
||||
func go_callback1() {
|
||||
// This is a separate function just to ensure we have another Go
|
||||
// function as the caller in the profile.
|
||||
go_callback1_callee()
|
||||
}
|
||||
|
||||
func go_callback1_callee() {
|
||||
C.c_callback()
|
||||
|
||||
// Spin for CPU samples.
|
||||
for {
|
||||
}
|
||||
}
|
||||
|
||||
//export go_callback2
|
||||
func go_callback2() {
|
||||
}
|
||||
Reference in New Issue
Block a user