ominfra 0.0.0.dev131__py3-none-any.whl → 0.0.0.dev133__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.
Files changed (50) hide show
  1. ominfra/{deploy → manage/deploy}/_executor.py +10 -10
  2. ominfra/{deploy → manage/deploy}/poly/_main.py +6 -6
  3. ominfra/{deploy → manage/deploy}/remote.py +1 -1
  4. ominfra/pyremote.py +357 -0
  5. ominfra/scripts/journald2aws.py +8 -0
  6. ominfra/scripts/supervisor.py +173 -67
  7. ominfra/supervisor/dispatchersimpl.py +4 -3
  8. ominfra/supervisor/events.py +5 -2
  9. ominfra/supervisor/http.py +8 -1
  10. ominfra/supervisor/inject.py +8 -2
  11. ominfra/supervisor/io.py +2 -2
  12. ominfra/supervisor/main.py +13 -10
  13. ominfra/supervisor/processimpl.py +0 -1
  14. ominfra/supervisor/states.py +11 -0
  15. ominfra/supervisor/supervisor.py +26 -26
  16. ominfra/supervisor/types.py +2 -1
  17. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/METADATA +3 -3
  18. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/RECORD +46 -49
  19. ominfra/pyremote/__init__.py +0 -0
  20. ominfra/pyremote/_runcommands.py +0 -1201
  21. ominfra/pyremote/bootstrap.py +0 -149
  22. ominfra/pyremote/runcommands.py +0 -56
  23. /ominfra/{deploy → manage/deploy}/__init__.py +0 -0
  24. /ominfra/{deploy → manage/deploy}/configs.py +0 -0
  25. /ominfra/{deploy → manage/deploy}/executor/__init__.py +0 -0
  26. /ominfra/{deploy → manage/deploy}/executor/base.py +0 -0
  27. /ominfra/{deploy → manage/deploy}/executor/concerns/__init__.py +0 -0
  28. /ominfra/{deploy → manage/deploy}/executor/concerns/dirs.py +0 -0
  29. /ominfra/{deploy → manage/deploy}/executor/concerns/nginx.py +0 -0
  30. /ominfra/{deploy → manage/deploy}/executor/concerns/repo.py +0 -0
  31. /ominfra/{deploy → manage/deploy}/executor/concerns/supervisor.py +0 -0
  32. /ominfra/{deploy → manage/deploy}/executor/concerns/systemd.py +0 -0
  33. /ominfra/{deploy → manage/deploy}/executor/concerns/user.py +0 -0
  34. /ominfra/{deploy → manage/deploy}/executor/concerns/venv.py +0 -0
  35. /ominfra/{deploy → manage/deploy}/executor/main.py +0 -0
  36. /ominfra/{deploy → manage/deploy}/poly/__init__.py +0 -0
  37. /ominfra/{deploy → manage/deploy}/poly/base.py +0 -0
  38. /ominfra/{deploy → manage/deploy}/poly/configs.py +0 -0
  39. /ominfra/{deploy → manage/deploy}/poly/deploy.py +0 -0
  40. /ominfra/{deploy → manage/deploy}/poly/main.py +0 -0
  41. /ominfra/{deploy → manage/deploy}/poly/nginx.py +0 -0
  42. /ominfra/{deploy → manage/deploy}/poly/repo.py +0 -0
  43. /ominfra/{deploy → manage/deploy}/poly/runtime.py +0 -0
  44. /ominfra/{deploy → manage/deploy}/poly/site.py +0 -0
  45. /ominfra/{deploy → manage/deploy}/poly/supervisor.py +0 -0
  46. /ominfra/{deploy → manage/deploy}/poly/venv.py +0 -0
  47. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/LICENSE +0 -0
  48. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/WHEEL +0 -0
  49. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/entry_points.txt +0 -0
  50. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  # @omlish-lite
4
4
  # @omlish-script
5
5
  # @omlish-amalg-output ../supervisor/main.py
6
- # ruff: noqa: N802 U006 UP006 UP007 UP012 UP036
6
+ # ruff: noqa: N802 UP006 UP007 UP012 UP036
7
7
  # Supervisor is licensed under the following license:
8
8
  #
9
9
  # A copyright notice accompanies this license document that identifies the copyright holders.
@@ -116,6 +116,10 @@ A2 = ta.TypeVar('A2')
116
116
 
117
117
  # events.py
118
118
  EventCallback = ta.Callable[['Event'], None]
119
+ ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
120
+
121
+ # ../../omlish/lite/contextmanagers.py
122
+ ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
119
123
 
120
124
  # ../../omlish/lite/http/parsing.py
121
125
  HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
@@ -1077,6 +1081,17 @@ class ProcessState(enum.IntEnum):
1077
1081
  return self in SIGNALABLE_STATES
1078
1082
 
1079
1083
 
1084
+ # http://supervisord.org/subprocess.html
1085
+ STATE_TRANSITIONS = {
1086
+ ProcessState.STOPPED: (ProcessState.STARTING,),
1087
+ ProcessState.STARTING: (ProcessState.RUNNING, ProcessState.BACKOFF, ProcessState.STOPPING),
1088
+ ProcessState.RUNNING: (ProcessState.STOPPING, ProcessState.EXITED),
1089
+ ProcessState.BACKOFF: (ProcessState.STARTING, ProcessState.FATAL),
1090
+ ProcessState.STOPPING: (ProcessState.STOPPED,),
1091
+ ProcessState.EXITED: (ProcessState.STARTING,),
1092
+ ProcessState.FATAL: (ProcessState.STARTING,),
1093
+ }
1094
+
1080
1095
  STOPPED_STATES = (
1081
1096
  ProcessState.STOPPED,
1082
1097
  ProcessState.EXITED,
@@ -1576,16 +1591,16 @@ class FdIoPoller(abc.ABC):
1576
1591
  def register_readable(self, fd: int) -> bool:
1577
1592
  if fd in self._readable:
1578
1593
  return False
1579
- self._readable.add(fd)
1580
1594
  self._register_readable(fd)
1595
+ self._readable.add(fd)
1581
1596
  return True
1582
1597
 
1583
1598
  @ta.final
1584
1599
  def register_writable(self, fd: int) -> bool:
1585
1600
  if fd in self._writable:
1586
1601
  return False
1587
- self._writable.add(fd)
1588
1602
  self._register_writable(fd)
1603
+ self._writable.add(fd)
1589
1604
  return True
1590
1605
 
1591
1606
  @ta.final
@@ -1689,24 +1704,24 @@ if hasattr(select, 'poll'):
1689
1704
 
1690
1705
  #
1691
1706
 
1692
- _READ = select.POLLIN | select.POLLPRI | select.POLLHUP
1693
- _WRITE = select.POLLOUT
1694
-
1695
1707
  def _register_readable(self, fd: int) -> None:
1696
- self._update_registration(fd)
1708
+ self._update_registration(fd, r=True, w=fd in self._writable)
1697
1709
 
1698
1710
  def _register_writable(self, fd: int) -> None:
1699
- self._update_registration(fd)
1711
+ self._update_registration(fd, r=fd in self._readable, w=True)
1700
1712
 
1701
1713
  def _unregister_readable(self, fd: int) -> None:
1702
- self._update_registration(fd)
1714
+ self._update_registration(fd, r=False, w=False)
1703
1715
 
1704
1716
  def _unregister_writable(self, fd: int) -> None:
1705
- self._update_registration(fd)
1717
+ self._update_registration(fd, r=fd in self._readable, w=False)
1706
1718
 
1707
- def _update_registration(self, fd: int) -> None:
1708
- r = fd in self._readable
1709
- w = fd in self._writable
1719
+ #
1720
+
1721
+ _READ = select.POLLIN | select.POLLPRI | select.POLLHUP
1722
+ _WRITE = select.POLLOUT
1723
+
1724
+ def _update_registration(self, fd: int, *, r: bool, w: bool) -> None:
1710
1725
  if r or w:
1711
1726
  self._poller.register(fd, (self._READ if r else 0) | (self._WRITE if w else 0))
1712
1727
  else:
@@ -2119,7 +2134,7 @@ class EventCallbacks:
2119
2134
 
2120
2135
 
2121
2136
  class ProcessLogEvent(Event, abc.ABC):
2122
- channel: ta.Optional[str] = None
2137
+ channel: ta.ClassVar[ProcessOutputChannel]
2123
2138
 
2124
2139
  def __init__(self, process, pid, data):
2125
2140
  super().__init__()
@@ -2144,7 +2159,7 @@ class ProcessCommunicationEvent(Event, abc.ABC):
2144
2159
  BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'
2145
2160
  END_TOKEN = b'<!--XSUPERVISOR:END-->'
2146
2161
 
2147
- channel: ta.ClassVar[str]
2162
+ channel: ta.ClassVar[ProcessOutputChannel]
2148
2163
 
2149
2164
  def __init__(self, process, pid, data):
2150
2165
  super().__init__()
@@ -2516,6 +2531,64 @@ def get_user(name: str) -> User:
2516
2531
  )
2517
2532
 
2518
2533
 
2534
+ ########################################
2535
+ # ../../../omlish/lite/contextmanagers.py
2536
+
2537
+
2538
+ ##
2539
+
2540
+
2541
+ class ExitStacked:
2542
+ _exit_stack: ta.Optional[contextlib.ExitStack] = None
2543
+
2544
+ def __enter__(self: ExitStackedT) -> ExitStackedT:
2545
+ check_state(self._exit_stack is None)
2546
+ es = self._exit_stack = contextlib.ExitStack()
2547
+ es.__enter__()
2548
+ return self
2549
+
2550
+ def __exit__(self, exc_type, exc_val, exc_tb):
2551
+ if (es := self._exit_stack) is None:
2552
+ return None
2553
+ self._exit_contexts()
2554
+ return es.__exit__(exc_type, exc_val, exc_tb)
2555
+
2556
+ def _exit_contexts(self) -> None:
2557
+ pass
2558
+
2559
+ def _enter_context(self, cm: ta.ContextManager[T]) -> T:
2560
+ es = check_not_none(self._exit_stack)
2561
+ return es.enter_context(cm)
2562
+
2563
+
2564
+ ##
2565
+
2566
+
2567
+ @contextlib.contextmanager
2568
+ def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
2569
+ try:
2570
+ yield fn
2571
+ finally:
2572
+ fn()
2573
+
2574
+
2575
+ @contextlib.contextmanager
2576
+ def attr_setting(obj, attr, val, *, default=None): # noqa
2577
+ not_set = object()
2578
+ orig = getattr(obj, attr, not_set)
2579
+ try:
2580
+ setattr(obj, attr, val)
2581
+ if orig is not not_set:
2582
+ yield orig
2583
+ else:
2584
+ yield default
2585
+ finally:
2586
+ if orig is not_set:
2587
+ delattr(obj, attr)
2588
+ else:
2589
+ setattr(obj, attr, orig)
2590
+
2591
+
2519
2592
  ########################################
2520
2593
  # ../../../omlish/lite/fdio/handlers.py
2521
2594
 
@@ -2624,19 +2697,40 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
2624
2697
  #
2625
2698
 
2626
2699
  def _register_readable(self, fd: int) -> None:
2627
- self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)
2700
+ self._update_registration(fd, 'read', 'add')
2628
2701
 
2629
2702
  def _register_writable(self, fd: int) -> None:
2630
- self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)
2703
+ self._update_registration(fd, 'write', 'add')
2631
2704
 
2632
2705
  def _unregister_readable(self, fd: int) -> None:
2633
- self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)
2706
+ self._update_registration(fd, 'read', 'del')
2634
2707
 
2635
2708
  def _unregister_writable(self, fd: int) -> None:
2636
- self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)
2709
+ self._update_registration(fd, 'write', 'del')
2710
+
2711
+ #
2637
2712
 
2638
- def _control(self, fd: int, filter: int, flags: int) -> None: # noqa
2639
- ke = select.kevent(fd, filter=filter, flags=flags)
2713
+ _CONTROL_FILTER_BY_READ_OR_WRITE: ta.ClassVar[ta.Mapping[ta.Literal['read', 'write'], int]] = {
2714
+ 'read': select.KQ_FILTER_READ,
2715
+ 'write': select.KQ_FILTER_WRITE,
2716
+ }
2717
+
2718
+ _CONTROL_FLAGS_BY_ADD_OR_DEL: ta.ClassVar[ta.Mapping[ta.Literal['add', 'del'], int]] = {
2719
+ 'add': select.KQ_EV_ADD,
2720
+ 'del': select.KQ_EV_DELETE,
2721
+ }
2722
+
2723
+ def _update_registration(
2724
+ self,
2725
+ fd: int,
2726
+ read_or_write: ta.Literal['read', 'write'],
2727
+ add_or_del: ta.Literal['add', 'del'],
2728
+ ) -> None: # noqa
2729
+ ke = select.kevent(
2730
+ fd,
2731
+ filter=self._CONTROL_FILTER_BY_READ_OR_WRITE[read_or_write],
2732
+ flags=self._CONTROL_FLAGS_BY_ADD_OR_DEL[add_or_del],
2733
+ )
2640
2734
  kq = self._get_kqueue()
2641
2735
  try:
2642
2736
  kq.control([ke], 0)
@@ -2647,7 +2741,8 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
2647
2741
  pass
2648
2742
  elif exc.errno == errno.ENOENT:
2649
2743
  # Can happen when trying to remove an already closed socket
2650
- pass
2744
+ if add_or_del == 'add':
2745
+ raise
2651
2746
  else:
2652
2747
  raise
2653
2748
 
@@ -5951,7 +6046,7 @@ class HasDispatchers(abc.ABC):
5951
6046
  class ProcessDispatcher(FdIoHandler, abc.ABC):
5952
6047
  @property
5953
6048
  @abc.abstractmethod
5954
- def channel(self) -> str:
6049
+ def channel(self) -> ProcessOutputChannel:
5955
6050
  raise NotImplementedError
5956
6051
 
5957
6052
  @property
@@ -6240,7 +6335,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
6240
6335
  def __init__(
6241
6336
  self,
6242
6337
  process: Process,
6243
- channel: str,
6338
+ channel: ProcessOutputChannel,
6244
6339
  fd: Fd,
6245
6340
  *,
6246
6341
  event_callbacks: EventCallbacks,
@@ -6268,7 +6363,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
6268
6363
  return self._process
6269
6364
 
6270
6365
  @property
6271
- def channel(self) -> str:
6366
+ def channel(self) -> ProcessOutputChannel:
6272
6367
  return self._channel
6273
6368
 
6274
6369
  def fd(self) -> Fd:
@@ -6515,7 +6610,7 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
6515
6610
  def __init__(
6516
6611
  self,
6517
6612
  process: Process,
6518
- channel: str,
6613
+ channel: ProcessOutputChannel,
6519
6614
  fd: Fd,
6520
6615
  *,
6521
6616
  event_callbacks: EventCallbacks,
@@ -7024,9 +7119,9 @@ class IoManager(HasDispatchers):
7024
7119
  )
7025
7120
 
7026
7121
  timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
7027
- log.info(f'Polling: {timeout=}') # noqa
7122
+
7028
7123
  polled = self._poller.poll(timeout)
7029
- log.info(f'Polled: {polled=}') # noqa
7124
+
7030
7125
  if polled.msg is not None:
7031
7126
  log.error(polled.msg)
7032
7127
  if polled.exc is not None:
@@ -7151,6 +7246,8 @@ class HttpServer(HasDispatchers):
7151
7246
  self,
7152
7247
  handler: Handler,
7153
7248
  addr: Address = Address(('localhost', 8000)),
7249
+ *,
7250
+ exit_stack: contextlib.ExitStack,
7154
7251
  ) -> None:
7155
7252
  super().__init__()
7156
7253
 
@@ -7161,6 +7258,8 @@ class HttpServer(HasDispatchers):
7161
7258
 
7162
7259
  self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
7163
7260
 
7261
+ exit_stack.enter_context(defer(self._server.close)) # noqa
7262
+
7164
7263
  def get_dispatchers(self) -> Dispatchers:
7165
7264
  l = []
7166
7265
  for c in self._conns:
@@ -7205,6 +7304,7 @@ class SupervisorHttpHandler:
7205
7304
  'processes': {
7206
7305
  p.name: {
7207
7306
  'pid': p.pid,
7307
+ 'state': p.state.name,
7208
7308
  }
7209
7309
  for p in g
7210
7310
  },
@@ -7572,7 +7672,6 @@ class ProcessImpl(Process):
7572
7672
  self._last_stop = now
7573
7673
 
7574
7674
  if now > self._last_start:
7575
- log.info(f'{now - self._last_start=}') # noqa
7576
7675
  too_quickly = now - self._last_start < self._config.start_secs
7577
7676
  else:
7578
7677
  too_quickly = False
@@ -8227,11 +8326,29 @@ class Supervisor:
8227
8326
  #
8228
8327
 
8229
8328
  def _run_once(self) -> None:
8230
- now = time.time()
8231
- self._poll()
8232
- log.info(f'Poll took {time.time() - now}') # noqa
8329
+ if self._states.state < SupervisorState.RUNNING:
8330
+ if not self._stopping:
8331
+ # first time, set the stopping flag, do a notification and set stop_groups
8332
+ self._stopping = True
8333
+ self._stop_groups = sorted(self._process_groups)
8334
+ self._event_callbacks.notify(SupervisorStoppingEvent())
8335
+
8336
+ self._ordered_stop_groups_phase_1()
8337
+
8338
+ if not self.shutdown_report():
8339
+ # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
8340
+ raise ExitNow
8341
+
8342
+ self._io.poll()
8343
+
8344
+ for group in sorted(self._process_groups):
8345
+ for process in group:
8346
+ process.transition()
8347
+
8233
8348
  self._reap()
8349
+
8234
8350
  self._signal_handler.handle_signals()
8351
+
8235
8352
  self._tick()
8236
8353
 
8237
8354
  if self._states.state < SupervisorState.RUNNING:
@@ -8253,38 +8370,18 @@ class Supervisor:
8253
8370
  # down, so push it back on to the end of the stop group queue
8254
8371
  self._stop_groups.append(group)
8255
8372
 
8256
- def _poll(self) -> None:
8257
- sorted_groups = list(self._process_groups)
8258
- sorted_groups.sort()
8259
-
8260
- if self._states.state < SupervisorState.RUNNING:
8261
- if not self._stopping:
8262
- # first time, set the stopping flag, do a notification and set stop_groups
8263
- self._stopping = True
8264
- self._stop_groups = sorted_groups[:]
8265
- self._event_callbacks.notify(SupervisorStoppingEvent())
8266
-
8267
- self._ordered_stop_groups_phase_1()
8268
-
8269
- if not self.shutdown_report():
8270
- # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
8271
- raise ExitNow
8272
-
8273
- self._io.poll()
8274
-
8275
- for group in sorted_groups:
8276
- for process in group:
8277
- process.transition()
8373
+ #
8278
8374
 
8279
8375
  def _reap(self, *, once: bool = False, depth: int = 0) -> None:
8280
8376
  if depth >= 100:
8281
8377
  return
8282
8378
 
8283
8379
  wp = waitpid()
8284
- log.info(f'Waited pid: {wp}') # noqa
8380
+
8285
8381
  if wp is None or not wp.pid:
8286
8382
  return
8287
8383
 
8384
+ log.info(f'Waited pid: {wp}') # noqa
8288
8385
  process = self._pid_history.get(wp.pid, None)
8289
8386
  if process is None:
8290
8387
  _, msg = decode_wait_status(wp.sts)
@@ -8297,6 +8394,8 @@ class Supervisor:
8297
8394
  # keep reaping until no more kids to reap, but don't recurse infinitely
8298
8395
  self._reap(once=False, depth=depth + 1)
8299
8396
 
8397
+ #
8398
+
8300
8399
  def _tick(self, now: ta.Optional[float] = None) -> None:
8301
8400
  """Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
8302
8401
 
@@ -8362,6 +8461,7 @@ class _FdIoPollerDaemonizeListener(DaemonizeListener):
8362
8461
 
8363
8462
 
8364
8463
  def bind_server(
8464
+ exit_stack: contextlib.ExitStack,
8365
8465
  config: ServerConfig,
8366
8466
  *,
8367
8467
  server_epoch: ta.Optional[ServerEpoch] = None,
@@ -8370,6 +8470,8 @@ def bind_server(
8370
8470
  lst: ta.List[InjectorBindingOrBindings] = [
8371
8471
  inj.bind(config),
8372
8472
 
8473
+ inj.bind(exit_stack),
8474
+
8373
8475
  inj.bind_array(DaemonizeListener),
8374
8476
  inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
8375
8477
 
@@ -8425,8 +8527,10 @@ def bind_server(
8425
8527
  PollFdIoPoller,
8426
8528
  SelectFdIoPoller,
8427
8529
  ]))
8428
- lst.append(inj.bind(poller_impl, key=FdIoPoller, singleton=True))
8429
- inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True)
8530
+ lst.extend([
8531
+ inj.bind(poller_impl, key=FdIoPoller, singleton=True),
8532
+ inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True),
8533
+ ])
8430
8534
 
8431
8535
  #
8432
8536
 
@@ -8495,18 +8599,20 @@ def main(
8495
8599
  prepare=prepare_server_config,
8496
8600
  )
8497
8601
 
8498
- injector = inj.create_injector(bind_server(
8499
- config,
8500
- server_epoch=ServerEpoch(epoch),
8501
- inherited_fds=inherited_fds,
8502
- ))
8602
+ with contextlib.ExitStack() as es:
8603
+ injector = inj.create_injector(bind_server(
8604
+ es,
8605
+ config,
8606
+ server_epoch=ServerEpoch(epoch),
8607
+ inherited_fds=inherited_fds,
8608
+ ))
8503
8609
 
8504
- supervisor = injector[Supervisor]
8610
+ supervisor = injector[Supervisor]
8505
8611
 
8506
- try:
8507
- supervisor.main()
8508
- except ExitNow:
8509
- pass
8612
+ try:
8613
+ supervisor.main()
8614
+ except ExitNow:
8615
+ pass
8510
8616
 
8511
8617
  if supervisor.state < SupervisorState.RESTARTING:
8512
8618
  break
@@ -13,6 +13,7 @@ from .events import EventCallbacks
13
13
  from .events import ProcessCommunicationEvent
14
14
  from .events import ProcessLogStderrEvent
15
15
  from .events import ProcessLogStdoutEvent
16
+ from .events import ProcessOutputChannel
16
17
  from .types import Process
17
18
  from .types import ProcessDispatcher
18
19
  from .types import ProcessInputDispatcher
@@ -29,7 +30,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
29
30
  def __init__(
30
31
  self,
31
32
  process: Process,
32
- channel: str,
33
+ channel: ProcessOutputChannel,
33
34
  fd: Fd,
34
35
  *,
35
36
  event_callbacks: EventCallbacks,
@@ -57,7 +58,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
57
58
  return self._process
58
59
 
59
60
  @property
60
- def channel(self) -> str:
61
+ def channel(self) -> ProcessOutputChannel:
61
62
  return self._channel
62
63
 
63
64
  def fd(self) -> Fd:
@@ -304,7 +305,7 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
304
305
  def __init__(
305
306
  self,
306
307
  process: Process,
307
- channel: str,
308
+ channel: ProcessOutputChannel,
308
309
  fd: Fd,
309
310
  *,
310
311
  event_callbacks: EventCallbacks,
@@ -8,6 +8,9 @@ from .states import ProcessState
8
8
  EventCallback = ta.Callable[['Event'], None]
9
9
 
10
10
 
11
+ ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
12
+
13
+
11
14
  ##
12
15
 
13
16
 
@@ -43,7 +46,7 @@ class EventCallbacks:
43
46
 
44
47
 
45
48
  class ProcessLogEvent(Event, abc.ABC):
46
- channel: ta.Optional[str] = None
49
+ channel: ta.ClassVar[ProcessOutputChannel]
47
50
 
48
51
  def __init__(self, process, pid, data):
49
52
  super().__init__()
@@ -68,7 +71,7 @@ class ProcessCommunicationEvent(Event, abc.ABC):
68
71
  BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'
69
72
  END_TOKEN = b'<!--XSUPERVISOR:END-->'
70
73
 
71
- channel: ta.ClassVar[str]
74
+ channel: ta.ClassVar[ProcessOutputChannel]
72
75
 
73
76
  def __init__(self, process, pid, data):
74
77
  super().__init__()
@@ -1,9 +1,11 @@
1
- # ruff: noqa: U006 UP007
1
+ # ruff: noqa: UP006 UP007
2
+ import contextlib
2
3
  import json
3
4
  import socket
4
5
  import typing as ta
5
6
 
6
7
  from omlish.lite.check import check_not_none
8
+ from omlish.lite.contextmanagers import defer
7
9
  from omlish.lite.fdio.corohttp import CoroHttpServerConnectionFdIoHandler
8
10
  from omlish.lite.fdio.handlers import SocketFdIoHandler
9
11
  from omlish.lite.http.handlers import HttpHandler
@@ -59,6 +61,8 @@ class HttpServer(HasDispatchers):
59
61
  self,
60
62
  handler: Handler,
61
63
  addr: Address = Address(('localhost', 8000)),
64
+ *,
65
+ exit_stack: contextlib.ExitStack,
62
66
  ) -> None:
63
67
  super().__init__()
64
68
 
@@ -69,6 +73,8 @@ class HttpServer(HasDispatchers):
69
73
 
70
74
  self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
71
75
 
76
+ exit_stack.enter_context(defer(self._server.close)) # noqa
77
+
72
78
  def get_dispatchers(self) -> Dispatchers:
73
79
  l = []
74
80
  for c in self._conns:
@@ -113,6 +119,7 @@ class SupervisorHttpHandler:
113
119
  'processes': {
114
120
  p.name: {
115
121
  'pid': p.pid,
122
+ 'state': p.state.name,
116
123
  }
117
124
  for p in g
118
125
  },
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import contextlib
2
3
  import dataclasses as dc
3
4
  import typing as ta
4
5
 
@@ -56,6 +57,7 @@ class _FdIoPollerDaemonizeListener(DaemonizeListener):
56
57
 
57
58
 
58
59
  def bind_server(
60
+ exit_stack: contextlib.ExitStack,
59
61
  config: ServerConfig,
60
62
  *,
61
63
  server_epoch: ta.Optional[ServerEpoch] = None,
@@ -64,6 +66,8 @@ def bind_server(
64
66
  lst: ta.List[InjectorBindingOrBindings] = [
65
67
  inj.bind(config),
66
68
 
69
+ inj.bind(exit_stack),
70
+
67
71
  inj.bind_array(DaemonizeListener),
68
72
  inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
69
73
 
@@ -119,8 +123,10 @@ def bind_server(
119
123
  PollFdIoPoller,
120
124
  SelectFdIoPoller,
121
125
  ]))
122
- lst.append(inj.bind(poller_impl, key=FdIoPoller, singleton=True))
123
- inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True)
126
+ lst.extend([
127
+ inj.bind(poller_impl, key=FdIoPoller, singleton=True),
128
+ inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True),
129
+ ])
124
130
 
125
131
  #
126
132
 
ominfra/supervisor/io.py CHANGED
@@ -44,9 +44,9 @@ class IoManager(HasDispatchers):
44
44
  )
45
45
 
46
46
  timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
47
- log.info(f'Polling: {timeout=}') # noqa
47
+
48
48
  polled = self._poller.poll(timeout)
49
- log.info(f'Polled: {polled=}') # noqa
49
+
50
50
  if polled.msg is not None:
51
51
  log.error(polled.msg)
52
52
  if polled.exc is not None:
@@ -29,6 +29,7 @@
29
29
  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30
30
  # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
31
31
  # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ import contextlib
32
33
  import itertools
33
34
  import os.path
34
35
  import typing as ta
@@ -96,18 +97,20 @@ def main(
96
97
  prepare=prepare_server_config,
97
98
  )
98
99
 
99
- injector = inj.create_injector(bind_server(
100
- config,
101
- server_epoch=ServerEpoch(epoch),
102
- inherited_fds=inherited_fds,
103
- ))
100
+ with contextlib.ExitStack() as es:
101
+ injector = inj.create_injector(bind_server(
102
+ es,
103
+ config,
104
+ server_epoch=ServerEpoch(epoch),
105
+ inherited_fds=inherited_fds,
106
+ ))
104
107
 
105
- supervisor = injector[Supervisor]
108
+ supervisor = injector[Supervisor]
106
109
 
107
- try:
108
- supervisor.main()
109
- except ExitNow:
110
- pass
110
+ try:
111
+ supervisor.main()
112
+ except ExitNow:
113
+ pass
111
114
 
112
115
  if supervisor.state < SupervisorState.RESTARTING:
113
116
  break
@@ -378,7 +378,6 @@ class ProcessImpl(Process):
378
378
  self._last_stop = now
379
379
 
380
380
  if now > self._last_start:
381
- log.info(f'{now - self._last_start=}') # noqa
382
381
  too_quickly = now - self._last_start < self._config.start_secs
383
382
  else:
384
383
  too_quickly = False
@@ -27,6 +27,17 @@ class ProcessState(enum.IntEnum):
27
27
  return self in SIGNALABLE_STATES
28
28
 
29
29
 
30
+ # http://supervisord.org/subprocess.html
31
+ STATE_TRANSITIONS = {
32
+ ProcessState.STOPPED: (ProcessState.STARTING,),
33
+ ProcessState.STARTING: (ProcessState.RUNNING, ProcessState.BACKOFF, ProcessState.STOPPING),
34
+ ProcessState.RUNNING: (ProcessState.STOPPING, ProcessState.EXITED),
35
+ ProcessState.BACKOFF: (ProcessState.STARTING, ProcessState.FATAL),
36
+ ProcessState.STOPPING: (ProcessState.STOPPED,),
37
+ ProcessState.EXITED: (ProcessState.STARTING,),
38
+ ProcessState.FATAL: (ProcessState.STARTING,),
39
+ }
40
+
30
41
  STOPPED_STATES = (
31
42
  ProcessState.STOPPED,
32
43
  ProcessState.EXITED,