Skip to content

Commit 88cdb34

Browse files
authored
Fix interrupt handler on Windows (#812)
Per documentation on os/signal from https://pkg.go.dev/os/signal#hdr-Windows, on Windows, SIGINT gets translated to os.Interrupt, not windows.SIGINT. This fixes the signal handler to listen to os.Interrupt as well so that SIGINTs on Windows gets handled properly. Fix #781. Internal Ref: GO-889.
1 parent 0c1b3fe commit 88cdb34

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ func (app *App) Done() <-chan os.Signal {
802802
return c
803803
}
804804

805-
signal.Notify(c, _sigINT, _sigTERM)
805+
signal.Notify(c, os.Interrupt, _sigINT, _sigTERM)
806806
app.dones = append(app.dones, c)
807807
return c
808808
}

app_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1683,7 +1683,27 @@ func TestOptionString(t *testing.T) {
16831683
}
16841684

16851685
func TestMain(m *testing.M) {
1686-
goleak.VerifyTestMain(m)
1686+
if os.Getenv("VerifySignalHandler") != "" {
1687+
app := New(
1688+
NopLogger,
1689+
Invoke(func(lifecycle Lifecycle) {
1690+
lifecycle.Append(
1691+
Hook{
1692+
OnStart: func(ctx context.Context) error {
1693+
fmt.Fprintf(os.Stderr, "ready\n")
1694+
return nil
1695+
},
1696+
OnStop: func(ctx context.Context) error {
1697+
fmt.Fprintf(os.Stdout, "ONSTOP\n")
1698+
return nil
1699+
},
1700+
})
1701+
}),
1702+
)
1703+
app.Run()
1704+
} else {
1705+
goleak.VerifyTestMain(m)
1706+
}
16871707
}
16881708

16891709
type testLogger struct{ t *testing.T }

app_windows_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2020-2021 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
//go:build windows
22+
// +build windows
23+
24+
package fx_test
25+
26+
import (
27+
"bytes"
28+
"os"
29+
"os/exec"
30+
"syscall"
31+
"testing"
32+
33+
"github.com/stretchr/testify/assert"
34+
"github.com/stretchr/testify/require"
35+
"golang.org/x/sys/windows"
36+
)
37+
38+
func TestCtrlCHandler(t *testing.T) {
39+
// Launch a separate process we will send SIGINT to.
40+
bin, err := os.Executable()
41+
require.NoError(t, err)
42+
cmd := exec.Command(bin)
43+
44+
// buffers used to capture the output of the child process.
45+
so, _ := cmd.StdoutPipe()
46+
se, _ := cmd.StderrPipe()
47+
48+
cmd.Env = []string{"VerifySignalHandler=1"}
49+
// CREATE_NEW_PROCESS_GROUP is required to send SIGINT to
50+
// the child process.
51+
cmd.SysProcAttr = &syscall.SysProcAttr{
52+
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
53+
}
54+
err = cmd.Start()
55+
require.NoError(t, err)
56+
childPid := cmd.Process.Pid
57+
58+
c := make(chan struct{}, 1)
59+
60+
go func() {
61+
se.Read(make([]byte, 1024))
62+
c <- struct{}{}
63+
}()
64+
65+
// block until child proc is ready.
66+
<-c
67+
68+
// Send signal to child proc.
69+
err = windows.GenerateConsoleCtrlEvent(1, uint32(childPid))
70+
require.NoError(t, err)
71+
72+
// Drain out stdout/stderr before waiting.
73+
buf := new(bytes.Buffer)
74+
buf.ReadFrom(se)
75+
buf.ReadFrom(so)
76+
77+
// Wait till child proc finishes
78+
err = cmd.Wait()
79+
80+
// stdout should have ONSTOP printed on it from OnStop handler.
81+
assert.Contains(t, buf.String(), "ONSTOP")
82+
assert.NoError(t, err)
83+
}

0 commit comments

Comments
 (0)