ominfra 0.0.0.dev122__py3-none-any.whl → 0.0.0.dev123__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.
@@ -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()
@@ -14,22 +14,30 @@ from omlish.lite.journald import journald_log_handler_factory
14
14
  from omlish.lite.logs import configure_standard_logging
15
15
 
16
16
  from ..configs import read_config_file
17
- from .compat import ExitNow
18
- from .compat import get_open_fds
19
17
  from .configs import ProcessConfig
20
18
  from .configs import ProcessGroupConfig
21
19
  from .configs import ServerConfig
22
20
  from .configs import prepare_server_config
23
- from .context import InheritedFds
24
21
  from .context import ServerContext
25
22
  from .context import ServerEpoch
26
- from .process import ProcessGroup
23
+ from .events import EventCallbacks
24
+ from .groups import ProcessGroup
25
+ from .groups import SubprocessFactory
26
+ from .poller import Poller
27
+ from .poller import get_poller_impl
28
+ from .process import InheritedFds
27
29
  from .process import Subprocess
28
- from .process import SubprocessFactory
29
- from .states import SupervisorStates
30
+ from .signals import SignalReceiver
31
+ from .states import SupervisorState
30
32
  from .supervisor import ProcessGroupFactory
33
+ from .supervisor import ProcessGroups
34
+ from .supervisor import SignalHandler
31
35
  from .supervisor import Supervisor
36
+ from .types import AbstractProcessGroup
32
37
  from .types import AbstractServerContext
38
+ from .types import AbstractSubprocess
39
+ from .utils import ExitNow
40
+ from .utils import get_open_fds
33
41
 
34
42
 
35
43
  ##
@@ -44,9 +52,17 @@ def build_server_bindings(
44
52
  lst: ta.List[InjectorBindingOrBindings] = [
45
53
  inj.bind(config),
46
54
 
55
+ inj.bind(get_poller_impl(), key=Poller, singleton=True),
56
+
47
57
  inj.bind(ServerContext, singleton=True),
48
58
  inj.bind(AbstractServerContext, to_key=ServerContext),
49
59
 
60
+ inj.bind(EventCallbacks, singleton=True),
61
+
62
+ inj.bind(SignalReceiver, singleton=True),
63
+
64
+ inj.bind(SignalHandler, singleton=True),
65
+ inj.bind(ProcessGroups, singleton=True),
50
66
  inj.bind(Supervisor, singleton=True),
51
67
  ]
52
68
 
@@ -59,7 +75,7 @@ def build_server_bindings(
59
75
  lst.append(inj.bind(make_process_group_factory))
60
76
 
61
77
  def make_subprocess_factory(injector: Injector) -> SubprocessFactory:
62
- def inner(process_config: ProcessConfig, group: ProcessGroup) -> Subprocess:
78
+ def inner(process_config: ProcessConfig, group: AbstractProcessGroup) -> AbstractSubprocess:
63
79
  return injector.inject(functools.partial(Subprocess, process_config, group))
64
80
  return SubprocessFactory(inner)
65
81
  lst.append(inj.bind(make_subprocess_factory))
@@ -105,9 +121,9 @@ def main(
105
121
 
106
122
  #
107
123
 
108
- initial_fds: ta.Optional[InheritedFds] = None
124
+ inherited_fds: ta.Optional[InheritedFds] = None
109
125
  if args.inherit_initial_fds:
110
- initial_fds = InheritedFds(get_open_fds(0x10000))
126
+ inherited_fds = InheritedFds(get_open_fds(0x10000))
111
127
 
112
128
  # if we hup, restart by making a new Supervisor()
113
129
  for epoch in itertools.count():
@@ -120,7 +136,7 @@ def main(
120
136
  injector = inj.create_injector(build_server_bindings(
121
137
  config,
122
138
  server_epoch=ServerEpoch(epoch),
123
- inherited_fds=initial_fds,
139
+ inherited_fds=inherited_fds,
124
140
  ))
125
141
 
126
142
  context = injector.provide(ServerContext)
@@ -131,7 +147,7 @@ def main(
131
147
  except ExitNow:
132
148
  pass
133
149
 
134
- if context.state < SupervisorStates.RESTARTING:
150
+ if context.state < SupervisorState.RESTARTING:
135
151
  break
136
152
 
137
153
 
@@ -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