From fb40f528cd42f9806056a9615289bf53d57b5445 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Fri, 10 Apr 2026 00:06:04 +0300 Subject: [PATCH] runtime: fix SIGSEGV in startEncoreG with -race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running `encore test -race` crashes with SIGSEGV at addr=0x10 on the first test. The crash is in __tsan_func_enter when it receives a nil ThreadState pointer (racectx=0). Root cause: the runtime patch calls startEncoreG from newproc1, which runs on the system stack (g0). startEncoreG is user code compiled with race instrumentation, so the compiler inserts a racefuncenter call in its prologue. The assembly implementation of racefuncenter (race_arm64.s / race_amd64.s) unconditionally loads g.racectx and passes it to __tsan_func_enter — there is no g0 guard in the assembly, despite the Go source in race.go having one (the assembly takes precedence, making the Go version dead code). On g0, racectx is initialized to 0 during runtime startup, so __tsan_func_enter dereferences 0+0x10 and crashes. Other runtime functions called from newproc1 (e.g. saveAncestors) don't crash because they are compiled as runtime code without race instrumentation. Fix: move the startEncoreG call from newproc1 (g0) to newproc (user goroutine stack) before the systemstack switch. On the user stack, g.racectx is properly initialized by racegostart, so race instrumentation works correctly. The result is passed into systemstack as a plain pointer assignment. Fixes https://github.com/encoredev/encore/issues/1943 --- patches/runtime_patch.diff | 44 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/patches/runtime_patch.diff b/patches/runtime_patch.diff index 91c385d2b1..4c73de6499 100644 --- a/patches/runtime_patch.diff +++ b/patches/runtime_patch.diff @@ -5,13 +5,13 @@ This patch modifies the Go runtime to add Encore specific tracking data to the G the and finish of a Go Routine executed due to a request made to your Encore app. diff --git a/src/runtime/proc.go b/src/runtime/proc.go -index aba2e2b27b..588a98ff74 100644 +index 005c875cbf..d4acd8b146 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go -@@ -3617,6 +3617,11 @@ func goexit0(gp *g) { +@@ -4504,6 +4504,11 @@ func gdestroy(gp *g) { mp := getg().m pp := mp.p.ptr() - + + if e := gp.encore; e != nil { + exitEncoreG(e) + gp.encore = nil @@ -20,29 +20,35 @@ index aba2e2b27b..588a98ff74 100644 casgstatus(gp, _Grunning, _Gdead) gcController.addScannableStack(pp, -int64(gp.stack.hi-gp.stack.lo)) if isSystemGoroutine(gp, false) { -@@ -4286,6 +4291,11 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g { - newg.parentGoid = callergp.goid - newg.gopc = callerpc - newg.ancestors = saveAncestors(callergp) +@@ -5295,8 +5300,18 @@ func malg(stacksize int32) *g { + func newproc(fn *funcval) { + gp := getg() + pc := sys.GetCallerPC() + -+ if e := callergp.encore; e != nil { -+ newg.encore = startEncoreG(e) ++ // Call startEncoreG on the user goroutine stack (not g0) so that ++ // race-instrumented code can safely access g.racectx, which is ++ // zero on the system stack and would crash __tsan_func_enter. ++ var encData unsafe.Pointer ++ if gp.encore != nil { ++ encData = startEncoreG(gp.encore) + } + - newg.startpc = fn.fn - if isSystemGoroutine(newg, false) { - sched.ngsys.Add(1) - + systemstack(func() { + newg := newproc1(fn, gp, pc, false, waitReasonZero) ++ newg.encore = encData + + pp := getg().m.p.ptr() + runqput(pp, newg, true) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go -index 4a78963961..87932dfd73 100644 +index be33932b24..3908123e8b 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go -@@ -516,6 +516,8 @@ type g struct { - +@@ -574,6 +574,8 @@ type g struct { coroarg *coro // argument during coroutine transfers + bubble *synctestBubble + encore unsafe.Pointer // encore-specific goroutine data + - // Per-G tracer state. - trace gTraceState - + // xRegs stores the extended register state if this G has been + // asynchronously preempted. + xRegs xRegPerG