ominfra 0.0.0.dev128__py3-none-any.whl → 0.0.0.dev130__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/deploy/_executor.py +39 -0
- ominfra/pyremote/_runcommands.py +39 -0
- ominfra/scripts/journald2aws.py +136 -0
- ominfra/scripts/supervisor.py +1732 -880
- ominfra/supervisor/dispatchers.py +10 -9
- ominfra/supervisor/dispatchersimpl.py +20 -18
- ominfra/supervisor/groups.py +16 -1
- ominfra/supervisor/groupsimpl.py +2 -2
- ominfra/supervisor/http.py +130 -0
- ominfra/supervisor/inject.py +61 -17
- ominfra/supervisor/io.py +97 -0
- ominfra/supervisor/main.py +5 -6
- ominfra/supervisor/processimpl.py +10 -18
- ominfra/supervisor/signals.py +66 -0
- ominfra/supervisor/spawningimpl.py +11 -11
- ominfra/supervisor/supervisor.py +70 -137
- ominfra/supervisor/types.py +21 -58
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/RECORD +23 -22
- ominfra/supervisor/context.py +0 -80
- ominfra/supervisor/poller.py +0 -240
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev128.dist-info → ominfra-0.0.0.dev130.dist-info}/top_level.txt +0 -0
@@ -22,10 +22,10 @@ from .spawning import ProcessSpawnError
|
|
22
22
|
from .spawning import ProcessSpawning
|
23
23
|
from .states import ProcessState
|
24
24
|
from .states import SupervisorState
|
25
|
-
from .types import InputDispatcher
|
26
25
|
from .types import Process
|
27
26
|
from .types import ProcessGroup
|
28
|
-
from .types import
|
27
|
+
from .types import ProcessInputDispatcher
|
28
|
+
from .types import SupervisorStateManager
|
29
29
|
from .utils.os import decode_wait_status
|
30
30
|
from .utils.ostypes import Pid
|
31
31
|
from .utils.ostypes import Rc
|
@@ -47,7 +47,7 @@ class ProcessImpl(Process):
|
|
47
47
|
config: ProcessConfig,
|
48
48
|
group: ProcessGroup,
|
49
49
|
*,
|
50
|
-
|
50
|
+
supervisor_states: SupervisorStateManager,
|
51
51
|
event_callbacks: EventCallbacks,
|
52
52
|
process_spawning_factory: ProcessSpawningFactory,
|
53
53
|
) -> None:
|
@@ -56,7 +56,7 @@ class ProcessImpl(Process):
|
|
56
56
|
self._config = config
|
57
57
|
self._group = group
|
58
58
|
|
59
|
-
self.
|
59
|
+
self._supervisor_states = supervisor_states
|
60
60
|
self._event_callbacks = event_callbacks
|
61
61
|
|
62
62
|
self._spawning = process_spawning_factory(self)
|
@@ -87,7 +87,7 @@ class ProcessImpl(Process):
|
|
87
87
|
#
|
88
88
|
|
89
89
|
def __repr__(self) -> str:
|
90
|
-
return f'<Subprocess at {id(self)} with name {self._config.name} in state {self.
|
90
|
+
return f'<Subprocess at {id(self)} with name {self._config.name} in state {self._state.name}>'
|
91
91
|
|
92
92
|
#
|
93
93
|
|
@@ -109,10 +109,6 @@ class ProcessImpl(Process):
|
|
109
109
|
|
110
110
|
#
|
111
111
|
|
112
|
-
@property
|
113
|
-
def context(self) -> ServerContext:
|
114
|
-
return self._context
|
115
|
-
|
116
112
|
@property
|
117
113
|
def state(self) -> ProcessState:
|
118
114
|
return self._state
|
@@ -175,7 +171,7 @@ class ProcessImpl(Process):
|
|
175
171
|
if stdin_fd is None:
|
176
172
|
raise OSError(errno.EPIPE, 'Process has no stdin channel')
|
177
173
|
|
178
|
-
dispatcher = check_isinstance(self._dispatchers[stdin_fd],
|
174
|
+
dispatcher = check_isinstance(self._dispatchers[stdin_fd], ProcessInputDispatcher)
|
179
175
|
if dispatcher.closed:
|
180
176
|
raise OSError(errno.EPIPE, "Process' stdin channel is closed")
|
181
177
|
|
@@ -382,6 +378,7 @@ class ProcessImpl(Process):
|
|
382
378
|
self._last_stop = now
|
383
379
|
|
384
380
|
if now > self._last_start:
|
381
|
+
log.info(f'{now - self._last_start=}') # noqa
|
385
382
|
too_quickly = now - self._last_start < self._config.startsecs
|
386
383
|
else:
|
387
384
|
too_quickly = False
|
@@ -445,18 +442,13 @@ class ProcessImpl(Process):
|
|
445
442
|
self._pipes = ProcessPipes()
|
446
443
|
self._dispatchers = Dispatchers([])
|
447
444
|
|
448
|
-
def get_state(self) -> ProcessState:
|
449
|
-
return self._state
|
450
|
-
|
451
445
|
def transition(self) -> None:
|
452
446
|
now = time.time()
|
453
447
|
state = self._state
|
454
448
|
|
455
449
|
self._check_and_adjust_for_system_clock_rollback(now)
|
456
450
|
|
457
|
-
|
458
|
-
|
459
|
-
if self.context.state > SupervisorState.RESTARTING:
|
451
|
+
if self._supervisor_states.state > SupervisorState.RESTARTING:
|
460
452
|
# dont start any processes if supervisor is shutting down
|
461
453
|
if state == ProcessState.EXITED:
|
462
454
|
if self._config.autorestart:
|
@@ -487,14 +479,14 @@ class ProcessImpl(Process):
|
|
487
479
|
self.check_in_state(ProcessState.STARTING)
|
488
480
|
self.change_state(ProcessState.RUNNING)
|
489
481
|
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (startsecs)' % self._config.startsecs) # noqa
|
490
|
-
|
482
|
+
log.info('success: %s %s', self.name, msg)
|
491
483
|
|
492
484
|
if state == ProcessState.BACKOFF:
|
493
485
|
if self._backoff > self._config.startretries:
|
494
486
|
# BACKOFF -> FATAL if the proc has exceeded its number of retries
|
495
487
|
self.give_up()
|
496
488
|
msg = ('entered FATAL state, too many start retries too quickly')
|
497
|
-
|
489
|
+
log.info('gave up: %s %s', self.name, msg)
|
498
490
|
|
499
491
|
elif state == ProcessState.STOPPING:
|
500
492
|
time_left = self._delay - now
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import signal
|
3
|
+
|
4
|
+
from omlish.lite.logs import log
|
5
|
+
|
6
|
+
from .groups import ProcessGroupManager
|
7
|
+
from .states import SupervisorState
|
8
|
+
from .types import ProcessOutputDispatcher
|
9
|
+
from .types import SupervisorStateManager
|
10
|
+
from .utils.signals import SignalReceiver
|
11
|
+
from .utils.signals import sig_name
|
12
|
+
|
13
|
+
|
14
|
+
class SignalHandler:
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
*,
|
18
|
+
states: SupervisorStateManager,
|
19
|
+
signal_receiver: SignalReceiver,
|
20
|
+
process_groups: ProcessGroupManager,
|
21
|
+
) -> None:
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
self._states = states
|
25
|
+
self._signal_receiver = signal_receiver
|
26
|
+
self._process_groups = process_groups
|
27
|
+
|
28
|
+
def set_signals(self) -> None:
|
29
|
+
self._signal_receiver.install(
|
30
|
+
signal.SIGTERM,
|
31
|
+
signal.SIGINT,
|
32
|
+
signal.SIGQUIT,
|
33
|
+
signal.SIGHUP,
|
34
|
+
signal.SIGCHLD,
|
35
|
+
signal.SIGUSR2,
|
36
|
+
)
|
37
|
+
|
38
|
+
def handle_signals(self) -> None:
|
39
|
+
sig = self._signal_receiver.get_signal()
|
40
|
+
if not sig:
|
41
|
+
return
|
42
|
+
|
43
|
+
if sig in (signal.SIGTERM, signal.SIGINT, signal.SIGQUIT):
|
44
|
+
log.warning('received %s indicating exit request', sig_name(sig))
|
45
|
+
self._states.set_state(SupervisorState.SHUTDOWN)
|
46
|
+
|
47
|
+
elif sig == signal.SIGHUP:
|
48
|
+
if self._states.state == SupervisorState.SHUTDOWN:
|
49
|
+
log.warning('ignored %s indicating restart request (shutdown in progress)', sig_name(sig)) # noqa
|
50
|
+
else:
|
51
|
+
log.warning('received %s indicating restart request', sig_name(sig)) # noqa
|
52
|
+
self._states.set_state(SupervisorState.RESTARTING)
|
53
|
+
|
54
|
+
elif sig == signal.SIGCHLD:
|
55
|
+
log.debug('received %s indicating a child quit', sig_name(sig))
|
56
|
+
|
57
|
+
elif sig == signal.SIGUSR2:
|
58
|
+
log.info('received %s indicating log reopen request', sig_name(sig))
|
59
|
+
|
60
|
+
for p in self._process_groups.all_processes():
|
61
|
+
for d in p.get_dispatchers():
|
62
|
+
if isinstance(d, ProcessOutputDispatcher):
|
63
|
+
d.reopen_logs()
|
64
|
+
|
65
|
+
else:
|
66
|
+
log.debug('received %s indicating nothing', sig_name(sig))
|
@@ -7,6 +7,7 @@ import typing as ta
|
|
7
7
|
|
8
8
|
from omlish.lite.check import check_isinstance
|
9
9
|
from omlish.lite.check import check_not_none
|
10
|
+
from omlish.lite.fdio.handlers import FdIoHandler
|
10
11
|
from omlish.lite.typing import Func3
|
11
12
|
|
12
13
|
from .configs import ProcessConfig
|
@@ -29,11 +30,10 @@ from .process import PidHistory
|
|
29
30
|
from .spawning import ProcessSpawnError
|
30
31
|
from .spawning import ProcessSpawning
|
31
32
|
from .spawning import SpawnedProcess
|
32
|
-
from .types import Dispatcher
|
33
|
-
from .types import InputDispatcher
|
34
|
-
from .types import OutputDispatcher
|
35
33
|
from .types import Process
|
36
34
|
from .types import ProcessGroup
|
35
|
+
from .types import ProcessInputDispatcher
|
36
|
+
from .types import ProcessOutputDispatcher
|
37
37
|
from .utils.diag import compact_traceback
|
38
38
|
from .utils.fds import close_fd
|
39
39
|
from .utils.fs import get_path
|
@@ -44,11 +44,11 @@ from .utils.ostypes import Rc
|
|
44
44
|
from .utils.strings import as_bytes
|
45
45
|
|
46
46
|
|
47
|
-
class
|
47
|
+
class ProcessOutputDispatcherFactory(Func3[Process, ta.Type[ProcessCommunicationEvent], Fd, ProcessOutputDispatcher]):
|
48
48
|
pass
|
49
49
|
|
50
50
|
|
51
|
-
class
|
51
|
+
class ProcessInputDispatcherFactory(Func3[Process, str, Fd, ProcessInputDispatcher]):
|
52
52
|
pass
|
53
53
|
|
54
54
|
|
@@ -66,8 +66,8 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
66
66
|
server_config: ServerConfig,
|
67
67
|
pid_history: PidHistory,
|
68
68
|
|
69
|
-
output_dispatcher_factory:
|
70
|
-
input_dispatcher_factory:
|
69
|
+
output_dispatcher_factory: ProcessOutputDispatcherFactory,
|
70
|
+
input_dispatcher_factory: ProcessInputDispatcherFactory,
|
71
71
|
|
72
72
|
inherited_fds: ta.Optional[InheritedFds] = None,
|
73
73
|
) -> None:
|
@@ -200,28 +200,28 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
200
200
|
return exe, args
|
201
201
|
|
202
202
|
def _make_dispatchers(self, pipes: ProcessPipes) -> Dispatchers:
|
203
|
-
dispatchers: ta.List[
|
203
|
+
dispatchers: ta.List[FdIoHandler] = []
|
204
204
|
|
205
205
|
if pipes.stdout is not None:
|
206
206
|
dispatchers.append(check_isinstance(self._output_dispatcher_factory(
|
207
207
|
self.process,
|
208
208
|
ProcessCommunicationStdoutEvent,
|
209
209
|
pipes.stdout,
|
210
|
-
),
|
210
|
+
), ProcessOutputDispatcher))
|
211
211
|
|
212
212
|
if pipes.stderr is not None:
|
213
213
|
dispatchers.append(check_isinstance(self._output_dispatcher_factory(
|
214
214
|
self.process,
|
215
215
|
ProcessCommunicationStderrEvent,
|
216
216
|
pipes.stderr,
|
217
|
-
),
|
217
|
+
), ProcessOutputDispatcher))
|
218
218
|
|
219
219
|
if pipes.stdin is not None:
|
220
220
|
dispatchers.append(check_isinstance(self._input_dispatcher_factory(
|
221
221
|
self.process,
|
222
222
|
'stdin',
|
223
223
|
pipes.stdin,
|
224
|
-
),
|
224
|
+
), ProcessInputDispatcher))
|
225
225
|
|
226
226
|
return Dispatchers(dispatchers)
|
227
227
|
|
ominfra/supervisor/supervisor.py
CHANGED
@@ -1,40 +1,38 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
import
|
2
|
+
import errno
|
3
|
+
import os
|
3
4
|
import time
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
from omlish.lite.check import check_isinstance
|
7
|
-
from omlish.lite.
|
8
|
+
from omlish.lite.fdio.pollers import FdIoPoller
|
8
9
|
from omlish.lite.logs import log
|
9
10
|
from omlish.lite.typing import Func1
|
10
11
|
|
11
12
|
from .configs import ProcessGroupConfig
|
12
|
-
from .
|
13
|
-
from .dispatchers import Dispatchers
|
13
|
+
from .configs import ServerConfig
|
14
14
|
from .events import TICK_EVENTS
|
15
15
|
from .events import EventCallbacks
|
16
16
|
from .events import SupervisorRunningEvent
|
17
17
|
from .events import SupervisorStoppingEvent
|
18
18
|
from .groups import ProcessGroup
|
19
19
|
from .groups import ProcessGroupManager
|
20
|
-
from .
|
20
|
+
from .io import IoManager
|
21
21
|
from .process import PidHistory
|
22
22
|
from .setup import SupervisorSetup
|
23
|
+
from .signals import SignalHandler
|
23
24
|
from .states import SupervisorState
|
24
|
-
from .types import
|
25
|
+
from .types import ExitNow
|
25
26
|
from .types import Process
|
27
|
+
from .types import SupervisorStateManager
|
26
28
|
from .utils.os import decode_wait_status
|
27
|
-
from .utils.
|
28
|
-
from .utils.
|
29
|
+
from .utils.ostypes import Pid
|
30
|
+
from .utils.ostypes import Rc
|
29
31
|
|
30
32
|
|
31
33
|
##
|
32
34
|
|
33
35
|
|
34
|
-
class ExitNow(Exception): # noqa
|
35
|
-
pass
|
36
|
-
|
37
|
-
|
38
36
|
def timeslice(period: int, when: float) -> int:
|
39
37
|
return int(when - (when % period))
|
40
38
|
|
@@ -42,59 +40,18 @@ def timeslice(period: int, when: float) -> int:
|
|
42
40
|
##
|
43
41
|
|
44
42
|
|
45
|
-
class
|
46
|
-
def __init__(
|
47
|
-
self,
|
48
|
-
*,
|
49
|
-
context: ServerContextImpl,
|
50
|
-
signal_receiver: SignalReceiver,
|
51
|
-
process_groups: ProcessGroupManager,
|
52
|
-
) -> None:
|
43
|
+
class SupervisorStateManagerImpl(SupervisorStateManager):
|
44
|
+
def __init__(self) -> None:
|
53
45
|
super().__init__()
|
54
46
|
|
55
|
-
self.
|
56
|
-
self._signal_receiver = signal_receiver
|
57
|
-
self._process_groups = process_groups
|
58
|
-
|
59
|
-
def set_signals(self) -> None:
|
60
|
-
self._signal_receiver.install(
|
61
|
-
signal.SIGTERM,
|
62
|
-
signal.SIGINT,
|
63
|
-
signal.SIGQUIT,
|
64
|
-
signal.SIGHUP,
|
65
|
-
signal.SIGCHLD,
|
66
|
-
signal.SIGUSR2,
|
67
|
-
)
|
68
|
-
|
69
|
-
def handle_signals(self) -> None:
|
70
|
-
sig = self._signal_receiver.get_signal()
|
71
|
-
if not sig:
|
72
|
-
return
|
73
|
-
|
74
|
-
if sig in (signal.SIGTERM, signal.SIGINT, signal.SIGQUIT):
|
75
|
-
log.warning('received %s indicating exit request', sig_name(sig))
|
76
|
-
self._context.set_state(SupervisorState.SHUTDOWN)
|
77
|
-
|
78
|
-
elif sig == signal.SIGHUP:
|
79
|
-
if self._context.state == SupervisorState.SHUTDOWN:
|
80
|
-
log.warning('ignored %s indicating restart request (shutdown in progress)', sig_name(sig)) # noqa
|
81
|
-
else:
|
82
|
-
log.warning('received %s indicating restart request', sig_name(sig)) # noqa
|
83
|
-
self._context.set_state(SupervisorState.RESTARTING)
|
47
|
+
self._state: SupervisorState = SupervisorState.RUNNING
|
84
48
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
elif sig == signal.SIGUSR2:
|
89
|
-
log.info('received %s indicating log reopen request', sig_name(sig))
|
90
|
-
|
91
|
-
for p in self._process_groups.all_processes():
|
92
|
-
for d in p.get_dispatchers():
|
93
|
-
if isinstance(d, OutputDispatcher):
|
94
|
-
d.reopen_logs()
|
49
|
+
@property
|
50
|
+
def state(self) -> SupervisorState:
|
51
|
+
return self._state
|
95
52
|
|
96
|
-
|
97
|
-
|
53
|
+
def set_state(self, state: SupervisorState) -> None:
|
54
|
+
self._state = state
|
98
55
|
|
99
56
|
|
100
57
|
##
|
@@ -108,18 +65,20 @@ class Supervisor:
|
|
108
65
|
def __init__(
|
109
66
|
self,
|
110
67
|
*,
|
111
|
-
|
112
|
-
poller:
|
68
|
+
config: ServerConfig,
|
69
|
+
poller: FdIoPoller,
|
113
70
|
process_groups: ProcessGroupManager,
|
114
71
|
signal_handler: SignalHandler,
|
115
72
|
event_callbacks: EventCallbacks,
|
116
73
|
process_group_factory: ProcessGroupFactory,
|
117
74
|
pid_history: PidHistory,
|
118
75
|
setup: SupervisorSetup,
|
76
|
+
states: SupervisorStateManager,
|
77
|
+
io: IoManager,
|
119
78
|
) -> None:
|
120
79
|
super().__init__()
|
121
80
|
|
122
|
-
self.
|
81
|
+
self._config = config
|
123
82
|
self._poller = poller
|
124
83
|
self._process_groups = process_groups
|
125
84
|
self._signal_handler = signal_handler
|
@@ -127,6 +86,8 @@ class Supervisor:
|
|
127
86
|
self._process_group_factory = process_group_factory
|
128
87
|
self._pid_history = pid_history
|
129
88
|
self._setup = setup
|
89
|
+
self._states = states
|
90
|
+
self._io = io
|
130
91
|
|
131
92
|
self._ticks: ta.Dict[int, float] = {}
|
132
93
|
self._stop_groups: ta.Optional[ta.List[ProcessGroup]] = None # list used for priority ordered shutdown
|
@@ -136,11 +97,8 @@ class Supervisor:
|
|
136
97
|
#
|
137
98
|
|
138
99
|
@property
|
139
|
-
def
|
140
|
-
return self.
|
141
|
-
|
142
|
-
def get_state(self) -> SupervisorState:
|
143
|
-
return self._context.state
|
100
|
+
def state(self) -> SupervisorState:
|
101
|
+
return self._states.state
|
144
102
|
|
145
103
|
#
|
146
104
|
|
@@ -181,7 +139,7 @@ class Supervisor:
|
|
181
139
|
log.info('waiting for %s to die', namestr)
|
182
140
|
self._last_shutdown_report = now
|
183
141
|
for proc in unstopped:
|
184
|
-
log.debug('%s state: %s', proc.config.name, proc.
|
142
|
+
log.debug('%s state: %s', proc.config.name, proc.state.name)
|
185
143
|
|
186
144
|
return unstopped
|
187
145
|
|
@@ -205,7 +163,7 @@ class Supervisor:
|
|
205
163
|
self._event_callbacks.clear()
|
206
164
|
|
207
165
|
try:
|
208
|
-
for config in self.
|
166
|
+
for config in self._config.groups or []:
|
209
167
|
self.add_process_group(config)
|
210
168
|
|
211
169
|
self._signal_handler.set_signals()
|
@@ -224,12 +182,14 @@ class Supervisor:
|
|
224
182
|
#
|
225
183
|
|
226
184
|
def _run_once(self) -> None:
|
185
|
+
now = time.time()
|
227
186
|
self._poll()
|
187
|
+
log.info(f'Poll took {time.time() - now}') # noqa
|
228
188
|
self._reap()
|
229
189
|
self._signal_handler.handle_signals()
|
230
190
|
self._tick()
|
231
191
|
|
232
|
-
if self.
|
192
|
+
if self._states.state < SupervisorState.RUNNING:
|
233
193
|
self._ordered_stop_groups_phase_2()
|
234
194
|
|
235
195
|
def _ordered_stop_groups_phase_1(self) -> None:
|
@@ -248,20 +208,11 @@ class Supervisor:
|
|
248
208
|
# down, so push it back on to the end of the stop group queue
|
249
209
|
self._stop_groups.append(group)
|
250
210
|
|
251
|
-
def get_dispatchers(self) -> Dispatchers:
|
252
|
-
return Dispatchers(
|
253
|
-
d
|
254
|
-
for p in self._process_groups.all_processes()
|
255
|
-
for d in p.get_dispatchers()
|
256
|
-
)
|
257
|
-
|
258
211
|
def _poll(self) -> None:
|
259
|
-
dispatchers = self.get_dispatchers()
|
260
|
-
|
261
212
|
sorted_groups = list(self._process_groups)
|
262
213
|
sorted_groups.sort()
|
263
214
|
|
264
|
-
if self.
|
215
|
+
if self._states.state < SupervisorState.RUNNING:
|
265
216
|
if not self._stopping:
|
266
217
|
# first time, set the stopping flag, do a notification and set stop_groups
|
267
218
|
self._stopping = True
|
@@ -274,54 +225,7 @@ class Supervisor:
|
|
274
225
|
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
275
226
|
raise ExitNow
|
276
227
|
|
277
|
-
|
278
|
-
if dispatcher.readable():
|
279
|
-
self._poller.register_readable(fd)
|
280
|
-
if dispatcher.writable():
|
281
|
-
self._poller.register_writable(fd)
|
282
|
-
|
283
|
-
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
284
|
-
r, w = self._poller.poll(timeout)
|
285
|
-
|
286
|
-
for fd in r:
|
287
|
-
if fd in dispatchers:
|
288
|
-
try:
|
289
|
-
dispatcher = dispatchers[fd]
|
290
|
-
log.debug('read event caused by %r', dispatcher)
|
291
|
-
dispatcher.handle_read_event()
|
292
|
-
if not dispatcher.readable():
|
293
|
-
self._poller.unregister_readable(fd)
|
294
|
-
except ExitNow:
|
295
|
-
raise
|
296
|
-
except Exception: # noqa
|
297
|
-
dispatchers[fd].handle_error()
|
298
|
-
else:
|
299
|
-
# if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
|
300
|
-
# time, which may cause 100% cpu usage
|
301
|
-
log.debug('unexpected read event from fd %r', fd)
|
302
|
-
try:
|
303
|
-
self._poller.unregister_readable(fd)
|
304
|
-
except Exception: # noqa
|
305
|
-
pass
|
306
|
-
|
307
|
-
for fd in w:
|
308
|
-
if fd in dispatchers:
|
309
|
-
try:
|
310
|
-
dispatcher = dispatchers[fd]
|
311
|
-
log.debug('write event caused by %r', dispatcher)
|
312
|
-
dispatcher.handle_write_event()
|
313
|
-
if not dispatcher.writable():
|
314
|
-
self._poller.unregister_writable(fd)
|
315
|
-
except ExitNow:
|
316
|
-
raise
|
317
|
-
except Exception: # noqa
|
318
|
-
dispatchers[fd].handle_error()
|
319
|
-
else:
|
320
|
-
log.debug('unexpected write event from fd %r', fd)
|
321
|
-
try:
|
322
|
-
self._poller.unregister_writable(fd)
|
323
|
-
except Exception: # noqa
|
324
|
-
pass
|
228
|
+
self._io.poll()
|
325
229
|
|
326
230
|
for group in sorted_groups:
|
327
231
|
for process in group:
|
@@ -331,17 +235,18 @@ class Supervisor:
|
|
331
235
|
if depth >= 100:
|
332
236
|
return
|
333
237
|
|
334
|
-
|
335
|
-
|
238
|
+
wp = waitpid()
|
239
|
+
log.info(f'Waited pid: {wp}') # noqa
|
240
|
+
if wp is None or not wp.pid:
|
336
241
|
return
|
337
242
|
|
338
|
-
process = self._pid_history.get(pid, None)
|
243
|
+
process = self._pid_history.get(wp.pid, None)
|
339
244
|
if process is None:
|
340
|
-
_, msg = decode_wait_status(
|
341
|
-
log.info('reaped unknown pid %s (%s)', pid, msg)
|
245
|
+
_, msg = decode_wait_status(wp.sts)
|
246
|
+
log.info('reaped unknown pid %s (%s)', wp.pid, msg)
|
342
247
|
else:
|
343
|
-
process.finish(
|
344
|
-
del self._pid_history[pid]
|
248
|
+
process.finish(wp.sts)
|
249
|
+
del self._pid_history[wp.pid]
|
345
250
|
|
346
251
|
if not once:
|
347
252
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
@@ -366,3 +271,31 @@ class Supervisor:
|
|
366
271
|
if this_tick != last_tick:
|
367
272
|
self._ticks[period] = this_tick
|
368
273
|
self._event_callbacks.notify(event(this_tick, self))
|
274
|
+
|
275
|
+
|
276
|
+
##
|
277
|
+
|
278
|
+
|
279
|
+
class WaitedPid(ta.NamedTuple):
|
280
|
+
pid: Pid
|
281
|
+
sts: Rc
|
282
|
+
|
283
|
+
|
284
|
+
def waitpid() -> ta.Optional[WaitedPid]:
|
285
|
+
# Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
|
286
|
+
# still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
|
287
|
+
# waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
|
288
|
+
# normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
|
289
|
+
# call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
|
290
|
+
# lying around.
|
291
|
+
try:
|
292
|
+
pid, sts = os.waitpid(-1, os.WNOHANG)
|
293
|
+
except OSError as exc:
|
294
|
+
code = exc.args[0]
|
295
|
+
if code not in (errno.ECHILD, errno.EINTR):
|
296
|
+
log.critical('waitpid error %r; a process may not be cleaned up properly', code)
|
297
|
+
if code == errno.EINTR:
|
298
|
+
log.debug('EINTR during reap')
|
299
|
+
return None
|
300
|
+
else:
|
301
|
+
return WaitedPid(pid, sts) # type: ignore
|