ominfra 0.0.0.dev127__py3-none-any.whl → 0.0.0.dev128__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/scripts/supervisor.py +723 -731
- ominfra/supervisor/configs.py +34 -11
- ominfra/supervisor/context.py +5 -9
- ominfra/supervisor/dispatchers.py +4 -3
- ominfra/supervisor/dispatchersimpl.py +10 -9
- ominfra/supervisor/groups.py +1 -1
- ominfra/supervisor/inject.py +5 -5
- ominfra/supervisor/main.py +2 -2
- ominfra/supervisor/pipes.py +15 -13
- ominfra/supervisor/poller.py +36 -35
- ominfra/supervisor/{processes.py → process.py} +2 -1
- ominfra/supervisor/{processesimpl.py → processimpl.py} +35 -40
- ominfra/supervisor/setup.py +1 -1
- ominfra/supervisor/setupimpl.py +4 -3
- ominfra/supervisor/spawning.py +2 -1
- ominfra/supervisor/spawningimpl.py +15 -12
- ominfra/supervisor/supervisor.py +16 -8
- ominfra/supervisor/types.py +7 -9
- ominfra/supervisor/utils/__init__.py +0 -0
- ominfra/supervisor/utils/diag.py +31 -0
- ominfra/supervisor/utils/fds.py +46 -0
- ominfra/supervisor/utils/fs.py +47 -0
- ominfra/supervisor/utils/os.py +45 -0
- ominfra/supervisor/utils/ostypes.py +9 -0
- ominfra/supervisor/utils/strings.py +105 -0
- ominfra/supervisor/{users.py → utils/users.py} +11 -8
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/RECORD +34 -29
- ominfra/supervisor/datatypes.py +0 -113
- ominfra/supervisor/utils.py +0 -206
- /ominfra/supervisor/{collections.py → utils/collections.py} +0 -0
- /ominfra/supervisor/{signals.py → utils/signals.py} +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev128.dist-info}/top_level.txt +0 -0
@@ -11,14 +11,13 @@ from omlish.lite.logs import log
|
|
11
11
|
from omlish.lite.typing import Func1
|
12
12
|
|
13
13
|
from .configs import ProcessConfig
|
14
|
-
from .
|
14
|
+
from .configs import RestartUnconditionally
|
15
15
|
from .dispatchers import Dispatchers
|
16
16
|
from .events import PROCESS_STATE_EVENT_MAP
|
17
17
|
from .events import EventCallbacks
|
18
18
|
from .pipes import ProcessPipes
|
19
19
|
from .pipes import close_parent_pipes
|
20
|
-
from .
|
21
|
-
from .signals import sig_name
|
20
|
+
from .process import ProcessStateError
|
22
21
|
from .spawning import ProcessSpawnError
|
23
22
|
from .spawning import ProcessSpawning
|
24
23
|
from .states import ProcessState
|
@@ -27,8 +26,10 @@ from .types import InputDispatcher
|
|
27
26
|
from .types import Process
|
28
27
|
from .types import ProcessGroup
|
29
28
|
from .types import ServerContext
|
30
|
-
from .utils import
|
31
|
-
from .utils import
|
29
|
+
from .utils.os import decode_wait_status
|
30
|
+
from .utils.ostypes import Pid
|
31
|
+
from .utils.ostypes import Rc
|
32
|
+
from .utils.signals import sig_name
|
32
33
|
|
33
34
|
|
34
35
|
class ProcessSpawningFactory(Func1[Process, ProcessSpawning]):
|
@@ -66,7 +67,7 @@ class ProcessImpl(Process):
|
|
66
67
|
self._pipes = ProcessPipes()
|
67
68
|
|
68
69
|
self._state = ProcessState.STOPPED
|
69
|
-
self._pid = 0 # 0 when not running
|
70
|
+
self._pid = Pid(0) # 0 when not running
|
70
71
|
|
71
72
|
self._last_start = 0. # Last time the subprocess was started; 0 if never
|
72
73
|
self._last_stop = 0. # Last time the subprocess was stopped; 0 if never
|
@@ -80,7 +81,7 @@ class ProcessImpl(Process):
|
|
80
81
|
|
81
82
|
self._backoff = 0 # backoff counter (to startretries)
|
82
83
|
|
83
|
-
self._exitstatus: ta.Optional[
|
84
|
+
self._exitstatus: ta.Optional[Rc] = None # status attached to dead process by finish()
|
84
85
|
self._spawn_err: ta.Optional[str] = None # error message attached by spawn() if any
|
85
86
|
|
86
87
|
#
|
@@ -103,7 +104,7 @@ class ProcessImpl(Process):
|
|
103
104
|
return self._group
|
104
105
|
|
105
106
|
@property
|
106
|
-
def pid(self) ->
|
107
|
+
def pid(self) -> Pid:
|
107
108
|
return self._pid
|
108
109
|
|
109
110
|
#
|
@@ -122,11 +123,9 @@ class ProcessImpl(Process):
|
|
122
123
|
|
123
124
|
#
|
124
125
|
|
125
|
-
def spawn(self) -> ta.Optional[
|
126
|
-
process_name = as_string(self._config.name)
|
127
|
-
|
126
|
+
def spawn(self) -> ta.Optional[Pid]:
|
128
127
|
if self.pid:
|
129
|
-
log.warning('process \'%s\' already running',
|
128
|
+
log.warning('process \'%s\' already running', self.name)
|
130
129
|
return None
|
131
130
|
|
132
131
|
self.check_in_state(
|
@@ -249,7 +248,7 @@ class ProcessImpl(Process):
|
|
249
248
|
self._check_and_adjust_for_system_clock_rollback(now)
|
250
249
|
|
251
250
|
if now > (self._last_stop_report + 2): # every 2 seconds
|
252
|
-
log.info('waiting for %s to stop',
|
251
|
+
log.info('waiting for %s to stop', self.name)
|
253
252
|
self._last_stop_report = now
|
254
253
|
|
255
254
|
def give_up(self) -> None:
|
@@ -269,18 +268,17 @@ class ProcessImpl(Process):
|
|
269
268
|
"""
|
270
269
|
now = time.time()
|
271
270
|
|
272
|
-
process_name = as_string(self._config.name)
|
273
271
|
# If the process is in BACKOFF and we want to stop or kill it, then BACKOFF -> STOPPED. This is needed because
|
274
272
|
# if startretries is a large number and the process isn't starting successfully, the stop request would be
|
275
273
|
# blocked for a long time waiting for the retries.
|
276
274
|
if self._state == ProcessState.BACKOFF:
|
277
|
-
log.debug('Attempted to kill %s, which is in BACKOFF state.',
|
275
|
+
log.debug('Attempted to kill %s, which is in BACKOFF state.', self.name)
|
278
276
|
self.change_state(ProcessState.STOPPED)
|
279
277
|
return None
|
280
278
|
|
281
279
|
args: tuple
|
282
280
|
if not self.pid:
|
283
|
-
fmt, args = "attempted to kill %s with sig %s but it wasn't running", (
|
281
|
+
fmt, args = "attempted to kill %s with sig %s but it wasn't running", (self.name, sig_name(sig))
|
284
282
|
log.debug(fmt, *args)
|
285
283
|
return fmt % args
|
286
284
|
|
@@ -294,7 +292,7 @@ class ProcessImpl(Process):
|
|
294
292
|
if killasgroup:
|
295
293
|
as_group = 'process group '
|
296
294
|
|
297
|
-
log.debug('killing %s (pid %s) %s with signal %s',
|
295
|
+
log.debug('killing %s (pid %s) %s with signal %s', self.name, self.pid, as_group, sig_name(sig))
|
298
296
|
|
299
297
|
# RUNNING/STARTING/STOPPING -> STOPPING
|
300
298
|
self._killing = True
|
@@ -303,24 +301,24 @@ class ProcessImpl(Process):
|
|
303
301
|
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
304
302
|
self.change_state(ProcessState.STOPPING)
|
305
303
|
|
306
|
-
|
304
|
+
kpid = int(self.pid)
|
307
305
|
if killasgroup:
|
308
306
|
# send to the whole process group instead
|
309
|
-
|
307
|
+
kpid = -kpid
|
310
308
|
|
311
309
|
try:
|
312
310
|
try:
|
313
|
-
os.kill(
|
311
|
+
os.kill(kpid, sig)
|
314
312
|
except OSError as exc:
|
315
313
|
if exc.errno == errno.ESRCH:
|
316
|
-
log.debug('unable to signal %s (pid %s), it probably just exited on its own: %s',
|
314
|
+
log.debug('unable to signal %s (pid %s), it probably just exited on its own: %s', self.name, self.pid, str(exc)) # noqa
|
317
315
|
# we could change the state here but we intentionally do not. we will do it during normal SIGCHLD
|
318
316
|
# processing.
|
319
317
|
return None
|
320
318
|
raise
|
321
319
|
except Exception: # noqa
|
322
320
|
tb = traceback.format_exc()
|
323
|
-
fmt, args = 'unknown problem killing %s (%s):%s', (
|
321
|
+
fmt, args = 'unknown problem killing %s (%s):%s', (self.name, self.pid, tb)
|
324
322
|
log.critical(fmt, *args)
|
325
323
|
self.change_state(ProcessState.UNKNOWN)
|
326
324
|
self._killing = False
|
@@ -336,14 +334,13 @@ class ProcessImpl(Process):
|
|
336
334
|
Return None if the signal was sent, or an error message string if an error occurred or if the subprocess is not
|
337
335
|
running.
|
338
336
|
"""
|
339
|
-
process_name = as_string(self._config.name)
|
340
337
|
args: tuple
|
341
338
|
if not self.pid:
|
342
|
-
fmt, args = "
|
339
|
+
fmt, args = "Attempted to send %s sig %s but it wasn't running", (self.name, sig_name(sig))
|
343
340
|
log.debug(fmt, *args)
|
344
341
|
return fmt % args
|
345
342
|
|
346
|
-
log.debug('sending %s (pid %s) sig %s',
|
343
|
+
log.debug('sending %s (pid %s) sig %s', self.name, self.pid, sig_name(sig))
|
347
344
|
|
348
345
|
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
349
346
|
|
@@ -354,7 +351,7 @@ class ProcessImpl(Process):
|
|
354
351
|
if exc.errno == errno.ESRCH:
|
355
352
|
log.debug(
|
356
353
|
'unable to signal %s (pid %s), it probably just now exited on its own: %s',
|
357
|
-
|
354
|
+
self.name,
|
358
355
|
self.pid,
|
359
356
|
str(exc),
|
360
357
|
)
|
@@ -364,14 +361,14 @@ class ProcessImpl(Process):
|
|
364
361
|
raise
|
365
362
|
except Exception: # noqa
|
366
363
|
tb = traceback.format_exc()
|
367
|
-
fmt, args = 'unknown problem sending sig %s (%s):%s', (
|
364
|
+
fmt, args = 'unknown problem sending sig %s (%s):%s', (self.name, self.pid, tb)
|
368
365
|
log.critical(fmt, *args)
|
369
366
|
self.change_state(ProcessState.UNKNOWN)
|
370
367
|
return fmt % args
|
371
368
|
|
372
369
|
return None
|
373
370
|
|
374
|
-
def finish(self, sts:
|
371
|
+
def finish(self, sts: Rc) -> None:
|
375
372
|
"""The process was reaped and we need to report and manage its state."""
|
376
373
|
|
377
374
|
self._dispatchers.drain()
|
@@ -383,7 +380,6 @@ class ProcessImpl(Process):
|
|
383
380
|
self._check_and_adjust_for_system_clock_rollback(now)
|
384
381
|
|
385
382
|
self._last_stop = now
|
386
|
-
process_name = as_string(self._config.name)
|
387
383
|
|
388
384
|
if now > self._last_start:
|
389
385
|
too_quickly = now - self._last_start < self._config.startsecs
|
@@ -392,7 +388,7 @@ class ProcessImpl(Process):
|
|
392
388
|
log.warning(
|
393
389
|
"process '%s' (%s) last_start time is in the future, don't know how long process was running so "
|
394
390
|
"assuming it did not exit too quickly",
|
395
|
-
|
391
|
+
self.name,
|
396
392
|
self.pid,
|
397
393
|
)
|
398
394
|
|
@@ -402,9 +398,9 @@ class ProcessImpl(Process):
|
|
402
398
|
# likely the result of a stop request implies STOPPING -> STOPPED
|
403
399
|
self._killing = False
|
404
400
|
self._delay = 0
|
405
|
-
self._exitstatus = es
|
401
|
+
self._exitstatus = Rc(es)
|
406
402
|
|
407
|
-
fmt, args = 'stopped: %s (%s)', (
|
403
|
+
fmt, args = 'stopped: %s (%s)', (self.name, msg)
|
408
404
|
self.check_in_state(ProcessState.STOPPING)
|
409
405
|
self.change_state(ProcessState.STOPPED)
|
410
406
|
if exit_expected:
|
@@ -418,7 +414,7 @@ class ProcessImpl(Process):
|
|
418
414
|
self._spawn_err = 'Exited too quickly (process log may have details)'
|
419
415
|
self.check_in_state(ProcessState.STARTING)
|
420
416
|
self.change_state(ProcessState.BACKOFF)
|
421
|
-
log.warning('exited: %s (%s)',
|
417
|
+
log.warning('exited: %s (%s)', self.name, msg + '; not expected')
|
422
418
|
|
423
419
|
else:
|
424
420
|
# this finish was not the result of a stop request, the program was in the RUNNING state but exited implies
|
@@ -437,14 +433,14 @@ class ProcessImpl(Process):
|
|
437
433
|
if exit_expected:
|
438
434
|
# expected exit code
|
439
435
|
self.change_state(ProcessState.EXITED, expected=True)
|
440
|
-
log.info('exited: %s (%s)',
|
436
|
+
log.info('exited: %s (%s)', self.name, msg + '; expected')
|
441
437
|
else:
|
442
438
|
# unexpected exit code
|
443
439
|
self._spawn_err = f'Bad exit code {es}'
|
444
440
|
self.change_state(ProcessState.EXITED, expected=False)
|
445
|
-
log.warning('exited: %s (%s)',
|
441
|
+
log.warning('exited: %s (%s)', self.name, msg + '; not expected')
|
446
442
|
|
447
|
-
self._pid = 0
|
443
|
+
self._pid = Pid(0)
|
448
444
|
close_parent_pipes(self._pipes)
|
449
445
|
self._pipes = ProcessPipes()
|
450
446
|
self._dispatchers = Dispatchers([])
|
@@ -482,7 +478,6 @@ class ProcessImpl(Process):
|
|
482
478
|
# BACKOFF -> STARTING
|
483
479
|
self.spawn()
|
484
480
|
|
485
|
-
process_name = as_string(self._config.name)
|
486
481
|
if state == ProcessState.STARTING:
|
487
482
|
if now - self._last_start > self._config.startsecs:
|
488
483
|
# STARTING -> RUNNING if the proc has started successfully and it has stayed up for at least
|
@@ -492,21 +487,21 @@ class ProcessImpl(Process):
|
|
492
487
|
self.check_in_state(ProcessState.STARTING)
|
493
488
|
self.change_state(ProcessState.RUNNING)
|
494
489
|
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (startsecs)' % self._config.startsecs) # noqa
|
495
|
-
logger.info('success: %s %s',
|
490
|
+
logger.info('success: %s %s', self.name, msg)
|
496
491
|
|
497
492
|
if state == ProcessState.BACKOFF:
|
498
493
|
if self._backoff > self._config.startretries:
|
499
494
|
# BACKOFF -> FATAL if the proc has exceeded its number of retries
|
500
495
|
self.give_up()
|
501
496
|
msg = ('entered FATAL state, too many start retries too quickly')
|
502
|
-
logger.info('gave up: %s %s',
|
497
|
+
logger.info('gave up: %s %s', self.name, msg)
|
503
498
|
|
504
499
|
elif state == ProcessState.STOPPING:
|
505
500
|
time_left = self._delay - now
|
506
501
|
if time_left <= 0:
|
507
502
|
# kill processes which are taking too long to stop with a final sigkill. if this doesn't kill it, the
|
508
503
|
# process will be stuck in the STOPPING state forever.
|
509
|
-
log.warning('killing \'%s\' (%s) with SIGKILL',
|
504
|
+
log.warning('killing \'%s\' (%s) with SIGKILL', self.name, self.pid)
|
510
505
|
self.kill(signal.SIGKILL)
|
511
506
|
|
512
507
|
def after_setuid(self) -> None:
|
ominfra/supervisor/setup.py
CHANGED
ominfra/supervisor/setupimpl.py
CHANGED
@@ -14,8 +14,9 @@ from .setup import DaemonizeListeners
|
|
14
14
|
from .setup import SupervisorSetup
|
15
15
|
from .setup import SupervisorUser
|
16
16
|
from .types import ServerEpoch
|
17
|
-
from .utils import
|
18
|
-
from .utils import
|
17
|
+
from .utils.fs import try_unlink
|
18
|
+
from .utils.os import real_exit
|
19
|
+
from .utils.ostypes import Rc
|
19
20
|
|
20
21
|
|
21
22
|
##
|
@@ -238,7 +239,7 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
238
239
|
if pid != 0:
|
239
240
|
# Parent
|
240
241
|
log.debug('supervisord forked; parent exiting')
|
241
|
-
real_exit(0)
|
242
|
+
real_exit(Rc(0))
|
242
243
|
|
243
244
|
# Child
|
244
245
|
log.info('daemonizing the supervisord process')
|
ominfra/supervisor/spawning.py
CHANGED
@@ -5,11 +5,12 @@ import dataclasses as dc
|
|
5
5
|
from .dispatchers import Dispatchers
|
6
6
|
from .pipes import ProcessPipes
|
7
7
|
from .types import Process
|
8
|
+
from .utils.ostypes import Pid
|
8
9
|
|
9
10
|
|
10
11
|
@dc.dataclass(frozen=True)
|
11
12
|
class SpawnedProcess:
|
12
|
-
pid:
|
13
|
+
pid: Pid
|
13
14
|
pipes: ProcessPipes
|
14
15
|
dispatchers: Dispatchers
|
15
16
|
|
@@ -25,7 +25,7 @@ from .pipes import close_child_pipes
|
|
25
25
|
from .pipes import close_pipes
|
26
26
|
from .pipes import make_process_pipes
|
27
27
|
from .privileges import drop_privileges
|
28
|
-
from .
|
28
|
+
from .process import PidHistory
|
29
29
|
from .spawning import ProcessSpawnError
|
30
30
|
from .spawning import ProcessSpawning
|
31
31
|
from .spawning import SpawnedProcess
|
@@ -34,22 +34,25 @@ from .types import InputDispatcher
|
|
34
34
|
from .types import OutputDispatcher
|
35
35
|
from .types import Process
|
36
36
|
from .types import ProcessGroup
|
37
|
-
from .utils import
|
38
|
-
from .utils import close_fd
|
39
|
-
from .utils import
|
40
|
-
from .utils import
|
41
|
-
from .utils import
|
37
|
+
from .utils.diag import compact_traceback
|
38
|
+
from .utils.fds import close_fd
|
39
|
+
from .utils.fs import get_path
|
40
|
+
from .utils.os import real_exit
|
41
|
+
from .utils.ostypes import Fd
|
42
|
+
from .utils.ostypes import Pid
|
43
|
+
from .utils.ostypes import Rc
|
44
|
+
from .utils.strings import as_bytes
|
42
45
|
|
43
46
|
|
44
|
-
class OutputDispatcherFactory(Func3[Process, ta.Type[ProcessCommunicationEvent],
|
47
|
+
class OutputDispatcherFactory(Func3[Process, ta.Type[ProcessCommunicationEvent], Fd, OutputDispatcher]):
|
45
48
|
pass
|
46
49
|
|
47
50
|
|
48
|
-
class InputDispatcherFactory(Func3[Process, str,
|
51
|
+
class InputDispatcherFactory(Func3[Process, str, Fd, InputDispatcher]):
|
49
52
|
pass
|
50
53
|
|
51
54
|
|
52
|
-
InheritedFds = ta.NewType('InheritedFds', ta.FrozenSet[
|
55
|
+
InheritedFds = ta.NewType('InheritedFds', ta.FrozenSet[Fd])
|
53
56
|
|
54
57
|
|
55
58
|
##
|
@@ -120,7 +123,7 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
120
123
|
raise ProcessSpawnError(f"Unknown error making dispatchers for '{self.process.name}': {exc}") from exc
|
121
124
|
|
122
125
|
try:
|
123
|
-
pid = os.fork()
|
126
|
+
pid = Pid(os.fork())
|
124
127
|
except OSError as exc:
|
125
128
|
code = exc.args[0]
|
126
129
|
if code == errno.EAGAIN:
|
@@ -299,7 +302,7 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
299
302
|
|
300
303
|
finally:
|
301
304
|
os.write(2, as_bytes('supervisor: child process was not spawned\n'))
|
302
|
-
real_exit(127) # exit process with code for spawn failure
|
305
|
+
real_exit(Rc(127)) # exit process with code for spawn failure
|
303
306
|
|
304
307
|
raise RuntimeError('Unreachable')
|
305
308
|
|
@@ -316,7 +319,7 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
316
319
|
for i in range(3, self._server_config.minfds):
|
317
320
|
if i in self._inherited_fds:
|
318
321
|
continue
|
319
|
-
close_fd(i)
|
322
|
+
close_fd(Fd(i))
|
320
323
|
|
321
324
|
def _set_uid(self) -> ta.Optional[str]:
|
322
325
|
if self.config.uid is None:
|
ominfra/supervisor/supervisor.py
CHANGED
@@ -18,17 +18,25 @@ from .events import SupervisorStoppingEvent
|
|
18
18
|
from .groups import ProcessGroup
|
19
19
|
from .groups import ProcessGroupManager
|
20
20
|
from .poller import Poller
|
21
|
-
from .
|
21
|
+
from .process import PidHistory
|
22
22
|
from .setup import SupervisorSetup
|
23
|
-
from .signals import SignalReceiver
|
24
|
-
from .signals import sig_name
|
25
23
|
from .states import SupervisorState
|
26
24
|
from .types import OutputDispatcher
|
27
25
|
from .types import Process
|
28
|
-
from .utils import
|
29
|
-
from .utils import
|
30
|
-
from .utils import
|
31
|
-
|
26
|
+
from .utils.os import decode_wait_status
|
27
|
+
from .utils.signals import SignalReceiver
|
28
|
+
from .utils.signals import sig_name
|
29
|
+
|
30
|
+
|
31
|
+
##
|
32
|
+
|
33
|
+
|
34
|
+
class ExitNow(Exception): # noqa
|
35
|
+
pass
|
36
|
+
|
37
|
+
|
38
|
+
def timeslice(period: int, when: float) -> int:
|
39
|
+
return int(when - (when % period))
|
32
40
|
|
33
41
|
|
34
42
|
##
|
@@ -168,7 +176,7 @@ class Supervisor:
|
|
168
176
|
# throttle 'waiting for x to die' reports
|
169
177
|
now = time.time()
|
170
178
|
if now > (self._last_shutdown_report + 3): # every 3 secs
|
171
|
-
names = [
|
179
|
+
names = [p.config.name for p in unstopped]
|
172
180
|
namestr = ', '.join(names)
|
173
181
|
log.info('waiting for %s to die', namestr)
|
174
182
|
self._last_shutdown_report = now
|
ominfra/supervisor/types.py
CHANGED
@@ -3,12 +3,15 @@ import abc
|
|
3
3
|
import functools
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
-
from .collections import KeyedCollectionAccessors
|
7
6
|
from .configs import ProcessConfig
|
8
7
|
from .configs import ProcessGroupConfig
|
9
8
|
from .configs import ServerConfig
|
10
9
|
from .states import ProcessState
|
11
10
|
from .states import SupervisorState
|
11
|
+
from .utils.collections import KeyedCollectionAccessors
|
12
|
+
from .utils.ostypes import Fd
|
13
|
+
from .utils.ostypes import Pid
|
14
|
+
from .utils.ostypes import Rc
|
12
15
|
|
13
16
|
|
14
17
|
if ta.TYPE_CHECKING:
|
@@ -56,11 +59,6 @@ class ServerContext(abc.ABC):
|
|
56
59
|
def set_state(self, state: SupervisorState) -> None:
|
57
60
|
raise NotImplementedError
|
58
61
|
|
59
|
-
@property
|
60
|
-
@abc.abstractmethod
|
61
|
-
def pid_history(self) -> ta.Dict[int, 'Process']:
|
62
|
-
raise NotImplementedError
|
63
|
-
|
64
62
|
|
65
63
|
##
|
66
64
|
|
@@ -78,7 +76,7 @@ class Dispatcher(abc.ABC):
|
|
78
76
|
|
79
77
|
@property
|
80
78
|
@abc.abstractmethod
|
81
|
-
def fd(self) ->
|
79
|
+
def fd(self) -> Fd:
|
82
80
|
raise NotImplementedError
|
83
81
|
|
84
82
|
@property
|
@@ -156,7 +154,7 @@ class Process(ConfigPriorityOrdered, abc.ABC):
|
|
156
154
|
|
157
155
|
@property
|
158
156
|
@abc.abstractmethod
|
159
|
-
def pid(self) ->
|
157
|
+
def pid(self) -> Pid:
|
160
158
|
raise NotImplementedError
|
161
159
|
|
162
160
|
#
|
@@ -167,7 +165,7 @@ class Process(ConfigPriorityOrdered, abc.ABC):
|
|
167
165
|
raise NotImplementedError
|
168
166
|
|
169
167
|
@abc.abstractmethod
|
170
|
-
def finish(self, sts:
|
168
|
+
def finish(self, sts: Rc) -> None:
|
171
169
|
raise NotImplementedError
|
172
170
|
|
173
171
|
@abc.abstractmethod
|
File without changes
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import sys
|
3
|
+
import types
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
def compact_traceback() -> ta.Tuple[
|
8
|
+
ta.Tuple[str, str, int],
|
9
|
+
ta.Type[BaseException],
|
10
|
+
BaseException,
|
11
|
+
types.TracebackType,
|
12
|
+
]:
|
13
|
+
t, v, tb = sys.exc_info()
|
14
|
+
if not tb:
|
15
|
+
raise RuntimeError('No traceback')
|
16
|
+
|
17
|
+
tbinfo = []
|
18
|
+
while tb:
|
19
|
+
tbinfo.append((
|
20
|
+
tb.tb_frame.f_code.co_filename,
|
21
|
+
tb.tb_frame.f_code.co_name,
|
22
|
+
str(tb.tb_lineno),
|
23
|
+
))
|
24
|
+
tb = tb.tb_next
|
25
|
+
|
26
|
+
# just to be safe
|
27
|
+
del tb
|
28
|
+
|
29
|
+
file, function, line = tbinfo[-1]
|
30
|
+
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) # noqa
|
31
|
+
return (file, function, line), t, v, info # type: ignore
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import errno
|
3
|
+
import os
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .ostypes import Fd
|
7
|
+
|
8
|
+
|
9
|
+
class PipeFds(ta.NamedTuple):
|
10
|
+
r: Fd
|
11
|
+
w: Fd
|
12
|
+
|
13
|
+
|
14
|
+
def make_pipe() -> PipeFds:
|
15
|
+
return PipeFds(*os.pipe()) # type: ignore
|
16
|
+
|
17
|
+
|
18
|
+
def read_fd(fd: Fd) -> bytes:
|
19
|
+
try:
|
20
|
+
data = os.read(fd, 2 << 16) # 128K
|
21
|
+
except OSError as why:
|
22
|
+
if why.args[0] not in (errno.EWOULDBLOCK, errno.EBADF, errno.EINTR):
|
23
|
+
raise
|
24
|
+
data = b''
|
25
|
+
return data
|
26
|
+
|
27
|
+
|
28
|
+
def close_fd(fd: Fd) -> bool:
|
29
|
+
try:
|
30
|
+
os.close(fd)
|
31
|
+
except OSError:
|
32
|
+
return False
|
33
|
+
return True
|
34
|
+
|
35
|
+
|
36
|
+
def is_fd_open(fd: Fd) -> bool:
|
37
|
+
try:
|
38
|
+
n = os.dup(fd)
|
39
|
+
except OSError:
|
40
|
+
return False
|
41
|
+
os.close(n)
|
42
|
+
return True
|
43
|
+
|
44
|
+
|
45
|
+
def get_open_fds(limit: int) -> ta.FrozenSet[Fd]:
|
46
|
+
return frozenset(fd for i in range(limit) if is_fd_open(fd := Fd(i)))
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import os
|
3
|
+
import tempfile
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
def try_unlink(path: str) -> bool:
|
8
|
+
try:
|
9
|
+
os.unlink(path)
|
10
|
+
except OSError:
|
11
|
+
return False
|
12
|
+
return True
|
13
|
+
|
14
|
+
|
15
|
+
def mktempfile(suffix: str, prefix: str, dir: str) -> str: # noqa
|
16
|
+
fd, filename = tempfile.mkstemp(suffix, prefix, dir)
|
17
|
+
os.close(fd)
|
18
|
+
return filename
|
19
|
+
|
20
|
+
|
21
|
+
def get_path() -> ta.Sequence[str]:
|
22
|
+
"""Return a list corresponding to $PATH, or a default."""
|
23
|
+
|
24
|
+
path = ['/bin', '/usr/bin', '/usr/local/bin']
|
25
|
+
if 'PATH' in os.environ:
|
26
|
+
p = os.environ['PATH']
|
27
|
+
if p:
|
28
|
+
path = p.split(os.pathsep)
|
29
|
+
return path
|
30
|
+
|
31
|
+
|
32
|
+
def check_existing_dir(v: str) -> str:
|
33
|
+
nv = os.path.expanduser(v)
|
34
|
+
if os.path.isdir(nv):
|
35
|
+
return nv
|
36
|
+
raise ValueError(f'{v} is not an existing directory')
|
37
|
+
|
38
|
+
|
39
|
+
def check_path_with_existing_dir(v: str) -> str:
|
40
|
+
nv = os.path.expanduser(v)
|
41
|
+
dir = os.path.dirname(nv) # noqa
|
42
|
+
if not dir:
|
43
|
+
# relative pathname with no directory component
|
44
|
+
return nv
|
45
|
+
if os.path.isdir(dir):
|
46
|
+
return nv
|
47
|
+
raise ValueError(f'The directory named as part of the path {v} does not exist')
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import os
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from .ostypes import Rc
|
6
|
+
from .signals import sig_name
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
def real_exit(code: Rc) -> None:
|
13
|
+
os._exit(code) # noqa
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
def decode_wait_status(sts: int) -> ta.Tuple[Rc, str]:
|
20
|
+
"""
|
21
|
+
Decode the status returned by wait() or waitpid().
|
22
|
+
|
23
|
+
Return a tuple (exitstatus, message) where exitstatus is the exit status, or -1 if the process was killed by a
|
24
|
+
signal; and message is a message telling what happened. It is the caller's responsibility to display the message.
|
25
|
+
"""
|
26
|
+
|
27
|
+
if os.WIFEXITED(sts):
|
28
|
+
es = os.WEXITSTATUS(sts) & 0xffff
|
29
|
+
msg = f'exit status {es}'
|
30
|
+
return Rc(es), msg
|
31
|
+
|
32
|
+
elif os.WIFSIGNALED(sts):
|
33
|
+
sig = os.WTERMSIG(sts)
|
34
|
+
msg = f'terminated by {sig_name(sig)}'
|
35
|
+
if hasattr(os, 'WCOREDUMP'):
|
36
|
+
iscore = os.WCOREDUMP(sts)
|
37
|
+
else:
|
38
|
+
iscore = bool(sts & 0x80)
|
39
|
+
if iscore:
|
40
|
+
msg += ' (core dumped)'
|
41
|
+
return Rc(-1), msg
|
42
|
+
|
43
|
+
else:
|
44
|
+
msg = 'unknown termination cause 0x%04x' % sts # noqa
|
45
|
+
return Rc(-1), msg
|