ominfra 0.0.0.dev131__tar.gz → 0.0.0.dev132__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. {ominfra-0.0.0.dev131/ominfra.egg-info → ominfra-0.0.0.dev132}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/scripts/journald2aws.py +8 -0
  3. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/scripts/supervisor.py +172 -66
  4. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/dispatchersimpl.py +4 -3
  5. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/events.py +5 -2
  6. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/http.py +7 -0
  7. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/inject.py +8 -2
  8. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/io.py +2 -2
  9. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/main.py +13 -10
  10. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/processimpl.py +0 -1
  11. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/states.py +11 -0
  12. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/supervisor.py +26 -26
  13. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/types.py +2 -1
  14. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132/ominfra.egg-info}/PKG-INFO +3 -3
  15. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra.egg-info/requires.txt +2 -2
  16. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/pyproject.toml +3 -3
  17. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/LICENSE +0 -0
  18. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/MANIFEST.in +0 -0
  19. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/README.rst +0 -0
  20. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/.manifests.json +0 -0
  21. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/__about__.py +0 -0
  22. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/__init__.py +0 -0
  23. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/__init__.py +0 -0
  24. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/__init__.py +0 -0
  25. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/__main__.py +0 -0
  26. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/auth.py +0 -0
  27. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/cli.py +0 -0
  28. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/dataclasses.py +0 -0
  29. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  30. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  31. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  32. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  33. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  34. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  35. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/logs.py +0 -0
  36. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/aws/metadata.py +0 -0
  37. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/gcp/__init__.py +0 -0
  38. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/clouds/gcp/auth.py +0 -0
  39. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/cmds.py +0 -0
  40. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/configs.py +0 -0
  41. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/__init__.py +0 -0
  42. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/_executor.py +0 -0
  43. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/configs.py +0 -0
  44. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/__init__.py +0 -0
  45. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/base.py +0 -0
  46. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/__init__.py +0 -0
  47. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/dirs.py +0 -0
  48. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/nginx.py +0 -0
  49. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/repo.py +0 -0
  50. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/supervisor.py +0 -0
  51. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/systemd.py +0 -0
  52. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/user.py +0 -0
  53. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/concerns/venv.py +0 -0
  54. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/executor/main.py +0 -0
  55. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/__init__.py +0 -0
  56. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/_main.py +0 -0
  57. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/base.py +0 -0
  58. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/configs.py +0 -0
  59. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/deploy.py +0 -0
  60. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/main.py +0 -0
  61. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/nginx.py +0 -0
  62. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/repo.py +0 -0
  63. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/runtime.py +0 -0
  64. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/site.py +0 -0
  65. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/supervisor.py +0 -0
  66. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/poly/venv.py +0 -0
  67. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/deploy/remote.py +0 -0
  68. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/journald/__init__.py +0 -0
  69. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/journald/fields.py +0 -0
  70. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/journald/genmessages.py +0 -0
  71. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/journald/messages.py +0 -0
  72. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/journald/tailer.py +0 -0
  73. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/manage/__init__.py +0 -0
  74. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/manage/manage.py +0 -0
  75. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/pyremote/__init__.py +0 -0
  76. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/pyremote/_runcommands.py +0 -0
  77. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/pyremote/bootstrap.py +0 -0
  78. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/pyremote/runcommands.py +0 -0
  79. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/scripts/__init__.py +0 -0
  80. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/ssh.py +0 -0
  81. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/LICENSE.txt +0 -0
  82. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/__init__.py +0 -0
  83. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/__main__.py +0 -0
  84. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/configs.py +0 -0
  85. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/dispatchers.py +0 -0
  86. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/exceptions.py +0 -0
  87. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/groups.py +0 -0
  88. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/groupsimpl.py +0 -0
  89. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/pipes.py +0 -0
  90. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/privileges.py +0 -0
  91. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/process.py +0 -0
  92. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/setup.py +0 -0
  93. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/setupimpl.py +0 -0
  94. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/signals.py +0 -0
  95. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/spawning.py +0 -0
  96. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/spawningimpl.py +0 -0
  97. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/__init__.py +0 -0
  98. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/collections.py +0 -0
  99. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/diag.py +0 -0
  100. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/fds.py +0 -0
  101. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/fs.py +0 -0
  102. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/os.py +0 -0
  103. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/ostypes.py +0 -0
  104. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/signals.py +0 -0
  105. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/strings.py +0 -0
  106. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/supervisor/utils/users.py +0 -0
  107. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/tailscale/__init__.py +0 -0
  108. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/tailscale/api.py +0 -0
  109. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/tailscale/cli.py +0 -0
  110. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/threadworkers.py +0 -0
  111. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/tools/__init__.py +0 -0
  112. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra/tools/listresources.py +0 -0
  113. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra.egg-info/SOURCES.txt +0 -0
  114. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra.egg-info/dependency_links.txt +0 -0
  115. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra.egg-info/entry_points.txt +0 -0
  116. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/ominfra.egg-info/top_level.txt +0 -0
  117. {ominfra-0.0.0.dev131 → ominfra-0.0.0.dev132}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev131
3
+ Version: 0.0.0.dev132
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.dev131
16
- Requires-Dist: omlish==0.0.0.dev131
15
+ Requires-Dist: omdev==0.0.0.dev132
16
+ Requires-Dist: omlish==0.0.0.dev132
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -1627,6 +1627,14 @@ class ExitStacked:
1627
1627
  ##
1628
1628
 
1629
1629
 
1630
+ @contextlib.contextmanager
1631
+ def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
1632
+ try:
1633
+ yield fn
1634
+ finally:
1635
+ fn()
1636
+
1637
+
1630
1638
  @contextlib.contextmanager
1631
1639
  def attr_setting(obj, attr, val, *, default=None): # noqa
1632
1640
  not_set = object()
@@ -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
1
  # ruff: noqa: U006 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
 
@@ -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,
@@ -182,11 +182,29 @@ class Supervisor:
182
182
  #
183
183
 
184
184
  def _run_once(self) -> None:
185
- now = time.time()
186
- self._poll()
187
- log.info(f'Poll took {time.time() - now}') # noqa
185
+ if self._states.state < SupervisorState.RUNNING:
186
+ if not self._stopping:
187
+ # first time, set the stopping flag, do a notification and set stop_groups
188
+ self._stopping = True
189
+ self._stop_groups = sorted(self._process_groups)
190
+ self._event_callbacks.notify(SupervisorStoppingEvent())
191
+
192
+ self._ordered_stop_groups_phase_1()
193
+
194
+ if not self.shutdown_report():
195
+ # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
196
+ raise ExitNow
197
+
198
+ self._io.poll()
199
+
200
+ for group in sorted(self._process_groups):
201
+ for process in group:
202
+ process.transition()
203
+
188
204
  self._reap()
205
+
189
206
  self._signal_handler.handle_signals()
207
+
190
208
  self._tick()
191
209
 
192
210
  if self._states.state < SupervisorState.RUNNING:
@@ -208,38 +226,18 @@ class Supervisor:
208
226
  # down, so push it back on to the end of the stop group queue
209
227
  self._stop_groups.append(group)
210
228
 
211
- def _poll(self) -> None:
212
- sorted_groups = list(self._process_groups)
213
- sorted_groups.sort()
214
-
215
- if self._states.state < SupervisorState.RUNNING:
216
- if not self._stopping:
217
- # first time, set the stopping flag, do a notification and set stop_groups
218
- self._stopping = True
219
- self._stop_groups = sorted_groups[:]
220
- self._event_callbacks.notify(SupervisorStoppingEvent())
221
-
222
- self._ordered_stop_groups_phase_1()
223
-
224
- if not self.shutdown_report():
225
- # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
226
- raise ExitNow
227
-
228
- self._io.poll()
229
-
230
- for group in sorted_groups:
231
- for process in group:
232
- process.transition()
229
+ #
233
230
 
234
231
  def _reap(self, *, once: bool = False, depth: int = 0) -> None:
235
232
  if depth >= 100:
236
233
  return
237
234
 
238
235
  wp = waitpid()
239
- log.info(f'Waited pid: {wp}') # noqa
236
+
240
237
  if wp is None or not wp.pid:
241
238
  return
242
239
 
240
+ log.info(f'Waited pid: {wp}') # noqa
243
241
  process = self._pid_history.get(wp.pid, None)
244
242
  if process is None:
245
243
  _, msg = decode_wait_status(wp.sts)
@@ -252,6 +250,8 @@ class Supervisor:
252
250
  # keep reaping until no more kids to reap, but don't recurse infinitely
253
251
  self._reap(once=False, depth=depth + 1)
254
252
 
253
+ #
254
+
255
255
  def _tick(self, now: ta.Optional[float] = None) -> None:
256
256
  """Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
257
257
 
@@ -7,6 +7,7 @@ from omlish.lite.fdio.handlers import FdIoHandler
7
7
 
8
8
  from .configs import ProcessConfig
9
9
  from .configs import ProcessGroupConfig
10
+ from .events import ProcessOutputChannel
10
11
  from .states import ProcessState
11
12
  from .states import SupervisorState
12
13
  from .utils.collections import KeyedCollectionAccessors
@@ -71,7 +72,7 @@ class HasDispatchers(abc.ABC):
71
72
  class ProcessDispatcher(FdIoHandler, abc.ABC):
72
73
  @property
73
74
  @abc.abstractmethod
74
- def channel(self) -> str:
75
+ def channel(self) -> ProcessOutputChannel:
75
76
  raise NotImplementedError
76
77
 
77
78
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev131
3
+ Version: 0.0.0.dev132
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.dev131
16
- Requires-Dist: omlish==0.0.0.dev131
15
+ Requires-Dist: omdev==0.0.0.dev132
16
+ Requires-Dist: omlish==0.0.0.dev132
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -1,5 +1,5 @@
1
- omdev==0.0.0.dev131
2
- omlish==0.0.0.dev131
1
+ omdev==0.0.0.dev132
2
+ omlish==0.0.0.dev132
3
3
 
4
4
  [all]
5
5
  paramiko~=3.5
@@ -12,7 +12,7 @@ authors = [
12
12
  urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = {text = 'BSD-3-Clause'}
14
14
  requires-python = '>=3.12'
15
- version = '0.0.0.dev131'
15
+ version = '0.0.0.dev132'
16
16
  classifiers = [
17
17
  'License :: OSI Approved :: BSD License',
18
18
  'Development Status :: 2 - Pre-Alpha',
@@ -22,8 +22,8 @@ classifiers = [
22
22
  ]
23
23
  description = 'ominfra'
24
24
  dependencies = [
25
- 'omdev == 0.0.0.dev131',
26
- 'omlish == 0.0.0.dev131',
25
+ 'omdev == 0.0.0.dev132',
26
+ 'omlish == 0.0.0.dev132',
27
27
  ]
28
28
 
29
29
  [project.optional-dependencies]
File without changes
File without changes