Source file src/runtime/testdata/testprogcgo/notingo.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build !plan9 && !windows
     6  
     7  package main
     8  
     9  /*
    10  #include <stdatomic.h>
    11  #include <stddef.h>
    12  #include <pthread.h>
    13  
    14  extern void Ready();
    15  extern void BlockForeverInGo();
    16  
    17  static _Atomic int spinning;
    18  static _Atomic int released;
    19  
    20  static void* enterGoThenSpinTwice(void* arg __attribute__ ((unused))) {
    21  	Ready();
    22  	atomic_fetch_add(&spinning, 1);
    23  	while(atomic_load(&released) == 0) {};
    24  
    25  	Ready();
    26  	atomic_fetch_add(&spinning, 1);
    27  	while(1) {};
    28  	return NULL;
    29  }
    30  
    31  static void SpinTwiceInNewCThread() {
    32  	pthread_t tid;
    33  	pthread_create(&tid, NULL, enterGoThenSpinTwice, NULL);
    34  }
    35  
    36  static int Spinning() {
    37  	return atomic_load(&spinning);
    38  }
    39  
    40  static void Release() {
    41  	atomic_store(&spinning, 0);
    42  	atomic_store(&released, 1);
    43  }
    44  
    45  static void* enterGoThenWait(void* arg __attribute__ ((unused))) {
    46  	BlockForeverInGo();
    47  	return NULL;
    48  }
    49  
    50  static void WaitInGoInNewCThread() {
    51  	pthread_t tid;
    52  	pthread_create(&tid, NULL, enterGoThenWait, NULL);
    53  }
    54  
    55  static void SpinForever() {
    56  	atomic_fetch_add(&spinning, 1);
    57  	while(1) {};
    58  }
    59  */
    60  import "C"
    61  
    62  import (
    63  	"os"
    64  	"runtime"
    65  	"runtime/metrics"
    66  	"sync/atomic"
    67  )
    68  
    69  func init() {
    70  	register("NotInGoMetricCgoCall", NotInGoMetricCgoCall)
    71  	register("NotInGoMetricCgoCallback", NotInGoMetricCgoCallback)
    72  	register("NotInGoMetricCgoCallAndCallback", NotInGoMetricCgoCallAndCallback)
    73  }
    74  
    75  // NotInGoMetric just double-checks that N goroutines in cgo count as the metric reading N.
    76  func NotInGoMetricCgoCall() {
    77  	const N = 10
    78  
    79  	// Spin up the same number of goroutines that will all wait in a cgo call.
    80  	for range N {
    81  		go func() {
    82  			C.SpinForever()
    83  		}()
    84  	}
    85  
    86  	// Make sure we're all blocked and spinning.
    87  	for C.Spinning() < N {
    88  	}
    89  
    90  	// Read not-in-go before taking the Ps back.
    91  	s := []metrics.Sample{{Name: "/sched/goroutines/not-in-go:goroutines"}}
    92  	failed := false
    93  	metrics.Read(s)
    94  	if n := s[0].Value.Uint64(); n != N {
    95  		println("pre-STW: expected", N, "not-in-go goroutines, found", n)
    96  	}
    97  
    98  	// Do something that stops the world to take all the Ps back.
    99  	//
   100  	// This will force a re-accounting of some of the goroutines and
   101  	// re-checking not-in-go will help catch bugs.
   102  	runtime.ReadMemStats(&m)
   103  
   104  	// Read not-in-go.
   105  	metrics.Read(s)
   106  	if n := s[0].Value.Uint64(); n != N {
   107  		println("post-STW: expected", N, "not-in-go goroutines, found", n)
   108  	}
   109  
   110  	// Fail if we get a bad reading.
   111  	if failed {
   112  		os.Exit(2)
   113  	}
   114  	println("OK")
   115  }
   116  
   117  // NotInGoMetricCgoCallback tests that threads that called into Go, then returned
   118  // to C with *no* Go on the stack, are *not* counted as not-in-go in the
   119  // runtime/metrics package.
   120  func NotInGoMetricCgoCallback() {
   121  	const N = 10
   122  
   123  	// Create N new C threads that have called into Go at least once.
   124  	for range N {
   125  		C.SpinTwiceInNewCThread()
   126  	}
   127  
   128  	// Synchronize with spinning threads twice.
   129  	//
   130  	// This helps catch bad accounting by taking at least a couple other
   131  	// codepaths which would cause the accounting to change.
   132  	for i := range 2 {
   133  		// Make sure they pass through Go.
   134  		// N.B. Ready is called twice by the new threads.
   135  		for j := range N {
   136  			<-readyCh
   137  			if j == 2 {
   138  				// Try to trigger an update in the immediate STW handoff case.
   139  				runtime.ReadMemStats(&m)
   140  			}
   141  		}
   142  
   143  		// Make sure they're back in C.
   144  		for C.Spinning() < N {
   145  		}
   146  
   147  		// Do something that stops the world to take all the Ps back.
   148  		runtime.ReadMemStats(&m)
   149  
   150  		if i == 0 {
   151  			C.Release()
   152  		}
   153  	}
   154  
   155  	// Read not-in-go.
   156  	s := []metrics.Sample{{Name: "/sched/goroutines/not-in-go:goroutines"}}
   157  	metrics.Read(s)
   158  	if n := s[0].Value.Uint64(); n != 0 {
   159  		println("expected 0 not-in-go goroutines, found", n)
   160  		os.Exit(2)
   161  	}
   162  	println("OK")
   163  }
   164  
   165  var m runtime.MemStats
   166  var readyCh = make(chan bool)
   167  
   168  //export Ready
   169  func Ready() {
   170  	readyCh <- true
   171  }
   172  
   173  // NotInGoMetricCgoCallAndCallback tests that threads that called into Go are not
   174  // keeping the count of not-in-go threads negative. Specifically, needm sets
   175  // isExtraInC to false, breaking some of the invariants behind the not-in-go
   176  // runtime/metrics metric, causing the underlying count to break if we don't
   177  // account for this. In go.dev/cl/726964 this amounts to nGsyscallNoP being negative.
   178  // Unfortunately the runtime/metrics package masks a negative nGsyscallNoP because
   179  // it can transiently go negative due to a race. Therefore, this test checks
   180  // the condition by making sure not-in-go is positive when we expect it to be.
   181  // That is, threads in a cgo callback are *not* cancelling out threads in a
   182  // regular cgo call.
   183  func NotInGoMetricCgoCallAndCallback() {
   184  	const N = 10
   185  
   186  	// Spin up some threads that will do a cgo callback and just wait in Go.
   187  	// These threads are the ones we're worried about having the incorrect
   188  	// accounting that skews the count later.
   189  	for range N {
   190  		C.WaitInGoInNewCThread()
   191  	}
   192  
   193  	// Spin up the same number of goroutines that will all wait in a cgo call.
   194  	for range N {
   195  		go func() {
   196  			C.SpinForever()
   197  		}()
   198  	}
   199  
   200  	// Make sure we're all blocked and spinning.
   201  	for C.Spinning() < N || blockedForever.Load() < N {
   202  	}
   203  
   204  	// Read not-in-go before taking the Ps back.
   205  	s := []metrics.Sample{{Name: "/sched/goroutines/not-in-go:goroutines"}}
   206  	failed := false
   207  	metrics.Read(s)
   208  	if n := s[0].Value.Uint64(); n != N {
   209  		println("pre-STW: expected", N, "not-in-go goroutines, found", n)
   210  	}
   211  
   212  	// Do something that stops the world to take all the Ps back.
   213  	//
   214  	// This will force a re-accounting of some of the goroutines and
   215  	// re-checking not-in-go will help catch bugs.
   216  	runtime.ReadMemStats(&m)
   217  
   218  	// Read not-in-go.
   219  	metrics.Read(s)
   220  	if n := s[0].Value.Uint64(); n != N {
   221  		println("post-STW: expected", N, "not-in-go goroutines, found", n)
   222  	}
   223  
   224  	// Fail if we get a bad reading.
   225  	if failed {
   226  		os.Exit(2)
   227  	}
   228  	println("OK")
   229  }
   230  
   231  var blockedForever atomic.Uint32
   232  
   233  //export BlockForeverInGo
   234  func BlockForeverInGo() {
   235  	blockedForever.Add(1)
   236  	select {}
   237  }
   238  

View as plain text