diff --git a/example/stopPause/main.go b/example/stopPause/main.go new file mode 100644 index 0000000..8d0fdfe --- /dev/null +++ b/example/stopPause/main.go @@ -0,0 +1,62 @@ +// Copyright 2015 Daniel Theophanes. +// Use of this source code is governed by a zlib-style +// license that can be found in the LICENSE file.package service + +// simple does nothing except block while running the service. +package main + +import ( + "log" + "os" + "time" + + "github.com/kardianos/service" +) + +var logger service.Logger + +type program struct{} + +func (p *program) Start(s service.Service) error { + // Start should not block. Do the actual work async. + go p.run() + return nil +} +func (p *program) run() { + // Do work here +} +func (p *program) Stop(s service.Service) error { + // Stop should not block. Return with a few seconds. + <-time.After(time.Second * 13) + return nil +} + +func main() { + svcConfig := &service.Config{ + Name: "GoServiceExampleStopPause", + DisplayName: "Go Service Example: Stop Pause", + Description: "This is an example Go service that pauses on stop.", + } + + prg := &program{} + s, err := service.New(prg, svcConfig) + if err != nil { + log.Fatal(err) + } + if len(os.Args) > 1 { + err = service.Control(s, os.Args[1]) + if err != nil { + log.Fatal(err) + } + return + } + + logger, err = s.Logger(nil) + if err != nil { + log.Fatal(err) + } + err = s.Run() + if err != nil { + logger.Error(err) + } +} diff --git a/service_windows.go b/service_windows.go index 9fb8aaf..ae5b0d8 100644 --- a/service_windows.go +++ b/service_windows.go @@ -8,9 +8,11 @@ import ( "fmt" "os" "os/signal" + "strconv" "sync" "time" + "golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" @@ -300,18 +302,78 @@ func (ws *windowsService) Stop() error { return err } defer s.Close() - _, err = s.Control(svc.Stop) - return err + + return ws.stopWait(s) } func (ws *windowsService) Restart() error { - err := ws.Stop() + m, err := mgr.Connect() if err != nil { return err } - time.Sleep(50 * time.Millisecond) - return ws.Start() + defer m.Disconnect() + + s, err := m.OpenService(ws.Name) + if err != nil { + return err + } + defer s.Close() + + err = ws.stopWait(s) + if err != nil { + return err + } + + return s.Start() } + +func (ws *windowsService) stopWait(s *mgr.Service) error { + // First stop the service. Then wait for the service to + // actually stop before starting it. + status, err := s.Control(svc.Stop) + if err != nil { + return err + } + + timeDuration := time.Millisecond * 50 + + timeout := time.After(getStopTimeout() + (timeDuration * 2)) + tick := time.NewTicker(timeDuration) + defer tick.Stop() + + for status.State != svc.Stopped { + select { + case <-tick.C: + status, err = s.Query() + if err != nil { + return err + } + case <-timeout: + break + } + } + return nil +} + +// getStopTimeout fetches the time before windows will kill the service. +func getStopTimeout() time.Duration { + // For default and paths see https://support.microsoft.com/en-us/kb/146092 + defaultTimeout := time.Millisecond * 20000 + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ) + if err != nil { + return defaultTimeout + } + sv, _, err := key.GetStringValue("WaitToKillServiceTimeout") + if err != nil { + return defaultTimeout + } + v, err := strconv.Atoi(sv) + if err != nil { + return defaultTimeout + } + return time.Millisecond * time.Duration(v) +} + func (ws *windowsService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil