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
@@ -1,13 +1,14 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
from .
|
3
|
-
|
2
|
+
from omlish.lite.fdio.handlers import FdIoHandler
|
3
|
+
|
4
|
+
from .types import ProcessOutputDispatcher
|
4
5
|
from .utils.collections import KeyedCollection
|
5
6
|
from .utils.ostypes import Fd
|
6
7
|
|
7
8
|
|
8
|
-
class Dispatchers(KeyedCollection[Fd,
|
9
|
-
def _key(self, v:
|
10
|
-
return v.fd
|
9
|
+
class Dispatchers(KeyedCollection[Fd, FdIoHandler]):
|
10
|
+
def _key(self, v: FdIoHandler) -> Fd:
|
11
|
+
return Fd(v.fd())
|
11
12
|
|
12
13
|
#
|
13
14
|
|
@@ -16,18 +17,18 @@ class Dispatchers(KeyedCollection[Fd, Dispatcher]):
|
|
16
17
|
# note that we *must* call readable() for every dispatcher, as it may have side effects for a given
|
17
18
|
# dispatcher (eg. call handle_listener_state_change for event listener processes)
|
18
19
|
if d.readable():
|
19
|
-
d.
|
20
|
+
d.on_readable()
|
20
21
|
if d.writable():
|
21
|
-
d.
|
22
|
+
d.on_writable()
|
22
23
|
|
23
24
|
#
|
24
25
|
|
25
26
|
def remove_logs(self) -> None:
|
26
27
|
for d in self:
|
27
|
-
if isinstance(d,
|
28
|
+
if isinstance(d, ProcessOutputDispatcher):
|
28
29
|
d.remove_logs()
|
29
30
|
|
30
31
|
def reopen_logs(self) -> None:
|
31
32
|
for d in self:
|
32
|
-
if isinstance(d,
|
33
|
+
if isinstance(d, ProcessOutputDispatcher):
|
33
34
|
d.reopen_logs()
|
@@ -8,14 +8,15 @@ import typing as ta
|
|
8
8
|
from omlish.lite.logs import log
|
9
9
|
|
10
10
|
from .configs import ProcessConfig
|
11
|
+
from .configs import ServerConfig
|
11
12
|
from .events import EventCallbacks
|
12
13
|
from .events import ProcessCommunicationEvent
|
13
14
|
from .events import ProcessLogStderrEvent
|
14
15
|
from .events import ProcessLogStdoutEvent
|
15
|
-
from .types import Dispatcher
|
16
|
-
from .types import InputDispatcher
|
17
|
-
from .types import OutputDispatcher
|
18
16
|
from .types import Process
|
17
|
+
from .types import ProcessDispatcher
|
18
|
+
from .types import ProcessInputDispatcher
|
19
|
+
from .types import ProcessOutputDispatcher
|
19
20
|
from .utils.diag import compact_traceback
|
20
21
|
from .utils.fds import read_fd
|
21
22
|
from .utils.ostypes import Fd
|
@@ -24,7 +25,7 @@ from .utils.strings import find_prefix_at_end
|
|
24
25
|
from .utils.strings import strip_escapes
|
25
26
|
|
26
27
|
|
27
|
-
class
|
28
|
+
class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
28
29
|
def __init__(
|
29
30
|
self,
|
30
31
|
process: Process,
|
@@ -32,6 +33,7 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
|
|
32
33
|
fd: Fd,
|
33
34
|
*,
|
34
35
|
event_callbacks: EventCallbacks,
|
36
|
+
server_config: ServerConfig,
|
35
37
|
) -> None:
|
36
38
|
super().__init__()
|
37
39
|
|
@@ -39,6 +41,7 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
|
|
39
41
|
self._channel = channel # 'stderr' or 'stdout'
|
40
42
|
self._fd = fd
|
41
43
|
self._event_callbacks = event_callbacks
|
44
|
+
self._server_config = server_config
|
42
45
|
|
43
46
|
self._closed = False # True if close() has been called
|
44
47
|
|
@@ -57,7 +60,6 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
|
|
57
60
|
def channel(self) -> str:
|
58
61
|
return self._channel
|
59
62
|
|
60
|
-
@property
|
61
63
|
def fd(self) -> Fd:
|
62
64
|
return self._fd
|
63
65
|
|
@@ -72,14 +74,14 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
|
|
72
74
|
log.debug('fd %s closed, stopped monitoring %s', self._fd, self)
|
73
75
|
self._closed = True
|
74
76
|
|
75
|
-
def
|
77
|
+
def on_error(self, exc: ta.Optional[BaseException] = None) -> None:
|
76
78
|
nil, t, v, tbinfo = compact_traceback()
|
77
79
|
|
78
80
|
log.critical('uncaptured python exception, closing channel %s (%s:%s %s)', repr(self), t, v, tbinfo)
|
79
81
|
self.close()
|
80
82
|
|
81
83
|
|
82
|
-
class
|
84
|
+
class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispatcher):
|
83
85
|
"""
|
84
86
|
Dispatcher for one channel (stdout or stderr) of one process. Serves several purposes:
|
85
87
|
|
@@ -95,12 +97,14 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
|
|
95
97
|
fd: Fd,
|
96
98
|
*,
|
97
99
|
event_callbacks: EventCallbacks,
|
100
|
+
server_config: ServerConfig,
|
98
101
|
) -> None:
|
99
102
|
super().__init__(
|
100
103
|
process,
|
101
104
|
event_type.channel,
|
102
105
|
fd,
|
103
106
|
event_callbacks=event_callbacks,
|
107
|
+
server_config=server_config,
|
104
108
|
)
|
105
109
|
|
106
110
|
self._event_type = event_type
|
@@ -124,11 +128,10 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
|
|
124
128
|
|
125
129
|
self._main_log_level = logging.DEBUG
|
126
130
|
|
127
|
-
self._log_to_main_log =
|
131
|
+
self._log_to_main_log = self._server_config.loglevel <= self._main_log_level
|
128
132
|
|
129
|
-
|
130
|
-
self.
|
131
|
-
self._stderr_events_enabled = config.stderr.events_enabled
|
133
|
+
self._stdout_events_enabled = self._process.config.stdout.events_enabled
|
134
|
+
self._stderr_events_enabled = self._process.config.stderr.events_enabled
|
132
135
|
|
133
136
|
_child_log: ta.Optional[logging.Logger] = None # the current logger (normal_log or capture_log)
|
134
137
|
_normal_log: ta.Optional[logging.Logger] = None # the "normal" (non-capture) logger
|
@@ -199,7 +202,7 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
|
|
199
202
|
if not data:
|
200
203
|
return
|
201
204
|
|
202
|
-
if self.
|
205
|
+
if self._server_config.strip_ansi:
|
203
206
|
data = strip_escapes(as_bytes(data))
|
204
207
|
|
205
208
|
if self._child_log:
|
@@ -287,7 +290,7 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
|
|
287
290
|
return False
|
288
291
|
return True
|
289
292
|
|
290
|
-
def
|
293
|
+
def on_readable(self) -> None:
|
291
294
|
data = read_fd(self._fd)
|
292
295
|
self._output_buffer += data
|
293
296
|
self.record_output()
|
@@ -297,7 +300,7 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
|
|
297
300
|
self.close()
|
298
301
|
|
299
302
|
|
300
|
-
class
|
303
|
+
class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatcher):
|
301
304
|
def __init__(
|
302
305
|
self,
|
303
306
|
process: Process,
|
@@ -305,12 +308,14 @@ class InputDispatcherImpl(BaseDispatcherImpl, InputDispatcher):
|
|
305
308
|
fd: Fd,
|
306
309
|
*,
|
307
310
|
event_callbacks: EventCallbacks,
|
311
|
+
server_config: ServerConfig,
|
308
312
|
) -> None:
|
309
313
|
super().__init__(
|
310
314
|
process,
|
311
315
|
channel,
|
312
316
|
fd,
|
313
317
|
event_callbacks=event_callbacks,
|
318
|
+
server_config=server_config,
|
314
319
|
)
|
315
320
|
|
316
321
|
self._input_buffer = b''
|
@@ -323,15 +328,12 @@ class InputDispatcherImpl(BaseDispatcherImpl, InputDispatcher):
|
|
323
328
|
return True
|
324
329
|
return False
|
325
330
|
|
326
|
-
def readable(self) -> bool:
|
327
|
-
return False
|
328
|
-
|
329
331
|
def flush(self) -> None:
|
330
332
|
# other code depends on this raising EPIPE if the pipe is closed
|
331
333
|
sent = os.write(self._fd, as_bytes(self._input_buffer))
|
332
334
|
self._input_buffer = self._input_buffer[sent:]
|
333
335
|
|
334
|
-
def
|
336
|
+
def on_writable(self) -> None:
|
335
337
|
if self._input_buffer:
|
336
338
|
try:
|
337
339
|
self.flush()
|
ominfra/supervisor/groups.py
CHANGED
@@ -2,15 +2,20 @@
|
|
2
2
|
import typing as ta
|
3
3
|
|
4
4
|
from .configs import ProcessGroupConfig
|
5
|
+
from .dispatchers import Dispatchers
|
5
6
|
from .events import EventCallbacks
|
6
7
|
from .events import ProcessGroupAddedEvent
|
7
8
|
from .events import ProcessGroupRemovedEvent
|
9
|
+
from .types import HasDispatchers
|
8
10
|
from .types import Process
|
9
11
|
from .types import ProcessGroup
|
10
12
|
from .utils.collections import KeyedCollectionAccessors
|
11
13
|
|
12
14
|
|
13
|
-
class ProcessGroupManager(
|
15
|
+
class ProcessGroupManager(
|
16
|
+
KeyedCollectionAccessors[str, ProcessGroup],
|
17
|
+
HasDispatchers,
|
18
|
+
):
|
14
19
|
def __init__(
|
15
20
|
self,
|
16
21
|
*,
|
@@ -34,6 +39,16 @@ class ProcessGroupManager(KeyedCollectionAccessors[str, ProcessGroup]):
|
|
34
39
|
|
35
40
|
#
|
36
41
|
|
42
|
+
def get_dispatchers(self) -> Dispatchers:
|
43
|
+
return Dispatchers(
|
44
|
+
d
|
45
|
+
for g in self
|
46
|
+
for p in g
|
47
|
+
for d in p.get_dispatchers()
|
48
|
+
)
|
49
|
+
|
50
|
+
#
|
51
|
+
|
37
52
|
def add(self, group: ProcessGroup) -> None:
|
38
53
|
if (name := group.name) in self._by_name:
|
39
54
|
raise KeyError(f'Process group already exists: {name}')
|
ominfra/supervisor/groupsimpl.py
CHANGED
@@ -61,7 +61,7 @@ class ProcessGroupImpl(ProcessGroup):
|
|
61
61
|
#
|
62
62
|
|
63
63
|
def get_unstopped_processes(self) -> ta.List[Process]:
|
64
|
-
return [x for x in self if not x.
|
64
|
+
return [x for x in self if not x.state.stopped]
|
65
65
|
|
66
66
|
def stop_all(self) -> None:
|
67
67
|
processes = list(self._by_name.values())
|
@@ -69,7 +69,7 @@ class ProcessGroupImpl(ProcessGroup):
|
|
69
69
|
processes.reverse() # stop in desc priority order
|
70
70
|
|
71
71
|
for proc in processes:
|
72
|
-
state = proc.
|
72
|
+
state = proc.state
|
73
73
|
if state == ProcessState.RUNNING:
|
74
74
|
# RUNNING -> STOPPING
|
75
75
|
proc.stop()
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# ruff: noqa: U006 UP007
|
2
|
+
import json
|
3
|
+
import socket
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.check import check_not_none
|
7
|
+
from omlish.lite.fdio.corohttp import CoroHttpServerConnectionFdIoHandler
|
8
|
+
from omlish.lite.fdio.handlers import SocketFdIoHandler
|
9
|
+
from omlish.lite.http.handlers import HttpHandler
|
10
|
+
from omlish.lite.http.handlers import HttpHandlerRequest
|
11
|
+
from omlish.lite.http.handlers import HttpHandlerResponse
|
12
|
+
from omlish.lite.json import JSON_PRETTY_KWARGS
|
13
|
+
from omlish.lite.socket import SocketAddress
|
14
|
+
|
15
|
+
from .dispatchers import Dispatchers
|
16
|
+
from .groups import ProcessGroupManager
|
17
|
+
from .types import HasDispatchers
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
class SocketServerFdIoHandler(SocketFdIoHandler):
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
addr: SocketAddress,
|
27
|
+
on_connect: ta.Callable[[socket.socket, SocketAddress], None],
|
28
|
+
) -> None:
|
29
|
+
sock = socket.create_server(addr)
|
30
|
+
sock.setblocking(False)
|
31
|
+
|
32
|
+
super().__init__(addr, sock)
|
33
|
+
|
34
|
+
self._on_connect = on_connect
|
35
|
+
|
36
|
+
sock.listen(1)
|
37
|
+
|
38
|
+
def readable(self) -> bool:
|
39
|
+
return True
|
40
|
+
|
41
|
+
def on_readable(self) -> None:
|
42
|
+
cli_sock, cli_addr = check_not_none(self._sock).accept()
|
43
|
+
cli_sock.setblocking(False)
|
44
|
+
|
45
|
+
self._on_connect(cli_sock, cli_addr)
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
|
50
|
+
|
51
|
+
class HttpServer(HasDispatchers):
|
52
|
+
class Address(ta.NamedTuple):
|
53
|
+
a: SocketAddress
|
54
|
+
|
55
|
+
class Handler(ta.NamedTuple):
|
56
|
+
h: HttpHandler
|
57
|
+
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
handler: Handler,
|
61
|
+
addr: Address = Address(('localhost', 8000)),
|
62
|
+
) -> None:
|
63
|
+
super().__init__()
|
64
|
+
|
65
|
+
self._handler = handler.h
|
66
|
+
self._addr = addr.a
|
67
|
+
|
68
|
+
self._server = SocketServerFdIoHandler(self._addr, self._on_connect)
|
69
|
+
|
70
|
+
self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
|
71
|
+
|
72
|
+
def get_dispatchers(self) -> Dispatchers:
|
73
|
+
l = []
|
74
|
+
for c in self._conns:
|
75
|
+
if not c.closed:
|
76
|
+
l.append(c)
|
77
|
+
self._conns = l
|
78
|
+
return Dispatchers([
|
79
|
+
self._server,
|
80
|
+
*l,
|
81
|
+
])
|
82
|
+
|
83
|
+
def _on_connect(self, sock: socket.socket, addr: SocketAddress) -> None:
|
84
|
+
conn = CoroHttpServerConnectionFdIoHandler(
|
85
|
+
addr,
|
86
|
+
sock,
|
87
|
+
self._handler,
|
88
|
+
)
|
89
|
+
|
90
|
+
self._conns.append(conn)
|
91
|
+
|
92
|
+
|
93
|
+
##
|
94
|
+
|
95
|
+
|
96
|
+
class SupervisorHttpHandler:
|
97
|
+
def __init__(
|
98
|
+
self,
|
99
|
+
*,
|
100
|
+
groups: ProcessGroupManager,
|
101
|
+
) -> None:
|
102
|
+
super().__init__()
|
103
|
+
|
104
|
+
self._groups = groups
|
105
|
+
|
106
|
+
def handle(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
|
107
|
+
dct = {
|
108
|
+
'method': req.method,
|
109
|
+
'path': req.path,
|
110
|
+
'data': len(req.data or b''),
|
111
|
+
'groups': {
|
112
|
+
g.name: {
|
113
|
+
'processes': {
|
114
|
+
p.name: {
|
115
|
+
'pid': p.pid,
|
116
|
+
}
|
117
|
+
for p in g
|
118
|
+
},
|
119
|
+
}
|
120
|
+
for g in self._groups
|
121
|
+
},
|
122
|
+
}
|
123
|
+
|
124
|
+
return HttpHandlerResponse(
|
125
|
+
200,
|
126
|
+
data=json.dumps(dct, **JSON_PRETTY_KWARGS).encode('utf-8') + b'\n',
|
127
|
+
headers={
|
128
|
+
'Content-Type': 'application/json',
|
129
|
+
},
|
130
|
+
)
|
ominfra/supervisor/inject.py
CHANGED
@@ -1,20 +1,26 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
2
3
|
import typing as ta
|
3
4
|
|
5
|
+
from omlish.lite.fdio.kqueue import KqueueFdIoPoller # noqa
|
6
|
+
from omlish.lite.fdio.pollers import FdIoPoller
|
7
|
+
from omlish.lite.fdio.pollers import PollFdIoPoller # noqa
|
8
|
+
from omlish.lite.fdio.pollers import SelectFdIoPoller
|
4
9
|
from omlish.lite.inject import InjectorBindingOrBindings
|
5
10
|
from omlish.lite.inject import InjectorBindings
|
6
11
|
from omlish.lite.inject import inj
|
7
12
|
|
8
13
|
from .configs import ServerConfig
|
9
|
-
from .
|
10
|
-
from .dispatchersimpl import
|
11
|
-
from .dispatchersimpl import OutputDispatcherImpl
|
14
|
+
from .dispatchersimpl import ProcessInputDispatcherImpl
|
15
|
+
from .dispatchersimpl import ProcessOutputDispatcherImpl
|
12
16
|
from .events import EventCallbacks
|
13
17
|
from .groups import ProcessGroupManager
|
14
18
|
from .groupsimpl import ProcessFactory
|
15
19
|
from .groupsimpl import ProcessGroupImpl
|
16
|
-
from .
|
17
|
-
from .
|
20
|
+
from .http import HttpServer
|
21
|
+
from .http import SupervisorHttpHandler
|
22
|
+
from .io import HasDispatchersList
|
23
|
+
from .io import IoManager
|
18
24
|
from .process import PidHistory
|
19
25
|
from .processimpl import ProcessImpl
|
20
26
|
from .processimpl import ProcessSpawningFactory
|
@@ -23,19 +29,32 @@ from .setup import DaemonizeListeners
|
|
23
29
|
from .setup import SupervisorUser
|
24
30
|
from .setupimpl import SupervisorSetup
|
25
31
|
from .setupimpl import SupervisorSetupImpl
|
32
|
+
from .signals import SignalHandler
|
26
33
|
from .spawningimpl import InheritedFds
|
27
|
-
from .spawningimpl import
|
28
|
-
from .spawningimpl import
|
34
|
+
from .spawningimpl import ProcessInputDispatcherFactory
|
35
|
+
from .spawningimpl import ProcessOutputDispatcherFactory
|
29
36
|
from .spawningimpl import ProcessSpawningImpl
|
30
37
|
from .supervisor import ProcessGroupFactory
|
31
|
-
from .supervisor import SignalHandler
|
32
38
|
from .supervisor import Supervisor
|
33
|
-
from .
|
39
|
+
from .supervisor import SupervisorStateManagerImpl
|
40
|
+
from .types import HasDispatchers
|
34
41
|
from .types import ServerEpoch
|
42
|
+
from .types import SupervisorStateManager
|
35
43
|
from .utils.signals import SignalReceiver
|
36
44
|
from .utils.users import get_user
|
37
45
|
|
38
46
|
|
47
|
+
@dc.dataclass(frozen=True)
|
48
|
+
class _FdIoPollerDaemonizeListener(DaemonizeListener):
|
49
|
+
_poller: FdIoPoller
|
50
|
+
|
51
|
+
def before_daemonize(self) -> None:
|
52
|
+
self._poller.close()
|
53
|
+
|
54
|
+
def after_daemonize(self) -> None:
|
55
|
+
self._poller.reopen()
|
56
|
+
|
57
|
+
|
39
58
|
def bind_server(
|
40
59
|
config: ServerConfig,
|
41
60
|
*,
|
@@ -45,24 +64,30 @@ def bind_server(
|
|
45
64
|
lst: ta.List[InjectorBindingOrBindings] = [
|
46
65
|
inj.bind(config),
|
47
66
|
|
67
|
+
inj.bind_array(DaemonizeListener),
|
48
68
|
inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
|
49
69
|
|
50
70
|
inj.bind(SupervisorSetupImpl, singleton=True),
|
51
71
|
inj.bind(SupervisorSetup, to_key=SupervisorSetupImpl),
|
52
72
|
|
53
|
-
inj.bind(DaemonizeListener, array=True, to_key=Poller),
|
54
|
-
|
55
|
-
inj.bind(ServerContextImpl, singleton=True),
|
56
|
-
inj.bind(ServerContext, to_key=ServerContextImpl),
|
57
|
-
|
58
73
|
inj.bind(EventCallbacks, singleton=True),
|
59
74
|
|
60
75
|
inj.bind(SignalReceiver, singleton=True),
|
61
76
|
|
77
|
+
inj.bind(IoManager, singleton=True),
|
78
|
+
inj.bind_array(HasDispatchers),
|
79
|
+
inj.bind_array_type(HasDispatchers, HasDispatchersList),
|
80
|
+
|
62
81
|
inj.bind(SignalHandler, singleton=True),
|
82
|
+
|
63
83
|
inj.bind(ProcessGroupManager, singleton=True),
|
84
|
+
inj.bind(HasDispatchers, array=True, to_key=ProcessGroupManager),
|
85
|
+
|
64
86
|
inj.bind(Supervisor, singleton=True),
|
65
87
|
|
88
|
+
inj.bind(SupervisorStateManagerImpl, singleton=True),
|
89
|
+
inj.bind(SupervisorStateManager, to_key=SupervisorStateManagerImpl),
|
90
|
+
|
66
91
|
inj.bind(PidHistory()),
|
67
92
|
|
68
93
|
inj.bind_factory(ProcessGroupImpl, ProcessGroupFactory),
|
@@ -70,8 +95,8 @@ def bind_server(
|
|
70
95
|
|
71
96
|
inj.bind_factory(ProcessSpawningImpl, ProcessSpawningFactory),
|
72
97
|
|
73
|
-
inj.bind_factory(
|
74
|
-
inj.bind_factory(
|
98
|
+
inj.bind_factory(ProcessOutputDispatcherImpl, ProcessOutputDispatcherFactory),
|
99
|
+
inj.bind_factory(ProcessInputDispatcherImpl, ProcessInputDispatcherFactory),
|
75
100
|
]
|
76
101
|
|
77
102
|
#
|
@@ -89,7 +114,26 @@ def bind_server(
|
|
89
114
|
|
90
115
|
#
|
91
116
|
|
92
|
-
|
117
|
+
poller_impl = next(filter(None, [
|
118
|
+
KqueueFdIoPoller,
|
119
|
+
PollFdIoPoller,
|
120
|
+
SelectFdIoPoller,
|
121
|
+
]))
|
122
|
+
lst.append(inj.bind(poller_impl, key=FdIoPoller, singleton=True))
|
123
|
+
inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True)
|
124
|
+
|
125
|
+
#
|
126
|
+
|
127
|
+
def _provide_http_handler(s: SupervisorHttpHandler) -> HttpServer.Handler:
|
128
|
+
return HttpServer.Handler(s.handle)
|
129
|
+
|
130
|
+
lst.extend([
|
131
|
+
inj.bind(HttpServer, singleton=True, eager=True),
|
132
|
+
inj.bind(HasDispatchers, array=True, to_key=HttpServer),
|
133
|
+
|
134
|
+
inj.bind(SupervisorHttpHandler, singleton=True),
|
135
|
+
inj.bind(_provide_http_handler),
|
136
|
+
])
|
93
137
|
|
94
138
|
#
|
95
139
|
|
ominfra/supervisor/io.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.fdio.pollers import FdIoPoller
|
5
|
+
from omlish.lite.logs import log
|
6
|
+
|
7
|
+
from .dispatchers import Dispatchers
|
8
|
+
from .types import ExitNow
|
9
|
+
from .types import HasDispatchers
|
10
|
+
from .utils.ostypes import Fd
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
HasDispatchersList = ta.NewType('HasDispatchersList', ta.Sequence[HasDispatchers])
|
17
|
+
|
18
|
+
|
19
|
+
class IoManager(HasDispatchers):
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
*,
|
23
|
+
poller: FdIoPoller,
|
24
|
+
has_dispatchers_list: HasDispatchersList,
|
25
|
+
) -> None:
|
26
|
+
super().__init__()
|
27
|
+
|
28
|
+
self._poller = poller
|
29
|
+
self._has_dispatchers_list = has_dispatchers_list
|
30
|
+
|
31
|
+
def get_dispatchers(self) -> Dispatchers:
|
32
|
+
return Dispatchers(
|
33
|
+
d
|
34
|
+
for hd in self._has_dispatchers_list
|
35
|
+
for d in hd.get_dispatchers()
|
36
|
+
)
|
37
|
+
|
38
|
+
def poll(self) -> None:
|
39
|
+
dispatchers = self.get_dispatchers()
|
40
|
+
|
41
|
+
self._poller.update(
|
42
|
+
{fd for fd, d in dispatchers.items() if d.readable()},
|
43
|
+
{fd for fd, d in dispatchers.items() if d.writable()},
|
44
|
+
)
|
45
|
+
|
46
|
+
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
47
|
+
log.info(f'Polling: {timeout=}') # noqa
|
48
|
+
polled = self._poller.poll(timeout)
|
49
|
+
log.info(f'Polled: {polled=}') # noqa
|
50
|
+
if polled.msg is not None:
|
51
|
+
log.error(polled.msg)
|
52
|
+
if polled.exc is not None:
|
53
|
+
log.error('Poll exception: %r', polled.exc)
|
54
|
+
|
55
|
+
for r in polled.r:
|
56
|
+
fd = Fd(r)
|
57
|
+
if fd in dispatchers:
|
58
|
+
dispatcher = dispatchers[fd]
|
59
|
+
try:
|
60
|
+
log.debug('read event caused by %r', dispatcher)
|
61
|
+
dispatcher.on_readable()
|
62
|
+
if not dispatcher.readable():
|
63
|
+
self._poller.unregister_readable(fd)
|
64
|
+
except ExitNow:
|
65
|
+
raise
|
66
|
+
except Exception as exc: # noqa
|
67
|
+
log.exception('Error in dispatcher: %r', dispatcher)
|
68
|
+
dispatcher.on_error(exc)
|
69
|
+
else:
|
70
|
+
# if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
|
71
|
+
# time, which may cause 100% cpu usage
|
72
|
+
log.debug('unexpected read event from fd %r', fd)
|
73
|
+
try:
|
74
|
+
self._poller.unregister_readable(fd)
|
75
|
+
except Exception: # noqa
|
76
|
+
pass
|
77
|
+
|
78
|
+
for w in polled.w:
|
79
|
+
fd = Fd(w)
|
80
|
+
if fd in dispatchers:
|
81
|
+
dispatcher = dispatchers[fd]
|
82
|
+
try:
|
83
|
+
log.debug('write event caused by %r', dispatcher)
|
84
|
+
dispatcher.on_writable()
|
85
|
+
if not dispatcher.writable():
|
86
|
+
self._poller.unregister_writable(fd)
|
87
|
+
except ExitNow:
|
88
|
+
raise
|
89
|
+
except Exception as exc: # noqa
|
90
|
+
log.exception('Error in dispatcher: %r', dispatcher)
|
91
|
+
dispatcher.on_error(exc)
|
92
|
+
else:
|
93
|
+
log.debug('unexpected write event from fd %r', fd)
|
94
|
+
try:
|
95
|
+
self._poller.unregister_writable(fd)
|
96
|
+
except Exception: # noqa
|
97
|
+
pass
|
ominfra/supervisor/main.py
CHANGED
@@ -37,17 +37,17 @@ from omlish.lite.http.coroserver import CoroHttpServer
|
|
37
37
|
from omlish.lite.inject import inj
|
38
38
|
from omlish.lite.journald import journald_log_handler_factory
|
39
39
|
from omlish.lite.logs import configure_standard_logging
|
40
|
+
from omlish.lite.runtime import is_debugger_attached
|
40
41
|
|
41
42
|
from ..configs import read_config_file
|
42
43
|
from .configs import ServerConfig
|
43
44
|
from .configs import prepare_server_config
|
44
|
-
from .context import ServerContextImpl
|
45
|
-
from .context import ServerEpoch
|
46
45
|
from .inject import bind_server
|
47
46
|
from .spawningimpl import InheritedFds
|
48
47
|
from .states import SupervisorState
|
49
|
-
from .supervisor import ExitNow
|
50
48
|
from .supervisor import Supervisor
|
49
|
+
from .types import ExitNow
|
50
|
+
from .types import ServerEpoch
|
51
51
|
from .utils.fds import get_open_fds
|
52
52
|
|
53
53
|
|
@@ -79,7 +79,7 @@ def main(
|
|
79
79
|
if not no_logging:
|
80
80
|
configure_standard_logging(
|
81
81
|
'INFO',
|
82
|
-
handler_factory=journald_log_handler_factory if not args.no_journald else None,
|
82
|
+
handler_factory=journald_log_handler_factory if not (args.no_journald or is_debugger_attached()) else None,
|
83
83
|
)
|
84
84
|
|
85
85
|
#
|
@@ -102,7 +102,6 @@ def main(
|
|
102
102
|
inherited_fds=inherited_fds,
|
103
103
|
))
|
104
104
|
|
105
|
-
context = injector[ServerContextImpl]
|
106
105
|
supervisor = injector[Supervisor]
|
107
106
|
|
108
107
|
try:
|
@@ -110,7 +109,7 @@ def main(
|
|
110
109
|
except ExitNow:
|
111
110
|
pass
|
112
111
|
|
113
|
-
if
|
112
|
+
if supervisor.state < SupervisorState.RESTARTING:
|
114
113
|
break
|
115
114
|
|
116
115
|
|