ominfra 0.0.0.dev131__py3-none-any.whl → 0.0.0.dev133__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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,