ominfra 0.0.0.dev127__py3-none-any.whl → 0.0.0.dev129__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/deploy/_executor.py +24 -0
- ominfra/pyremote/_runcommands.py +24 -0
- ominfra/scripts/journald2aws.py +24 -0
- ominfra/scripts/supervisor.py +1320 -1225
- ominfra/supervisor/configs.py +34 -11
- ominfra/supervisor/dispatchers.py +7 -6
- ominfra/supervisor/dispatchersimpl.py +29 -22
- ominfra/supervisor/groups.py +1 -1
- ominfra/supervisor/groupsimpl.py +2 -2
- ominfra/supervisor/inject.py +22 -17
- ominfra/supervisor/io.py +82 -0
- ominfra/supervisor/main.py +6 -7
- 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} +42 -54
- ominfra/supervisor/setup.py +1 -1
- ominfra/supervisor/setupimpl.py +4 -3
- ominfra/supervisor/signals.py +56 -50
- ominfra/supervisor/spawning.py +2 -1
- ominfra/supervisor/spawningimpl.py +24 -21
- ominfra/supervisor/supervisor.py +72 -134
- ominfra/supervisor/types.py +45 -34
- 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/signals.py +60 -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.dev129.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/RECORD +39 -33
- ominfra/supervisor/context.py +0 -84
- ominfra/supervisor/datatypes.py +0 -113
- ominfra/supervisor/utils.py +0 -206
- /ominfra/supervisor/{collections.py → utils/collections.py} +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/top_level.txt +0 -0
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,31 +25,34 @@ 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
|
32
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
|
37
|
-
from .
|
38
|
-
from .
|
39
|
-
from .utils import compact_traceback
|
40
|
-
from .utils import
|
41
|
-
from .utils import
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
from .types import ProcessInputDispatcher
|
36
|
+
from .types import ProcessOutputDispatcher
|
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
|
45
|
+
|
46
|
+
|
47
|
+
class ProcessOutputDispatcherFactory(Func3[Process, ta.Type[ProcessCommunicationEvent], Fd, ProcessOutputDispatcher]):
|
45
48
|
pass
|
46
49
|
|
47
50
|
|
48
|
-
class
|
51
|
+
class ProcessInputDispatcherFactory(Func3[Process, str, Fd, ProcessInputDispatcher]):
|
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
|
##
|
@@ -63,8 +66,8 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
63
66
|
server_config: ServerConfig,
|
64
67
|
pid_history: PidHistory,
|
65
68
|
|
66
|
-
output_dispatcher_factory:
|
67
|
-
input_dispatcher_factory:
|
69
|
+
output_dispatcher_factory: ProcessOutputDispatcherFactory,
|
70
|
+
input_dispatcher_factory: ProcessInputDispatcherFactory,
|
68
71
|
|
69
72
|
inherited_fds: ta.Optional[InheritedFds] = None,
|
70
73
|
) -> None:
|
@@ -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:
|
@@ -204,21 +207,21 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
204
207
|
self.process,
|
205
208
|
ProcessCommunicationStdoutEvent,
|
206
209
|
pipes.stdout,
|
207
|
-
),
|
210
|
+
), ProcessOutputDispatcher))
|
208
211
|
|
209
212
|
if pipes.stderr is not None:
|
210
213
|
dispatchers.append(check_isinstance(self._output_dispatcher_factory(
|
211
214
|
self.process,
|
212
215
|
ProcessCommunicationStderrEvent,
|
213
216
|
pipes.stderr,
|
214
|
-
),
|
217
|
+
), ProcessOutputDispatcher))
|
215
218
|
|
216
219
|
if pipes.stdin is not None:
|
217
220
|
dispatchers.append(check_isinstance(self._input_dispatcher_factory(
|
218
221
|
self.process,
|
219
222
|
'stdin',
|
220
223
|
pipes.stdin,
|
221
|
-
),
|
224
|
+
), ProcessInputDispatcher))
|
222
225
|
|
223
226
|
return Dispatchers(dispatchers)
|
224
227
|
|
@@ -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
@@ -1,92 +1,57 @@
|
|
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.check import check_not_none
|
8
8
|
from omlish.lite.logs import log
|
9
9
|
from omlish.lite.typing import Func1
|
10
10
|
|
11
11
|
from .configs import ProcessGroupConfig
|
12
|
-
from .
|
13
|
-
from .dispatchers import Dispatchers
|
12
|
+
from .configs import ServerConfig
|
14
13
|
from .events import TICK_EVENTS
|
15
14
|
from .events import EventCallbacks
|
16
15
|
from .events import SupervisorRunningEvent
|
17
16
|
from .events import SupervisorStoppingEvent
|
18
17
|
from .groups import ProcessGroup
|
19
18
|
from .groups import ProcessGroupManager
|
19
|
+
from .io import IoManager
|
20
20
|
from .poller import Poller
|
21
|
-
from .
|
21
|
+
from .process import PidHistory
|
22
22
|
from .setup import SupervisorSetup
|
23
|
-
from .signals import
|
24
|
-
from .signals import sig_name
|
23
|
+
from .signals import SignalHandler
|
25
24
|
from .states import SupervisorState
|
26
|
-
from .types import
|
25
|
+
from .types import ExitNow
|
27
26
|
from .types import Process
|
28
|
-
from .
|
29
|
-
from .utils import
|
30
|
-
from .utils import
|
31
|
-
from .utils import
|
27
|
+
from .types import SupervisorStateManager
|
28
|
+
from .utils.os import decode_wait_status
|
29
|
+
from .utils.ostypes import Pid
|
30
|
+
from .utils.ostypes import Rc
|
32
31
|
|
33
32
|
|
34
33
|
##
|
35
34
|
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
self,
|
40
|
-
*,
|
41
|
-
context: ServerContextImpl,
|
42
|
-
signal_receiver: SignalReceiver,
|
43
|
-
process_groups: ProcessGroupManager,
|
44
|
-
) -> None:
|
45
|
-
super().__init__()
|
36
|
+
def timeslice(period: int, when: float) -> int:
|
37
|
+
return int(when - (when % period))
|
46
38
|
|
47
|
-
self._context = context
|
48
|
-
self._signal_receiver = signal_receiver
|
49
|
-
self._process_groups = process_groups
|
50
|
-
|
51
|
-
def set_signals(self) -> None:
|
52
|
-
self._signal_receiver.install(
|
53
|
-
signal.SIGTERM,
|
54
|
-
signal.SIGINT,
|
55
|
-
signal.SIGQUIT,
|
56
|
-
signal.SIGHUP,
|
57
|
-
signal.SIGCHLD,
|
58
|
-
signal.SIGUSR2,
|
59
|
-
)
|
60
|
-
|
61
|
-
def handle_signals(self) -> None:
|
62
|
-
sig = self._signal_receiver.get_signal()
|
63
|
-
if not sig:
|
64
|
-
return
|
65
39
|
|
66
|
-
|
67
|
-
log.warning('received %s indicating exit request', sig_name(sig))
|
68
|
-
self._context.set_state(SupervisorState.SHUTDOWN)
|
40
|
+
##
|
69
41
|
|
70
|
-
elif sig == signal.SIGHUP:
|
71
|
-
if self._context.state == SupervisorState.SHUTDOWN:
|
72
|
-
log.warning('ignored %s indicating restart request (shutdown in progress)', sig_name(sig)) # noqa
|
73
|
-
else:
|
74
|
-
log.warning('received %s indicating restart request', sig_name(sig)) # noqa
|
75
|
-
self._context.set_state(SupervisorState.RESTARTING)
|
76
42
|
|
77
|
-
|
78
|
-
|
43
|
+
class SupervisorStateManagerImpl(SupervisorStateManager):
|
44
|
+
def __init__(self) -> None:
|
45
|
+
super().__init__()
|
79
46
|
|
80
|
-
|
81
|
-
log.info('received %s indicating log reopen request', sig_name(sig))
|
47
|
+
self._state: SupervisorState = SupervisorState.RUNNING
|
82
48
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
d.reopen_logs()
|
49
|
+
@property
|
50
|
+
def state(self) -> SupervisorState:
|
51
|
+
return self._state
|
87
52
|
|
88
|
-
|
89
|
-
|
53
|
+
def set_state(self, state: SupervisorState) -> None:
|
54
|
+
self._state = state
|
90
55
|
|
91
56
|
|
92
57
|
##
|
@@ -100,7 +65,7 @@ class Supervisor:
|
|
100
65
|
def __init__(
|
101
66
|
self,
|
102
67
|
*,
|
103
|
-
|
68
|
+
config: ServerConfig,
|
104
69
|
poller: Poller,
|
105
70
|
process_groups: ProcessGroupManager,
|
106
71
|
signal_handler: SignalHandler,
|
@@ -108,10 +73,12 @@ class Supervisor:
|
|
108
73
|
process_group_factory: ProcessGroupFactory,
|
109
74
|
pid_history: PidHistory,
|
110
75
|
setup: SupervisorSetup,
|
76
|
+
states: SupervisorStateManager,
|
77
|
+
io: IoManager,
|
111
78
|
) -> None:
|
112
79
|
super().__init__()
|
113
80
|
|
114
|
-
self.
|
81
|
+
self._config = config
|
115
82
|
self._poller = poller
|
116
83
|
self._process_groups = process_groups
|
117
84
|
self._signal_handler = signal_handler
|
@@ -119,6 +86,8 @@ class Supervisor:
|
|
119
86
|
self._process_group_factory = process_group_factory
|
120
87
|
self._pid_history = pid_history
|
121
88
|
self._setup = setup
|
89
|
+
self._states = states
|
90
|
+
self._io = io
|
122
91
|
|
123
92
|
self._ticks: ta.Dict[int, float] = {}
|
124
93
|
self._stop_groups: ta.Optional[ta.List[ProcessGroup]] = None # list used for priority ordered shutdown
|
@@ -128,11 +97,8 @@ class Supervisor:
|
|
128
97
|
#
|
129
98
|
|
130
99
|
@property
|
131
|
-
def
|
132
|
-
return self.
|
133
|
-
|
134
|
-
def get_state(self) -> SupervisorState:
|
135
|
-
return self._context.state
|
100
|
+
def state(self) -> SupervisorState:
|
101
|
+
return self._states.state
|
136
102
|
|
137
103
|
#
|
138
104
|
|
@@ -168,12 +134,12 @@ class Supervisor:
|
|
168
134
|
# throttle 'waiting for x to die' reports
|
169
135
|
now = time.time()
|
170
136
|
if now > (self._last_shutdown_report + 3): # every 3 secs
|
171
|
-
names = [
|
137
|
+
names = [p.config.name for p in unstopped]
|
172
138
|
namestr = ', '.join(names)
|
173
139
|
log.info('waiting for %s to die', namestr)
|
174
140
|
self._last_shutdown_report = now
|
175
141
|
for proc in unstopped:
|
176
|
-
log.debug('%s state: %s', proc.config.name, proc.
|
142
|
+
log.debug('%s state: %s', proc.config.name, proc.state.name)
|
177
143
|
|
178
144
|
return unstopped
|
179
145
|
|
@@ -197,7 +163,7 @@ class Supervisor:
|
|
197
163
|
self._event_callbacks.clear()
|
198
164
|
|
199
165
|
try:
|
200
|
-
for config in self.
|
166
|
+
for config in self._config.groups or []:
|
201
167
|
self.add_process_group(config)
|
202
168
|
|
203
169
|
self._signal_handler.set_signals()
|
@@ -221,7 +187,7 @@ class Supervisor:
|
|
221
187
|
self._signal_handler.handle_signals()
|
222
188
|
self._tick()
|
223
189
|
|
224
|
-
if self.
|
190
|
+
if self._states.state < SupervisorState.RUNNING:
|
225
191
|
self._ordered_stop_groups_phase_2()
|
226
192
|
|
227
193
|
def _ordered_stop_groups_phase_1(self) -> None:
|
@@ -240,20 +206,11 @@ class Supervisor:
|
|
240
206
|
# down, so push it back on to the end of the stop group queue
|
241
207
|
self._stop_groups.append(group)
|
242
208
|
|
243
|
-
def get_dispatchers(self) -> Dispatchers:
|
244
|
-
return Dispatchers(
|
245
|
-
d
|
246
|
-
for p in self._process_groups.all_processes()
|
247
|
-
for d in p.get_dispatchers()
|
248
|
-
)
|
249
|
-
|
250
209
|
def _poll(self) -> None:
|
251
|
-
dispatchers = self.get_dispatchers()
|
252
|
-
|
253
210
|
sorted_groups = list(self._process_groups)
|
254
211
|
sorted_groups.sort()
|
255
212
|
|
256
|
-
if self.
|
213
|
+
if self._states.state < SupervisorState.RUNNING:
|
257
214
|
if not self._stopping:
|
258
215
|
# first time, set the stopping flag, do a notification and set stop_groups
|
259
216
|
self._stopping = True
|
@@ -266,54 +223,7 @@ class Supervisor:
|
|
266
223
|
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
267
224
|
raise ExitNow
|
268
225
|
|
269
|
-
|
270
|
-
if dispatcher.readable():
|
271
|
-
self._poller.register_readable(fd)
|
272
|
-
if dispatcher.writable():
|
273
|
-
self._poller.register_writable(fd)
|
274
|
-
|
275
|
-
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
276
|
-
r, w = self._poller.poll(timeout)
|
277
|
-
|
278
|
-
for fd in r:
|
279
|
-
if fd in dispatchers:
|
280
|
-
try:
|
281
|
-
dispatcher = dispatchers[fd]
|
282
|
-
log.debug('read event caused by %r', dispatcher)
|
283
|
-
dispatcher.handle_read_event()
|
284
|
-
if not dispatcher.readable():
|
285
|
-
self._poller.unregister_readable(fd)
|
286
|
-
except ExitNow:
|
287
|
-
raise
|
288
|
-
except Exception: # noqa
|
289
|
-
dispatchers[fd].handle_error()
|
290
|
-
else:
|
291
|
-
# if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
|
292
|
-
# time, which may cause 100% cpu usage
|
293
|
-
log.debug('unexpected read event from fd %r', fd)
|
294
|
-
try:
|
295
|
-
self._poller.unregister_readable(fd)
|
296
|
-
except Exception: # noqa
|
297
|
-
pass
|
298
|
-
|
299
|
-
for fd in w:
|
300
|
-
if fd in dispatchers:
|
301
|
-
try:
|
302
|
-
dispatcher = dispatchers[fd]
|
303
|
-
log.debug('write event caused by %r', dispatcher)
|
304
|
-
dispatcher.handle_write_event()
|
305
|
-
if not dispatcher.writable():
|
306
|
-
self._poller.unregister_writable(fd)
|
307
|
-
except ExitNow:
|
308
|
-
raise
|
309
|
-
except Exception: # noqa
|
310
|
-
dispatchers[fd].handle_error()
|
311
|
-
else:
|
312
|
-
log.debug('unexpected write event from fd %r', fd)
|
313
|
-
try:
|
314
|
-
self._poller.unregister_writable(fd)
|
315
|
-
except Exception: # noqa
|
316
|
-
pass
|
226
|
+
self._io.poll()
|
317
227
|
|
318
228
|
for group in sorted_groups:
|
319
229
|
for process in group:
|
@@ -323,17 +233,17 @@ class Supervisor:
|
|
323
233
|
if depth >= 100:
|
324
234
|
return
|
325
235
|
|
326
|
-
|
327
|
-
if not pid:
|
236
|
+
wp = waitpid()
|
237
|
+
if wp is None or not wp.pid:
|
328
238
|
return
|
329
239
|
|
330
|
-
process = self._pid_history.get(pid, None)
|
240
|
+
process = self._pid_history.get(wp.pid, None)
|
331
241
|
if process is None:
|
332
|
-
_, msg = decode_wait_status(
|
333
|
-
log.info('reaped unknown pid %s (%s)', pid, msg)
|
242
|
+
_, msg = decode_wait_status(wp.sts)
|
243
|
+
log.info('reaped unknown pid %s (%s)', wp.pid, msg)
|
334
244
|
else:
|
335
|
-
process.finish(
|
336
|
-
del self._pid_history[pid]
|
245
|
+
process.finish(wp.sts)
|
246
|
+
del self._pid_history[wp.pid]
|
337
247
|
|
338
248
|
if not once:
|
339
249
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
@@ -358,3 +268,31 @@ class Supervisor:
|
|
358
268
|
if this_tick != last_tick:
|
359
269
|
self._ticks[period] = this_tick
|
360
270
|
self._event_callbacks.notify(event(this_tick, self))
|
271
|
+
|
272
|
+
|
273
|
+
##
|
274
|
+
|
275
|
+
|
276
|
+
class WaitedPid(ta.NamedTuple):
|
277
|
+
pid: Pid
|
278
|
+
sts: Rc
|
279
|
+
|
280
|
+
|
281
|
+
def waitpid() -> ta.Optional[WaitedPid]:
|
282
|
+
# Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
|
283
|
+
# still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
|
284
|
+
# waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
|
285
|
+
# normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
|
286
|
+
# call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
|
287
|
+
# lying around.
|
288
|
+
try:
|
289
|
+
pid, sts = os.waitpid(-1, os.WNOHANG)
|
290
|
+
except OSError as exc:
|
291
|
+
code = exc.args[0]
|
292
|
+
if code not in (errno.ECHILD, errno.EINTR):
|
293
|
+
log.critical('waitpid error %r; a process may not be cleaned up properly', code)
|
294
|
+
if code == errno.EINTR:
|
295
|
+
log.debug('EINTR during reap')
|
296
|
+
return None
|
297
|
+
else:
|
298
|
+
return WaitedPid(pid, sts) # type: ignore
|
ominfra/supervisor/types.py
CHANGED
@@ -3,12 +3,14 @@ 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
|
-
from .configs import ServerConfig
|
10
8
|
from .states import ProcessState
|
11
9
|
from .states import SupervisorState
|
10
|
+
from .utils.collections import KeyedCollectionAccessors
|
11
|
+
from .utils.ostypes import Fd
|
12
|
+
from .utils.ostypes import Pid
|
13
|
+
from .utils.ostypes import Rc
|
12
14
|
|
13
15
|
|
14
16
|
if ta.TYPE_CHECKING:
|
@@ -18,6 +20,10 @@ if ta.TYPE_CHECKING:
|
|
18
20
|
##
|
19
21
|
|
20
22
|
|
23
|
+
class ExitNow(Exception): # noqa
|
24
|
+
pass
|
25
|
+
|
26
|
+
|
21
27
|
ServerEpoch = ta.NewType('ServerEpoch', int)
|
22
28
|
|
23
29
|
|
@@ -41,12 +47,7 @@ class ConfigPriorityOrdered(abc.ABC):
|
|
41
47
|
##
|
42
48
|
|
43
49
|
|
44
|
-
class
|
45
|
-
@property
|
46
|
-
@abc.abstractmethod
|
47
|
-
def config(self) -> ServerConfig:
|
48
|
-
raise NotImplementedError
|
49
|
-
|
50
|
+
class SupervisorStateManager(abc.ABC):
|
50
51
|
@property
|
51
52
|
@abc.abstractmethod
|
52
53
|
def state(self) -> SupervisorState:
|
@@ -56,21 +57,11 @@ class ServerContext(abc.ABC):
|
|
56
57
|
def set_state(self, state: SupervisorState) -> None:
|
57
58
|
raise NotImplementedError
|
58
59
|
|
59
|
-
@property
|
60
|
-
@abc.abstractmethod
|
61
|
-
def pid_history(self) -> ta.Dict[int, 'Process']:
|
62
|
-
raise NotImplementedError
|
63
|
-
|
64
60
|
|
65
61
|
##
|
66
62
|
|
67
63
|
|
68
64
|
class Dispatcher(abc.ABC):
|
69
|
-
@property
|
70
|
-
@abc.abstractmethod
|
71
|
-
def process(self) -> 'Process':
|
72
|
-
raise NotImplementedError
|
73
|
-
|
74
65
|
@property
|
75
66
|
@abc.abstractmethod
|
76
67
|
def channel(self) -> str:
|
@@ -78,7 +69,7 @@ class Dispatcher(abc.ABC):
|
|
78
69
|
|
79
70
|
@property
|
80
71
|
@abc.abstractmethod
|
81
|
-
def fd(self) ->
|
72
|
+
def fd(self) -> Fd:
|
82
73
|
raise NotImplementedError
|
83
74
|
|
84
75
|
@property
|
@@ -114,8 +105,32 @@ class Dispatcher(abc.ABC):
|
|
114
105
|
def handle_write_event(self) -> None:
|
115
106
|
raise TypeError
|
116
107
|
|
108
|
+
#
|
109
|
+
|
110
|
+
def handle_connect(self) -> None:
|
111
|
+
raise TypeError
|
112
|
+
|
113
|
+
def handle_close(self) -> None:
|
114
|
+
raise TypeError
|
115
|
+
|
116
|
+
def handle_accepted(self, sock, addr) -> None:
|
117
|
+
raise TypeError
|
118
|
+
|
117
119
|
|
118
|
-
class
|
120
|
+
class HasDispatchers(abc.ABC):
|
121
|
+
@abc.abstractmethod
|
122
|
+
def get_dispatchers(self) -> 'Dispatchers':
|
123
|
+
raise NotImplementedError
|
124
|
+
|
125
|
+
|
126
|
+
class ProcessDispatcher(Dispatcher, abc.ABC):
|
127
|
+
@property
|
128
|
+
@abc.abstractmethod
|
129
|
+
def process(self) -> 'Process':
|
130
|
+
raise NotImplementedError
|
131
|
+
|
132
|
+
|
133
|
+
class ProcessOutputDispatcher(ProcessDispatcher, abc.ABC):
|
119
134
|
@abc.abstractmethod
|
120
135
|
def remove_logs(self) -> None:
|
121
136
|
raise NotImplementedError
|
@@ -125,7 +140,7 @@ class OutputDispatcher(Dispatcher, abc.ABC):
|
|
125
140
|
raise NotImplementedError
|
126
141
|
|
127
142
|
|
128
|
-
class
|
143
|
+
class ProcessInputDispatcher(ProcessDispatcher, abc.ABC):
|
129
144
|
@abc.abstractmethod
|
130
145
|
def write(self, chars: ta.Union[bytes, str]) -> None:
|
131
146
|
raise NotImplementedError
|
@@ -138,7 +153,11 @@ class InputDispatcher(Dispatcher, abc.ABC):
|
|
138
153
|
##
|
139
154
|
|
140
155
|
|
141
|
-
class Process(
|
156
|
+
class Process(
|
157
|
+
ConfigPriorityOrdered,
|
158
|
+
HasDispatchers,
|
159
|
+
abc.ABC,
|
160
|
+
):
|
142
161
|
@property
|
143
162
|
@abc.abstractmethod
|
144
163
|
def name(self) -> str:
|
@@ -156,18 +175,13 @@ class Process(ConfigPriorityOrdered, abc.ABC):
|
|
156
175
|
|
157
176
|
@property
|
158
177
|
@abc.abstractmethod
|
159
|
-
def pid(self) ->
|
178
|
+
def pid(self) -> Pid:
|
160
179
|
raise NotImplementedError
|
161
180
|
|
162
181
|
#
|
163
182
|
|
164
|
-
@property
|
165
|
-
@abc.abstractmethod
|
166
|
-
def context(self) -> ServerContext:
|
167
|
-
raise NotImplementedError
|
168
|
-
|
169
183
|
@abc.abstractmethod
|
170
|
-
def finish(self, sts:
|
184
|
+
def finish(self, sts: Rc) -> None:
|
171
185
|
raise NotImplementedError
|
172
186
|
|
173
187
|
@abc.abstractmethod
|
@@ -182,18 +196,15 @@ class Process(ConfigPriorityOrdered, abc.ABC):
|
|
182
196
|
def transition(self) -> None:
|
183
197
|
raise NotImplementedError
|
184
198
|
|
199
|
+
@property
|
185
200
|
@abc.abstractmethod
|
186
|
-
def
|
201
|
+
def state(self) -> ProcessState:
|
187
202
|
raise NotImplementedError
|
188
203
|
|
189
204
|
@abc.abstractmethod
|
190
205
|
def after_setuid(self) -> None:
|
191
206
|
raise NotImplementedError
|
192
207
|
|
193
|
-
@abc.abstractmethod
|
194
|
-
def get_dispatchers(self) -> 'Dispatchers':
|
195
|
-
raise NotImplementedError
|
196
|
-
|
197
208
|
|
198
209
|
##
|
199
210
|
|
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)))
|