-
Notifications
You must be signed in to change notification settings - Fork 85
/
svc_windows.go
172 lines (145 loc) · 3.66 KB
/
svc_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// +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}
}
var ctx context.Context
if s, ok := service.(Context); ok {
ctx = s.Context()
} else {
ctx = context.Background()
}
ws := &windowsService{
i: service,
isWindowsService: isWindowsService,
signals: sig,
ctx: ctx,
}
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
}
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:
}
}
}