Skip to content

runtime: signal 27 received on thread with no signal stack #77588

Closed

Description

@ukai

Go version

go version go1.25.7 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/ukai/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/ukai/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/fg/y90rmtzj3hz3hqtpwn0qwzmc000p7r/T/go-build960639305=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/ukai/src/crbuild/build/siso/go.mod'
GOMODCACHE='/Users/ukai/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/ukai/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/ukai/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.25.7'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

go binary (without cgo) runs with profilers

$ go version -m third_party/siso/cipd/siso
...
        build   -buildmode=exe
        build   -compiler=gc
        build   -trimpath=true
        build   CGO_ENABLED=0
        build   GOARCH=arm64
        build   GOOS=darwin
        build   GOARM64=v8.0

https://ci.chromium.org/ui/p/chromium/builders/build/ios-build-perf-developer/7889/overview
https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket/8691654715001248849/+/u/Clean_builds/Build_chrome_without_remote_cache_with_Siso_in_native_mode/stdout

https://ci.chromium.org/ui/p/chromium/builders/build/ios-build-perf-siso/25669/overview
https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket/8690003051682517681/+/u/Clean_builds/Build_all_with_remote_cache/stdout

What did you see happen?

signal 27 received on thread with no signal stack
fatal error: non-Go code disabled sigaltstack

runtime stack:
runtime.throw({0x10304c6af?, 0x0?})
	runtime/panic.go:1094 +0x34 fp=0x42ec56860 sp=0x42ec56830 pc=0x10219ebe4
runtime.noSignalStack(...)
	runtime/signal_unix.go:1106
runtime.adjustSignalStack2(0x1b, 0x42ec56918, 0x140797ed008, 0x1)
	runtime/signal_unix.go:599 +0xa0 fp=0x42ec56890 sp=0x42ec56860 pc=0x102180500
runtime.adjustSignalStack(0x1b, 0x140797ed008, 0x42ec56940)
	runtime/signal_unix.go:588 +0x230 fp=0x42ec56910 sp=0x42ec56890 pc=0x102180450
runtime.sigtrampgo(0x1b, 0x42ec56ab0, 0x42ec56b18)
	runtime/signal_unix.go:480 +0x84 fp=0x42ec56990 sp=0x42ec56910 pc=0x10217ff94
runtime.sigtrampgo(0x1b, 0x42ec56ab0, 0x42ec56b18)
	<autogenerated>:1 +0x1c fp=0x42ec569c0 sp=0x42ec56990 pc=0x1021a9d0c
runtime.sigtramp()
	runtime/sys_darwin_arm64.s:227 +0x4c fp=0x42ec56a80 sp=0x42ec569c0 pc=0x1021a884c

goroutine 17 gp=0x14000002700 m=1 mp=0x140000a8008 [syscall, locked to thread]:
runtime.goexit({})
	runtime/asm_arm64.s:1268 +0x4 fp=0x140000b6fe0 sp=0x140000b6fe0 pc=0x1021a7824

What did you expect to see?

no panic

Activity

  1. added
    BugReportIssues describing a possible bug in the Go implementation.
    on Feb 13, 2026
  2. JunyangShao commented on Feb 13, 2026

    @JunyangShao
    Contributor

    @golang/runtime

  3. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Feb 13, 2026
  4. ianlancetaylor commented on Feb 13, 2026

    @ianlancetaylor
    Contributor

    The runtime error message is trying to tell you the problem: "fatal error: non-Go code disabled sigaltstack".

    Quoting https://pkg.go.dev/os/signal:

    If the non-Go code installs any signal handlers, it must use the SA_ONSTACK flag with sigaction. Failing to do so is likely to cause the program to crash if the signal is received. Go programs routinely run with a limited stack, and therefore set up an alternate signal stack.

    In this case the signal is SIGPROF. Look for non-Go code linked into your program that is setting up a signal handler for SIGPROF.

  5. ukai commented on Feb 13, 2026

    @ukai
    ContributorAuthor

    No non-Go code linked into the program, as build CGO_ENABLED=0.

    I think SIGPROF is used by runtime/pprof

  6. ianlancetaylor commented on Feb 13, 2026

    @ianlancetaylor
    Contributor

    Yes, runtime/pprof uses SIGPROF.

    If there is no non-Go code involved in this program, then this should be impossible. The Go runtime always installs signal handlers with SA_ONSTACK set. Is this problem reproducible? Is there a way that we can reproduce it ourselves?

  7. ukai commented on Feb 13, 2026

    @ukai
    ContributorAuthor

    It happened several times.

    https://ci.chromium.org/ui/p/chromium/builders/build/ios-build-perf-developer/7889/overview
    https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket/8691654715001248849/+/u/Clean_builds/Build_chrome_without_remote_cache_with_Siso_in_native_mode/stdout

    https://ci.chromium.org/ui/p/chromium/builders/build/ios-build-perf-siso/25663/overview
    https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket/8690040953658448481/+/u/Clean_builds/Build_all_without_remote_cache_with_Siso_disabling_clang_modules/stdout

    https://ci.chromium.org/ui/p/chromium/builders/build/ios-build-perf-siso/25669/overview
    https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket/8690003051682517681/+/u/Clean_builds/Build_all_with_remote_cache/stdout

    but no idea how to reproduce this yet.
    happened rarely when pprof.StartCPUProfile is called?

    $ go mod vendor
    ...
    $ grep -r -i sigaltstack .
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_riscv64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go
    ./vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go
    ./vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_ppc64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_mips64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
    ./vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_386.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go
    ./vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_freebsd_riscv64.go
    ./vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go
    ./vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_netbsd.go
    

    suspect github.com/ebitengine/purego ?

    $ go mod why github.com/ebitengine/purego
    # github.com/ebitengine/purego
    go.chromium.org/build/siso/subcmd/collector
    go.opentelemetry.io/collector/otelcol
    go.opentelemetry.io/collector/service
    go.opentelemetry.io/collector/service/internal/proctelemetry
    github.com/shirou/gopsutil/v4/process
    github.com/shirou/gopsutil/v4/internal/common
    github.com/ebitengine/purego
    
  8. prattmic commented on Feb 13, 2026

    @prattmic
    Member

    The purpose of purego is to call non-Go code (in an unsafe manner unsupported by the Go toolchain), so the libraries used from there would be my top suspects.

  9. cherrymui commented on Feb 25, 2026

    @cherrymui
    Member

    For now we suspect the issue is the C library that this program uses clears the SA_ONSTACK. Could you check if that is the case? Thanks.

  10. added
    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.
    on Feb 25, 2026
  11. gopherbot commented on Mar 25, 2026

    @gopherbot
    Contributor

    Timed out in state WaitingForInfo. Closing.

    (I am just a bot, though. Please speak up if this is a mistake or you have the requested information.)

  12. ukai commented on Jun 25, 2026

    @ukai
    ContributorAuthor

    I would like to reopen this issue as we have performed a deep dive analysis and identified the exact root cause, proving that this is indeed a Go runtime race condition and not caused by external C libraries (the crash still occurs with CGO_ENABLED=0).

    The issue is a race condition in dropm/mexit during thread teardown when SIGPROF is active.

    Here is the detailed analysis

    Description

    We are observing intermittent crashes in a high-concurrency Go application (siso build tool) running on macOS builders with CPU profiling enabled. The crash presents with the following fatal error:

    signal 27 received on thread with no signal stack
    fatal error: non-Go code disabled sigaltstack
    
    runtime stack:
    runtime.throw({0x103ed4678?, 0x0?})
    	runtime/panic.go:1229 +0x38 fp=0x1738567a0 sp=0x173856770 pc=0x102fe8768
    runtime.noSignalStack(...)
    	runtime/signal_unix.go:1112
    runtime.adjustSignalStack2(0x1b, 0x173856858, 0x7f6150f81808, 0x1)
    	runtime/signal_unix.go:605 +0xa0 fp=0x1738567d0 sp=0x1738567a0 pc=0x102fc9ba0
    runtime.adjustSignalStack(0x1b, 0x7f6150f81808, 0x173856880)
    	runtime/signal_unix.go:594 +0x238 fp=0x173856850 sp=0x1738567d0 pc=0x102fc9ae8
    runtime.sigtrampgo(0x1b, 0x1738569f0, 0x173856a58)
    	runtime/signal_unix.go:481 +0x84 fp=0x1738568d0 sp=0x173856850 pc=0x102fc9624
    runtime.sigtrampgo(0x1b, 0x1738569f0, 0x173856a58)
    	<autogenerated>:1 +0x1c fp=0x173856900 sp=0x1738568d0 pc=0x102ff284c
    runtime.sigtramp()
    	runtime/sys_darwin_arm64.s:227 +0x4c fp=0x1738569c0 sp=0x173856900 pc=0x102ff16ac
    

    Signal 27 is SIGPROF. The goroutine status at the time of the crash (visible in fuller logs) is often [syscall, locked to thread] with the stack top at runtime.goexit, indicating a thread teardown path.

    Root Cause Analysis

    There is a race condition in runtime.dropm and runtime.mexit during thread de-initialization.

    1. The Race Window

    In src/runtime/proc.go, runtime.dropm performs the following teardown:

    	sigblock(false)
    	unminit()
    
    	setg(nil)
    1. sigblock(false) blocks signals.
    2. unminit() calls unminitSignals() (on Darwin), which disables the alternate signal stack via sigaltstack (setting ss_flags = _SS_DISABLE).
    3. setg(nil) clears the g pointer.

    A critical race window exists between unminit() (disabling sigaltstack) and setg(nil) (clearing g).

    2. SIGPROF is Unblockable

    In src/runtime/signal_darwin.go, SIGPROF is defined as:

    	/* 27 */ {_SigNotify + _SigUnblock, "SIGPROF: profiling alarm clock"},

    Because it has the _SigUnblock flag, it is not blocked during normal Go execution. More importantly, even though sigblock attempts to block all signals via pthread_sigmask using sigset_all (which has all bits set), on macOS, pthread_sigmask may not immediately prevent delivery of a SIGPROF that is already pending or in flight in the kernel. It can still be delivered when transitioning through syscalls (like the sigaltstack call inside unminit).

    3. Failure Mode

    If SIGPROF arrives after unminit() has disabled sigaltstack, but before setg(nil) has cleared g:

    1. The kernel delivers SIGPROF to the thread.
    2. Since sigaltstack is disabled, the signal handler (runtime.sigtramp) runs on the normal thread stack.
    3. sigtramp calls runtime.load_g, which returns the still-valid g (since setg(nil) hasn't run yet).
    4. sigtrampgo is called with gp != nil.
    5. sigtrampgo calls runtime.adjustSignalStack.
    6. adjustSignalStack queries the signal stack, sees it is disabled (_SS_DISABLE), and since gp != nil, it assumes non-Go code disabled it and throws:
      fatal error: non-Go code disabled sigaltstack

    This race is highly reproducible under heavy load with CPU profiling enabled, especially when threads are frequently entering/exiting Go (cgo transitions) or exiting (locked goroutines exiting).

    WDYT? @ianlancetaylor @cherrymui

  13. randall77 commented on Jun 25, 2026

    @randall77
    Contributor

    We're going to need some way to reproduce this to make any progress.

    This race is highly reproducible under heavy load with CPU profiling enabled, especially when threads are frequently entering/exiting Go (cgo transitions) or exiting (locked goroutines exiting).

    Does that mean you have a reproducer?

    You say it reproduces with CGO_ENABLED=0, but then say there are a lot of cgo transitions. I don't see how both can be true.

    More importantly, even though sigblock attempts to block all signals via pthread_sigmask using sigset_all (which has all bits set), on macOS, pthread_sigmask may not immediately prevent delivery of a SIGPROF that is already pending or in flight in the kernel. It can still be delivered when transitioning through syscalls (like the sigaltstack call inside unminit).

    Do you have a reference for this? That sounds like a kernel bug to me. On return from sigblock, the kernel should not deliver signals of any sort, "pending or in flight" notwithstanding.

  14. ukai commented on Jun 26, 2026

    @ukai
    ContributorAuthor

    We're going to need some way to reproduce this to make any progress.

    This race is highly reproducible under heavy load with CPU profiling enabled, especially when threads are frequently entering/exiting Go (cgo transitions) or exiting (locked goroutines exiting).

    Does that mean you have a reproducer?

    no, since race windows would be small, so happens one a month in our CI.

    You say it reproduces with CGO_ENABLED=0, but then say there are a lot of cgo transitions. I don't see how both can be true.

    Ah, siso is built with CGO_ENABLED=0. cgo transitions are irrelevant here, I think.

    More importantly, even though sigblock attempts to block all signals via pthread_sigmask using sigset_all (which has all bits set), on macOS, pthread_sigmask may not immediately prevent delivery of a SIGPROF that is already pending or in flight in the kernel. It can still be delivered when transitioning through syscalls (like the sigaltstack call inside unminit).

    Do you have a reference for this? That sounds like a kernel bug to me. On return from sigblock, the kernel should not deliver signals of any sort, "pending or in flight" notwithstanding.

    Our guess is that

    • timer context - Timer Expiration (psignal): Kernel timer fires SIGPROF. psignal() checks uu_sigmask and picks a thread where SIGPROF is unblocked (Go's normal state).
    • victim thread - sigblock(false): Thread begins teardown in dropm(). sigblock(false) calls pthread_sigmask to update uu_sigmask for future signals and returns to user space.
    • timer context - Signal Queuing: psignal_internal() sets SIGPROF on uu_siglist and marks the AST_SIG trap flag on the thread context.
    • victim thread - unminit(): Thread calls sigaltstack(SS_DISABLE). Inside kern_sig.c, uu_sigstk.ss_flags is updated to SS_DISABLE.
    • victim thread - Syscall Trap Return (sendsig): As sigaltstack finishes, the kernel trap return handler checks AST_SIG and uu_siglist. sendsig() builds the signal frame on the main stack (since SS_DISABLE was just set).
    • victim thread - Panic in sigtramp: sigtramp executes on the main stack. Because setg(nil) hasn't run yet, gp != nil. Go checks sigaltstack, sees SS_DISABLE, and panics: fatal error: non-Go code disabled sigaltstack.
  15. randall77 commented on Jun 26, 2026

    @randall77
    Contributor

    I don't see how that is possible without violating what sigblock is supposed to do.

    var global bool = false
    sigblock(...)
    global = true
    

    The signal handler should never see global == true. Even if the signal is async, and the kernel was preparing to deliver the async signal to this thread. It must either deliver the signal during the call to sigblock, or retry and deliver it to some other thread. It cannot deliver that signal after sigblock returns.

    If it is true that Darwin violates this, make a C reproducer and submit it to Apple.

  16. ukai commented on Oct 15, 2026

    @ukai
    ContributorAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions