ominfra 0.0.0.dev122__py3-none-any.whl → 0.0.0.dev124__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,162 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from .configs import ProcessConfig
6
+ from .configs import ProcessGroupConfig
7
+ from .context import ServerContext
8
+ from .dispatchers import Dispatcher
9
+ from .events import EventCallbacks
10
+ from .events import ProcessGroupAddedEvent
11
+ from .events import ProcessGroupRemovedEvent
12
+ from .states import ProcessState
13
+ from .types import AbstractProcessGroup
14
+ from .types import AbstractServerContext
15
+ from .types import AbstractSubprocess
16
+
17
+
18
+ ##
19
+
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class SubprocessFactory:
23
+ fn: ta.Callable[[ProcessConfig, AbstractProcessGroup], AbstractSubprocess]
24
+
25
+ def __call__(self, config: ProcessConfig, group: AbstractProcessGroup) -> AbstractSubprocess:
26
+ return self.fn(config, group)
27
+
28
+
29
+ class ProcessGroup(AbstractProcessGroup):
30
+ def __init__(
31
+ self,
32
+ config: ProcessGroupConfig,
33
+ context: ServerContext,
34
+ *,
35
+ subprocess_factory: SubprocessFactory,
36
+ ):
37
+ super().__init__()
38
+
39
+ self._config = config
40
+ self._context = context
41
+ self._subprocess_factory = subprocess_factory
42
+
43
+ self._processes = {}
44
+ for pconfig in self._config.processes or []:
45
+ process = self._subprocess_factory(pconfig, self)
46
+ self._processes[pconfig.name] = process
47
+
48
+ @property
49
+ def config(self) -> ProcessGroupConfig:
50
+ return self._config
51
+
52
+ @property
53
+ def name(self) -> str:
54
+ return self._config.name
55
+
56
+ @property
57
+ def context(self) -> AbstractServerContext:
58
+ return self._context
59
+
60
+ def __repr__(self):
61
+ # repr can't return anything other than a native string, but the name might be unicode - a problem on Python 2.
62
+ name = self._config.name
63
+ return f'<{self.__class__.__name__} instance at {id(self)} named {name}>'
64
+
65
+ def remove_logs(self) -> None:
66
+ for process in self._processes.values():
67
+ process.remove_logs()
68
+
69
+ def reopen_logs(self) -> None:
70
+ for process in self._processes.values():
71
+ process.reopen_logs()
72
+
73
+ def stop_all(self) -> None:
74
+ processes = list(self._processes.values())
75
+ processes.sort()
76
+ processes.reverse() # stop in desc priority order
77
+
78
+ for proc in processes:
79
+ state = proc.get_state()
80
+ if state == ProcessState.RUNNING:
81
+ # RUNNING -> STOPPING
82
+ proc.stop()
83
+
84
+ elif state == ProcessState.STARTING:
85
+ # STARTING -> STOPPING
86
+ proc.stop()
87
+
88
+ elif state == ProcessState.BACKOFF:
89
+ # BACKOFF -> FATAL
90
+ proc.give_up()
91
+
92
+ def get_unstopped_processes(self) -> ta.List[AbstractSubprocess]:
93
+ return [x for x in self._processes.values() if not x.get_state().stopped]
94
+
95
+ def get_dispatchers(self) -> ta.Dict[int, Dispatcher]:
96
+ dispatchers: dict = {}
97
+ for process in self._processes.values():
98
+ dispatchers.update(process.get_dispatchers())
99
+ return dispatchers
100
+
101
+ def before_remove(self) -> None:
102
+ pass
103
+
104
+ def transition(self) -> None:
105
+ for proc in self._processes.values():
106
+ proc.transition()
107
+
108
+ def after_setuid(self) -> None:
109
+ for proc in self._processes.values():
110
+ proc.create_auto_child_logs()
111
+
112
+
113
+ ##
114
+
115
+
116
+ class ProcessGroups:
117
+ def __init__(
118
+ self,
119
+ *,
120
+ event_callbacks: EventCallbacks,
121
+ ) -> None:
122
+ super().__init__()
123
+
124
+ self._event_callbacks = event_callbacks
125
+
126
+ self._by_name: ta.Dict[str, ProcessGroup] = {}
127
+
128
+ def get(self, name: str) -> ta.Optional[ProcessGroup]:
129
+ return self._by_name.get(name)
130
+
131
+ def __getitem__(self, name: str) -> ProcessGroup:
132
+ return self._by_name[name]
133
+
134
+ def __len__(self) -> int:
135
+ return len(self._by_name)
136
+
137
+ def __iter__(self) -> ta.Iterator[ProcessGroup]:
138
+ return iter(self._by_name.values())
139
+
140
+ def all(self) -> ta.Mapping[str, ProcessGroup]:
141
+ return self._by_name
142
+
143
+ def add(self, group: ProcessGroup) -> None:
144
+ if (name := group.name) in self._by_name:
145
+ raise KeyError(f'Process group already exists: {name}')
146
+
147
+ self._by_name[name] = group
148
+
149
+ self._event_callbacks.notify(ProcessGroupAddedEvent(name))
150
+
151
+ def remove(self, name: str) -> None:
152
+ group = self._by_name[name]
153
+
154
+ group.before_remove()
155
+
156
+ del self._by_name[name]
157
+
158
+ self._event_callbacks.notify(ProcessGroupRemovedEvent(name))
159
+
160
+ def clear(self) -> None:
161
+ # FIXME: events?
162
+ self._by_name.clear()
@@ -0,0 +1,81 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import functools
3
+ import typing as ta
4
+
5
+ from omlish.lite.inject import Injector
6
+ from omlish.lite.inject import InjectorBindingOrBindings
7
+ from omlish.lite.inject import InjectorBindings
8
+ from omlish.lite.inject import inj
9
+
10
+ from .configs import ProcessConfig
11
+ from .configs import ProcessGroupConfig
12
+ from .configs import ServerConfig
13
+ from .context import ServerContext
14
+ from .context import ServerEpoch
15
+ from .events import EventCallbacks
16
+ from .groups import ProcessGroup
17
+ from .groups import SubprocessFactory
18
+ from .poller import Poller
19
+ from .poller import get_poller_impl
20
+ from .process import InheritedFds
21
+ from .process import Subprocess
22
+ from .signals import SignalReceiver
23
+ from .supervisor import ProcessGroupFactory
24
+ from .supervisor import ProcessGroups
25
+ from .supervisor import SignalHandler
26
+ from .supervisor import Supervisor
27
+ from .types import AbstractProcessGroup
28
+ from .types import AbstractServerContext
29
+ from .types import AbstractSubprocess
30
+
31
+
32
+ ##
33
+
34
+
35
+ def bind_server(
36
+ config: ServerConfig,
37
+ *,
38
+ server_epoch: ta.Optional[ServerEpoch] = None,
39
+ inherited_fds: ta.Optional[InheritedFds] = None,
40
+ ) -> InjectorBindings:
41
+ lst: ta.List[InjectorBindingOrBindings] = [
42
+ inj.bind(config),
43
+
44
+ inj.bind(get_poller_impl(), key=Poller, singleton=True),
45
+
46
+ inj.bind(ServerContext, singleton=True),
47
+ inj.bind(AbstractServerContext, to_key=ServerContext),
48
+
49
+ inj.bind(EventCallbacks, singleton=True),
50
+
51
+ inj.bind(SignalReceiver, singleton=True),
52
+
53
+ inj.bind(SignalHandler, singleton=True),
54
+ inj.bind(ProcessGroups, singleton=True),
55
+ inj.bind(Supervisor, singleton=True),
56
+ ]
57
+
58
+ #
59
+
60
+ def make_process_group_factory(injector: Injector) -> ProcessGroupFactory:
61
+ def inner(group_config: ProcessGroupConfig) -> ProcessGroup:
62
+ return injector.inject(functools.partial(ProcessGroup, group_config))
63
+ return ProcessGroupFactory(inner)
64
+ lst.append(inj.bind(make_process_group_factory))
65
+
66
+ def make_subprocess_factory(injector: Injector) -> SubprocessFactory:
67
+ def inner(process_config: ProcessConfig, group: AbstractProcessGroup) -> AbstractSubprocess:
68
+ return injector.inject(functools.partial(Subprocess, process_config, group))
69
+ return SubprocessFactory(inner)
70
+ lst.append(inj.bind(make_subprocess_factory))
71
+
72
+ #
73
+
74
+ if server_epoch is not None:
75
+ lst.append(inj.bind(server_epoch, key=ServerEpoch))
76
+ if inherited_fds is not None:
77
+ lst.append(inj.bind(inherited_fds, key=InheritedFds))
78
+
79
+ #
80
+
81
+ return inj.as_bindings(*lst)
@@ -1,79 +1,26 @@
1
1
  #!/usr/bin/env python3
2
2
  # ruff: noqa: UP006 UP007
3
3
  # @omlish-amalg ../scripts/supervisor.py
4
- import functools
5
4
  import itertools
6
5
  import os.path
7
6
  import typing as ta
8
7
 
9
- from omlish.lite.inject import Injector
10
- from omlish.lite.inject import InjectorBindingOrBindings
11
- from omlish.lite.inject import InjectorBindings
8
+ from omlish.lite.http.coroserver import CoroHttpServer
12
9
  from omlish.lite.inject import inj
13
10
  from omlish.lite.journald import journald_log_handler_factory
14
11
  from omlish.lite.logs import configure_standard_logging
15
12
 
16
13
  from ..configs import read_config_file
17
- from .compat import ExitNow
18
- from .compat import get_open_fds
19
- from .configs import ProcessConfig
20
- from .configs import ProcessGroupConfig
21
14
  from .configs import ServerConfig
22
15
  from .configs import prepare_server_config
23
- from .context import InheritedFds
24
16
  from .context import ServerContext
25
17
  from .context import ServerEpoch
26
- from .process import ProcessGroup
27
- from .process import Subprocess
28
- from .process import SubprocessFactory
29
- from .states import SupervisorStates
30
- from .supervisor import ProcessGroupFactory
18
+ from .inject import bind_server
19
+ from .process import InheritedFds
20
+ from .states import SupervisorState
31
21
  from .supervisor import Supervisor
32
- from .types import AbstractServerContext
33
-
34
-
35
- ##
36
-
37
-
38
- def build_server_bindings(
39
- config: ServerConfig,
40
- *,
41
- server_epoch: ta.Optional[ServerEpoch] = None,
42
- inherited_fds: ta.Optional[InheritedFds] = None,
43
- ) -> InjectorBindings:
44
- lst: ta.List[InjectorBindingOrBindings] = [
45
- inj.bind(config),
46
-
47
- inj.bind(ServerContext, singleton=True),
48
- inj.bind(AbstractServerContext, to_key=ServerContext),
49
-
50
- inj.bind(Supervisor, singleton=True),
51
- ]
52
-
53
- #
54
-
55
- def make_process_group_factory(injector: Injector) -> ProcessGroupFactory:
56
- def inner(group_config: ProcessGroupConfig) -> ProcessGroup:
57
- return injector.inject(functools.partial(ProcessGroup, group_config))
58
- return ProcessGroupFactory(inner)
59
- lst.append(inj.bind(make_process_group_factory))
60
-
61
- def make_subprocess_factory(injector: Injector) -> SubprocessFactory:
62
- def inner(process_config: ProcessConfig, group: ProcessGroup) -> Subprocess:
63
- return injector.inject(functools.partial(Subprocess, process_config, group))
64
- return SubprocessFactory(inner)
65
- lst.append(inj.bind(make_subprocess_factory))
66
-
67
- #
68
-
69
- if server_epoch is not None:
70
- lst.append(inj.bind(server_epoch, key=ServerEpoch))
71
- if inherited_fds is not None:
72
- lst.append(inj.bind(inherited_fds, key=InheritedFds))
73
-
74
- #
75
-
76
- return inj.as_bindings(*lst)
22
+ from .utils import ExitNow
23
+ from .utils import get_open_fds
77
24
 
78
25
 
79
26
  ##
@@ -84,6 +31,10 @@ def main(
84
31
  *,
85
32
  no_logging: bool = False,
86
33
  ) -> None:
34
+ server_cls = CoroHttpServer # noqa
35
+
36
+ #
37
+
87
38
  import argparse
88
39
 
89
40
  parser = argparse.ArgumentParser()
@@ -105,9 +56,9 @@ def main(
105
56
 
106
57
  #
107
58
 
108
- initial_fds: ta.Optional[InheritedFds] = None
59
+ inherited_fds: ta.Optional[InheritedFds] = None
109
60
  if args.inherit_initial_fds:
110
- initial_fds = InheritedFds(get_open_fds(0x10000))
61
+ inherited_fds = InheritedFds(get_open_fds(0x10000))
111
62
 
112
63
  # if we hup, restart by making a new Supervisor()
113
64
  for epoch in itertools.count():
@@ -117,21 +68,21 @@ def main(
117
68
  prepare=prepare_server_config,
118
69
  )
119
70
 
120
- injector = inj.create_injector(build_server_bindings(
71
+ injector = inj.create_injector(bind_server(
121
72
  config,
122
73
  server_epoch=ServerEpoch(epoch),
123
- inherited_fds=initial_fds,
74
+ inherited_fds=inherited_fds,
124
75
  ))
125
76
 
126
- context = injector.provide(ServerContext)
127
- supervisor = injector.provide(Supervisor)
77
+ context = injector[ServerContext]
78
+ supervisor = injector[Supervisor]
128
79
 
129
80
  try:
130
81
  supervisor.main()
131
82
  except ExitNow:
132
83
  pass
133
84
 
134
- if context.state < SupervisorStates.RESTARTING:
85
+ if context.state < SupervisorState.RESTARTING:
135
86
  break
136
87
 
137
88
 
@@ -8,8 +8,7 @@ import typing as ta
8
8
  from omlish.lite.logs import log
9
9
 
10
10
 
11
- class BasePoller(abc.ABC):
12
-
11
+ class Poller(abc.ABC):
13
12
  def __init__(self) -> None:
14
13
  super().__init__()
15
14
 
@@ -43,35 +42,34 @@ class BasePoller(abc.ABC):
43
42
  pass
44
43
 
45
44
 
46
- class SelectPoller(BasePoller):
47
-
45
+ class SelectPoller(Poller):
48
46
  def __init__(self) -> None:
49
47
  super().__init__()
50
48
 
51
- self._readables: ta.Set[int] = set()
52
- self._writables: ta.Set[int] = set()
49
+ self._readable: ta.Set[int] = set()
50
+ self._writable: ta.Set[int] = set()
53
51
 
54
52
  def register_readable(self, fd: int) -> None:
55
- self._readables.add(fd)
53
+ self._readable.add(fd)
56
54
 
57
55
  def register_writable(self, fd: int) -> None:
58
- self._writables.add(fd)
56
+ self._writable.add(fd)
59
57
 
60
58
  def unregister_readable(self, fd: int) -> None:
61
- self._readables.discard(fd)
59
+ self._readable.discard(fd)
62
60
 
63
61
  def unregister_writable(self, fd: int) -> None:
64
- self._writables.discard(fd)
62
+ self._writable.discard(fd)
65
63
 
66
64
  def unregister_all(self) -> None:
67
- self._readables.clear()
68
- self._writables.clear()
65
+ self._readable.clear()
66
+ self._writable.clear()
69
67
 
70
68
  def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
71
69
  try:
72
70
  r, w, x = select.select(
73
- self._readables,
74
- self._writables,
71
+ self._readable,
72
+ self._writable,
75
73
  [], timeout,
76
74
  )
77
75
  except OSError as err:
@@ -86,7 +84,7 @@ class SelectPoller(BasePoller):
86
84
  return r, w
87
85
 
88
86
 
89
- class PollPoller(BasePoller):
87
+ class PollPoller(Poller):
90
88
  _READ = select.POLLIN | select.POLLPRI | select.POLLHUP
91
89
  _WRITE = select.POLLOUT
92
90
 
@@ -94,40 +92,40 @@ class PollPoller(BasePoller):
94
92
  super().__init__()
95
93
 
96
94
  self._poller = select.poll()
97
- self._readables: set[int] = set()
98
- self._writables: set[int] = set()
95
+ self._readable: set[int] = set()
96
+ self._writable: set[int] = set()
99
97
 
100
98
  def register_readable(self, fd: int) -> None:
101
99
  self._poller.register(fd, self._READ)
102
- self._readables.add(fd)
100
+ self._readable.add(fd)
103
101
 
104
102
  def register_writable(self, fd: int) -> None:
105
103
  self._poller.register(fd, self._WRITE)
106
- self._writables.add(fd)
104
+ self._writable.add(fd)
107
105
 
108
106
  def unregister_readable(self, fd: int) -> None:
109
- self._readables.discard(fd)
107
+ self._readable.discard(fd)
110
108
  self._poller.unregister(fd)
111
- if fd in self._writables:
109
+ if fd in self._writable:
112
110
  self._poller.register(fd, self._WRITE)
113
111
 
114
112
  def unregister_writable(self, fd: int) -> None:
115
- self._writables.discard(fd)
113
+ self._writable.discard(fd)
116
114
  self._poller.unregister(fd)
117
- if fd in self._readables:
115
+ if fd in self._readable:
118
116
  self._poller.register(fd, self._READ)
119
117
 
120
118
  def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
121
119
  fds = self._poll_fds(timeout) # type: ignore
122
- readables, writables = [], []
120
+ readable, writable = [], []
123
121
  for fd, eventmask in fds:
124
122
  if self._ignore_invalid(fd, eventmask):
125
123
  continue
126
124
  if eventmask & self._READ:
127
- readables.append(fd)
125
+ readable.append(fd)
128
126
  if eventmask & self._WRITE:
129
- writables.append(fd)
130
- return readables, writables
127
+ writable.append(fd)
128
+ return readable, writable
131
129
 
132
130
  def _poll_fds(self, timeout: float) -> ta.List[ta.Tuple[int, int]]:
133
131
  try:
@@ -143,41 +141,41 @@ class PollPoller(BasePoller):
143
141
  # POLLNVAL means `fd` value is invalid, not open. When a process quits it's `fd`s are closed so there is no
144
142
  # more reason to keep this `fd` registered If the process restarts it's `fd`s are registered again.
145
143
  self._poller.unregister(fd)
146
- self._readables.discard(fd)
147
- self._writables.discard(fd)
144
+ self._readable.discard(fd)
145
+ self._writable.discard(fd)
148
146
  return True
149
147
  return False
150
148
 
151
149
 
152
150
  if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
153
- class KqueuePoller(BasePoller):
151
+ class KqueuePoller(Poller):
154
152
  max_events = 1000
155
153
 
156
154
  def __init__(self) -> None:
157
155
  super().__init__()
158
156
 
159
157
  self._kqueue: ta.Optional[ta.Any] = select.kqueue()
160
- self._readables: set[int] = set()
161
- self._writables: set[int] = set()
158
+ self._readable: set[int] = set()
159
+ self._writable: set[int] = set()
162
160
 
163
161
  def register_readable(self, fd: int) -> None:
164
- self._readables.add(fd)
162
+ self._readable.add(fd)
165
163
  kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD)
166
164
  self._kqueue_control(fd, kevent)
167
165
 
168
166
  def register_writable(self, fd: int) -> None:
169
- self._writables.add(fd)
167
+ self._writable.add(fd)
170
168
  kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_ADD)
171
169
  self._kqueue_control(fd, kevent)
172
170
 
173
171
  def unregister_readable(self, fd: int) -> None:
174
172
  kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_DELETE)
175
- self._readables.discard(fd)
173
+ self._readable.discard(fd)
176
174
  self._kqueue_control(fd, kevent)
177
175
 
178
176
  def unregister_writable(self, fd: int) -> None:
179
177
  kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_DELETE)
180
- self._writables.discard(fd)
178
+ self._writable.discard(fd)
181
179
  self._kqueue_control(fd, kevent)
182
180
 
183
181
  def _kqueue_control(self, fd: int, kevent: 'select.kevent') -> None:
@@ -190,32 +188,32 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
190
188
  raise
191
189
 
192
190
  def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
193
- readables, writables = [], [] # type: ignore
191
+ readable, writable = [], [] # type: ignore
194
192
 
195
193
  try:
196
194
  kevents = self._kqueue.control(None, self.max_events, timeout) # type: ignore
197
195
  except OSError as error:
198
196
  if error.errno == errno.EINTR:
199
197
  log.debug('EINTR encountered in poll')
200
- return readables, writables
198
+ return readable, writable
201
199
  raise
202
200
 
203
201
  for kevent in kevents:
204
202
  if kevent.filter == select.KQ_FILTER_READ:
205
- readables.append(kevent.ident)
203
+ readable.append(kevent.ident)
206
204
  if kevent.filter == select.KQ_FILTER_WRITE:
207
- writables.append(kevent.ident)
205
+ writable.append(kevent.ident)
208
206
 
209
- return readables, writables
207
+ return readable, writable
210
208
 
211
209
  def before_daemonize(self) -> None:
212
210
  self.close()
213
211
 
214
212
  def after_daemonize(self) -> None:
215
213
  self._kqueue = select.kqueue()
216
- for fd in self._readables:
214
+ for fd in self._readable:
217
215
  self.register_readable(fd)
218
- for fd in self._writables:
216
+ for fd in self._writable:
219
217
  self.register_writable(fd)
220
218
 
221
219
  def close(self) -> None:
@@ -226,13 +224,13 @@ else:
226
224
  KqueuePoller = None
227
225
 
228
226
 
229
- Poller: ta.Type[BasePoller]
230
- if (
231
- sys.platform == 'darwin' or sys.platform.startswith('freebsd') and
232
- hasattr(select, 'kqueue') and KqueuePoller is not None
233
- ):
234
- Poller = KqueuePoller
235
- elif hasattr(select, 'poll'):
236
- Poller = PollPoller
237
- else:
238
- Poller = SelectPoller
227
+ def get_poller_impl() -> ta.Type[Poller]:
228
+ if (
229
+ sys.platform == 'darwin' or sys.platform.startswith('freebsd') and
230
+ hasattr(select, 'kqueue') and KqueuePoller is not None
231
+ ):
232
+ return KqueuePoller
233
+ elif hasattr(select, 'poll'):
234
+ return PollPoller
235
+ else:
236
+ return SelectPoller