From 4f8aa414f0ff4af04df1554113178ee57959c706 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 10 Feb 2026 10:17:58 -0800 Subject: [PATCH] runtime: put a bool in front of initialization-done channel Calls to Go functions from threads not started by Go have to wait for Go initialization to be complete. Before this CL they did this by receiving from a channel that is closed when initialization is done. That works well but introduces a channel operation into cgo calls. This is particularly frustrating because the channel is approximately always closed. This CL adds an atomic bool before the channel, so that in the normal case we are just adding a single locked memory load. We still use the channel as a fallback. For #77522 Change-Id: I8f609bf349bb0f836cefa5f6ad6d0c3c7bbfe5e0 Reviewed-on: https://go-review.googlesource.com/c/go/+/743940 Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Ian Lance Taylor --- src/runtime/cgocall.go | 9 +++++++-- src/runtime/proc.go | 18 +++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index b36da2f12b..7f05c13aee 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -424,8 +424,13 @@ func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) { // The C call to Go came from a thread not currently running // any Go. In the case of -buildmode=c-archive or c-shared, // this call may be coming in before package initialization - // is complete. Wait until it is. - <-main_init_done + // is complete. Don't proceed until it is. + // + // We check a bool first for speed, and wait on a channel + // if it's not ready. + if !mainInitDone.Load() { + <-mainInitDoneChan + } } // Check whether the profiler needs to be turned on or off; this route to diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 005c875cbf..8027ac5bee 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -127,11 +127,14 @@ var ( // done to start up the runtime. It is built by the linker. var runtime_inittasks []*initTask -// main_init_done is a signal used by cgocallbackg that initialization -// has been completed. It is made before _cgo_notify_runtime_init_done, -// so all cgo calls can rely on it existing. When main_init is complete, -// it is closed, meaning cgocallbackg can reliably receive from it. -var main_init_done chan bool +// mainInitDone is a signal used by cgocallbackg that initialization +// has been completed. If this is false, wait on mainInitDoneChan. +var mainInitDone atomic.Bool + +// mainInitDoneChan is closed after initialization has been completed. +// It is made before _cgo_notify_runtime_init_done, so all cgo +// calls can rely on it existing. +var mainInitDoneChan chan bool //go:linkname main_main main.main func main_main() @@ -213,7 +216,7 @@ func main() { gcenable() defaultGOMAXPROCSUpdateEnable() // don't STW before runtime initialized. - main_init_done = make(chan bool) + mainInitDoneChan = make(chan bool) if iscgo { if _cgo_pthread_key_created == nil { throw("_cgo_pthread_key_created missing") @@ -265,7 +268,8 @@ func main() { // of collecting statistics in malloc and newproc inittrace.active = false - close(main_init_done) + mainInitDone.Store(true) + close(mainInitDoneChan) needUnlock = false unlockOSThread() -- 2.52.0