123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- // +build windows
- package svc
- import (
- "context"
- "os"
- "path/filepath"
- "sync"
- "syscall"
- wsvc "golang.org/x/sys/windows/svc"
- )
- // Create variables for svc and signal functions so we can mock them in tests
- var svcIsWindowsService = wsvc.IsWindowsService
- var svcRun = wsvc.Run
- type windowsService struct {
- i Service
- errSync sync.Mutex
- stopStartErr error
- isWindowsService bool
- signals []os.Signal
- Name string
- ctx context.Context
- }
- // Run runs an implementation of the Service interface.
- //
- // Run will block until the Windows Service is stopped or Ctrl+C is pressed if
- // running from the console.
- //
- // Stopping the Windows Service and Ctrl+C will call the Service's Stop method to
- // initiate a graceful shutdown.
- //
- // Note that WM_CLOSE is not handled (end task) and the Service's Stop method will
- // not be called.
- //
- // The sig parameter is to keep parity with the non-Windows API. Only syscall.SIGINT
- // (Ctrl+C) can be handled on Windows. Nevertheless, you can override the default
- // signals which are handled by specifying sig.
- func Run(service Service, sig ...os.Signal) error {
- var err error
- isWindowsService, err := svcIsWindowsService()
- if err != nil {
- return err
- }
- if len(sig) == 0 {
- sig = []os.Signal{syscall.SIGINT}
- }
- ws := &windowsService{
- i: service,
- isWindowsService: isWindowsService,
- signals: sig,
- ctx: context.Background(),
- }
- if ws.IsWindowsService() {
- // the working directory for a Windows Service is C:\Windows\System32
- // this is almost certainly not what the user wants.
- dir := filepath.Dir(os.Args[0])
- if err = os.Chdir(dir); err != nil {
- return err
- }
- }
- if err = service.Init(ws); err != nil {
- return err
- }
- // call service.Context() after service.Init()
- if s, ok := service.(Context); ok {
- ws.ctx = s.Context()
- }
- return ws.run()
- }
- func (ws *windowsService) setError(err error) {
- ws.errSync.Lock()
- ws.stopStartErr = err
- ws.errSync.Unlock()
- }
- func (ws *windowsService) getError() error {
- ws.errSync.Lock()
- err := ws.stopStartErr
- ws.errSync.Unlock()
- return err
- }
- func (ws *windowsService) IsWindowsService() bool {
- return ws.isWindowsService
- }
- func (ws *windowsService) run() error {
- ws.setError(nil)
- if ws.IsWindowsService() {
- // Return error messages from start and stop routines
- // that get executed in the Execute method.
- // Guarded with a mutex as it may run a different thread
- // (callback from Windows).
- runErr := svcRun(ws.Name, ws)
- startStopErr := ws.getError()
- if startStopErr != nil {
- return startStopErr
- }
- if runErr != nil {
- return runErr
- }
- return nil
- }
- err := ws.i.Start()
- if err != nil {
- return err
- }
- signalChan := make(chan os.Signal, 1)
- signalNotify(signalChan, ws.signals...)
- select {
- case <-signalChan:
- case <-ws.ctx.Done():
- }
- err = ws.i.Stop()
- return err
- }
- // Execute is invoked by Windows
- func (ws *windowsService) Execute(args []string, r <-chan wsvc.ChangeRequest, changes chan<- wsvc.Status) (bool, uint32) {
- const cmdsAccepted = wsvc.AcceptStop | wsvc.AcceptShutdown
- changes <- wsvc.Status{State: wsvc.StartPending}
- if err := ws.i.Start(); err != nil {
- ws.setError(err)
- return true, 1
- }
- changes <- wsvc.Status{State: wsvc.Running, Accepts: cmdsAccepted}
- for {
- var c wsvc.ChangeRequest
- select {
- case c = <-r:
- case <-ws.ctx.Done():
- c = wsvc.ChangeRequest{Cmd: wsvc.Stop}
- }
- switch c.Cmd {
- case wsvc.Interrogate:
- changes <- c.CurrentStatus
- case wsvc.Stop, wsvc.Shutdown:
- changes <- wsvc.Status{State: wsvc.StopPending}
- err := ws.i.Stop()
- if err != nil {
- ws.setError(err)
- return true, 2
- }
- return false, 0
- default:
- }
- }
- }
|