ominfra 0.0.0.dev126__py3-none-any.whl → 0.0.0.dev127__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/clouds/aws/auth.py +1 -1
- ominfra/deploy/_executor.py +1 -1
- ominfra/deploy/poly/_main.py +1 -1
- ominfra/pyremote/_runcommands.py +1 -1
- ominfra/scripts/journald2aws.py +2 -2
- ominfra/scripts/supervisor.py +1796 -1218
- ominfra/supervisor/collections.py +52 -0
- ominfra/supervisor/context.py +2 -336
- ominfra/supervisor/datatypes.py +1 -63
- ominfra/supervisor/dispatchers.py +20 -324
- ominfra/supervisor/dispatchersimpl.py +342 -0
- ominfra/supervisor/groups.py +33 -111
- ominfra/supervisor/groupsimpl.py +86 -0
- ominfra/supervisor/inject.py +44 -19
- ominfra/supervisor/main.py +1 -1
- ominfra/supervisor/pipes.py +83 -0
- ominfra/supervisor/poller.py +6 -3
- ominfra/supervisor/privileges.py +65 -0
- ominfra/supervisor/processes.py +18 -0
- ominfra/supervisor/{process.py → processesimpl.py} +96 -330
- ominfra/supervisor/setup.py +38 -0
- ominfra/supervisor/setupimpl.py +261 -0
- ominfra/supervisor/signals.py +24 -16
- ominfra/supervisor/spawning.py +31 -0
- ominfra/supervisor/spawningimpl.py +347 -0
- ominfra/supervisor/supervisor.py +52 -77
- ominfra/supervisor/types.py +101 -45
- ominfra/supervisor/users.py +64 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/RECORD +34 -23
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import errno
|
3
3
|
import os.path
|
4
|
-
import shlex
|
5
4
|
import signal
|
6
5
|
import time
|
7
6
|
import traceback
|
@@ -9,47 +8,31 @@ import typing as ta
|
|
9
8
|
|
10
9
|
from omlish.lite.check import check_isinstance
|
11
10
|
from omlish.lite.logs import log
|
12
|
-
from omlish.lite.typing import
|
11
|
+
from omlish.lite.typing import Func1
|
13
12
|
|
14
13
|
from .configs import ProcessConfig
|
15
|
-
from .context import check_execv_args
|
16
|
-
from .context import close_child_pipes
|
17
|
-
from .context import close_parent_pipes
|
18
|
-
from .context import drop_privileges
|
19
|
-
from .context import make_pipes
|
20
14
|
from .datatypes import RestartUnconditionally
|
15
|
+
from .dispatchers import Dispatchers
|
21
16
|
from .events import PROCESS_STATE_EVENT_MAP
|
22
17
|
from .events import EventCallbacks
|
23
|
-
from .
|
24
|
-
from .
|
25
|
-
from .
|
26
|
-
from .exceptions import BadCommandError
|
27
|
-
from .exceptions import ProcessError
|
18
|
+
from .pipes import ProcessPipes
|
19
|
+
from .pipes import close_parent_pipes
|
20
|
+
from .processes import ProcessStateError
|
28
21
|
from .signals import sig_name
|
22
|
+
from .spawning import ProcessSpawnError
|
23
|
+
from .spawning import ProcessSpawning
|
29
24
|
from .states import ProcessState
|
30
25
|
from .states import SupervisorState
|
31
|
-
from .types import Dispatcher
|
32
26
|
from .types import InputDispatcher
|
33
|
-
from .types import OutputDispatcher
|
34
27
|
from .types import Process
|
35
28
|
from .types import ProcessGroup
|
36
29
|
from .types import ServerContext
|
37
|
-
from .utils import as_bytes
|
38
30
|
from .utils import as_string
|
39
|
-
from .utils import close_fd
|
40
|
-
from .utils import compact_traceback
|
41
31
|
from .utils import decode_wait_status
|
42
|
-
from .utils import get_path
|
43
|
-
from .utils import real_exit
|
44
32
|
|
45
33
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# (process: Process, event_type: ta.Type[ProcessCommunicationEvent], fd: int)
|
50
|
-
InputDispatcherFactory = ta.NewType('InputDispatcherFactory', Func[InputDispatcher])
|
51
|
-
|
52
|
-
InheritedFds = ta.NewType('InheritedFds', ta.FrozenSet[int])
|
34
|
+
class ProcessSpawningFactory(Func1[Process, ProcessSpawning]):
|
35
|
+
pass
|
53
36
|
|
54
37
|
|
55
38
|
##
|
@@ -65,12 +48,7 @@ class ProcessImpl(Process):
|
|
65
48
|
*,
|
66
49
|
context: ServerContext,
|
67
50
|
event_callbacks: EventCallbacks,
|
68
|
-
|
69
|
-
output_dispatcher_factory: OutputDispatcherFactory,
|
70
|
-
input_dispatcher_factory: InputDispatcherFactory,
|
71
|
-
|
72
|
-
inherited_fds: ta.Optional[InheritedFds] = None,
|
73
|
-
|
51
|
+
process_spawning_factory: ProcessSpawningFactory,
|
74
52
|
) -> None:
|
75
53
|
super().__init__()
|
76
54
|
|
@@ -80,13 +58,12 @@ class ProcessImpl(Process):
|
|
80
58
|
self._context = context
|
81
59
|
self._event_callbacks = event_callbacks
|
82
60
|
|
83
|
-
self.
|
84
|
-
self._input_dispatcher_factory = input_dispatcher_factory
|
61
|
+
self._spawning = process_spawning_factory(self)
|
85
62
|
|
86
|
-
|
63
|
+
#
|
87
64
|
|
88
|
-
self._dispatchers
|
89
|
-
self._pipes
|
65
|
+
self._dispatchers = Dispatchers([])
|
66
|
+
self._pipes = ProcessPipes()
|
90
67
|
|
91
68
|
self._state = ProcessState.STOPPED
|
92
69
|
self._pid = 0 # 0 when not running
|
@@ -106,17 +83,30 @@ class ProcessImpl(Process):
|
|
106
83
|
self._exitstatus: ta.Optional[int] = None # status attached to dead process by finish()
|
107
84
|
self._spawn_err: ta.Optional[str] = None # error message attached by spawn() if any
|
108
85
|
|
86
|
+
#
|
87
|
+
|
88
|
+
def __repr__(self) -> str:
|
89
|
+
return f'<Subprocess at {id(self)} with name {self._config.name} in state {self.get_state().name}>'
|
90
|
+
|
91
|
+
#
|
92
|
+
|
109
93
|
@property
|
110
|
-
def
|
111
|
-
return self.
|
94
|
+
def name(self) -> str:
|
95
|
+
return self._config.name
|
96
|
+
|
97
|
+
@property
|
98
|
+
def config(self) -> ProcessConfig:
|
99
|
+
return self._config
|
112
100
|
|
113
101
|
@property
|
114
102
|
def group(self) -> ProcessGroup:
|
115
103
|
return self._group
|
116
104
|
|
117
105
|
@property
|
118
|
-
def
|
119
|
-
return self.
|
106
|
+
def pid(self) -> int:
|
107
|
+
return self._pid
|
108
|
+
|
109
|
+
#
|
120
110
|
|
121
111
|
@property
|
122
112
|
def context(self) -> ServerContext:
|
@@ -130,33 +120,59 @@ class ProcessImpl(Process):
|
|
130
120
|
def backoff(self) -> int:
|
131
121
|
return self._backoff
|
132
122
|
|
133
|
-
|
134
|
-
|
123
|
+
#
|
124
|
+
|
125
|
+
def spawn(self) -> ta.Optional[int]:
|
126
|
+
process_name = as_string(self._config.name)
|
135
127
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
128
|
+
if self.pid:
|
129
|
+
log.warning('process \'%s\' already running', process_name)
|
130
|
+
return None
|
131
|
+
|
132
|
+
self.check_in_state(
|
133
|
+
ProcessState.EXITED,
|
134
|
+
ProcessState.FATAL,
|
135
|
+
ProcessState.BACKOFF,
|
136
|
+
ProcessState.STOPPED,
|
137
|
+
)
|
138
|
+
|
139
|
+
self._killing = False
|
140
|
+
self._spawn_err = None
|
141
|
+
self._exitstatus = None
|
142
|
+
self._system_stop = False
|
143
|
+
self._administrative_stop = False
|
144
|
+
|
145
|
+
self._last_start = time.time()
|
146
|
+
|
147
|
+
self.change_state(ProcessState.STARTING)
|
148
|
+
|
149
|
+
try:
|
150
|
+
sp = self._spawning.spawn()
|
151
|
+
except ProcessSpawnError as err:
|
152
|
+
log.exception('Spawn error')
|
153
|
+
self._spawn_err = err.args[0]
|
154
|
+
self.check_in_state(ProcessState.STARTING)
|
155
|
+
self.change_state(ProcessState.BACKOFF)
|
156
|
+
return None
|
157
|
+
|
158
|
+
log.info("Spawned: '%s' with pid %s", self.name, sp.pid)
|
159
|
+
|
160
|
+
self._pid = sp.pid
|
161
|
+
self._pipes = sp.pipes
|
162
|
+
self._dispatchers = sp.dispatchers
|
163
|
+
|
164
|
+
self._delay = time.time() + self.config.startsecs
|
165
|
+
|
166
|
+
return sp.pid
|
167
|
+
|
168
|
+
def get_dispatchers(self) -> Dispatchers:
|
169
|
+
return self._dispatchers
|
154
170
|
|
155
171
|
def write(self, chars: ta.Union[bytes, str]) -> None:
|
156
172
|
if not self.pid or self._killing:
|
157
173
|
raise OSError(errno.EPIPE, 'Process already closed')
|
158
174
|
|
159
|
-
stdin_fd = self._pipes
|
175
|
+
stdin_fd = self._pipes.stdin
|
160
176
|
if stdin_fd is None:
|
161
177
|
raise OSError(errno.EPIPE, 'Process has no stdin channel')
|
162
178
|
|
@@ -167,51 +183,7 @@ class ProcessImpl(Process):
|
|
167
183
|
dispatcher.write(chars)
|
168
184
|
dispatcher.flush() # this must raise EPIPE if the pipe is closed
|
169
185
|
|
170
|
-
|
171
|
-
"""
|
172
|
-
Internal: turn a program name into a file name, using $PATH, make sure it exists / is executable, raising a
|
173
|
-
ProcessError if not
|
174
|
-
"""
|
175
|
-
|
176
|
-
try:
|
177
|
-
commandargs = shlex.split(self._config.command)
|
178
|
-
except ValueError as e:
|
179
|
-
raise BadCommandError(f"can't parse command {self._config.command!r}: {e}") # noqa
|
180
|
-
|
181
|
-
if commandargs:
|
182
|
-
program = commandargs[0]
|
183
|
-
else:
|
184
|
-
raise BadCommandError('command is empty')
|
185
|
-
|
186
|
-
if '/' in program:
|
187
|
-
filename = program
|
188
|
-
try:
|
189
|
-
st = os.stat(filename)
|
190
|
-
except OSError:
|
191
|
-
st = None
|
192
|
-
|
193
|
-
else:
|
194
|
-
path = get_path()
|
195
|
-
found = None
|
196
|
-
st = None
|
197
|
-
for dir in path: # noqa
|
198
|
-
found = os.path.join(dir, program)
|
199
|
-
try:
|
200
|
-
st = os.stat(found)
|
201
|
-
except OSError:
|
202
|
-
pass
|
203
|
-
else:
|
204
|
-
break
|
205
|
-
if st is None:
|
206
|
-
filename = program
|
207
|
-
else:
|
208
|
-
filename = found # type: ignore
|
209
|
-
|
210
|
-
# check_execv_args will raise a ProcessError if the execv args are bogus, we break it out into a separate
|
211
|
-
# options method call here only to service unit tests
|
212
|
-
check_execv_args(filename, commandargs, st)
|
213
|
-
|
214
|
-
return filename, commandargs
|
186
|
+
#
|
215
187
|
|
216
188
|
def change_state(self, new_state: ProcessState, expected: bool = True) -> bool:
|
217
189
|
old_state = self._state
|
@@ -231,209 +203,14 @@ class ProcessImpl(Process):
|
|
231
203
|
|
232
204
|
return True
|
233
205
|
|
234
|
-
def
|
206
|
+
def check_in_state(self, *states: ProcessState) -> None:
|
235
207
|
if self._state not in states:
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
def _record_spawn_err(self, msg: str) -> None:
|
242
|
-
self._spawn_err = msg
|
243
|
-
log.info('_spawn_err: %s', msg)
|
244
|
-
|
245
|
-
def spawn(self) -> ta.Optional[int]:
|
246
|
-
process_name = as_string(self._config.name)
|
247
|
-
|
248
|
-
if self.pid:
|
249
|
-
log.warning('process \'%s\' already running', process_name)
|
250
|
-
return None
|
251
|
-
|
252
|
-
self._killing = False
|
253
|
-
self._spawn_err = None
|
254
|
-
self._exitstatus = None
|
255
|
-
self._system_stop = False
|
256
|
-
self._administrative_stop = False
|
257
|
-
|
258
|
-
self._last_start = time.time()
|
259
|
-
|
260
|
-
self._check_in_state(
|
261
|
-
ProcessState.EXITED,
|
262
|
-
ProcessState.FATAL,
|
263
|
-
ProcessState.BACKOFF,
|
264
|
-
ProcessState.STOPPED,
|
265
|
-
)
|
266
|
-
|
267
|
-
self.change_state(ProcessState.STARTING)
|
268
|
-
|
269
|
-
try:
|
270
|
-
filename, argv = self._get_execv_args()
|
271
|
-
except ProcessError as what:
|
272
|
-
self._record_spawn_err(what.args[0])
|
273
|
-
self._check_in_state(ProcessState.STARTING)
|
274
|
-
self.change_state(ProcessState.BACKOFF)
|
275
|
-
return None
|
276
|
-
|
277
|
-
try:
|
278
|
-
self._dispatchers, self._pipes = self._make_dispatchers() # type: ignore
|
279
|
-
except OSError as why:
|
280
|
-
code = why.args[0]
|
281
|
-
if code == errno.EMFILE:
|
282
|
-
# too many file descriptors open
|
283
|
-
msg = f"too many open files to spawn '{process_name}'"
|
284
|
-
else:
|
285
|
-
msg = f"unknown error making dispatchers for '{process_name}': {errno.errorcode.get(code, code)}"
|
286
|
-
self._record_spawn_err(msg)
|
287
|
-
self._check_in_state(ProcessState.STARTING)
|
288
|
-
self.change_state(ProcessState.BACKOFF)
|
289
|
-
return None
|
290
|
-
|
291
|
-
try:
|
292
|
-
pid = os.fork()
|
293
|
-
except OSError as why:
|
294
|
-
code = why.args[0]
|
295
|
-
if code == errno.EAGAIN:
|
296
|
-
# process table full
|
297
|
-
msg = f'Too many processes in process table to spawn \'{process_name}\''
|
298
|
-
else:
|
299
|
-
msg = f'unknown error during fork for \'{process_name}\': {errno.errorcode.get(code, code)}'
|
300
|
-
self._record_spawn_err(msg)
|
301
|
-
self._check_in_state(ProcessState.STARTING)
|
302
|
-
self.change_state(ProcessState.BACKOFF)
|
303
|
-
close_parent_pipes(self._pipes)
|
304
|
-
close_child_pipes(self._pipes)
|
305
|
-
return None
|
306
|
-
|
307
|
-
if pid != 0:
|
308
|
-
return self._spawn_as_parent(pid)
|
309
|
-
|
310
|
-
else:
|
311
|
-
self._spawn_as_child(filename, argv)
|
312
|
-
return None
|
313
|
-
|
314
|
-
def _make_dispatchers(self) -> ta.Tuple[ta.Mapping[int, Dispatcher], ta.Mapping[str, int]]:
|
315
|
-
use_stderr = not self._config.redirect_stderr
|
316
|
-
|
317
|
-
p = make_pipes(use_stderr)
|
318
|
-
stdout_fd, stderr_fd, stdin_fd = p['stdout'], p['stderr'], p['stdin']
|
319
|
-
|
320
|
-
dispatchers: ta.Dict[int, Dispatcher] = {}
|
321
|
-
|
322
|
-
dispatcher_kw = dict(
|
323
|
-
event_callbacks=self._event_callbacks,
|
324
|
-
)
|
325
|
-
|
326
|
-
etype: ta.Type[ProcessCommunicationEvent]
|
327
|
-
if stdout_fd is not None:
|
328
|
-
etype = ProcessCommunicationStdoutEvent
|
329
|
-
dispatchers[stdout_fd] = check_isinstance(self._output_dispatcher_factory(
|
330
|
-
self,
|
331
|
-
etype,
|
332
|
-
stdout_fd,
|
333
|
-
**dispatcher_kw,
|
334
|
-
), OutputDispatcher)
|
335
|
-
|
336
|
-
if stderr_fd is not None:
|
337
|
-
etype = ProcessCommunicationStderrEvent
|
338
|
-
dispatchers[stderr_fd] = check_isinstance(self._output_dispatcher_factory(
|
339
|
-
self,
|
340
|
-
etype,
|
341
|
-
stderr_fd,
|
342
|
-
**dispatcher_kw,
|
343
|
-
), OutputDispatcher)
|
344
|
-
|
345
|
-
if stdin_fd is not None:
|
346
|
-
dispatchers[stdin_fd] = check_isinstance(self._input_dispatcher_factory(
|
347
|
-
self,
|
348
|
-
'stdin',
|
349
|
-
stdin_fd,
|
350
|
-
**dispatcher_kw,
|
351
|
-
), InputDispatcher)
|
352
|
-
|
353
|
-
return dispatchers, p
|
354
|
-
|
355
|
-
def _spawn_as_parent(self, pid: int) -> int:
|
356
|
-
# Parent
|
357
|
-
self._pid = pid
|
358
|
-
close_child_pipes(self._pipes)
|
359
|
-
log.info('spawned: \'%s\' with pid %s', as_string(self._config.name), pid)
|
360
|
-
self._spawn_err = None
|
361
|
-
self._delay = time.time() + self._config.startsecs
|
362
|
-
self.context.pid_history[pid] = self
|
363
|
-
return pid
|
364
|
-
|
365
|
-
def _prepare_child_fds(self) -> None:
|
366
|
-
os.dup2(self._pipes['child_stdin'], 0)
|
367
|
-
os.dup2(self._pipes['child_stdout'], 1)
|
368
|
-
if self._config.redirect_stderr:
|
369
|
-
os.dup2(self._pipes['child_stdout'], 2)
|
370
|
-
else:
|
371
|
-
os.dup2(self._pipes['child_stderr'], 2)
|
208
|
+
raise ProcessStateError(
|
209
|
+
f'Check failed for {self._config.name}: '
|
210
|
+
f'{self._state.name} not in {" ".join(s.name for s in states)}',
|
211
|
+
)
|
372
212
|
|
373
|
-
|
374
|
-
if i in self._inherited_fds:
|
375
|
-
continue
|
376
|
-
close_fd(i)
|
377
|
-
|
378
|
-
def _spawn_as_child(self, filename: str, argv: ta.Sequence[str]) -> None:
|
379
|
-
try:
|
380
|
-
# prevent child from receiving signals sent to the parent by calling os.setpgrp to create a new process
|
381
|
-
# group for the child; this prevents, for instance, the case of child processes being sent a SIGINT when
|
382
|
-
# running supervisor in foreground mode and Ctrl-C in the terminal window running supervisord is pressed.
|
383
|
-
# Presumably it also prevents HUP, etc received by supervisord from being sent to children.
|
384
|
-
os.setpgrp()
|
385
|
-
|
386
|
-
self._prepare_child_fds()
|
387
|
-
# sending to fd 2 will put this output in the stderr log
|
388
|
-
|
389
|
-
# set user
|
390
|
-
setuid_msg = self.set_uid()
|
391
|
-
if setuid_msg:
|
392
|
-
uid = self._config.uid
|
393
|
-
msg = f"couldn't setuid to {uid}: {setuid_msg}\n"
|
394
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
395
|
-
return # finally clause will exit the child process
|
396
|
-
|
397
|
-
# set environment
|
398
|
-
env = os.environ.copy()
|
399
|
-
env['SUPERVISOR_ENABLED'] = '1'
|
400
|
-
env['SUPERVISOR_PROCESS_NAME'] = self._config.name
|
401
|
-
if self._group:
|
402
|
-
env['SUPERVISOR_GROUP_NAME'] = self._group.config.name
|
403
|
-
if self._config.environment is not None:
|
404
|
-
env.update(self._config.environment)
|
405
|
-
|
406
|
-
# change directory
|
407
|
-
cwd = self._config.directory
|
408
|
-
try:
|
409
|
-
if cwd is not None:
|
410
|
-
os.chdir(os.path.expanduser(cwd))
|
411
|
-
except OSError as why:
|
412
|
-
code = errno.errorcode.get(why.args[0], why.args[0])
|
413
|
-
msg = f"couldn't chdir to {cwd}: {code}\n"
|
414
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
415
|
-
return # finally clause will exit the child process
|
416
|
-
|
417
|
-
# set umask, then execve
|
418
|
-
try:
|
419
|
-
if self._config.umask is not None:
|
420
|
-
os.umask(self._config.umask)
|
421
|
-
os.execve(filename, list(argv), env)
|
422
|
-
except OSError as why:
|
423
|
-
code = errno.errorcode.get(why.args[0], why.args[0])
|
424
|
-
msg = f"couldn't exec {argv[0]}: {code}\n"
|
425
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
426
|
-
except Exception: # noqa
|
427
|
-
(file, fun, line), t, v, tbinfo = compact_traceback()
|
428
|
-
error = f'{t}, {v}: file: {file} line: {line}'
|
429
|
-
msg = f"couldn't exec {filename}: {error}\n"
|
430
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
431
|
-
|
432
|
-
# this point should only be reached if execve failed. the finally clause will exit the child process.
|
433
|
-
|
434
|
-
finally:
|
435
|
-
os.write(2, as_bytes('supervisor: child process was not spawned\n'))
|
436
|
-
real_exit(127) # exit process with code for spawn failure
|
213
|
+
#
|
437
214
|
|
438
215
|
def _check_and_adjust_for_system_clock_rollback(self, test_time):
|
439
216
|
"""
|
@@ -479,7 +256,7 @@ class ProcessImpl(Process):
|
|
479
256
|
self._delay = 0
|
480
257
|
self._backoff = 0
|
481
258
|
self._system_stop = True
|
482
|
-
self.
|
259
|
+
self.check_in_state(ProcessState.BACKOFF)
|
483
260
|
self.change_state(ProcessState.FATAL)
|
484
261
|
|
485
262
|
def kill(self, sig: int) -> ta.Optional[str]:
|
@@ -523,7 +300,7 @@ class ProcessImpl(Process):
|
|
523
300
|
self._killing = True
|
524
301
|
self._delay = now + self._config.stopwaitsecs
|
525
302
|
# we will already be in the STOPPING state if we're doing a SIGKILL as a result of overrunning stopwaitsecs
|
526
|
-
self.
|
303
|
+
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
527
304
|
self.change_state(ProcessState.STOPPING)
|
528
305
|
|
529
306
|
pid = self.pid
|
@@ -568,7 +345,7 @@ class ProcessImpl(Process):
|
|
568
345
|
|
569
346
|
log.debug('sending %s (pid %s) sig %s', process_name, self.pid, sig_name(sig))
|
570
347
|
|
571
|
-
self.
|
348
|
+
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
572
349
|
|
573
350
|
try:
|
574
351
|
try:
|
@@ -597,7 +374,7 @@ class ProcessImpl(Process):
|
|
597
374
|
def finish(self, sts: int) -> None:
|
598
375
|
"""The process was reaped and we need to report and manage its state."""
|
599
376
|
|
600
|
-
self.drain()
|
377
|
+
self._dispatchers.drain()
|
601
378
|
|
602
379
|
es, msg = decode_wait_status(sts)
|
603
380
|
|
@@ -628,7 +405,7 @@ class ProcessImpl(Process):
|
|
628
405
|
self._exitstatus = es
|
629
406
|
|
630
407
|
fmt, args = 'stopped: %s (%s)', (process_name, msg)
|
631
|
-
self.
|
408
|
+
self.check_in_state(ProcessState.STOPPING)
|
632
409
|
self.change_state(ProcessState.STOPPED)
|
633
410
|
if exit_expected:
|
634
411
|
log.info(fmt, *args)
|
@@ -639,7 +416,7 @@ class ProcessImpl(Process):
|
|
639
416
|
# the program did not stay up long enough to make it to RUNNING implies STARTING -> BACKOFF
|
640
417
|
self._exitstatus = None
|
641
418
|
self._spawn_err = 'Exited too quickly (process log may have details)'
|
642
|
-
self.
|
419
|
+
self.check_in_state(ProcessState.STARTING)
|
643
420
|
self.change_state(ProcessState.BACKOFF)
|
644
421
|
log.warning('exited: %s (%s)', process_name, msg + '; not expected')
|
645
422
|
|
@@ -655,7 +432,7 @@ class ProcessImpl(Process):
|
|
655
432
|
if self._state == ProcessState.STARTING:
|
656
433
|
self.change_state(ProcessState.RUNNING)
|
657
434
|
|
658
|
-
self.
|
435
|
+
self.check_in_state(ProcessState.RUNNING)
|
659
436
|
|
660
437
|
if exit_expected:
|
661
438
|
# expected exit code
|
@@ -669,19 +446,8 @@ class ProcessImpl(Process):
|
|
669
446
|
|
670
447
|
self._pid = 0
|
671
448
|
close_parent_pipes(self._pipes)
|
672
|
-
self._pipes =
|
673
|
-
self._dispatchers =
|
674
|
-
|
675
|
-
def set_uid(self) -> ta.Optional[str]:
|
676
|
-
if self._config.uid is None:
|
677
|
-
return None
|
678
|
-
msg = drop_privileges(self._config.uid)
|
679
|
-
return msg
|
680
|
-
|
681
|
-
def __repr__(self) -> str:
|
682
|
-
# repr can't return anything other than a native string, but the name might be unicode - a problem on Python 2.
|
683
|
-
name = self._config.name
|
684
|
-
return f'<Subprocess at {id(self)} with name {name} in state {self.get_state().name}>'
|
449
|
+
self._pipes = ProcessPipes()
|
450
|
+
self._dispatchers = Dispatchers([])
|
685
451
|
|
686
452
|
def get_state(self) -> ProcessState:
|
687
453
|
return self._state
|
@@ -723,7 +489,7 @@ class ProcessImpl(Process):
|
|
723
489
|
# proc.config.startsecs,
|
724
490
|
self._delay = 0
|
725
491
|
self._backoff = 0
|
726
|
-
self.
|
492
|
+
self.check_in_state(ProcessState.STARTING)
|
727
493
|
self.change_state(ProcessState.RUNNING)
|
728
494
|
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (startsecs)' % self._config.startsecs) # noqa
|
729
495
|
logger.info('success: %s %s', process_name, msg)
|
@@ -743,7 +509,7 @@ class ProcessImpl(Process):
|
|
743
509
|
log.warning('killing \'%s\' (%s) with SIGKILL', process_name, self.pid)
|
744
510
|
self.kill(signal.SIGKILL)
|
745
511
|
|
746
|
-
def
|
512
|
+
def after_setuid(self) -> None:
|
747
513
|
# temporary logfiles which are erased at start time
|
748
514
|
# get_autoname = self.context.get_auto_child_log_name # noqa
|
749
515
|
# sid = self.context.config.identifier # noqa
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from .users import User
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
SupervisorUser = ta.NewType('SupervisorUser', User)
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class DaemonizeListener(abc.ABC): # noqa
|
18
|
+
def before_daemonize(self) -> None: # noqa
|
19
|
+
pass
|
20
|
+
|
21
|
+
def after_daemonize(self) -> None: # noqa
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
DaemonizeListeners = ta.NewType('DaemonizeListeners', ta.Sequence[DaemonizeListener])
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
class SupervisorSetup(abc.ABC):
|
32
|
+
@abc.abstractmethod
|
33
|
+
def setup(self) -> None:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def cleanup(self) -> None:
|
38
|
+
raise NotImplementedError
|