mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: block console ctrlhandler when the signal is handled
Fixes #41884
I can confirm this change fixes my issue.
I can't confirm that this doesn't break any and everything else.
I see that this code has been tweaked repeatedly, so I would really welcome guidance into further testing.
Change-Id: I1986dd0c2f30cfe10257f0d8c658988d6986f7a6
GitHub-Last-Rev: 92f02c9697
GitHub-Pull-Request: golang/go#41886
Reviewed-on: https://go-review.googlesource.com/c/go/+/261057
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Trust: Alex Brainman <alex.brainman@gmail.com>
This commit is contained in:
parent
ff9e8364c6
commit
8cfa01943a
3 changed files with 90 additions and 0 deletions
|
|
@ -46,6 +46,7 @@ const (
|
||||||
//go:cgo_import_dynamic runtime._SetThreadPriority SetThreadPriority%2 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._SetThreadPriority SetThreadPriority%2 "kernel32.dll"
|
||||||
//go:cgo_import_dynamic runtime._SetUnhandledExceptionFilter SetUnhandledExceptionFilter%1 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._SetUnhandledExceptionFilter SetUnhandledExceptionFilter%1 "kernel32.dll"
|
||||||
//go:cgo_import_dynamic runtime._SetWaitableTimer SetWaitableTimer%6 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._SetWaitableTimer SetWaitableTimer%6 "kernel32.dll"
|
||||||
|
//go:cgo_import_dynamic runtime._Sleep Sleep%1 "kernel32.dll"
|
||||||
//go:cgo_import_dynamic runtime._SuspendThread SuspendThread%1 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._SuspendThread SuspendThread%1 "kernel32.dll"
|
||||||
//go:cgo_import_dynamic runtime._SwitchToThread SwitchToThread%0 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._SwitchToThread SwitchToThread%0 "kernel32.dll"
|
||||||
//go:cgo_import_dynamic runtime._TlsAlloc TlsAlloc%0 "kernel32.dll"
|
//go:cgo_import_dynamic runtime._TlsAlloc TlsAlloc%0 "kernel32.dll"
|
||||||
|
|
@ -97,6 +98,7 @@ var (
|
||||||
_SetThreadPriority,
|
_SetThreadPriority,
|
||||||
_SetUnhandledExceptionFilter,
|
_SetUnhandledExceptionFilter,
|
||||||
_SetWaitableTimer,
|
_SetWaitableTimer,
|
||||||
|
_Sleep,
|
||||||
_SuspendThread,
|
_SuspendThread,
|
||||||
_SwitchToThread,
|
_SwitchToThread,
|
||||||
_TlsAlloc,
|
_TlsAlloc,
|
||||||
|
|
@ -1094,6 +1096,11 @@ func ctrlhandler1(_type uint32) uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if sigsend(s) {
|
if sigsend(s) {
|
||||||
|
if s == _SIGTERM {
|
||||||
|
// Windows terminates the process after this handler returns.
|
||||||
|
// Block indefinitely to give signal handlers a chance to clean up.
|
||||||
|
stdcall1(_Sleep, uintptr(_INFINITE))
|
||||||
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -79,6 +80,69 @@ func sendCtrlBreak(pid int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCtrlHandler tests that Go can gracefully handle closing the console window.
|
||||||
|
// See https://golang.org/issues/41884.
|
||||||
|
func TestCtrlHandler(t *testing.T) {
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// build go program
|
||||||
|
exe := filepath.Join(t.TempDir(), "test.exe")
|
||||||
|
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "testdata/testwinsignal/main.go")
|
||||||
|
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to build go exe: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run test program
|
||||||
|
cmd = exec.Command(exe)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
outPipe, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create stdout pipe: %v", err)
|
||||||
|
}
|
||||||
|
outReader := bufio.NewReader(outPipe)
|
||||||
|
|
||||||
|
// in a new command window
|
||||||
|
const _CREATE_NEW_CONSOLE = 0x00000010
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
CreationFlags: _CREATE_NEW_CONSOLE,
|
||||||
|
HideWindow: true,
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatalf("Start failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait for child to be ready to receive signals
|
||||||
|
if line, err := outReader.ReadString('\n'); err != nil {
|
||||||
|
t.Fatalf("could not read stdout: %v", err)
|
||||||
|
} else if strings.TrimSpace(line) != "ready" {
|
||||||
|
t.Fatalf("unexpected message: %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gracefully kill pid, this closes the command window
|
||||||
|
if err := exec.Command("taskkill.exe", "/pid", strconv.Itoa(cmd.Process.Pid)).Run(); err != nil {
|
||||||
|
t.Fatalf("failed to kill: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check child received, handled SIGTERM
|
||||||
|
if line, err := outReader.ReadString('\n'); err != nil {
|
||||||
|
t.Fatalf("could not read stdout: %v", err)
|
||||||
|
} else if expected, got := syscall.SIGTERM.String(), strings.TrimSpace(line); expected != got {
|
||||||
|
t.Fatalf("Expected '%s' got: %s", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check child exited gracefully, did not timeout
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestLibraryCtrlHandler tests that Go DLL allows calling program to handle console control events.
|
// TestLibraryCtrlHandler tests that Go DLL allows calling program to handle console control events.
|
||||||
// See https://golang.org/issues/35965.
|
// See https://golang.org/issues/35965.
|
||||||
func TestLibraryCtrlHandler(t *testing.T) {
|
func TestLibraryCtrlHandler(t *testing.T) {
|
||||||
|
|
|
||||||
19
src/runtime/testdata/testwinsignal/main.go
vendored
Normal file
19
src/runtime/testdata/testwinsignal/main.go
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c)
|
||||||
|
|
||||||
|
fmt.Println("ready")
|
||||||
|
sig := <-c
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
fmt.Println(sig)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue