Skip to content

os/signal: Passing bad values to Notify makes Stop hang on Windows #77076

@matthallcb

Description

@matthallcb

Go version

go version go1.25.5 windows/amd64
go version go1.24.4 windows/amd64

(others untested)

Output of go env in your module/workspace:

set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=1
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=C:\Users\Matt Hall\AppData\Local\go-build
set GOCACHEPROG=
set GODEBUG=
set GOENV=C:\Users\Matt Hall\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\MATTHA~1\AppData\Local\Temp\go-build4154539945=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=C:\Users\Matt Hall\Documents\test\go.mod
set GOMODCACHE=C:\Users\Matt Hall\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Matt Hall\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\Matt Hall\AppData\Roaming\go\telemetry
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.25.5
set GOWORK=
set PKG_CONFIG=pkg-config

What did you do?

Ran the following program

package main

import (
        "context"
        "fmt"
        "os"
        "os/signal"
        "sync"

        "golang.org/x/sys/windows"
)

type SignalHandler struct {
        wg     sync.WaitGroup
        cancel context.CancelFunc
}

func (s *SignalHandler) Start() {
        ctx, cancel := context.WithCancel(context.Background())
        s.cancel = cancel

        s.wg.Add(1)
        go s.handle(ctx)
}

func (s *SignalHandler) Stop() {
        s.cancel()
        s.wg.Wait()
}

func (s *SignalHandler) handle(ctx context.Context) {
        defer s.wg.Done()

        ch := make(chan os.Signal, 1)
        // 0xaa is some nonsense signal. windows.SIGTERM etc work just as well.
        // Anything but os.Interrupt
        signal.Notify(ch, windows.Signal(0xaa))
        defer signal.Stop(ch)

        for {
                select {
                case <-ctx.Done():
                        return
                case <-ch:
                        continue
                }
        }
}

func main() {
        h := &SignalHandler{}
        fmt.Println("start")
        h.Start()
        fmt.Println("stop")
        h.Stop()
}

What did you see happen?

"start" and "stop" are both printed but the process then hangs. The issue is signal.Stop:

0  0x00007ff7a4b83694 in runtime.mcall
   at C:/Program Files/Go/src/runtime/asm_amd64.s:457
1  0x00007ff7a4b7f9f6 in runtime.Gosched
   at C:/Program Files/Go/src/runtime/proc.go:389
2  0x00007ff7a4b7f9f6 in os/signal.signalWaitUntilIdle
   at C:/Program Files/Go/src/runtime/sigqueue.go:193
3  0x00007ff7a4bbf4aa in os/signal.Stop
   at C:/Program Files/Go/src/os/signal/signal.go:215
4  0x00007ff7a4bca1e5 in main.(*SignalHandler).handle.deferwrap2
   at C:/Users/Matt Hall/Documents/test/main.go:36
5  0x00007ff7a4bca157 in main.(*SignalHandler).handle
   at C:/Users/Matt Hall/Documents/test/main.go:42
6  0x00007ff7a4bc9fa8 in main.(*SignalHandler).Start.gowrap1
   at C:/Users/Matt Hall/Documents/test/main.go:23
7  0x00007ff7a4b856e1 in runtime.goexit
   at C:/Program Files/Go/src/runtime/asm_amd64.s:1693

Digging around a bit more sig.state is stuck in idle. I believe this is because idle is the zero value and sigsend/signal_recv - which advance the state - are never run.

If I pass os.Interrupt to signal.Notify instead signal_recv is called and the process exists. I can't find where signal_recv is called. The backtrace ends in assembly which looks suspicious:

0  0x00007ff76581e94e in os/signal.signal_recv
   at C:/Program Files/Go/src/runtime/sigqueue.go:130
1  0x00007ff76585e9d3 in os/signal.loop
   at C:/Program Files/Go/src/os/signal/signal_unix.go:23
2  0x00007ff765824741 in runtime.goexit
   at C:/Program Files/Go/src/runtime/asm_amd64.s:1693

What did you expect to see?

The process should terminate.

I think the docs could also be clearer here. Reading between the lines it appears the only valid value to be passed on Windows is os.Interrupted, but this is not stated outright.

Metadata

Metadata

Assignees

Labels

BugReportIssues describing a possible bug in the Go implementation.DocumentationIssues describing a change to documentation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.OS-Windowscompiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

In Progress

Relationships

None yet

Development

No branches or pull requests

Issue actions