ominfra 0.0.0.dev127__py3-none-any.whl → 0.0.0.dev128__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/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
|