ominfra 0.0.0.dev127__py3-none-any.whl → 0.0.0.dev129__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 +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)))
|