Skip to content

internal/fakecgo: use per-OS values for pthread_sigmask how - #553

Merged
hajimehoshi merged 1 commit into
ebitengine:mainfrom
hajimehoshi:claude/purego-issue-552-c5522a
Oct 15, 2026
Merged

hajimehoshi merged 1 commit into
ebitengine:mainfrom
hajimehoshi:claude/purego-issue-552-c5522a

Conversation

@hajimehoshi

Copy link
Copy Markdown
Member

What issue is this addressing?

Closes #552

What type of issue is this addressing?

bug

What this PR does | solves

SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once in internal/fakecgo/libcgo.go with the Linux values 0, 1 and 2. Darwin, FreeBSD and NetBSD use 1, 2 and 3, so pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as SIG_UNBLOCK there. Instead of blocking all signals across pthread_create, it unblocked them, and the new thread started with signals unblocked. A SIGPROF that arrived after threadentry installed g0, but before mstart set the g0 stack bounds, crashed the runtime with signal 27 received on thread with no signal stack (golang/go#77588).

This PR moves the constants into the per-OS libcgo_*.go files and gives each OS its own values, with a link to the header they come from. The values match the Go runtime's own definitions (os_darwin.go, os2_freebsd.go, os_netbsd.go, os_linux_generic.go).

Testing

  • Repro on darwin/arm64 with CGO_ENABLED=0: a program runs CPU profiling at runtime.SetCPUProfileRate(100000) while goroutines call runtime.LockOSThread and exit without unlocking, so threads are constantly created and destroyed. Before this change, 9 of 13 ten-second runs crashed with the error above. With it, 0 of 10 crashed.
  • go test ./... passes on darwin/arm64 with CGO_ENABLED=0 and CGO_ENABLED=1.
  • Cross-builds pass for darwin, freebsd (with -gcflags=github.com/ebitengine/purego/internal/fakecgo=-std), netbsd and all supported linux architectures.
  • Not run on FreeBSD or NetBSD.

Filed by Claude (Claude Code), on behalf of @hajimehoshi.

🤖 Generated with Claude Code

SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once with the
Linux values 0, 1 and 2. Darwin, FreeBSD and NetBSD use 1, 2 and 3, so
pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as
SIG_UNBLOCK there. Instead of blocking all signals across
pthread_create, it unblocked them, and the new thread started with
signals unblocked. A SIGPROF delivered after threadentry installed g0
but before mstart set the g0 stack bounds then crashed the runtime with
"signal 27 received on thread with no signal stack".

Define the constants in the per-OS files with each OS's values.

Closes ebitengine#552

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 10:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused constant correction preserves existing callers and platform selection, with no unresolved findings.

0 open findings

What changed in this PR

Fixes #552 by using OS-specific signal-mask constants in fakecgo, preventing incorrect signal handling during thread creation on Darwin and BSD.

Changes:

  • Moves signal-mask constants from shared code into per-OS files.
  • Corrects Darwin, FreeBSD, and NetBSD values while preserving Linux values.
  • Adds source-header references.
File Description
internal/​fakecgo/​libcgo.go Removes shared Linux-specific constants.
internal/​fakecgo/​libcgo_netbsd.go Defines correct NetBSD values.
internal/​fakecgo/​libcgo_linux.go Preserves Linux values locally.
internal/​fakecgo/​libcgo_freebsd.go Defines correct FreeBSD values.
internal/​fakecgo/​libcgo_darwin.go Defines correct Darwin values.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@hajimehoshi

Copy link
Copy Markdown
Member Author

@TotallyGamerJet PTAL

@TotallyGamerJet TotallyGamerJet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hajimehoshi
hajimehoshi merged commit d456b1b into ebitengine:main Oct 15, 2026
25 checks passed
@hajimehoshi
hajimehoshi deleted the claude/purego-issue-552-c5522a branch October 15, 2026 13:05
hajimehoshi added a commit that referenced this pull request Oct 15, 2026
SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once with the
Linux values 0, 1 and 2. Darwin, FreeBSD and NetBSD use 1, 2 and 3, so
pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as
SIG_UNBLOCK there. Instead of blocking all signals across
pthread_create, it unblocked them, and the new thread started with
signals unblocked. A SIGPROF delivered after threadentry installed g0
but before mstart set the g0 stack bounds then crashed the runtime with
"signal 27 received on thread with no signal stack".

Define the constants in the per-OS files with each OS's values.

Closes #552

Co-authored-by: Claude Opus 5.5 <[email protected]>
kolkov added a commit to go-webgpu/goffi that referenced this pull request Oct 15, 2026
SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once in libcgo.go
with the Linux values 0/1/2. Darwin, FreeBSD and NetBSD use 1/2/3, so
pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as
SIG_UNBLOCK there: new runtime threads started with every signal
unblocked instead of blocked, and the restore call unblocked the saved
mask instead of reinstating it. A signal arriving before the new thread
had its g0 stack set up could crash the process.

Define the constants in libcgo_{darwin,freebsd,linux,netbsd}.go with
each system's values. Linux and Android were not affected.

Add TestSighowMatchesSystemHeaders, which compiles a probe with the
system C compiler and checks the constants against the host's
<signal.h>, so CI on macOS catches a mismatch.

Ported from ebitengine/purego#553 (ebitengine/purego#552).
kolkov added a commit to go-webgpu/goffi that referenced this pull request Oct 15, 2026
SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once in libcgo.go
with the Linux values 0/1/2. Darwin, FreeBSD and NetBSD use 1/2/3, so
pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as
SIG_UNBLOCK there: new runtime threads started with every signal
unblocked instead of blocked, and the restore call unblocked the saved
mask instead of reinstating it. A signal arriving before the new thread
had its g0 stack set up could crash the process.

Define the constants in libcgo_{darwin,freebsd,linux,netbsd}.go with
each system's values. Linux and Android were not affected.

Add TestSighowMatchesSystemHeaders, which compiles a probe with the
system C compiler and checks the constants against the host's
<signal.h>, so CI on macOS catches a mismatch.

Ported from ebitengine/purego#553 (ebitengine/purego#552).
kolkov added a commit to go-webgpu/goffi that referenced this pull request Oct 15, 2026
SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were defined once in libcgo.go
with the Linux values 0/1/2. Darwin, FreeBSD and NetBSD use 1/2/3, so
pthread_sigmask(SIG_SETMASK, ...) in _cgo_sys_thread_start ran as
SIG_UNBLOCK there: new runtime threads started with every signal
unblocked instead of blocked, and the restore call unblocked the saved
mask instead of reinstating it. A signal arriving before the new thread
had its g0 stack set up could crash the process.

Define the constants in libcgo_{darwin,freebsd,linux,netbsd}.go with
each system's values. Linux and Android were not affected.

Add TestSighowMatchesSystemHeaders, which compiles a probe with the
system C compiler and checks the constants against the host's
<signal.h>, so CI on macOS catches a mismatch.

Ported from ebitengine/purego#553 (ebitengine/purego#552).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

internal/fakecgo: pthread_sigmask uses Linux constants on Darwin/FreeBSD/NetBSD (SIG_SETMASK unblocks all signals)

3 participants