diff --git a/pkg/windows/service/BUILD b/pkg/windows/service/BUILD index e64c40488b4..e518a527080 100644 --- a/pkg/windows/service/BUILD +++ b/pkg/windows/service/BUILD @@ -11,6 +11,7 @@ go_library( importpath = "k8s.io/kubernetes/pkg/windows/service", deps = select({ "@io_bazel_rules_go//go/platform:windows": [ + "//staging/src/k8s.io/apiserver/pkg/server:go_default_library", "//vendor/golang.org/x/sys/windows:go_default_library", "//vendor/golang.org/x/sys/windows/svc:go_default_library", "//vendor/k8s.io/klog:go_default_library", diff --git a/pkg/windows/service/service.go b/pkg/windows/service/service.go index a5bffa1822e..47961b842ef 100644 --- a/pkg/windows/service/service.go +++ b/pkg/windows/service/service.go @@ -20,7 +20,9 @@ package service import ( "os" + "time" + "k8s.io/apiserver/pkg/server" "k8s.io/klog" "golang.org/x/sys/windows" @@ -80,9 +82,31 @@ 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. + graceful := 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} + + // If we cannot exit gracefully, we really only can exit our process, so atleast the + // service manager will think that we gracefully exited. At the time of writing this comment this is + // needed for applications that do not use signals (e.g. kube-proxy) + if !graceful { + go func() { + // Ensure the SCM was notified (The operation above (send to s) was received and communicated to the + // service control manager - so it doesn't look like the service crashes) + time.Sleep(1 * time.Second) + os.Exit(0) + }() + } + break Loop } } } diff --git a/staging/src/k8s.io/apiserver/pkg/server/signal.go b/staging/src/k8s.io/apiserver/pkg/server/signal.go index 6f0cff4baed..0ea19d6606d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/signal.go +++ b/staging/src/k8s.io/apiserver/pkg/server/signal.go @@ -22,6 +22,7 @@ import ( ) var onlyOneSignalHandler = make(chan struct{}) +var shutdownHandler chan os.Signal // 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 @@ -29,15 +30,30 @@ var onlyOneSignalHandler = make(chan struct{}) func SetupSignalHandler() <-chan struct{} { close(onlyOneSignalHandler) // panics when called twice + shutdownHandler = make(chan os.Signal, 2) + 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) +// This returns whether a handler was notified +func RequestShutdown() bool { + if shutdownHandler != nil { + select { + case shutdownHandler <- shutdownSignals[0]: + return true + default: + } + } + + return false +}