ominfra 0.0.0.dev126__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/clouds/aws/auth.py +1 -1
- ominfra/deploy/_executor.py +1 -1
- ominfra/deploy/poly/_main.py +1 -1
- ominfra/pyremote/_runcommands.py +1 -1
- ominfra/scripts/journald2aws.py +2 -2
- ominfra/scripts/supervisor.py +4736 -4166
- ominfra/supervisor/configs.py +34 -11
- ominfra/supervisor/context.py +7 -345
- ominfra/supervisor/dispatchers.py +21 -324
- ominfra/supervisor/dispatchersimpl.py +343 -0
- ominfra/supervisor/groups.py +33 -111
- ominfra/supervisor/groupsimpl.py +86 -0
- ominfra/supervisor/inject.py +45 -20
- ominfra/supervisor/main.py +3 -3
- ominfra/supervisor/pipes.py +85 -0
- ominfra/supervisor/poller.py +42 -38
- ominfra/supervisor/privileges.py +65 -0
- ominfra/supervisor/process.py +6 -742
- ominfra/supervisor/processimpl.py +516 -0
- ominfra/supervisor/setup.py +38 -0
- ominfra/supervisor/setupimpl.py +262 -0
- ominfra/supervisor/spawning.py +32 -0
- ominfra/supervisor/spawningimpl.py +350 -0
- ominfra/supervisor/supervisor.py +67 -84
- ominfra/supervisor/types.py +101 -47
- ominfra/supervisor/utils/__init__.py +0 -0
- ominfra/supervisor/utils/collections.py +52 -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/utils/users.py +67 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/RECORD +41 -25
- ominfra/supervisor/datatypes.py +0 -175
- ominfra/supervisor/signals.py +0 -52
- ominfra/supervisor/utils.py +0 -206
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev128.dist-info}/top_level.txt +0 -0
ominfra/supervisor/supervisor.py
CHANGED
@@ -3,30 +3,40 @@ import signal
|
|
3
3
|
import time
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
-
from omlish.lite.cached import cached_nullary
|
7
6
|
from omlish.lite.check import check_isinstance
|
8
7
|
from omlish.lite.check import check_not_none
|
9
8
|
from omlish.lite.logs import log
|
10
|
-
from omlish.lite.typing import
|
9
|
+
from omlish.lite.typing import Func1
|
11
10
|
|
12
11
|
from .configs import ProcessGroupConfig
|
13
12
|
from .context import ServerContextImpl
|
13
|
+
from .dispatchers import Dispatchers
|
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
|
-
from .groups import
|
19
|
+
from .groups import ProcessGroupManager
|
20
20
|
from .poller import Poller
|
21
|
-
from .
|
22
|
-
from .
|
21
|
+
from .process import PidHistory
|
22
|
+
from .setup import SupervisorSetup
|
23
23
|
from .states import SupervisorState
|
24
|
-
from .types import
|
24
|
+
from .types import OutputDispatcher
|
25
25
|
from .types import Process
|
26
|
-
from .utils import
|
27
|
-
from .utils import
|
28
|
-
from .utils import
|
29
|
-
|
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))
|
30
40
|
|
31
41
|
|
32
42
|
##
|
@@ -38,7 +48,7 @@ class SignalHandler:
|
|
38
48
|
*,
|
39
49
|
context: ServerContextImpl,
|
40
50
|
signal_receiver: SignalReceiver,
|
41
|
-
process_groups:
|
51
|
+
process_groups: ProcessGroupManager,
|
42
52
|
) -> None:
|
43
53
|
super().__init__()
|
44
54
|
|
@@ -78,8 +88,10 @@ class SignalHandler:
|
|
78
88
|
elif sig == signal.SIGUSR2:
|
79
89
|
log.info('received %s indicating log reopen request', sig_name(sig))
|
80
90
|
|
81
|
-
for
|
82
|
-
|
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()
|
83
95
|
|
84
96
|
else:
|
85
97
|
log.debug('received %s indicating nothing', sig_name(sig))
|
@@ -88,7 +100,8 @@ class SignalHandler:
|
|
88
100
|
##
|
89
101
|
|
90
102
|
|
91
|
-
ProcessGroupFactory
|
103
|
+
class ProcessGroupFactory(Func1[ProcessGroupConfig, ProcessGroup]):
|
104
|
+
pass
|
92
105
|
|
93
106
|
|
94
107
|
class Supervisor:
|
@@ -97,10 +110,12 @@ class Supervisor:
|
|
97
110
|
*,
|
98
111
|
context: ServerContextImpl,
|
99
112
|
poller: Poller,
|
100
|
-
process_groups:
|
113
|
+
process_groups: ProcessGroupManager,
|
101
114
|
signal_handler: SignalHandler,
|
102
115
|
event_callbacks: EventCallbacks,
|
103
116
|
process_group_factory: ProcessGroupFactory,
|
117
|
+
pid_history: PidHistory,
|
118
|
+
setup: SupervisorSetup,
|
104
119
|
) -> None:
|
105
120
|
super().__init__()
|
106
121
|
|
@@ -110,6 +125,8 @@ class Supervisor:
|
|
110
125
|
self._signal_handler = signal_handler
|
111
126
|
self._event_callbacks = event_callbacks
|
112
127
|
self._process_group_factory = process_group_factory
|
128
|
+
self._pid_history = pid_history
|
129
|
+
self._setup = setup
|
113
130
|
|
114
131
|
self._ticks: ta.Dict[int, float] = {}
|
115
132
|
self._stop_groups: ta.Optional[ta.List[ProcessGroup]] = None # list used for priority ordered shutdown
|
@@ -127,31 +144,13 @@ class Supervisor:
|
|
127
144
|
|
128
145
|
#
|
129
146
|
|
130
|
-
class DiffToActive(ta.NamedTuple):
|
131
|
-
added: ta.List[ProcessGroupConfig]
|
132
|
-
changed: ta.List[ProcessGroupConfig]
|
133
|
-
removed: ta.List[ProcessGroupConfig]
|
134
|
-
|
135
|
-
def diff_to_active(self) -> DiffToActive:
|
136
|
-
new = self._context.config.groups or []
|
137
|
-
cur = [group.config for group in self._process_groups]
|
138
|
-
|
139
|
-
curdict = dict(zip([cfg.name for cfg in cur], cur))
|
140
|
-
newdict = dict(zip([cfg.name for cfg in new], new))
|
141
|
-
|
142
|
-
added = [cand for cand in new if cand.name not in curdict]
|
143
|
-
removed = [cand for cand in cur if cand.name not in newdict]
|
144
|
-
|
145
|
-
changed = [cand for cand in new if cand != curdict.get(cand.name, cand)]
|
146
|
-
|
147
|
-
return Supervisor.DiffToActive(added, changed, removed)
|
148
|
-
|
149
147
|
def add_process_group(self, config: ProcessGroupConfig) -> bool:
|
150
148
|
if self._process_groups.get(config.name) is not None:
|
151
149
|
return False
|
152
150
|
|
153
151
|
group = check_isinstance(self._process_group_factory(config), ProcessGroup)
|
154
|
-
group
|
152
|
+
for process in group:
|
153
|
+
process.after_setuid()
|
155
154
|
|
156
155
|
self._process_groups.add(group)
|
157
156
|
|
@@ -165,11 +164,7 @@ class Supervisor:
|
|
165
164
|
|
166
165
|
return True
|
167
166
|
|
168
|
-
|
169
|
-
process_map: ta.Dict[int, Dispatcher] = {}
|
170
|
-
for group in self._process_groups:
|
171
|
-
process_map.update(group.get_dispatchers())
|
172
|
-
return process_map
|
167
|
+
#
|
173
168
|
|
174
169
|
def shutdown_report(self) -> ta.List[Process]:
|
175
170
|
unstopped: ta.List[Process] = []
|
@@ -181,7 +176,7 @@ class Supervisor:
|
|
181
176
|
# throttle 'waiting for x to die' reports
|
182
177
|
now = time.time()
|
183
178
|
if now > (self._last_shutdown_report + 3): # every 3 secs
|
184
|
-
names = [
|
179
|
+
names = [p.config.name for p in unstopped]
|
185
180
|
namestr = ', '.join(names)
|
186
181
|
log.info('waiting for %s to die', namestr)
|
187
182
|
self._last_shutdown_report = now
|
@@ -192,25 +187,12 @@ class Supervisor:
|
|
192
187
|
|
193
188
|
#
|
194
189
|
|
195
|
-
def main(self) -> None:
|
196
|
-
self.setup()
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
if not self._context.first:
|
202
|
-
# prevent crash on libdispatch-based systems, at least for the first request
|
203
|
-
self._context.cleanup_fds()
|
204
|
-
|
205
|
-
self._context.set_uid_or_exit()
|
206
|
-
|
207
|
-
if self._context.first:
|
208
|
-
self._context.set_rlimits_or_exit()
|
209
|
-
|
210
|
-
# this sets the options.logger object delay logger instantiation until after setuid
|
211
|
-
if not self._context.config.nocleanup:
|
212
|
-
# clean up old automatic logs
|
213
|
-
self._context.clear_auto_child_logdir()
|
190
|
+
def main(self, **kwargs: ta.Any) -> None:
|
191
|
+
self._setup.setup()
|
192
|
+
try:
|
193
|
+
self.run(**kwargs)
|
194
|
+
finally:
|
195
|
+
self._setup.cleanup()
|
214
196
|
|
215
197
|
def run(
|
216
198
|
self,
|
@@ -228,12 +210,6 @@ class Supervisor:
|
|
228
210
|
|
229
211
|
self._signal_handler.set_signals()
|
230
212
|
|
231
|
-
if not self._context.config.nodaemon and self._context.first:
|
232
|
-
self._context.daemonize()
|
233
|
-
|
234
|
-
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
235
|
-
self._context.write_pidfile()
|
236
|
-
|
237
213
|
self._event_callbacks.notify(SupervisorRunningEvent())
|
238
214
|
|
239
215
|
while True:
|
@@ -243,7 +219,7 @@ class Supervisor:
|
|
243
219
|
self._run_once()
|
244
220
|
|
245
221
|
finally:
|
246
|
-
self.
|
222
|
+
self._poller.close()
|
247
223
|
|
248
224
|
#
|
249
225
|
|
@@ -272,18 +248,24 @@ class Supervisor:
|
|
272
248
|
# down, so push it back on to the end of the stop group queue
|
273
249
|
self._stop_groups.append(group)
|
274
250
|
|
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
|
+
|
275
258
|
def _poll(self) -> None:
|
276
|
-
|
277
|
-
combined_map.update(self.get_process_map())
|
259
|
+
dispatchers = self.get_dispatchers()
|
278
260
|
|
279
|
-
|
280
|
-
|
261
|
+
sorted_groups = list(self._process_groups)
|
262
|
+
sorted_groups.sort()
|
281
263
|
|
282
264
|
if self._context.state < SupervisorState.RUNNING:
|
283
265
|
if not self._stopping:
|
284
266
|
# first time, set the stopping flag, do a notification and set stop_groups
|
285
267
|
self._stopping = True
|
286
|
-
self._stop_groups =
|
268
|
+
self._stop_groups = sorted_groups[:]
|
287
269
|
self._event_callbacks.notify(SupervisorStoppingEvent())
|
288
270
|
|
289
271
|
self._ordered_stop_groups_phase_1()
|
@@ -292,7 +274,7 @@ class Supervisor:
|
|
292
274
|
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
293
275
|
raise ExitNow
|
294
276
|
|
295
|
-
for fd, dispatcher in
|
277
|
+
for fd, dispatcher in dispatchers.items():
|
296
278
|
if dispatcher.readable():
|
297
279
|
self._poller.register_readable(fd)
|
298
280
|
if dispatcher.writable():
|
@@ -302,9 +284,9 @@ class Supervisor:
|
|
302
284
|
r, w = self._poller.poll(timeout)
|
303
285
|
|
304
286
|
for fd in r:
|
305
|
-
if fd in
|
287
|
+
if fd in dispatchers:
|
306
288
|
try:
|
307
|
-
dispatcher =
|
289
|
+
dispatcher = dispatchers[fd]
|
308
290
|
log.debug('read event caused by %r', dispatcher)
|
309
291
|
dispatcher.handle_read_event()
|
310
292
|
if not dispatcher.readable():
|
@@ -312,9 +294,9 @@ class Supervisor:
|
|
312
294
|
except ExitNow:
|
313
295
|
raise
|
314
296
|
except Exception: # noqa
|
315
|
-
|
297
|
+
dispatchers[fd].handle_error()
|
316
298
|
else:
|
317
|
-
# if the fd is not in
|
299
|
+
# if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
|
318
300
|
# time, which may cause 100% cpu usage
|
319
301
|
log.debug('unexpected read event from fd %r', fd)
|
320
302
|
try:
|
@@ -323,9 +305,9 @@ class Supervisor:
|
|
323
305
|
pass
|
324
306
|
|
325
307
|
for fd in w:
|
326
|
-
if fd in
|
308
|
+
if fd in dispatchers:
|
327
309
|
try:
|
328
|
-
dispatcher =
|
310
|
+
dispatcher = dispatchers[fd]
|
329
311
|
log.debug('write event caused by %r', dispatcher)
|
330
312
|
dispatcher.handle_write_event()
|
331
313
|
if not dispatcher.writable():
|
@@ -333,7 +315,7 @@ class Supervisor:
|
|
333
315
|
except ExitNow:
|
334
316
|
raise
|
335
317
|
except Exception: # noqa
|
336
|
-
|
318
|
+
dispatchers[fd].handle_error()
|
337
319
|
else:
|
338
320
|
log.debug('unexpected write event from fd %r', fd)
|
339
321
|
try:
|
@@ -341,8 +323,9 @@ class Supervisor:
|
|
341
323
|
except Exception: # noqa
|
342
324
|
pass
|
343
325
|
|
344
|
-
for group in
|
345
|
-
group
|
326
|
+
for group in sorted_groups:
|
327
|
+
for process in group:
|
328
|
+
process.transition()
|
346
329
|
|
347
330
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
348
331
|
if depth >= 100:
|
@@ -352,13 +335,13 @@ class Supervisor:
|
|
352
335
|
if not pid:
|
353
336
|
return
|
354
337
|
|
355
|
-
process = self.
|
338
|
+
process = self._pid_history.get(pid, None)
|
356
339
|
if process is None:
|
357
340
|
_, msg = decode_wait_status(check_not_none(sts))
|
358
341
|
log.info('reaped unknown pid %s (%s)', pid, msg)
|
359
342
|
else:
|
360
343
|
process.finish(check_not_none(sts))
|
361
|
-
del self.
|
344
|
+
del self._pid_history[pid]
|
362
345
|
|
363
346
|
if not once:
|
364
347
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
ominfra/supervisor/types.py
CHANGED
@@ -8,6 +8,40 @@ from .configs import ProcessGroupConfig
|
|
8
8
|
from .configs import ServerConfig
|
9
9
|
from .states import ProcessState
|
10
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
|
15
|
+
|
16
|
+
|
17
|
+
if ta.TYPE_CHECKING:
|
18
|
+
from .dispatchers import Dispatchers
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
ServerEpoch = ta.NewType('ServerEpoch', int)
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
@functools.total_ordering
|
31
|
+
class ConfigPriorityOrdered(abc.ABC):
|
32
|
+
@property
|
33
|
+
@abc.abstractmethod
|
34
|
+
def config(self) -> ta.Any:
|
35
|
+
raise NotImplementedError
|
36
|
+
|
37
|
+
def __lt__(self, other):
|
38
|
+
return self.config.priority < other.config.priority
|
39
|
+
|
40
|
+
def __eq__(self, other):
|
41
|
+
return self.config.priority == other.config.priority
|
42
|
+
|
43
|
+
|
44
|
+
##
|
11
45
|
|
12
46
|
|
13
47
|
class ServerContext(abc.ABC):
|
@@ -25,13 +59,43 @@ class ServerContext(abc.ABC):
|
|
25
59
|
def set_state(self, state: SupervisorState) -> None:
|
26
60
|
raise NotImplementedError
|
27
61
|
|
62
|
+
|
63
|
+
##
|
64
|
+
|
65
|
+
|
66
|
+
class Dispatcher(abc.ABC):
|
67
|
+
@property
|
68
|
+
@abc.abstractmethod
|
69
|
+
def process(self) -> 'Process':
|
70
|
+
raise NotImplementedError
|
71
|
+
|
28
72
|
@property
|
29
73
|
@abc.abstractmethod
|
30
|
-
def
|
74
|
+
def channel(self) -> str:
|
31
75
|
raise NotImplementedError
|
32
76
|
|
77
|
+
@property
|
78
|
+
@abc.abstractmethod
|
79
|
+
def fd(self) -> Fd:
|
80
|
+
raise NotImplementedError
|
81
|
+
|
82
|
+
@property
|
83
|
+
@abc.abstractmethod
|
84
|
+
def closed(self) -> bool:
|
85
|
+
raise NotImplementedError
|
86
|
+
|
87
|
+
#
|
88
|
+
|
89
|
+
@abc.abstractmethod
|
90
|
+
def close(self) -> None:
|
91
|
+
raise NotImplementedError
|
92
|
+
|
93
|
+
@abc.abstractmethod
|
94
|
+
def handle_error(self) -> None:
|
95
|
+
raise NotImplementedError
|
96
|
+
|
97
|
+
#
|
33
98
|
|
34
|
-
class Dispatcher(abc.ABC):
|
35
99
|
@abc.abstractmethod
|
36
100
|
def readable(self) -> bool:
|
37
101
|
raise NotImplementedError
|
@@ -40,26 +104,25 @@ class Dispatcher(abc.ABC):
|
|
40
104
|
def writable(self) -> bool:
|
41
105
|
raise NotImplementedError
|
42
106
|
|
107
|
+
#
|
108
|
+
|
43
109
|
def handle_read_event(self) -> None:
|
44
110
|
raise TypeError
|
45
111
|
|
46
112
|
def handle_write_event(self) -> None:
|
47
113
|
raise TypeError
|
48
114
|
|
115
|
+
|
116
|
+
class OutputDispatcher(Dispatcher, abc.ABC):
|
49
117
|
@abc.abstractmethod
|
50
|
-
def
|
118
|
+
def remove_logs(self) -> None:
|
51
119
|
raise NotImplementedError
|
52
120
|
|
53
|
-
@property
|
54
121
|
@abc.abstractmethod
|
55
|
-
def
|
122
|
+
def reopen_logs(self) -> None:
|
56
123
|
raise NotImplementedError
|
57
124
|
|
58
125
|
|
59
|
-
class OutputDispatcher(Dispatcher, abc.ABC):
|
60
|
-
pass
|
61
|
-
|
62
|
-
|
63
126
|
class InputDispatcher(Dispatcher, abc.ABC):
|
64
127
|
@abc.abstractmethod
|
65
128
|
def write(self, chars: ta.Union[bytes, str]) -> None:
|
@@ -70,11 +133,13 @@ class InputDispatcher(Dispatcher, abc.ABC):
|
|
70
133
|
raise NotImplementedError
|
71
134
|
|
72
135
|
|
73
|
-
|
74
|
-
|
136
|
+
##
|
137
|
+
|
138
|
+
|
139
|
+
class Process(ConfigPriorityOrdered, abc.ABC):
|
75
140
|
@property
|
76
141
|
@abc.abstractmethod
|
77
|
-
def
|
142
|
+
def name(self) -> str:
|
78
143
|
raise NotImplementedError
|
79
144
|
|
80
145
|
@property
|
@@ -82,27 +147,25 @@ class Process(abc.ABC):
|
|
82
147
|
def config(self) -> ProcessConfig:
|
83
148
|
raise NotImplementedError
|
84
149
|
|
85
|
-
def __lt__(self, other):
|
86
|
-
return self.config.priority < other.config.priority
|
87
|
-
|
88
|
-
def __eq__(self, other):
|
89
|
-
return self.config.priority == other.config.priority
|
90
|
-
|
91
150
|
@property
|
92
151
|
@abc.abstractmethod
|
93
|
-
def
|
152
|
+
def group(self) -> 'ProcessGroup':
|
94
153
|
raise NotImplementedError
|
95
154
|
|
155
|
+
@property
|
96
156
|
@abc.abstractmethod
|
97
|
-
def
|
157
|
+
def pid(self) -> Pid:
|
98
158
|
raise NotImplementedError
|
99
159
|
|
160
|
+
#
|
161
|
+
|
162
|
+
@property
|
100
163
|
@abc.abstractmethod
|
101
|
-
def
|
164
|
+
def context(self) -> ServerContext:
|
102
165
|
raise NotImplementedError
|
103
166
|
|
104
167
|
@abc.abstractmethod
|
105
|
-
def
|
168
|
+
def finish(self, sts: Rc) -> None:
|
106
169
|
raise NotImplementedError
|
107
170
|
|
108
171
|
@abc.abstractmethod
|
@@ -122,50 +185,41 @@ class Process(abc.ABC):
|
|
122
185
|
raise NotImplementedError
|
123
186
|
|
124
187
|
@abc.abstractmethod
|
125
|
-
def
|
126
|
-
raise NotImplementedError
|
127
|
-
|
128
|
-
@abc.abstractmethod
|
129
|
-
def get_dispatchers(self) -> ta.Mapping[int, Dispatcher]:
|
188
|
+
def after_setuid(self) -> None:
|
130
189
|
raise NotImplementedError
|
131
190
|
|
132
|
-
|
133
|
-
@functools.total_ordering
|
134
|
-
class ProcessGroup(abc.ABC):
|
135
|
-
@property
|
136
191
|
@abc.abstractmethod
|
137
|
-
def
|
192
|
+
def get_dispatchers(self) -> 'Dispatchers':
|
138
193
|
raise NotImplementedError
|
139
194
|
|
140
|
-
def __lt__(self, other):
|
141
|
-
return self.config.priority < other.config.priority
|
142
|
-
|
143
|
-
def __eq__(self, other):
|
144
|
-
return self.config.priority == other.config.priority
|
145
195
|
|
146
|
-
|
147
|
-
def transition(self) -> None:
|
148
|
-
raise NotImplementedError
|
196
|
+
##
|
149
197
|
|
150
|
-
@abc.abstractmethod
|
151
|
-
def stop_all(self) -> None:
|
152
|
-
raise NotImplementedError
|
153
198
|
|
199
|
+
class ProcessGroup(
|
200
|
+
ConfigPriorityOrdered,
|
201
|
+
KeyedCollectionAccessors[str, Process],
|
202
|
+
abc.ABC,
|
203
|
+
):
|
154
204
|
@property
|
155
205
|
@abc.abstractmethod
|
156
206
|
def name(self) -> str:
|
157
207
|
raise NotImplementedError
|
158
208
|
|
209
|
+
@property
|
159
210
|
@abc.abstractmethod
|
160
|
-
def
|
211
|
+
def config(self) -> ProcessGroupConfig:
|
161
212
|
raise NotImplementedError
|
162
213
|
|
214
|
+
@property
|
163
215
|
@abc.abstractmethod
|
164
|
-
def
|
216
|
+
def by_name(self) -> ta.Mapping[str, Process]:
|
165
217
|
raise NotImplementedError
|
166
218
|
|
219
|
+
#
|
220
|
+
|
167
221
|
@abc.abstractmethod
|
168
|
-
def
|
222
|
+
def stop_all(self) -> None:
|
169
223
|
raise NotImplementedError
|
170
224
|
|
171
225
|
@abc.abstractmethod
|
@@ -173,5 +227,5 @@ class ProcessGroup(abc.ABC):
|
|
173
227
|
raise NotImplementedError
|
174
228
|
|
175
229
|
@abc.abstractmethod
|
176
|
-
def
|
230
|
+
def before_remove(self) -> None:
|
177
231
|
raise NotImplementedError
|
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
|
6
|
+
K = ta.TypeVar('K')
|
7
|
+
V = ta.TypeVar('V')
|
8
|
+
|
9
|
+
|
10
|
+
class KeyedCollectionAccessors(abc.ABC, ta.Generic[K, V]):
|
11
|
+
@property
|
12
|
+
@abc.abstractmethod
|
13
|
+
def _by_key(self) -> ta.Mapping[K, V]:
|
14
|
+
raise NotImplementedError
|
15
|
+
|
16
|
+
def __iter__(self) -> ta.Iterator[V]:
|
17
|
+
return iter(self._by_key.values())
|
18
|
+
|
19
|
+
def __len__(self) -> int:
|
20
|
+
return len(self._by_key)
|
21
|
+
|
22
|
+
def __contains__(self, key: K) -> bool:
|
23
|
+
return key in self._by_key
|
24
|
+
|
25
|
+
def __getitem__(self, key: K) -> V:
|
26
|
+
return self._by_key[key]
|
27
|
+
|
28
|
+
def get(self, key: K, default: ta.Optional[V] = None) -> ta.Optional[V]:
|
29
|
+
return self._by_key.get(key, default)
|
30
|
+
|
31
|
+
def items(self) -> ta.Iterator[ta.Tuple[K, V]]:
|
32
|
+
return iter(self._by_key.items())
|
33
|
+
|
34
|
+
|
35
|
+
class KeyedCollection(KeyedCollectionAccessors[K, V]):
|
36
|
+
def __init__(self, items: ta.Iterable[V]) -> None:
|
37
|
+
super().__init__()
|
38
|
+
|
39
|
+
by_key: ta.Dict[K, V] = {}
|
40
|
+
for v in items:
|
41
|
+
if (k := self._key(v)) in by_key:
|
42
|
+
raise KeyError(f'key {k} of {v} already registered by {by_key[k]}')
|
43
|
+
by_key[k] = v
|
44
|
+
self.__by_key = by_key
|
45
|
+
|
46
|
+
@property
|
47
|
+
def _by_key(self) -> ta.Mapping[K, V]:
|
48
|
+
return self.__by_key
|
49
|
+
|
50
|
+
@abc.abstractmethod
|
51
|
+
def _key(self, v: V) -> K:
|
52
|
+
raise NotImplementedError
|
@@ -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)))
|