ominfra 0.0.0.dev128__py3-none-any.whl → 0.0.0.dev129__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,40 +1,38 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import signal
2
+ import errno
3
+ import os
3
4
  import time
4
5
  import typing as ta
5
6
 
6
7
  from omlish.lite.check import check_isinstance
7
- from omlish.lite.check import check_not_none
8
8
  from omlish.lite.logs import log
9
9
  from omlish.lite.typing import Func1
10
10
 
11
11
  from .configs import ProcessGroupConfig
12
- from .context import ServerContextImpl
13
- from .dispatchers import Dispatchers
12
+ from .configs import ServerConfig
14
13
  from .events import TICK_EVENTS
15
14
  from .events import EventCallbacks
16
15
  from .events import SupervisorRunningEvent
17
16
  from .events import SupervisorStoppingEvent
18
17
  from .groups import ProcessGroup
19
18
  from .groups import ProcessGroupManager
19
+ from .io import IoManager
20
20
  from .poller import Poller
21
21
  from .process import PidHistory
22
22
  from .setup import SupervisorSetup
23
+ from .signals import SignalHandler
23
24
  from .states import SupervisorState
24
- from .types import OutputDispatcher
25
+ from .types import ExitNow
25
26
  from .types import Process
27
+ from .types import SupervisorStateManager
26
28
  from .utils.os import decode_wait_status
27
- from .utils.signals import SignalReceiver
28
- from .utils.signals import sig_name
29
+ from .utils.ostypes import Pid
30
+ from .utils.ostypes import Rc
29
31
 
30
32
 
31
33
  ##
32
34
 
33
35
 
34
- class ExitNow(Exception): # noqa
35
- pass
36
-
37
-
38
36
  def timeslice(period: int, when: float) -> int:
39
37
  return int(when - (when % period))
40
38
 
@@ -42,59 +40,18 @@ def timeslice(period: int, when: float) -> int:
42
40
  ##
43
41
 
44
42
 
45
- class SignalHandler:
46
- def __init__(
47
- self,
48
- *,
49
- context: ServerContextImpl,
50
- signal_receiver: SignalReceiver,
51
- process_groups: ProcessGroupManager,
52
- ) -> None:
43
+ class SupervisorStateManagerImpl(SupervisorStateManager):
44
+ def __init__(self) -> None:
53
45
  super().__init__()
54
46
 
55
- self._context = context
56
- self._signal_receiver = signal_receiver
57
- self._process_groups = process_groups
58
-
59
- def set_signals(self) -> None:
60
- self._signal_receiver.install(
61
- signal.SIGTERM,
62
- signal.SIGINT,
63
- signal.SIGQUIT,
64
- signal.SIGHUP,
65
- signal.SIGCHLD,
66
- signal.SIGUSR2,
67
- )
68
-
69
- def handle_signals(self) -> None:
70
- sig = self._signal_receiver.get_signal()
71
- if not sig:
72
- return
73
-
74
- if sig in (signal.SIGTERM, signal.SIGINT, signal.SIGQUIT):
75
- log.warning('received %s indicating exit request', sig_name(sig))
76
- self._context.set_state(SupervisorState.SHUTDOWN)
77
-
78
- elif sig == signal.SIGHUP:
79
- if self._context.state == SupervisorState.SHUTDOWN:
80
- log.warning('ignored %s indicating restart request (shutdown in progress)', sig_name(sig)) # noqa
81
- else:
82
- log.warning('received %s indicating restart request', sig_name(sig)) # noqa
83
- self._context.set_state(SupervisorState.RESTARTING)
47
+ self._state: SupervisorState = SupervisorState.RUNNING
84
48
 
85
- elif sig == signal.SIGCHLD:
86
- log.debug('received %s indicating a child quit', sig_name(sig))
87
-
88
- elif sig == signal.SIGUSR2:
89
- log.info('received %s indicating log reopen request', sig_name(sig))
90
-
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()
49
+ @property
50
+ def state(self) -> SupervisorState:
51
+ return self._state
95
52
 
96
- else:
97
- log.debug('received %s indicating nothing', sig_name(sig))
53
+ def set_state(self, state: SupervisorState) -> None:
54
+ self._state = state
98
55
 
99
56
 
100
57
  ##
@@ -108,7 +65,7 @@ class Supervisor:
108
65
  def __init__(
109
66
  self,
110
67
  *,
111
- context: ServerContextImpl,
68
+ config: ServerConfig,
112
69
  poller: Poller,
113
70
  process_groups: ProcessGroupManager,
114
71
  signal_handler: SignalHandler,
@@ -116,10 +73,12 @@ class Supervisor:
116
73
  process_group_factory: ProcessGroupFactory,
117
74
  pid_history: PidHistory,
118
75
  setup: SupervisorSetup,
76
+ states: SupervisorStateManager,
77
+ io: IoManager,
119
78
  ) -> None:
120
79
  super().__init__()
121
80
 
122
- self._context = context
81
+ self._config = config
123
82
  self._poller = poller
124
83
  self._process_groups = process_groups
125
84
  self._signal_handler = signal_handler
@@ -127,6 +86,8 @@ class Supervisor:
127
86
  self._process_group_factory = process_group_factory
128
87
  self._pid_history = pid_history
129
88
  self._setup = setup
89
+ self._states = states
90
+ self._io = io
130
91
 
131
92
  self._ticks: ta.Dict[int, float] = {}
132
93
  self._stop_groups: ta.Optional[ta.List[ProcessGroup]] = None # list used for priority ordered shutdown
@@ -136,11 +97,8 @@ class Supervisor:
136
97
  #
137
98
 
138
99
  @property
139
- def context(self) -> ServerContextImpl:
140
- return self._context
141
-
142
- def get_state(self) -> SupervisorState:
143
- return self._context.state
100
+ def state(self) -> SupervisorState:
101
+ return self._states.state
144
102
 
145
103
  #
146
104
 
@@ -181,7 +139,7 @@ class Supervisor:
181
139
  log.info('waiting for %s to die', namestr)
182
140
  self._last_shutdown_report = now
183
141
  for proc in unstopped:
184
- log.debug('%s state: %s', proc.config.name, proc.get_state().name)
142
+ log.debug('%s state: %s', proc.config.name, proc.state.name)
185
143
 
186
144
  return unstopped
187
145
 
@@ -205,7 +163,7 @@ class Supervisor:
205
163
  self._event_callbacks.clear()
206
164
 
207
165
  try:
208
- for config in self._context.config.groups or []:
166
+ for config in self._config.groups or []:
209
167
  self.add_process_group(config)
210
168
 
211
169
  self._signal_handler.set_signals()
@@ -229,7 +187,7 @@ class Supervisor:
229
187
  self._signal_handler.handle_signals()
230
188
  self._tick()
231
189
 
232
- if self._context.state < SupervisorState.RUNNING:
190
+ if self._states.state < SupervisorState.RUNNING:
233
191
  self._ordered_stop_groups_phase_2()
234
192
 
235
193
  def _ordered_stop_groups_phase_1(self) -> None:
@@ -248,20 +206,11 @@ class Supervisor:
248
206
  # down, so push it back on to the end of the stop group queue
249
207
  self._stop_groups.append(group)
250
208
 
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
-
258
209
  def _poll(self) -> None:
259
- dispatchers = self.get_dispatchers()
260
-
261
210
  sorted_groups = list(self._process_groups)
262
211
  sorted_groups.sort()
263
212
 
264
- if self._context.state < SupervisorState.RUNNING:
213
+ if self._states.state < SupervisorState.RUNNING:
265
214
  if not self._stopping:
266
215
  # first time, set the stopping flag, do a notification and set stop_groups
267
216
  self._stopping = True
@@ -274,54 +223,7 @@ class Supervisor:
274
223
  # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
275
224
  raise ExitNow
276
225
 
277
- for fd, dispatcher in dispatchers.items():
278
- if dispatcher.readable():
279
- self._poller.register_readable(fd)
280
- if dispatcher.writable():
281
- self._poller.register_writable(fd)
282
-
283
- timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
284
- r, w = self._poller.poll(timeout)
285
-
286
- for fd in r:
287
- if fd in dispatchers:
288
- try:
289
- dispatcher = dispatchers[fd]
290
- log.debug('read event caused by %r', dispatcher)
291
- dispatcher.handle_read_event()
292
- if not dispatcher.readable():
293
- self._poller.unregister_readable(fd)
294
- except ExitNow:
295
- raise
296
- except Exception: # noqa
297
- dispatchers[fd].handle_error()
298
- else:
299
- # if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
300
- # time, which may cause 100% cpu usage
301
- log.debug('unexpected read event from fd %r', fd)
302
- try:
303
- self._poller.unregister_readable(fd)
304
- except Exception: # noqa
305
- pass
306
-
307
- for fd in w:
308
- if fd in dispatchers:
309
- try:
310
- dispatcher = dispatchers[fd]
311
- log.debug('write event caused by %r', dispatcher)
312
- dispatcher.handle_write_event()
313
- if not dispatcher.writable():
314
- self._poller.unregister_writable(fd)
315
- except ExitNow:
316
- raise
317
- except Exception: # noqa
318
- dispatchers[fd].handle_error()
319
- else:
320
- log.debug('unexpected write event from fd %r', fd)
321
- try:
322
- self._poller.unregister_writable(fd)
323
- except Exception: # noqa
324
- pass
226
+ self._io.poll()
325
227
 
326
228
  for group in sorted_groups:
327
229
  for process in group:
@@ -331,17 +233,17 @@ class Supervisor:
331
233
  if depth >= 100:
332
234
  return
333
235
 
334
- pid, sts = self._context.waitpid()
335
- if not pid:
236
+ wp = waitpid()
237
+ if wp is None or not wp.pid:
336
238
  return
337
239
 
338
- process = self._pid_history.get(pid, None)
240
+ process = self._pid_history.get(wp.pid, None)
339
241
  if process is None:
340
- _, msg = decode_wait_status(check_not_none(sts))
341
- log.info('reaped unknown pid %s (%s)', pid, msg)
242
+ _, msg = decode_wait_status(wp.sts)
243
+ log.info('reaped unknown pid %s (%s)', wp.pid, msg)
342
244
  else:
343
- process.finish(check_not_none(sts))
344
- del self._pid_history[pid]
245
+ process.finish(wp.sts)
246
+ del self._pid_history[wp.pid]
345
247
 
346
248
  if not once:
347
249
  # keep reaping until no more kids to reap, but don't recurse infinitely
@@ -366,3 +268,31 @@ class Supervisor:
366
268
  if this_tick != last_tick:
367
269
  self._ticks[period] = this_tick
368
270
  self._event_callbacks.notify(event(this_tick, self))
271
+
272
+
273
+ ##
274
+
275
+
276
+ class WaitedPid(ta.NamedTuple):
277
+ pid: Pid
278
+ sts: Rc
279
+
280
+
281
+ def waitpid() -> ta.Optional[WaitedPid]:
282
+ # Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
283
+ # still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
284
+ # waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
285
+ # normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
286
+ # call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
287
+ # lying around.
288
+ try:
289
+ pid, sts = os.waitpid(-1, os.WNOHANG)
290
+ except OSError as exc:
291
+ code = exc.args[0]
292
+ if code not in (errno.ECHILD, errno.EINTR):
293
+ log.critical('waitpid error %r; a process may not be cleaned up properly', code)
294
+ if code == errno.EINTR:
295
+ log.debug('EINTR during reap')
296
+ return None
297
+ else:
298
+ return WaitedPid(pid, sts) # type: ignore
@@ -5,7 +5,6 @@ import typing as ta
5
5
 
6
6
  from .configs import ProcessConfig
7
7
  from .configs import ProcessGroupConfig
8
- from .configs import ServerConfig
9
8
  from .states import ProcessState
10
9
  from .states import SupervisorState
11
10
  from .utils.collections import KeyedCollectionAccessors
@@ -21,6 +20,10 @@ if ta.TYPE_CHECKING:
21
20
  ##
22
21
 
23
22
 
23
+ class ExitNow(Exception): # noqa
24
+ pass
25
+
26
+
24
27
  ServerEpoch = ta.NewType('ServerEpoch', int)
25
28
 
26
29
 
@@ -44,12 +47,7 @@ class ConfigPriorityOrdered(abc.ABC):
44
47
  ##
45
48
 
46
49
 
47
- class ServerContext(abc.ABC):
48
- @property
49
- @abc.abstractmethod
50
- def config(self) -> ServerConfig:
51
- raise NotImplementedError
52
-
50
+ class SupervisorStateManager(abc.ABC):
53
51
  @property
54
52
  @abc.abstractmethod
55
53
  def state(self) -> SupervisorState:
@@ -64,11 +62,6 @@ class ServerContext(abc.ABC):
64
62
 
65
63
 
66
64
  class Dispatcher(abc.ABC):
67
- @property
68
- @abc.abstractmethod
69
- def process(self) -> 'Process':
70
- raise NotImplementedError
71
-
72
65
  @property
73
66
  @abc.abstractmethod
74
67
  def channel(self) -> str:
@@ -112,8 +105,32 @@ class Dispatcher(abc.ABC):
112
105
  def handle_write_event(self) -> None:
113
106
  raise TypeError
114
107
 
108
+ #
109
+
110
+ def handle_connect(self) -> None:
111
+ raise TypeError
112
+
113
+ def handle_close(self) -> None:
114
+ raise TypeError
115
+
116
+ def handle_accepted(self, sock, addr) -> None:
117
+ raise TypeError
118
+
119
+
120
+ class HasDispatchers(abc.ABC):
121
+ @abc.abstractmethod
122
+ def get_dispatchers(self) -> 'Dispatchers':
123
+ raise NotImplementedError
124
+
115
125
 
116
- class OutputDispatcher(Dispatcher, abc.ABC):
126
+ class ProcessDispatcher(Dispatcher, abc.ABC):
127
+ @property
128
+ @abc.abstractmethod
129
+ def process(self) -> 'Process':
130
+ raise NotImplementedError
131
+
132
+
133
+ class ProcessOutputDispatcher(ProcessDispatcher, abc.ABC):
117
134
  @abc.abstractmethod
118
135
  def remove_logs(self) -> None:
119
136
  raise NotImplementedError
@@ -123,7 +140,7 @@ class OutputDispatcher(Dispatcher, abc.ABC):
123
140
  raise NotImplementedError
124
141
 
125
142
 
126
- class InputDispatcher(Dispatcher, abc.ABC):
143
+ class ProcessInputDispatcher(ProcessDispatcher, abc.ABC):
127
144
  @abc.abstractmethod
128
145
  def write(self, chars: ta.Union[bytes, str]) -> None:
129
146
  raise NotImplementedError
@@ -136,7 +153,11 @@ class InputDispatcher(Dispatcher, abc.ABC):
136
153
  ##
137
154
 
138
155
 
139
- class Process(ConfigPriorityOrdered, abc.ABC):
156
+ class Process(
157
+ ConfigPriorityOrdered,
158
+ HasDispatchers,
159
+ abc.ABC,
160
+ ):
140
161
  @property
141
162
  @abc.abstractmethod
142
163
  def name(self) -> str:
@@ -159,11 +180,6 @@ class Process(ConfigPriorityOrdered, abc.ABC):
159
180
 
160
181
  #
161
182
 
162
- @property
163
- @abc.abstractmethod
164
- def context(self) -> ServerContext:
165
- raise NotImplementedError
166
-
167
183
  @abc.abstractmethod
168
184
  def finish(self, sts: Rc) -> None:
169
185
  raise NotImplementedError
@@ -180,18 +196,15 @@ class Process(ConfigPriorityOrdered, abc.ABC):
180
196
  def transition(self) -> None:
181
197
  raise NotImplementedError
182
198
 
199
+ @property
183
200
  @abc.abstractmethod
184
- def get_state(self) -> ProcessState:
201
+ def state(self) -> ProcessState:
185
202
  raise NotImplementedError
186
203
 
187
204
  @abc.abstractmethod
188
205
  def after_setuid(self) -> None:
189
206
  raise NotImplementedError
190
207
 
191
- @abc.abstractmethod
192
- def get_dispatchers(self) -> 'Dispatchers':
193
- raise NotImplementedError
194
-
195
208
 
196
209
  ##
197
210
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev128
3
+ Version: 0.0.0.dev129
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omdev==0.0.0.dev128
16
- Requires-Dist: omlish==0.0.0.dev128
15
+ Requires-Dist: omdev==0.0.0.dev129
16
+ Requires-Dist: omlish==0.0.0.dev129
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -22,7 +22,7 @@ ominfra/clouds/aws/journald2aws/poster.py,sha256=hz1XuctW8GtLmfjhRvCFY6py52D4BzX
22
22
  ominfra/clouds/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  ominfra/clouds/gcp/auth.py,sha256=3PyfRJNgajjMqJFem3SKui0CqGeHEsZlvbRhuxFcZG8,1348
24
24
  ominfra/deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- ominfra/deploy/_executor.py,sha256=osVBYirgpX1iJKyLHf7JwKD3AhPqJrnCrjI4qVrNFJo,34662
25
+ ominfra/deploy/_executor.py,sha256=1DQxeHJjxtRGxdFO7PiPhzxwiJlm8K2BelJfLXb_OxU,35079
26
26
  ominfra/deploy/configs.py,sha256=qi0kwT7G2NH7dXLOQic-u6R3yeadup_QtvrjwWIggbM,435
27
27
  ominfra/deploy/remote.py,sha256=6ACmpXU1uBdyGs3Xsp97ktKFq30cJlzN9LRWNUWlGY4,2144
28
28
  ominfra/deploy/executor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
@@ -56,37 +56,38 @@ ominfra/journald/tailer.py,sha256=5abcFMfgi7fnY9ZEQe2ZVobaJxjQkeu6d9Kagw33a1w,33
56
56
  ominfra/manage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  ominfra/manage/manage.py,sha256=BttL8LFEknHZE_h2Pt5dAqbfUkv6qy43WI0raXBZ1a8,151
58
58
  ominfra/pyremote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- ominfra/pyremote/_runcommands.py,sha256=86bIzkhi5YLmtxQYyk8SBwPv8fCCfdu2NHHlxug4ACk,28498
59
+ ominfra/pyremote/_runcommands.py,sha256=bzN-253qyoKwS-5OIqPmfhxVJdt4HtrnspeUbpKKPWM,28915
60
60
  ominfra/pyremote/bootstrap.py,sha256=RvMO3YGaN1E4sgUi1JEtiPak8cjvqtc_vRCq1yqbeZg,3370
61
61
  ominfra/pyremote/runcommands.py,sha256=bviS0_TDIoZVAe4h-_iavbvJtVSFu8lnk7fQ5iasCWE,1571
62
62
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- ominfra/scripts/journald2aws.py,sha256=UpwV8P1ZE8ywXW-8xpCaTCBRWe94BuG_MS1CXfD3wcw,128547
64
- ominfra/scripts/supervisor.py,sha256=ZqUDlxRxSML5Ty0sOKuils35km5-9xjYBQF4yQDnVaY,222164
63
+ ominfra/scripts/journald2aws.py,sha256=1fqTSLiZE3BCCxdyj5QMCWGHN4leS5INORaDuKyRm_A,128964
64
+ ominfra/scripts/supervisor.py,sha256=y84cLJj98qvyHiwWf0bDly2Rwexvc9QaT0qrQx06lGc,224746
65
65
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
66
66
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
67
67
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
68
68
  ominfra/supervisor/configs.py,sha256=AhBlbifwDXc0acEhcbdv9jphJL-SBFODFDAWDVckzAE,3945
69
- ominfra/supervisor/context.py,sha256=UDJc0mVHHFg3T7MyO7gehWX1wwmFvw-7xeKLl-cC8jc,2595
70
- ominfra/supervisor/dispatchers.py,sha256=qWtVLx-4H3I-Io7OPcVgRpIYehGsA4q3Duo5ZWdUM4Y,970
71
- ominfra/supervisor/dispatchersimpl.py,sha256=t1VFcofj1kTH1q13Z-S1OUTXixPwgSqJkh5A5IkHKPA,10956
69
+ ominfra/supervisor/dispatchers.py,sha256=8yReui-dVPfvqYrHLsCU7M0siluOs1p5Up7Ag4nMAeI,991
70
+ ominfra/supervisor/dispatchersimpl.py,sha256=EZYxK_RbOkjMv4N3pjH6-_fBhXDmcxFhEyZEgEAbtUk,11294
72
71
  ominfra/supervisor/events.py,sha256=w3HQFrq-SuroYWoQfNFYeU1phnTvHTgsAqA6TGtAafI,6593
73
72
  ominfra/supervisor/exceptions.py,sha256=Qbu211H3CLlSmi9LsSikOwrcL5HgJP9ugvcKWlGTAoI,750
74
73
  ominfra/supervisor/groups.py,sha256=g5Zp_lkVhn1FSe6GSEPbaELincG5a46ctv1xpB-WmnQ,2163
75
- ominfra/supervisor/groupsimpl.py,sha256=_5Mxf6VdVV375KgwcuUTpErUH4FBpmzbNm3vbGB_JaQ,2304
76
- ominfra/supervisor/inject.py,sha256=ZCfloXxURjiVTutzudGFWybrVCvjDD_C3HayMATCMrY,3042
77
- ominfra/supervisor/main.py,sha256=RCCpMX4D2RKT8vZMF82pENKpXfW3GUdNgMEUzUtpqL0,4140
74
+ ominfra/supervisor/groupsimpl.py,sha256=nIrW4SmB0W6c2jOR_HhkfVcH4eHyLZnG1FJ0MCzc6mQ,2292
75
+ ominfra/supervisor/inject.py,sha256=J1XGUkgCuaD-MPASso-2sbTSoVuDDIhcGbazQLVjajY,3217
76
+ ominfra/supervisor/io.py,sha256=iYYqQO0Hu1FIYzYXdSGHEIHYW-tGm4SCWky_ZTHaKWY,2727
77
+ ominfra/supervisor/main.py,sha256=ebe7skFPfwXV2meMVRndhuLZmz-LiuHH1x1CgiarR0o,4132
78
78
  ominfra/supervisor/pipes.py,sha256=XrJ9lD04tPdzZD3xhhYKxpBKHWhZ0Ii315E78bgj7ws,2233
79
79
  ominfra/supervisor/poller.py,sha256=LnQVttPCm8a1UtnDvsho6zLw8NP-2_2VUiNM-d0w_FU,7776
80
80
  ominfra/supervisor/privileges.py,sha256=bO7rJGT7cMOBALK_4D4NiQnOS5dOYb14Sz66R-ymG24,2071
81
81
  ominfra/supervisor/process.py,sha256=UaubVxsxVqDnbuWVpTH0DTGbJGLO0vGJ9mNcvy2kCXM,217
82
- ominfra/supervisor/processimpl.py,sha256=0i0P3gdxuJsgcvy2VTKN6-NYgcBjozUaPsA_tUcEmb4,18769
82
+ ominfra/supervisor/processimpl.py,sha256=bFhf9RqAB_yNlMM5dM1_4m964T9m3vKThEv3n7OYQN4,18684
83
83
  ominfra/supervisor/setup.py,sha256=7HwwwI-WT_Z0WjZ9_l5Orr4K298nKKhQ1f_ZgGsi9TU,622
84
84
  ominfra/supervisor/setupimpl.py,sha256=S_YgCH3XzLsFIAriJROvDMUDh7OzVVJoxzEzCkbb4g4,9648
85
+ ominfra/supervisor/signals.py,sha256=jY52naUifcAjd6nICTP1ZW3IQSPsHB4cvbsJo8_QV_U,2196
85
86
  ominfra/supervisor/spawning.py,sha256=i1k3tmqWyU-KIN7kel-JVxTVGnLiTIVmZzlstJSZpjM,622
86
- ominfra/supervisor/spawningimpl.py,sha256=0InDyRPh7gAEX07lg6eUYMymX0RikHOz_ieAghxKx8Y,11063
87
+ ominfra/supervisor/spawningimpl.py,sha256=E702F10TexP5-V6i97U6ysiBcTKyLSPzQoJZtedUjQs,11140
87
88
  ominfra/supervisor/states.py,sha256=9yoNOSwalRcKEnCP9zG6tVS0oivo5tCeuH6AaaW7Jpc,890
88
- ominfra/supervisor/supervisor.py,sha256=3yBaGJHJGqDXlARjpHWg7371N6jb29HaFEI-_fSp-xU,12028
89
- ominfra/supervisor/types.py,sha256=i2C7ZBvLqjVliIYAYQnPx2WwIQ14HIVV1hTUgB2mbBM,4740
89
+ ominfra/supervisor/supervisor.py,sha256=bLl4cHu6G4Kr_nDAr6Q6jG6g5oRWIx0rQaPHGVG0WMQ,9359
90
+ ominfra/supervisor/types.py,sha256=TZYvdX_3XQ82PgpOOJF2PT5VMlOD9zqMVZhB-ZofTRI,4891
90
91
  ominfra/supervisor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
92
  ominfra/supervisor/utils/collections.py,sha256=vcfmVYS4QngMdtEI1DvdRIcubmy55Wj40NCzW27_rIY,1361
92
93
  ominfra/supervisor/utils/diag.py,sha256=ujz4gkW7p3wmbaKFM8Hz5eHEwpoUkbB8JeDvcHilCz0,705
@@ -102,9 +103,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
102
103
  ominfra/tailscale/cli.py,sha256=DSGp4hn5xwOW-l_u_InKlSF6kIobxtUtVssf_73STs0,3567
103
104
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
105
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
105
- ominfra-0.0.0.dev128.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
106
- ominfra-0.0.0.dev128.dist-info/METADATA,sha256=JV6Hw2ZOpDub73wDD4p69Qq9F6XbbKlthV7CmH6G_-k,731
107
- ominfra-0.0.0.dev128.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
108
- ominfra-0.0.0.dev128.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
109
- ominfra-0.0.0.dev128.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
110
- ominfra-0.0.0.dev128.dist-info/RECORD,,
106
+ ominfra-0.0.0.dev129.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
107
+ ominfra-0.0.0.dev129.dist-info/METADATA,sha256=VsF9d4SW55_llwPjsmpCXtrLrw5L0r86L5DndQ7YOwo,731
108
+ ominfra-0.0.0.dev129.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
109
+ ominfra-0.0.0.dev129.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
110
+ ominfra-0.0.0.dev129.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
111
+ ominfra-0.0.0.dev129.dist-info/RECORD,,
@@ -1,80 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- import errno
3
- import os
4
- import typing as ta
5
-
6
- from omlish.lite.logs import log
7
-
8
- from .configs import ServerConfig
9
- from .poller import Poller
10
- from .states import SupervisorState
11
- from .types import ServerContext
12
- from .types import ServerEpoch
13
- from .utils.fs import mktempfile
14
- from .utils.ostypes import Pid
15
- from .utils.ostypes import Rc
16
-
17
-
18
- class ServerContextImpl(ServerContext):
19
- def __init__(
20
- self,
21
- config: ServerConfig,
22
- poller: Poller,
23
- *,
24
- epoch: ServerEpoch = ServerEpoch(0),
25
- ) -> None:
26
- super().__init__()
27
-
28
- self._config = config
29
- self._poller = poller
30
- self._epoch = epoch
31
-
32
- self._state: SupervisorState = SupervisorState.RUNNING
33
-
34
- @property
35
- def config(self) -> ServerConfig:
36
- return self._config
37
-
38
- @property
39
- def epoch(self) -> ServerEpoch:
40
- return self._epoch
41
-
42
- @property
43
- def first(self) -> bool:
44
- return not self._epoch
45
-
46
- @property
47
- def state(self) -> SupervisorState:
48
- return self._state
49
-
50
- def set_state(self, state: SupervisorState) -> None:
51
- self._state = state
52
-
53
- #
54
-
55
- def waitpid(self) -> ta.Tuple[ta.Optional[Pid], ta.Optional[Rc]]:
56
- # Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
57
- # still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
58
- # waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
59
- # normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
60
- # call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
61
- # lying around.
62
- try:
63
- pid, sts = os.waitpid(-1, os.WNOHANG)
64
- except OSError as exc:
65
- code = exc.args[0]
66
- if code not in (errno.ECHILD, errno.EINTR):
67
- log.critical('waitpid error %r; a process may not be cleaned up properly', code)
68
- if code == errno.EINTR:
69
- log.debug('EINTR during reap')
70
- pid, sts = None, None
71
- return pid, sts # type: ignore
72
-
73
- def get_auto_child_log_name(self, name: str, identifier: str, channel: str) -> str:
74
- prefix = f'{name}-{channel}---{identifier}-'
75
- logfile = mktempfile(
76
- suffix='.log',
77
- prefix=prefix,
78
- dir=self.config.child_logdir,
79
- )
80
- return logfile