ominfra 0.0.0.dev121__py3-none-any.whl → 0.0.0.dev123__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()
@@ -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