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