-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
runtime: handling of CTRL_CLOSE_EVENT seems broken #41884
Comments
Change https://golang.org/cl/261057 mentions this issue: |
I don't understand yet what you goes wrong. I compiled this code and run #include <windows.h>
#include <stdio.h>
#include <signal.h>
BOOL WINAPI ctrl_handler(DWORD dwCtrlType) {
switch (dwCtrlType) {
case CTRL_C_EVENT:
puts("ctrl-c");
break;
case CTRL_BREAK_EVENT:
puts("ctrl-break");
break;
case CTRL_LOGOFF_EVENT:
puts("ctrl-logoff");
break;
case CTRL_SHUTDOWN_EVENT:
puts("ctrl-shutdown");
break;
case CTRL_CLOSE_EVENT:
puts("ctrl-close");
break;
}
return TRUE;
}
int
main(int argc, char* argv[]) {
SetConsoleCtrlHandler(ctrl_handler, TRUE);
puts("waiting...");
while (TRUE) {
Sleep(5000);
}
return 0;
} FYI, CTRL_CLOSE_EVENT has a timeout constraint. https://docs.microsoft.com/en-us/windows/console/handlerroutine?redirectedfrom=MSDN#timeouts |
The point isn't quitting immediately. Not handling the signal does that for you (Windows just kills your process). The point is exiting gracefully: closing DB connections, cleaning temporary files, notifying child processes, etc. For that you do need to handle the signal. The Windows API is designed in such a way that cleanup code should run synchronously in the handler. Go decided in cl/187739 to take these ( This works fine for This does not work at all for What happens in https://gist.github.com/ncruces/20dbdc73d0da6e211ee56b68c2240bae is that the handling code does not get a chance to run. If you look at the log you get with a
If you instead close the console ( I hope this covers the need for this fix. The current checked in code is quite frankly pointless. Cleanup code may run, only to be forcefully interrupted, or it may not run at all, depending on scheduling. Now for the rational behind the fix in #41886 (basically Why is this not a problem? As you mentioned, Windows sets an upper bound into how much it will let you wait (5s for console close, 20s for shutdown). So, you'll never wait more than this. The other question is, does this Also Finally, and if it helps, as I mentioned in cl/187739, |
I didn't investigate service issues. I added a test case for this (though it briefly flashes a console window and syncs over UDP, not sure that's acceptable). There were no test cases for services, if there are any pointers to stuff I can test for regressions, please link them here. |
This seems to have stalled on lack of reviews. There's now a merge conflict. Should I pursue this? Fix the conflict, anything else I can do? |
@ncruces see my comment in https://go-review.googlesource.com/c/go/+/187739/1..9//COMMIT_MSG#b19 starting with
My test results contradicts your findings. Are you are saying I got lucky (or unlucky whichever way you look at it)? That there is a race in my test?
Are you saying that Go does not provide function to handle CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT events? According to your CL 261057, you propose that Go runtime change to hang in event handler. I can see two problems with your change:
/cc @ianlancetaylor and @zx2c4 for opinion. Alex You propose that we change handling of CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT events Thank you. Alex |
@alexbrainman thanks for reviewing. My comments inline.
Yes, that's what I'm saying. If you modify said example code by inserting a func main() {
for {
c := make(chan os.Signal, 1)
signal.Notify(c)
s := <-c
time.Sleep(time.Second) // ** ADDED THIS **
fmt.Println("Got signal:", s)
}
}
I'm saying at a minimum there's a race, and that you get killed before getting to do any significant clean up (I'm trying to notify child processes).
Regarding 1, only programs that handle The conditions below ensure this (sleeping only happens if the signal is handled and if it is SIGTERM): Lines 1012 to 1019 in 03abc2d
You can test this by modifying the sample code to only listen to func main() {
for {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT) // ** MODIFIED THIS **
s := <-c
time.Sleep(time.Second)
fmt.Println("Got signal:", s)
}
} Additionally, if in handling You can test this by modifying sample code as follows. func main() {
for {
c := make(chan os.Signal, 1)
signal.Notify(c)
s := <-c
time.Sleep(time.Second)
fmt.Println("Got signal:", s)
if s == syscall.SIGTERM {
// comment the break to be forcefully terminated after 5 seconds
// uncomment to exit immediately after the 1 second sleep
break
}
}
} In the original issue, I commented that Regarding number 2, signal handling through |
Fixed the merge conflict (#41886, cl/261057), and updated the test to also ensure that the child exits before the 5 second deadline. The test starts a child process, then closes its console window ( To be successful the test requires the parent to be notified through UDP, that the signal received is |
Sorry to bother you, and thanks for your time, @alexbrainman. |
@alexbrainman @dmitshur @bradfitz @ianlancetaylor - This has a working/reviewed fix now -- https://go-review.googlesource.com/c/go/+/261057 -- and it fixes a bug. Is this something we can consider submitting for 1.16? |
I am fine to include https://go-review.googlesource.com/c/go/+/261057 in Go 1.16. But it is up to release team to decide. Alex |
@zx2c4 @alexbrainman Discussed with @toothrot, this looks okay to try for Go 1.16 if you can land it before RC 1. It's contained, fixing a bug, and has a test. It'll go through testing in RC 1 and we can roll it back and try again in Go 1.17 if there are problems. Thanks. |
@ncruces - Could you address the last-minute feedback on Gerrit, and then I'll submit this? |
Done. Sorry for stepping on your toes. |
What version of Go are you using (
go version
)?go version go1.15.2 windows/amd64
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?set GO111MODULE=
set GOARCH=amd64
set GOBIN=D:\Apps\Go\work\bin
set GOCACHE=C:\Users\ncruc\AppData\Local\go-build
set GOENV=C:\Users\ncruc\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=D:\Apps\Go\work\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=D:\Apps\Go\work
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=D:\Apps\Go\root
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=D:\Apps\Go\root\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\ncruc\AppData\Local\Temp\go-build526790066=/tmp/go-build -gno-record-gcc-switches
What did you do?
I have a MCVE on https://gist.github.com/ncruces/20dbdc73d0da6e211ee56b68c2240bae
Open a new console window, build and run the example.
Then close the console window and check the log file it creates next to the executable.
What did you expect to see?
Given cl/187739 has been merged, I expected to see a
SIGTERM
being received bysignal.Notify
, and having 5 to 20s to handle cleanup.Specifically I want to see these log lines:
What did you see instead?
Either
SIGTERM
isn't being received at all, or we're not given any time to cleanup.The MCVE also includes code to call
SetConsoleCtrlHandler
(which shouldn't be needed given cl/187739).You can activate it by running either of:
The MCVE works consistently if the handler blocks giving the rest of the program time to gracefully terminate.
This is consistent with the documentation:
So, I'm assuming that's what needs to be changed, this return needs to become a
select {}
or similar:go/src/runtime/os_windows.go
Lines 1010 to 1012 in c0dded0
I could easily do a PR, but I'm not well versed with building go from source and testing the change.
Maybe commenters from #7479 can help: @alexbrainman, @tianon?
The text was updated successfully, but these errors were encountered: