ominfra 0.0.0.dev125__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 +1825 -1217
- ominfra/supervisor/collections.py +52 -0
- ominfra/supervisor/context.py +2 -336
- ominfra/supervisor/datatypes.py +1 -63
- ominfra/supervisor/dispatchers.py +22 -338
- ominfra/supervisor/dispatchersimpl.py +342 -0
- ominfra/supervisor/groups.py +33 -110
- ominfra/supervisor/groupsimpl.py +86 -0
- ominfra/supervisor/inject.py +45 -13
- 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} +99 -317
- 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 +54 -78
- ominfra/supervisor/types.py +122 -39
- ominfra/supervisor/users.py +64 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/RECORD +34 -23
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev125.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,40 +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
|
11
|
+
from omlish.lite.typing import Func1
|
12
12
|
|
13
13
|
from .configs import ProcessConfig
|
14
|
-
from .context import check_execv_args
|
15
|
-
from .context import close_child_pipes
|
16
|
-
from .context import close_parent_pipes
|
17
|
-
from .context import drop_privileges
|
18
|
-
from .context import make_pipes
|
19
14
|
from .datatypes import RestartUnconditionally
|
20
|
-
from .dispatchers import
|
21
|
-
from .dispatchers import InputDispatcher
|
22
|
-
from .dispatchers import OutputDispatcher
|
15
|
+
from .dispatchers import Dispatchers
|
23
16
|
from .events import PROCESS_STATE_EVENT_MAP
|
24
17
|
from .events import EventCallbacks
|
25
|
-
from .
|
26
|
-
from .
|
27
|
-
from .
|
28
|
-
from .exceptions import BadCommandError
|
29
|
-
from .exceptions import ProcessError
|
18
|
+
from .pipes import ProcessPipes
|
19
|
+
from .pipes import close_parent_pipes
|
20
|
+
from .processes import ProcessStateError
|
30
21
|
from .signals import sig_name
|
22
|
+
from .spawning import ProcessSpawnError
|
23
|
+
from .spawning import ProcessSpawning
|
31
24
|
from .states import ProcessState
|
32
25
|
from .states import SupervisorState
|
26
|
+
from .types import InputDispatcher
|
33
27
|
from .types import Process
|
34
28
|
from .types import ProcessGroup
|
35
29
|
from .types import ServerContext
|
36
|
-
from .utils import as_bytes
|
37
30
|
from .utils import as_string
|
38
|
-
from .utils import close_fd
|
39
|
-
from .utils import compact_traceback
|
40
31
|
from .utils import decode_wait_status
|
41
|
-
from .utils import get_path
|
42
|
-
from .utils import real_exit
|
43
32
|
|
44
33
|
|
45
|
-
|
34
|
+
class ProcessSpawningFactory(Func1[Process, ProcessSpawning]):
|
35
|
+
pass
|
46
36
|
|
47
37
|
|
48
38
|
##
|
@@ -58,19 +48,22 @@ class ProcessImpl(Process):
|
|
58
48
|
*,
|
59
49
|
context: ServerContext,
|
60
50
|
event_callbacks: EventCallbacks,
|
61
|
-
|
62
|
-
inherited_fds: ta.Optional[InheritedFds] = None,
|
51
|
+
process_spawning_factory: ProcessSpawningFactory,
|
63
52
|
) -> None:
|
64
53
|
super().__init__()
|
65
54
|
|
66
55
|
self._config = config
|
67
56
|
self._group = group
|
57
|
+
|
68
58
|
self._context = context
|
69
59
|
self._event_callbacks = event_callbacks
|
70
|
-
self._inherited_fds = InheritedFds(frozenset(inherited_fds or []))
|
71
60
|
|
72
|
-
self.
|
73
|
-
|
61
|
+
self._spawning = process_spawning_factory(self)
|
62
|
+
|
63
|
+
#
|
64
|
+
|
65
|
+
self._dispatchers = Dispatchers([])
|
66
|
+
self._pipes = ProcessPipes()
|
74
67
|
|
75
68
|
self._state = ProcessState.STOPPED
|
76
69
|
self._pid = 0 # 0 when not running
|
@@ -90,17 +83,30 @@ class ProcessImpl(Process):
|
|
90
83
|
self._exitstatus: ta.Optional[int] = None # status attached to dead process by finish()
|
91
84
|
self._spawn_err: ta.Optional[str] = None # error message attached by spawn() if any
|
92
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
|
+
|
93
93
|
@property
|
94
|
-
def
|
95
|
-
return self.
|
94
|
+
def name(self) -> str:
|
95
|
+
return self._config.name
|
96
|
+
|
97
|
+
@property
|
98
|
+
def config(self) -> ProcessConfig:
|
99
|
+
return self._config
|
96
100
|
|
97
101
|
@property
|
98
102
|
def group(self) -> ProcessGroup:
|
99
103
|
return self._group
|
100
104
|
|
101
105
|
@property
|
102
|
-
def
|
103
|
-
return self.
|
106
|
+
def pid(self) -> int:
|
107
|
+
return self._pid
|
108
|
+
|
109
|
+
#
|
104
110
|
|
105
111
|
@property
|
106
112
|
def context(self) -> ServerContext:
|
@@ -114,33 +120,59 @@ class ProcessImpl(Process):
|
|
114
120
|
def backoff(self) -> int:
|
115
121
|
return self._backoff
|
116
122
|
|
117
|
-
|
118
|
-
|
123
|
+
#
|
124
|
+
|
125
|
+
def spawn(self) -> ta.Optional[int]:
|
126
|
+
process_name = as_string(self._config.name)
|
127
|
+
|
128
|
+
if self.pid:
|
129
|
+
log.warning('process \'%s\' already running', process_name)
|
130
|
+
return None
|
119
131
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
138
170
|
|
139
171
|
def write(self, chars: ta.Union[bytes, str]) -> None:
|
140
172
|
if not self.pid or self._killing:
|
141
173
|
raise OSError(errno.EPIPE, 'Process already closed')
|
142
174
|
|
143
|
-
stdin_fd = self._pipes
|
175
|
+
stdin_fd = self._pipes.stdin
|
144
176
|
if stdin_fd is None:
|
145
177
|
raise OSError(errno.EPIPE, 'Process has no stdin channel')
|
146
178
|
|
@@ -151,51 +183,7 @@ class ProcessImpl(Process):
|
|
151
183
|
dispatcher.write(chars)
|
152
184
|
dispatcher.flush() # this must raise EPIPE if the pipe is closed
|
153
185
|
|
154
|
-
|
155
|
-
"""
|
156
|
-
Internal: turn a program name into a file name, using $PATH, make sure it exists / is executable, raising a
|
157
|
-
ProcessError if not
|
158
|
-
"""
|
159
|
-
|
160
|
-
try:
|
161
|
-
commandargs = shlex.split(self._config.command)
|
162
|
-
except ValueError as e:
|
163
|
-
raise BadCommandError(f"can't parse command {self._config.command!r}: {e}") # noqa
|
164
|
-
|
165
|
-
if commandargs:
|
166
|
-
program = commandargs[0]
|
167
|
-
else:
|
168
|
-
raise BadCommandError('command is empty')
|
169
|
-
|
170
|
-
if '/' in program:
|
171
|
-
filename = program
|
172
|
-
try:
|
173
|
-
st = os.stat(filename)
|
174
|
-
except OSError:
|
175
|
-
st = None
|
176
|
-
|
177
|
-
else:
|
178
|
-
path = get_path()
|
179
|
-
found = None
|
180
|
-
st = None
|
181
|
-
for dir in path: # noqa
|
182
|
-
found = os.path.join(dir, program)
|
183
|
-
try:
|
184
|
-
st = os.stat(found)
|
185
|
-
except OSError:
|
186
|
-
pass
|
187
|
-
else:
|
188
|
-
break
|
189
|
-
if st is None:
|
190
|
-
filename = program
|
191
|
-
else:
|
192
|
-
filename = found # type: ignore
|
193
|
-
|
194
|
-
# check_execv_args will raise a ProcessError if the execv args are bogus, we break it out into a separate
|
195
|
-
# options method call here only to service unit tests
|
196
|
-
check_execv_args(filename, commandargs, st)
|
197
|
-
|
198
|
-
return filename, commandargs
|
186
|
+
#
|
199
187
|
|
200
188
|
def change_state(self, new_state: ProcessState, expected: bool = True) -> bool:
|
201
189
|
old_state = self._state
|
@@ -215,209 +203,14 @@ class ProcessImpl(Process):
|
|
215
203
|
|
216
204
|
return True
|
217
205
|
|
218
|
-
def
|
206
|
+
def check_in_state(self, *states: ProcessState) -> None:
|
219
207
|
if self._state not in states:
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
raise RuntimeError('Assertion failed for %s: %s not in %s' % (process_name, current_state, allowable_states)) # noqa
|
224
|
-
|
225
|
-
def _record_spawn_err(self, msg: str) -> None:
|
226
|
-
self._spawn_err = msg
|
227
|
-
log.info('_spawn_err: %s', msg)
|
228
|
-
|
229
|
-
def spawn(self) -> ta.Optional[int]:
|
230
|
-
process_name = as_string(self._config.name)
|
231
|
-
|
232
|
-
if self.pid:
|
233
|
-
log.warning('process \'%s\' already running', process_name)
|
234
|
-
return None
|
235
|
-
|
236
|
-
self._killing = False
|
237
|
-
self._spawn_err = None
|
238
|
-
self._exitstatus = None
|
239
|
-
self._system_stop = False
|
240
|
-
self._administrative_stop = False
|
241
|
-
|
242
|
-
self._last_start = time.time()
|
243
|
-
|
244
|
-
self._check_in_state(
|
245
|
-
ProcessState.EXITED,
|
246
|
-
ProcessState.FATAL,
|
247
|
-
ProcessState.BACKOFF,
|
248
|
-
ProcessState.STOPPED,
|
249
|
-
)
|
250
|
-
|
251
|
-
self.change_state(ProcessState.STARTING)
|
252
|
-
|
253
|
-
try:
|
254
|
-
filename, argv = self._get_execv_args()
|
255
|
-
except ProcessError as what:
|
256
|
-
self._record_spawn_err(what.args[0])
|
257
|
-
self._check_in_state(ProcessState.STARTING)
|
258
|
-
self.change_state(ProcessState.BACKOFF)
|
259
|
-
return None
|
260
|
-
|
261
|
-
try:
|
262
|
-
self._dispatchers, self._pipes = self._make_dispatchers() # type: ignore
|
263
|
-
except OSError as why:
|
264
|
-
code = why.args[0]
|
265
|
-
if code == errno.EMFILE:
|
266
|
-
# too many file descriptors open
|
267
|
-
msg = f"too many open files to spawn '{process_name}'"
|
268
|
-
else:
|
269
|
-
msg = f"unknown error making dispatchers for '{process_name}': {errno.errorcode.get(code, code)}"
|
270
|
-
self._record_spawn_err(msg)
|
271
|
-
self._check_in_state(ProcessState.STARTING)
|
272
|
-
self.change_state(ProcessState.BACKOFF)
|
273
|
-
return None
|
274
|
-
|
275
|
-
try:
|
276
|
-
pid = os.fork()
|
277
|
-
except OSError as why:
|
278
|
-
code = why.args[0]
|
279
|
-
if code == errno.EAGAIN:
|
280
|
-
# process table full
|
281
|
-
msg = f'Too many processes in process table to spawn \'{process_name}\''
|
282
|
-
else:
|
283
|
-
msg = f'unknown error during fork for \'{process_name}\': {errno.errorcode.get(code, code)}'
|
284
|
-
self._record_spawn_err(msg)
|
285
|
-
self._check_in_state(ProcessState.STARTING)
|
286
|
-
self.change_state(ProcessState.BACKOFF)
|
287
|
-
close_parent_pipes(self._pipes)
|
288
|
-
close_child_pipes(self._pipes)
|
289
|
-
return None
|
290
|
-
|
291
|
-
if pid != 0:
|
292
|
-
return self._spawn_as_parent(pid)
|
293
|
-
|
294
|
-
else:
|
295
|
-
self._spawn_as_child(filename, argv)
|
296
|
-
return None
|
297
|
-
|
298
|
-
def _make_dispatchers(self) -> ta.Tuple[ta.Mapping[int, Dispatcher], ta.Mapping[str, int]]:
|
299
|
-
use_stderr = not self._config.redirect_stderr
|
300
|
-
|
301
|
-
p = make_pipes(use_stderr)
|
302
|
-
stdout_fd, stderr_fd, stdin_fd = p['stdout'], p['stderr'], p['stdin']
|
303
|
-
|
304
|
-
dispatchers: ta.Dict[int, Dispatcher] = {}
|
305
|
-
|
306
|
-
dispatcher_kw = dict(
|
307
|
-
event_callbacks=self._event_callbacks,
|
308
|
-
)
|
309
|
-
|
310
|
-
etype: ta.Type[ProcessCommunicationEvent]
|
311
|
-
if stdout_fd is not None:
|
312
|
-
etype = ProcessCommunicationStdoutEvent
|
313
|
-
dispatchers[stdout_fd] = OutputDispatcher(
|
314
|
-
self,
|
315
|
-
etype,
|
316
|
-
stdout_fd,
|
317
|
-
**dispatcher_kw,
|
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)}',
|
318
211
|
)
|
319
212
|
|
320
|
-
|
321
|
-
etype = ProcessCommunicationStderrEvent
|
322
|
-
dispatchers[stderr_fd] = OutputDispatcher(
|
323
|
-
self,
|
324
|
-
etype,
|
325
|
-
stderr_fd,
|
326
|
-
**dispatcher_kw,
|
327
|
-
)
|
328
|
-
|
329
|
-
if stdin_fd is not None:
|
330
|
-
dispatchers[stdin_fd] = InputDispatcher(
|
331
|
-
self,
|
332
|
-
'stdin',
|
333
|
-
stdin_fd,
|
334
|
-
**dispatcher_kw,
|
335
|
-
)
|
336
|
-
|
337
|
-
return dispatchers, p
|
338
|
-
|
339
|
-
def _spawn_as_parent(self, pid: int) -> int:
|
340
|
-
# Parent
|
341
|
-
self._pid = pid
|
342
|
-
close_child_pipes(self._pipes)
|
343
|
-
log.info('spawned: \'%s\' with pid %s', as_string(self._config.name), pid)
|
344
|
-
self._spawn_err = None
|
345
|
-
self._delay = time.time() + self._config.startsecs
|
346
|
-
self.context.pid_history[pid] = self
|
347
|
-
return pid
|
348
|
-
|
349
|
-
def _prepare_child_fds(self) -> None:
|
350
|
-
os.dup2(self._pipes['child_stdin'], 0)
|
351
|
-
os.dup2(self._pipes['child_stdout'], 1)
|
352
|
-
if self._config.redirect_stderr:
|
353
|
-
os.dup2(self._pipes['child_stdout'], 2)
|
354
|
-
else:
|
355
|
-
os.dup2(self._pipes['child_stderr'], 2)
|
356
|
-
|
357
|
-
for i in range(3, self.context.config.minfds):
|
358
|
-
if i in self._inherited_fds:
|
359
|
-
continue
|
360
|
-
close_fd(i)
|
361
|
-
|
362
|
-
def _spawn_as_child(self, filename: str, argv: ta.Sequence[str]) -> None:
|
363
|
-
try:
|
364
|
-
# prevent child from receiving signals sent to the parent by calling os.setpgrp to create a new process
|
365
|
-
# group for the child; this prevents, for instance, the case of child processes being sent a SIGINT when
|
366
|
-
# running supervisor in foreground mode and Ctrl-C in the terminal window running supervisord is pressed.
|
367
|
-
# Presumably it also prevents HUP, etc received by supervisord from being sent to children.
|
368
|
-
os.setpgrp()
|
369
|
-
|
370
|
-
self._prepare_child_fds()
|
371
|
-
# sending to fd 2 will put this output in the stderr log
|
372
|
-
|
373
|
-
# set user
|
374
|
-
setuid_msg = self.set_uid()
|
375
|
-
if setuid_msg:
|
376
|
-
uid = self._config.uid
|
377
|
-
msg = f"couldn't setuid to {uid}: {setuid_msg}\n"
|
378
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
379
|
-
return # finally clause will exit the child process
|
380
|
-
|
381
|
-
# set environment
|
382
|
-
env = os.environ.copy()
|
383
|
-
env['SUPERVISOR_ENABLED'] = '1'
|
384
|
-
env['SUPERVISOR_PROCESS_NAME'] = self._config.name
|
385
|
-
if self._group:
|
386
|
-
env['SUPERVISOR_GROUP_NAME'] = self._group.config.name
|
387
|
-
if self._config.environment is not None:
|
388
|
-
env.update(self._config.environment)
|
389
|
-
|
390
|
-
# change directory
|
391
|
-
cwd = self._config.directory
|
392
|
-
try:
|
393
|
-
if cwd is not None:
|
394
|
-
os.chdir(os.path.expanduser(cwd))
|
395
|
-
except OSError as why:
|
396
|
-
code = errno.errorcode.get(why.args[0], why.args[0])
|
397
|
-
msg = f"couldn't chdir to {cwd}: {code}\n"
|
398
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
399
|
-
return # finally clause will exit the child process
|
400
|
-
|
401
|
-
# set umask, then execve
|
402
|
-
try:
|
403
|
-
if self._config.umask is not None:
|
404
|
-
os.umask(self._config.umask)
|
405
|
-
os.execve(filename, list(argv), env)
|
406
|
-
except OSError as why:
|
407
|
-
code = errno.errorcode.get(why.args[0], why.args[0])
|
408
|
-
msg = f"couldn't exec {argv[0]}: {code}\n"
|
409
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
410
|
-
except Exception: # noqa
|
411
|
-
(file, fun, line), t, v, tbinfo = compact_traceback()
|
412
|
-
error = f'{t}, {v}: file: {file} line: {line}'
|
413
|
-
msg = f"couldn't exec {filename}: {error}\n"
|
414
|
-
os.write(2, as_bytes('supervisor: ' + msg))
|
415
|
-
|
416
|
-
# this point should only be reached if execve failed. the finally clause will exit the child process.
|
417
|
-
|
418
|
-
finally:
|
419
|
-
os.write(2, as_bytes('supervisor: child process was not spawned\n'))
|
420
|
-
real_exit(127) # exit process with code for spawn failure
|
213
|
+
#
|
421
214
|
|
422
215
|
def _check_and_adjust_for_system_clock_rollback(self, test_time):
|
423
216
|
"""
|
@@ -463,7 +256,7 @@ class ProcessImpl(Process):
|
|
463
256
|
self._delay = 0
|
464
257
|
self._backoff = 0
|
465
258
|
self._system_stop = True
|
466
|
-
self.
|
259
|
+
self.check_in_state(ProcessState.BACKOFF)
|
467
260
|
self.change_state(ProcessState.FATAL)
|
468
261
|
|
469
262
|
def kill(self, sig: int) -> ta.Optional[str]:
|
@@ -507,7 +300,7 @@ class ProcessImpl(Process):
|
|
507
300
|
self._killing = True
|
508
301
|
self._delay = now + self._config.stopwaitsecs
|
509
302
|
# we will already be in the STOPPING state if we're doing a SIGKILL as a result of overrunning stopwaitsecs
|
510
|
-
self.
|
303
|
+
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
511
304
|
self.change_state(ProcessState.STOPPING)
|
512
305
|
|
513
306
|
pid = self.pid
|
@@ -552,7 +345,7 @@ class ProcessImpl(Process):
|
|
552
345
|
|
553
346
|
log.debug('sending %s (pid %s) sig %s', process_name, self.pid, sig_name(sig))
|
554
347
|
|
555
|
-
self.
|
348
|
+
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
556
349
|
|
557
350
|
try:
|
558
351
|
try:
|
@@ -581,7 +374,7 @@ class ProcessImpl(Process):
|
|
581
374
|
def finish(self, sts: int) -> None:
|
582
375
|
"""The process was reaped and we need to report and manage its state."""
|
583
376
|
|
584
|
-
self.drain()
|
377
|
+
self._dispatchers.drain()
|
585
378
|
|
586
379
|
es, msg = decode_wait_status(sts)
|
587
380
|
|
@@ -612,7 +405,7 @@ class ProcessImpl(Process):
|
|
612
405
|
self._exitstatus = es
|
613
406
|
|
614
407
|
fmt, args = 'stopped: %s (%s)', (process_name, msg)
|
615
|
-
self.
|
408
|
+
self.check_in_state(ProcessState.STOPPING)
|
616
409
|
self.change_state(ProcessState.STOPPED)
|
617
410
|
if exit_expected:
|
618
411
|
log.info(fmt, *args)
|
@@ -623,7 +416,7 @@ class ProcessImpl(Process):
|
|
623
416
|
# the program did not stay up long enough to make it to RUNNING implies STARTING -> BACKOFF
|
624
417
|
self._exitstatus = None
|
625
418
|
self._spawn_err = 'Exited too quickly (process log may have details)'
|
626
|
-
self.
|
419
|
+
self.check_in_state(ProcessState.STARTING)
|
627
420
|
self.change_state(ProcessState.BACKOFF)
|
628
421
|
log.warning('exited: %s (%s)', process_name, msg + '; not expected')
|
629
422
|
|
@@ -639,7 +432,7 @@ class ProcessImpl(Process):
|
|
639
432
|
if self._state == ProcessState.STARTING:
|
640
433
|
self.change_state(ProcessState.RUNNING)
|
641
434
|
|
642
|
-
self.
|
435
|
+
self.check_in_state(ProcessState.RUNNING)
|
643
436
|
|
644
437
|
if exit_expected:
|
645
438
|
# expected exit code
|
@@ -653,19 +446,8 @@ class ProcessImpl(Process):
|
|
653
446
|
|
654
447
|
self._pid = 0
|
655
448
|
close_parent_pipes(self._pipes)
|
656
|
-
self._pipes =
|
657
|
-
self._dispatchers =
|
658
|
-
|
659
|
-
def set_uid(self) -> ta.Optional[str]:
|
660
|
-
if self._config.uid is None:
|
661
|
-
return None
|
662
|
-
msg = drop_privileges(self._config.uid)
|
663
|
-
return msg
|
664
|
-
|
665
|
-
def __repr__(self) -> str:
|
666
|
-
# repr can't return anything other than a native string, but the name might be unicode - a problem on Python 2.
|
667
|
-
name = self._config.name
|
668
|
-
return f'<Subprocess at {id(self)} with name {name} in state {self.get_state().name}>'
|
449
|
+
self._pipes = ProcessPipes()
|
450
|
+
self._dispatchers = Dispatchers([])
|
669
451
|
|
670
452
|
def get_state(self) -> ProcessState:
|
671
453
|
return self._state
|
@@ -707,7 +489,7 @@ class ProcessImpl(Process):
|
|
707
489
|
# proc.config.startsecs,
|
708
490
|
self._delay = 0
|
709
491
|
self._backoff = 0
|
710
|
-
self.
|
492
|
+
self.check_in_state(ProcessState.STARTING)
|
711
493
|
self.change_state(ProcessState.RUNNING)
|
712
494
|
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (startsecs)' % self._config.startsecs) # noqa
|
713
495
|
logger.info('success: %s %s', process_name, msg)
|
@@ -727,7 +509,7 @@ class ProcessImpl(Process):
|
|
727
509
|
log.warning('killing \'%s\' (%s) with SIGKILL', process_name, self.pid)
|
728
510
|
self.kill(signal.SIGKILL)
|
729
511
|
|
730
|
-
def
|
512
|
+
def after_setuid(self) -> None:
|
731
513
|
# temporary logfiles which are erased at start time
|
732
514
|
# get_autoname = self.context.get_auto_child_log_name # noqa
|
733
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
|