windows/service: implement graceful shutdown when run as windows service

- Fixes https://github.com/kubernetes/kubernetes/issues/72900
The issue here originally is that os.Exit() is called which exits
the process too early (before svc.Execute updates the status to stopped).
This is picked up as service error and leads to restarting,
if restart-on-fail is configured for the windows service.
svc.Execute already guarantees that the application is exited after,
so that os.Exit call would be unnecessary.

This rework also adds graceful shutdown, which also resolves the
underlying root cause. The graceful shutdown is not guaranteed
to succeed, since the service controller can decide to kill
the service any time after exceeding a shutdown timeout.
This commit is contained in:
Steffen Butzer 2019-01-25 00:34:42 +01:00
parent ccd83ce1c5
commit f243c88779
2 changed files with 26 additions and 9 deletions

View File

@ -19,8 +19,7 @@ limitations under the License.
package service
import (
"os"
"k8s.io/apiserver/pkg/server"
"k8s.io/klog"
"golang.org/x/sys/windows"
@ -80,9 +79,19 @@ Loop:
case svc.Interrogate:
s <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
s <- svc.Status{State: svc.Stopped}
// TODO: Stop the kubelet gracefully instead of killing the process
os.Exit(0)
klog.Infof("Service stopping")
// We need to translate this request into a signal that can be handled by the the signal handler
// handling shutdowns normally (currently apiserver/pkg/server/signal.go).
// If we do not do this, our main threads won't be notified of the upcoming shutdown.
// Since Windows services do not use any console, we cannot simply generate a CTRL_BREAK_EVENT
// but need a dedicated notification mechanism.
server.RequestShutdown()
// Free up the control handler and let us terminate as gracefully as possible.
// If that takes too long, the service controller will kill the remaining threads.
// As per https://docs.microsoft.com/en-us/windows/desktop/services/service-control-handler-function
s <- svc.Status{State: svc.StopPending}
break Loop
}
}
}

View File

@ -22,6 +22,7 @@ import (
)
var onlyOneSignalHandler = make(chan struct{})
var shutdownHandler = make(chan os.Signal, 2)
// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned
// which is closed on one of these signals. If a second signal is caught, the program
@ -30,14 +31,21 @@ func SetupSignalHandler() <-chan struct{} {
close(onlyOneSignalHandler) // panics when called twice
stop := make(chan struct{})
c := make(chan os.Signal, 2)
signal.Notify(c, shutdownSignals...)
signal.Notify(shutdownHandler, shutdownSignals...)
go func() {
<-c
<-shutdownHandler
close(stop)
<-c
<-shutdownHandler
os.Exit(1) // second signal. Exit directly.
}()
return stop
}
// RequestShutdown emulates a received event that is considered as shutdown signal (SIGTERM/SIGINT)
func RequestShutdown() {
select {
case shutdownHandler <- shutdownSignals[0]:
default:
}
}