svc_windows.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // +build windows
  2. package svc
  3. import (
  4. "context"
  5. "os"
  6. "path/filepath"
  7. "sync"
  8. "syscall"
  9. wsvc "golang.org/x/sys/windows/svc"
  10. )
  11. // Create variables for svc and signal functions so we can mock them in tests
  12. var svcIsWindowsService = wsvc.IsWindowsService
  13. var svcRun = wsvc.Run
  14. type windowsService struct {
  15. i Service
  16. errSync sync.Mutex
  17. stopStartErr error
  18. isWindowsService bool
  19. signals []os.Signal
  20. Name string
  21. ctx context.Context
  22. }
  23. // Run runs an implementation of the Service interface.
  24. //
  25. // Run will block until the Windows Service is stopped or Ctrl+C is pressed if
  26. // running from the console.
  27. //
  28. // Stopping the Windows Service and Ctrl+C will call the Service's Stop method to
  29. // initiate a graceful shutdown.
  30. //
  31. // Note that WM_CLOSE is not handled (end task) and the Service's Stop method will
  32. // not be called.
  33. //
  34. // The sig parameter is to keep parity with the non-Windows API. Only syscall.SIGINT
  35. // (Ctrl+C) can be handled on Windows. Nevertheless, you can override the default
  36. // signals which are handled by specifying sig.
  37. func Run(service Service, sig ...os.Signal) error {
  38. var err error
  39. isWindowsService, err := svcIsWindowsService()
  40. if err != nil {
  41. return err
  42. }
  43. if len(sig) == 0 {
  44. sig = []os.Signal{syscall.SIGINT}
  45. }
  46. ws := &windowsService{
  47. i: service,
  48. isWindowsService: isWindowsService,
  49. signals: sig,
  50. ctx: context.Background(),
  51. }
  52. if ws.IsWindowsService() {
  53. // the working directory for a Windows Service is C:\Windows\System32
  54. // this is almost certainly not what the user wants.
  55. dir := filepath.Dir(os.Args[0])
  56. if err = os.Chdir(dir); err != nil {
  57. return err
  58. }
  59. }
  60. if err = service.Init(ws); err != nil {
  61. return err
  62. }
  63. // call service.Context() after service.Init()
  64. if s, ok := service.(Context); ok {
  65. ws.ctx = s.Context()
  66. }
  67. return ws.run()
  68. }
  69. func (ws *windowsService) setError(err error) {
  70. ws.errSync.Lock()
  71. ws.stopStartErr = err
  72. ws.errSync.Unlock()
  73. }
  74. func (ws *windowsService) getError() error {
  75. ws.errSync.Lock()
  76. err := ws.stopStartErr
  77. ws.errSync.Unlock()
  78. return err
  79. }
  80. func (ws *windowsService) IsWindowsService() bool {
  81. return ws.isWindowsService
  82. }
  83. func (ws *windowsService) run() error {
  84. ws.setError(nil)
  85. if ws.IsWindowsService() {
  86. // Return error messages from start and stop routines
  87. // that get executed in the Execute method.
  88. // Guarded with a mutex as it may run a different thread
  89. // (callback from Windows).
  90. runErr := svcRun(ws.Name, ws)
  91. startStopErr := ws.getError()
  92. if startStopErr != nil {
  93. return startStopErr
  94. }
  95. if runErr != nil {
  96. return runErr
  97. }
  98. return nil
  99. }
  100. err := ws.i.Start()
  101. if err != nil {
  102. return err
  103. }
  104. signalChan := make(chan os.Signal, 1)
  105. signalNotify(signalChan, ws.signals...)
  106. select {
  107. case <-signalChan:
  108. case <-ws.ctx.Done():
  109. }
  110. err = ws.i.Stop()
  111. return err
  112. }
  113. // Execute is invoked by Windows
  114. func (ws *windowsService) Execute(args []string, r <-chan wsvc.ChangeRequest, changes chan<- wsvc.Status) (bool, uint32) {
  115. const cmdsAccepted = wsvc.AcceptStop | wsvc.AcceptShutdown
  116. changes <- wsvc.Status{State: wsvc.StartPending}
  117. if err := ws.i.Start(); err != nil {
  118. ws.setError(err)
  119. return true, 1
  120. }
  121. changes <- wsvc.Status{State: wsvc.Running, Accepts: cmdsAccepted}
  122. for {
  123. var c wsvc.ChangeRequest
  124. select {
  125. case c = <-r:
  126. case <-ws.ctx.Done():
  127. c = wsvc.ChangeRequest{Cmd: wsvc.Stop}
  128. }
  129. switch c.Cmd {
  130. case wsvc.Interrogate:
  131. changes <- c.CurrentStatus
  132. case wsvc.Stop, wsvc.Shutdown:
  133. changes <- wsvc.Status{State: wsvc.StopPending}
  134. err := ws.i.Stop()
  135. if err != nil {
  136. ws.setError(err)
  137. return true, 2
  138. }
  139. return false, 0
  140. default:
  141. }
  142. }
  143. }