ominfra 0.0.0.dev130__py3-none-any.whl → 0.0.0.dev132__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/deploy/_executor.py +2 -2
- ominfra/deploy/poly/_main.py +2 -2
- ominfra/pyremote/_runcommands.py +2 -2
- ominfra/scripts/journald2aws.py +14 -6
- ominfra/scripts/supervisor.py +266 -162
- ominfra/supervisor/configs.py +17 -17
- ominfra/supervisor/dispatchersimpl.py +12 -11
- ominfra/supervisor/events.py +5 -2
- ominfra/supervisor/http.py +7 -0
- ominfra/supervisor/inject.py +8 -2
- ominfra/supervisor/io.py +2 -2
- ominfra/supervisor/main.py +13 -10
- ominfra/supervisor/pipes.py +2 -2
- ominfra/supervisor/privileges.py +4 -6
- ominfra/supervisor/processimpl.py +30 -31
- ominfra/supervisor/setupimpl.py +16 -16
- ominfra/supervisor/spawningimpl.py +1 -1
- ominfra/supervisor/states.py +11 -0
- ominfra/supervisor/supervisor.py +27 -27
- ominfra/supervisor/types.py +2 -1
- ominfra/supervisor/utils/os.py +1 -1
- ominfra/supervisor/utils/strings.py +2 -2
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/RECORD +28 -28
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev130.dist-info → ominfra-0.0.0.dev132.dist-info}/top_level.txt +0 -0
ominfra/scripts/supervisor.py
CHANGED
@@ -90,50 +90,54 @@ if sys.version_info < (3, 8):
|
|
90
90
|
########################################
|
91
91
|
|
92
92
|
|
93
|
-
#
|
93
|
+
# ../../omdev/toml/parser.py
|
94
94
|
TomlParseFloat = ta.Callable[[str], ta.Any]
|
95
95
|
TomlKey = ta.Tuple[str, ...]
|
96
96
|
TomlPos = int # ta.TypeAlias
|
97
97
|
|
98
|
-
#
|
98
|
+
# utils/collections.py
|
99
99
|
K = ta.TypeVar('K')
|
100
100
|
V = ta.TypeVar('V')
|
101
101
|
|
102
|
-
#
|
102
|
+
# ../../omlish/lite/cached.py
|
103
103
|
T = ta.TypeVar('T')
|
104
104
|
|
105
|
-
#
|
105
|
+
# ../../omlish/lite/check.py
|
106
106
|
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
107
107
|
|
108
|
-
#
|
108
|
+
# ../../omlish/lite/socket.py
|
109
109
|
SocketAddress = ta.Any
|
110
110
|
SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
|
111
111
|
|
112
|
-
#
|
112
|
+
# ../../omlish/lite/typing.py
|
113
113
|
A0 = ta.TypeVar('A0')
|
114
114
|
A1 = ta.TypeVar('A1')
|
115
115
|
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
|
119
120
|
|
120
|
-
#
|
121
|
+
# ../../omlish/lite/contextmanagers.py
|
122
|
+
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
123
|
+
|
124
|
+
# ../../omlish/lite/http/parsing.py
|
121
125
|
HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
122
126
|
|
123
|
-
#
|
127
|
+
# ../../omlish/lite/inject.py
|
124
128
|
U = ta.TypeVar('U')
|
125
129
|
InjectorKeyCls = ta.Union[type, ta.NewType]
|
126
130
|
InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
127
131
|
InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
128
132
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
129
133
|
|
130
|
-
#
|
134
|
+
# ../configs.py
|
131
135
|
ConfigMapping = ta.Mapping[str, ta.Any]
|
132
136
|
|
133
|
-
#
|
137
|
+
# ../../omlish/lite/http/handlers.py
|
134
138
|
HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse']
|
135
139
|
|
136
|
-
#
|
140
|
+
# ../../omlish/lite/http/coroserver.py
|
137
141
|
CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
|
138
142
|
|
139
143
|
|
@@ -991,9 +995,8 @@ class NoPermissionError(ProcessError):
|
|
991
995
|
|
992
996
|
def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
|
993
997
|
"""
|
994
|
-
Drop privileges to become the specified user, which may be a username or uid.
|
995
|
-
|
996
|
-
dropped.
|
998
|
+
Drop privileges to become the specified user, which may be a username or uid. Called for supervisord startup and
|
999
|
+
when spawning subprocesses. Returns None on success or a string error message if privileges could not be dropped.
|
997
1000
|
"""
|
998
1001
|
|
999
1002
|
if user is None:
|
@@ -1017,9 +1020,8 @@ def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
|
|
1017
1020
|
current_uid = os.getuid()
|
1018
1021
|
|
1019
1022
|
if current_uid == uid:
|
1020
|
-
# do nothing and return successfully if the uid is already the current one.
|
1021
|
-
#
|
1022
|
-
# it.
|
1023
|
+
# do nothing and return successfully if the uid is already the current one. this allows a supervisord running as
|
1024
|
+
# an unprivileged user "foo" to start a process where the config has "user=foo" (same user) in it.
|
1023
1025
|
return None
|
1024
1026
|
|
1025
1027
|
if current_uid != 0:
|
@@ -1079,6 +1081,17 @@ class ProcessState(enum.IntEnum):
|
|
1079
1081
|
return self in SIGNALABLE_STATES
|
1080
1082
|
|
1081
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
|
+
|
1082
1095
|
STOPPED_STATES = (
|
1083
1096
|
ProcessState.STOPPED,
|
1084
1097
|
ProcessState.EXITED,
|
@@ -1373,8 +1386,8 @@ def strip_escapes(s: bytes) -> bytes:
|
|
1373
1386
|
|
1374
1387
|
|
1375
1388
|
class SuffixMultiplier:
|
1376
|
-
# d is a dictionary of suffixes to integer multipliers.
|
1377
|
-
#
|
1389
|
+
# d is a dictionary of suffixes to integer multipliers. If no suffixes match, default is the multiplier. Matches are
|
1390
|
+
# case insensitive. Return values are in the fundamental unit.
|
1378
1391
|
def __init__(self, d, default=1):
|
1379
1392
|
super().__init__()
|
1380
1393
|
self._d = d
|
@@ -1578,16 +1591,16 @@ class FdIoPoller(abc.ABC):
|
|
1578
1591
|
def register_readable(self, fd: int) -> bool:
|
1579
1592
|
if fd in self._readable:
|
1580
1593
|
return False
|
1581
|
-
self._readable.add(fd)
|
1582
1594
|
self._register_readable(fd)
|
1595
|
+
self._readable.add(fd)
|
1583
1596
|
return True
|
1584
1597
|
|
1585
1598
|
@ta.final
|
1586
1599
|
def register_writable(self, fd: int) -> bool:
|
1587
1600
|
if fd in self._writable:
|
1588
1601
|
return False
|
1589
|
-
self._writable.add(fd)
|
1590
1602
|
self._register_writable(fd)
|
1603
|
+
self._writable.add(fd)
|
1591
1604
|
return True
|
1592
1605
|
|
1593
1606
|
@ta.final
|
@@ -1691,24 +1704,24 @@ if hasattr(select, 'poll'):
|
|
1691
1704
|
|
1692
1705
|
#
|
1693
1706
|
|
1694
|
-
_READ = select.POLLIN | select.POLLPRI | select.POLLHUP
|
1695
|
-
_WRITE = select.POLLOUT
|
1696
|
-
|
1697
1707
|
def _register_readable(self, fd: int) -> None:
|
1698
|
-
self._update_registration(fd)
|
1708
|
+
self._update_registration(fd, r=True, w=fd in self._writable)
|
1699
1709
|
|
1700
1710
|
def _register_writable(self, fd: int) -> None:
|
1701
|
-
self._update_registration(fd)
|
1711
|
+
self._update_registration(fd, r=fd in self._readable, w=True)
|
1702
1712
|
|
1703
1713
|
def _unregister_readable(self, fd: int) -> None:
|
1704
|
-
self._update_registration(fd)
|
1714
|
+
self._update_registration(fd, r=False, w=False)
|
1705
1715
|
|
1706
1716
|
def _unregister_writable(self, fd: int) -> None:
|
1707
|
-
self._update_registration(fd)
|
1717
|
+
self._update_registration(fd, r=fd in self._readable, w=False)
|
1708
1718
|
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
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:
|
1712
1725
|
if r or w:
|
1713
1726
|
self._poller.register(fd, (self._READ if r else 0) | (self._WRITE if w else 0))
|
1714
1727
|
else:
|
@@ -2121,7 +2134,7 @@ class EventCallbacks:
|
|
2121
2134
|
|
2122
2135
|
|
2123
2136
|
class ProcessLogEvent(Event, abc.ABC):
|
2124
|
-
channel: ta.
|
2137
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
2125
2138
|
|
2126
2139
|
def __init__(self, process, pid, data):
|
2127
2140
|
super().__init__()
|
@@ -2146,7 +2159,7 @@ class ProcessCommunicationEvent(Event, abc.ABC):
|
|
2146
2159
|
BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'
|
2147
2160
|
END_TOKEN = b'<!--XSUPERVISOR:END-->'
|
2148
2161
|
|
2149
|
-
channel: ta.ClassVar[
|
2162
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
2150
2163
|
|
2151
2164
|
def __init__(self, process, pid, data):
|
2152
2165
|
super().__init__()
|
@@ -2430,7 +2443,7 @@ def decode_wait_status(sts: int) -> ta.Tuple[Rc, str]:
|
|
2430
2443
|
Decode the status returned by wait() or waitpid().
|
2431
2444
|
|
2432
2445
|
Return a tuple (exitstatus, message) where exitstatus is the exit status, or -1 if the process was killed by a
|
2433
|
-
signal; and message is a message telling what happened.
|
2446
|
+
signal; and message is a message telling what happened. It is the caller's responsibility to display the message.
|
2434
2447
|
"""
|
2435
2448
|
|
2436
2449
|
if os.WIFEXITED(sts):
|
@@ -2518,6 +2531,64 @@ def get_user(name: str) -> User:
|
|
2518
2531
|
)
|
2519
2532
|
|
2520
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
|
+
|
2521
2592
|
########################################
|
2522
2593
|
# ../../../omlish/lite/fdio/handlers.py
|
2523
2594
|
|
@@ -2626,19 +2697,40 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
|
|
2626
2697
|
#
|
2627
2698
|
|
2628
2699
|
def _register_readable(self, fd: int) -> None:
|
2629
|
-
self.
|
2700
|
+
self._update_registration(fd, 'read', 'add')
|
2630
2701
|
|
2631
2702
|
def _register_writable(self, fd: int) -> None:
|
2632
|
-
self.
|
2703
|
+
self._update_registration(fd, 'write', 'add')
|
2633
2704
|
|
2634
2705
|
def _unregister_readable(self, fd: int) -> None:
|
2635
|
-
self.
|
2706
|
+
self._update_registration(fd, 'read', 'del')
|
2636
2707
|
|
2637
2708
|
def _unregister_writable(self, fd: int) -> None:
|
2638
|
-
self.
|
2709
|
+
self._update_registration(fd, 'write', 'del')
|
2710
|
+
|
2711
|
+
#
|
2639
2712
|
|
2640
|
-
|
2641
|
-
|
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
|
+
)
|
2642
2734
|
kq = self._get_kqueue()
|
2643
2735
|
try:
|
2644
2736
|
kq.control([ke], 0)
|
@@ -2649,7 +2741,8 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
|
|
2649
2741
|
pass
|
2650
2742
|
elif exc.errno == errno.ENOENT:
|
2651
2743
|
# Can happen when trying to remove an already closed socket
|
2652
|
-
|
2744
|
+
if add_or_del == 'add':
|
2745
|
+
raise
|
2653
2746
|
else:
|
2654
2747
|
raise
|
2655
2748
|
|
@@ -5068,8 +5161,8 @@ class ProcessPipes:
|
|
5068
5161
|
|
5069
5162
|
def make_process_pipes(stderr=True) -> ProcessPipes:
|
5070
5163
|
"""
|
5071
|
-
Create pipes for parent to child stdin/stdout/stderr communications.
|
5072
|
-
|
5164
|
+
Create pipes for parent to child stdin/stdout/stderr communications. Open fd in non-blocking mode so we can read
|
5165
|
+
them in the mainloop without blocking. If stderr is False, don't create a pipe for stderr.
|
5073
5166
|
"""
|
5074
5167
|
|
5075
5168
|
pipes: ta.Dict[str, ta.Optional[Fd]] = {
|
@@ -5219,32 +5312,32 @@ class ProcessConfig:
|
|
5219
5312
|
umask: ta.Optional[int] = None
|
5220
5313
|
priority: int = 999
|
5221
5314
|
|
5222
|
-
|
5223
|
-
|
5315
|
+
auto_start: bool = True
|
5316
|
+
auto_restart: str = 'unexpected'
|
5224
5317
|
|
5225
|
-
|
5226
|
-
|
5318
|
+
start_secs: int = 1
|
5319
|
+
start_retries: int = 3
|
5227
5320
|
|
5228
|
-
|
5229
|
-
|
5321
|
+
num_procs: int = 1
|
5322
|
+
num_procs_start: int = 0
|
5230
5323
|
|
5231
5324
|
@dc.dataclass(frozen=True)
|
5232
5325
|
class Log:
|
5233
5326
|
file: ta.Optional[str] = None
|
5234
|
-
|
5327
|
+
capture_max_bytes: ta.Optional[int] = None
|
5235
5328
|
events_enabled: bool = False
|
5236
5329
|
syslog: bool = False
|
5237
5330
|
backups: ta.Optional[int] = None
|
5238
|
-
|
5331
|
+
max_bytes: ta.Optional[int] = None
|
5239
5332
|
|
5240
5333
|
stdout: Log = Log()
|
5241
5334
|
stderr: Log = Log()
|
5242
5335
|
|
5243
|
-
|
5244
|
-
|
5245
|
-
|
5336
|
+
stop_signal: int = signal.SIGTERM
|
5337
|
+
stop_wait_secs: int = 10
|
5338
|
+
stop_as_group: bool = False
|
5246
5339
|
|
5247
|
-
|
5340
|
+
kill_as_group: bool = False
|
5248
5341
|
|
5249
5342
|
exitcodes: ta.Sequence[int] = (0,)
|
5250
5343
|
|
@@ -5269,14 +5362,14 @@ class ServerConfig:
|
|
5269
5362
|
umask: int = 0o22
|
5270
5363
|
directory: ta.Optional[str] = None
|
5271
5364
|
logfile: str = 'supervisord.log'
|
5272
|
-
|
5365
|
+
logfile_max_bytes: int = 50 * 1024 * 1024
|
5273
5366
|
logfile_backups: int = 10
|
5274
5367
|
loglevel: int = logging.INFO
|
5275
5368
|
pidfile: str = 'supervisord.pid'
|
5276
5369
|
identifier: str = 'supervisor'
|
5277
5370
|
child_logdir: str = '/dev/null'
|
5278
|
-
|
5279
|
-
|
5371
|
+
min_fds: int = 1024
|
5372
|
+
min_procs: int = 200
|
5280
5373
|
nocleanup: bool = False
|
5281
5374
|
strip_ansi: bool = False
|
5282
5375
|
silent: bool = False
|
@@ -5289,7 +5382,7 @@ class ServerConfig:
|
|
5289
5382
|
umask: ta.Union[int, str] = 0o22,
|
5290
5383
|
directory: ta.Optional[str] = None,
|
5291
5384
|
logfile: str = 'supervisord.log',
|
5292
|
-
|
5385
|
+
logfile_max_bytes: ta.Union[int, str] = 50 * 1024 * 1024,
|
5293
5386
|
loglevel: ta.Union[int, str] = logging.INFO,
|
5294
5387
|
pidfile: str = 'supervisord.pid',
|
5295
5388
|
child_logdir: ta.Optional[str] = None,
|
@@ -5299,7 +5392,7 @@ class ServerConfig:
|
|
5299
5392
|
umask=parse_octal(umask),
|
5300
5393
|
directory=check_existing_dir(directory) if directory is not None else None,
|
5301
5394
|
logfile=check_path_with_existing_dir(logfile),
|
5302
|
-
|
5395
|
+
logfile_max_bytes=parse_bytes_size(logfile_max_bytes),
|
5303
5396
|
loglevel=parse_logging_level(loglevel),
|
5304
5397
|
pidfile=check_path_with_existing_dir(pidfile),
|
5305
5398
|
child_logdir=child_logdir if child_logdir else tempfile.gettempdir(),
|
@@ -5953,7 +6046,7 @@ class HasDispatchers(abc.ABC):
|
|
5953
6046
|
class ProcessDispatcher(FdIoHandler, abc.ABC):
|
5954
6047
|
@property
|
5955
6048
|
@abc.abstractmethod
|
5956
|
-
def channel(self) ->
|
6049
|
+
def channel(self) -> ProcessOutputChannel:
|
5957
6050
|
raise NotImplementedError
|
5958
6051
|
|
5959
6052
|
@property
|
@@ -6242,7 +6335,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
6242
6335
|
def __init__(
|
6243
6336
|
self,
|
6244
6337
|
process: Process,
|
6245
|
-
channel:
|
6338
|
+
channel: ProcessOutputChannel,
|
6246
6339
|
fd: Fd,
|
6247
6340
|
*,
|
6248
6341
|
event_callbacks: EventCallbacks,
|
@@ -6270,7 +6363,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
6270
6363
|
return self._process
|
6271
6364
|
|
6272
6365
|
@property
|
6273
|
-
def channel(self) ->
|
6366
|
+
def channel(self) -> ProcessOutputChannel:
|
6274
6367
|
return self._channel
|
6275
6368
|
|
6276
6369
|
def fd(self) -> Fd:
|
@@ -6360,7 +6453,7 @@ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispat
|
|
6360
6453
|
channel = self._channel # noqa
|
6361
6454
|
|
6362
6455
|
logfile = self._lc.file
|
6363
|
-
|
6456
|
+
max_bytes = self._lc.max_bytes # noqa
|
6364
6457
|
backups = self._lc.backups # noqa
|
6365
6458
|
to_syslog = self._lc.syslog
|
6366
6459
|
|
@@ -6372,8 +6465,8 @@ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispat
|
|
6372
6465
|
# self.normal_log,
|
6373
6466
|
# filename=logfile,
|
6374
6467
|
# fmt='%(message)s',
|
6375
|
-
# rotating=bool(
|
6376
|
-
#
|
6468
|
+
# rotating=bool(max_bytes), # optimization
|
6469
|
+
# max_bytes=max_bytes,
|
6377
6470
|
# backups=backups,
|
6378
6471
|
# )
|
6379
6472
|
|
@@ -6385,17 +6478,17 @@ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispat
|
|
6385
6478
|
|
6386
6479
|
def _init_capture_log(self) -> None:
|
6387
6480
|
"""
|
6388
|
-
Configure the capture log for this process.
|
6481
|
+
Configure the capture log for this process. This log is used to temporarily capture output when special output
|
6389
6482
|
is detected. Sets self.capture_log if capturing is enabled.
|
6390
6483
|
"""
|
6391
6484
|
|
6392
|
-
|
6393
|
-
if
|
6485
|
+
capture_max_bytes = self._lc.capture_max_bytes
|
6486
|
+
if capture_max_bytes:
|
6394
6487
|
self._capture_log = logging.getLogger(__name__)
|
6395
6488
|
# loggers.handle_boundIO(
|
6396
6489
|
# self._capture_log,
|
6397
6490
|
# fmt='%(message)s',
|
6398
|
-
#
|
6491
|
+
# max_bytes=capture_max_bytes,
|
6399
6492
|
# )
|
6400
6493
|
|
6401
6494
|
def remove_logs(self) -> None:
|
@@ -6508,7 +6601,7 @@ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispat
|
|
6508
6601
|
self._output_buffer += data
|
6509
6602
|
self.record_output()
|
6510
6603
|
if not data:
|
6511
|
-
# if we get no data back from the pipe, it means that the child process has ended.
|
6604
|
+
# if we get no data back from the pipe, it means that the child process has ended. See
|
6512
6605
|
# mail.python.org/pipermail/python-dev/2004-August/046850.html
|
6513
6606
|
self.close()
|
6514
6607
|
|
@@ -6517,7 +6610,7 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
|
|
6517
6610
|
def __init__(
|
6518
6611
|
self,
|
6519
6612
|
process: Process,
|
6520
|
-
channel:
|
6613
|
+
channel: ProcessOutputChannel,
|
6521
6614
|
fd: Fd,
|
6522
6615
|
*,
|
6523
6616
|
event_callbacks: EventCallbacks,
|
@@ -6717,21 +6810,21 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6717
6810
|
def _cleanup_fds(self) -> None:
|
6718
6811
|
# try to close any leaked file descriptors (for reload)
|
6719
6812
|
start = 5
|
6720
|
-
os.closerange(start, self._config.
|
6813
|
+
os.closerange(start, self._config.min_fds)
|
6721
6814
|
|
6722
6815
|
#
|
6723
6816
|
|
6724
6817
|
def _set_uid_or_exit(self) -> None:
|
6725
6818
|
"""
|
6726
|
-
Set the uid of the supervisord process.
|
6819
|
+
Set the uid of the supervisord process. Called during supervisord startup only. No return value. Exits the
|
6727
6820
|
process via usage() if privileges could not be dropped.
|
6728
6821
|
"""
|
6729
6822
|
|
6730
6823
|
if self._user is None:
|
6731
6824
|
if os.getuid() == 0:
|
6732
6825
|
warnings.warn(
|
6733
|
-
'Supervisor is running as root.
|
6734
|
-
'config file.
|
6826
|
+
'Supervisor is running as root. Privileges were not dropped because no user is specified in the '
|
6827
|
+
'config file. If you intend to run as root, you can set user=root in the config file to avoid '
|
6735
6828
|
'this message.',
|
6736
6829
|
)
|
6737
6830
|
else:
|
@@ -6745,8 +6838,8 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6745
6838
|
|
6746
6839
|
def _set_rlimits_or_exit(self) -> None:
|
6747
6840
|
"""
|
6748
|
-
Set the rlimits of the supervisord process.
|
6749
|
-
|
6841
|
+
Set the rlimits of the supervisord process. Called during supervisord startup only. No return value. Exits the
|
6842
|
+
process via usage() if any rlimits could not be set.
|
6750
6843
|
"""
|
6751
6844
|
|
6752
6845
|
limits = []
|
@@ -6755,12 +6848,12 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6755
6848
|
limits.append({
|
6756
6849
|
'msg': (
|
6757
6850
|
'The minimum number of file descriptors required to run this process is %(min_limit)s as per the '
|
6758
|
-
'"
|
6759
|
-
'you to open %(hard)s file descriptors.
|
6760
|
-
'your environment (see README.rst) or lower the
|
6851
|
+
'"min_fds" command-line argument or config file setting. The current environment will only allow '
|
6852
|
+
'you to open %(hard)s file descriptors. Either raise the number of usable file descriptors in '
|
6853
|
+
'your environment (see README.rst) or lower the min_fds setting in the config file to allow the '
|
6761
6854
|
'process to start.'
|
6762
6855
|
),
|
6763
|
-
'min': self._config.
|
6856
|
+
'min': self._config.min_fds,
|
6764
6857
|
'resource': resource.RLIMIT_NOFILE,
|
6765
6858
|
'name': 'RLIMIT_NOFILE',
|
6766
6859
|
})
|
@@ -6770,11 +6863,11 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6770
6863
|
'msg': (
|
6771
6864
|
'The minimum number of available processes required to run this program is %(min_limit)s as per '
|
6772
6865
|
'the "minprocs" command-line argument or config file setting. The current environment will only '
|
6773
|
-
'allow you to open %(hard)s processes.
|
6866
|
+
'allow you to open %(hard)s processes. Either raise the number of usable processes in your '
|
6774
6867
|
'environment (see README.rst) or lower the minprocs setting in the config file to allow the '
|
6775
6868
|
'program to start.'
|
6776
6869
|
),
|
6777
|
-
'min': self._config.
|
6870
|
+
'min': self._config.min_procs,
|
6778
6871
|
'resource': resource.RLIMIT_NPROC,
|
6779
6872
|
'name': 'RLIMIT_NPROC',
|
6780
6873
|
})
|
@@ -6860,11 +6953,11 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6860
6953
|
dl.after_daemonize()
|
6861
6954
|
|
6862
6955
|
def _do_daemonize(self) -> None:
|
6863
|
-
# To daemonize, we need to become the leader of our own session (process) group.
|
6864
|
-
# our parent process will also be sent to us.
|
6956
|
+
# To daemonize, we need to become the leader of our own session (process) group. If we do not, signals sent to
|
6957
|
+
# our parent process will also be sent to us. This might be bad because signals such as SIGINT can be sent to
|
6865
6958
|
# our parent process during normal (uninteresting) operations such as when we press Ctrl-C in the parent
|
6866
6959
|
# terminal window to escape from a logtail command. To disassociate ourselves from our parent's session group we
|
6867
|
-
# use os.setsid.
|
6960
|
+
# use os.setsid. It means "set session id", which has the effect of disassociating a process from is current
|
6868
6961
|
# session and process group and setting itself up as a new session leader.
|
6869
6962
|
#
|
6870
6963
|
# Unfortunately we cannot call setsid if we're already a session group leader, so we use "fork" to make a copy
|
@@ -6896,7 +6989,7 @@ class SupervisorSetupImpl(SupervisorSetup):
|
|
6896
6989
|
os.dup2(2, os.open('/dev/null', os.O_WRONLY))
|
6897
6990
|
|
6898
6991
|
# XXX Stevens, in his Advanced Unix book, section 13.3 (page 417) recommends calling umask(0) and closing unused
|
6899
|
-
# file descriptors.
|
6992
|
+
# file descriptors. In his Network Programming book, he additionally recommends ignoring SIGHUP and forking
|
6900
6993
|
# again after the setsid() call, for obscure SVR4 reasons.
|
6901
6994
|
os.setsid()
|
6902
6995
|
os.umask(self._config.umask)
|
@@ -7026,9 +7119,9 @@ class IoManager(HasDispatchers):
|
|
7026
7119
|
)
|
7027
7120
|
|
7028
7121
|
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
7029
|
-
|
7122
|
+
|
7030
7123
|
polled = self._poller.poll(timeout)
|
7031
|
-
|
7124
|
+
|
7032
7125
|
if polled.msg is not None:
|
7033
7126
|
log.error(polled.msg)
|
7034
7127
|
if polled.exc is not None:
|
@@ -7153,6 +7246,8 @@ class HttpServer(HasDispatchers):
|
|
7153
7246
|
self,
|
7154
7247
|
handler: Handler,
|
7155
7248
|
addr: Address = Address(('localhost', 8000)),
|
7249
|
+
*,
|
7250
|
+
exit_stack: contextlib.ExitStack,
|
7156
7251
|
) -> None:
|
7157
7252
|
super().__init__()
|
7158
7253
|
|
@@ -7163,6 +7258,8 @@ class HttpServer(HasDispatchers):
|
|
7163
7258
|
|
7164
7259
|
self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
|
7165
7260
|
|
7261
|
+
exit_stack.enter_context(defer(self._server.close)) # noqa
|
7262
|
+
|
7166
7263
|
def get_dispatchers(self) -> Dispatchers:
|
7167
7264
|
l = []
|
7168
7265
|
for c in self._conns:
|
@@ -7207,6 +7304,7 @@ class SupervisorHttpHandler:
|
|
7207
7304
|
'processes': {
|
7208
7305
|
p.name: {
|
7209
7306
|
'pid': p.pid,
|
7307
|
+
'state': p.state.name,
|
7210
7308
|
}
|
7211
7309
|
for p in g
|
7212
7310
|
},
|
@@ -7275,7 +7373,7 @@ class ProcessImpl(Process):
|
|
7275
7373
|
|
7276
7374
|
self._killing = False # true if we are trying to kill this process
|
7277
7375
|
|
7278
|
-
self._backoff = 0 # backoff counter (to
|
7376
|
+
self._backoff = 0 # backoff counter (to start_retries)
|
7279
7377
|
|
7280
7378
|
self._exitstatus: ta.Optional[Rc] = None # status attached to dead process by finish()
|
7281
7379
|
self._spawn_err: ta.Optional[str] = None # error message attached by spawn() if any
|
@@ -7352,7 +7450,7 @@ class ProcessImpl(Process):
|
|
7352
7450
|
self._pipes = sp.pipes
|
7353
7451
|
self._dispatchers = sp.dispatchers
|
7354
7452
|
|
7355
|
-
self._delay = time.time() + self.config.
|
7453
|
+
self._delay = time.time() + self.config.start_secs
|
7356
7454
|
|
7357
7455
|
return sp.pid
|
7358
7456
|
|
@@ -7410,17 +7508,17 @@ class ProcessImpl(Process):
|
|
7410
7508
|
|
7411
7509
|
if self._state == ProcessState.STARTING:
|
7412
7510
|
self._last_start = min(test_time, self._last_start)
|
7413
|
-
if self._delay > 0 and test_time < (self._delay - self._config.
|
7414
|
-
self._delay = test_time + self._config.
|
7511
|
+
if self._delay > 0 and test_time < (self._delay - self._config.start_secs):
|
7512
|
+
self._delay = test_time + self._config.start_secs
|
7415
7513
|
|
7416
7514
|
elif self._state == ProcessState.RUNNING:
|
7417
|
-
if test_time > self._last_start and test_time < (self._last_start + self._config.
|
7418
|
-
self._last_start = test_time - self._config.
|
7515
|
+
if test_time > self._last_start and test_time < (self._last_start + self._config.start_secs):
|
7516
|
+
self._last_start = test_time - self._config.start_secs
|
7419
7517
|
|
7420
7518
|
elif self._state == ProcessState.STOPPING:
|
7421
7519
|
self._last_stop_report = min(test_time, self._last_stop_report)
|
7422
|
-
if self._delay > 0 and test_time < (self._delay - self._config.
|
7423
|
-
self._delay = test_time + self._config.
|
7520
|
+
if self._delay > 0 and test_time < (self._delay - self._config.stop_wait_secs):
|
7521
|
+
self._delay = test_time + self._config.stop_wait_secs
|
7424
7522
|
|
7425
7523
|
elif self._state == ProcessState.BACKOFF:
|
7426
7524
|
if self._delay > 0 and test_time < (self._delay - self._backoff):
|
@@ -7429,7 +7527,7 @@ class ProcessImpl(Process):
|
|
7429
7527
|
def stop(self) -> ta.Optional[str]:
|
7430
7528
|
self._administrative_stop = True
|
7431
7529
|
self._last_stop_report = 0
|
7432
|
-
return self.kill(self._config.
|
7530
|
+
return self.kill(self._config.stop_signal)
|
7433
7531
|
|
7434
7532
|
def stop_report(self) -> None:
|
7435
7533
|
"""Log a 'waiting for x to stop' message with throttling."""
|
@@ -7452,7 +7550,7 @@ class ProcessImpl(Process):
|
|
7452
7550
|
|
7453
7551
|
def kill(self, sig: int) -> ta.Optional[str]:
|
7454
7552
|
"""
|
7455
|
-
Send a signal to the subprocess with the intention to kill it (to make it exit).
|
7553
|
+
Send a signal to the subprocess with the intention to kill it (to make it exit). This may or may not actually
|
7456
7554
|
kill it.
|
7457
7555
|
|
7458
7556
|
Return None if the signal was sent, or an error message string if an error occurred or if the subprocess is not
|
@@ -7460,8 +7558,8 @@ class ProcessImpl(Process):
|
|
7460
7558
|
"""
|
7461
7559
|
now = time.time()
|
7462
7560
|
|
7463
|
-
# If the process is in BACKOFF and we want to stop or kill it, then BACKOFF -> STOPPED.
|
7464
|
-
# if
|
7561
|
+
# If the process is in BACKOFF and we want to stop or kill it, then BACKOFF -> STOPPED. This is needed because
|
7562
|
+
# if start_retries is a large number and the process isn't starting successfully, the stop request would be
|
7465
7563
|
# blocked for a long time waiting for the retries.
|
7466
7564
|
if self._state == ProcessState.BACKOFF:
|
7467
7565
|
log.debug('Attempted to kill %s, which is in BACKOFF state.', self.name)
|
@@ -7476,25 +7574,25 @@ class ProcessImpl(Process):
|
|
7476
7574
|
|
7477
7575
|
# If we're in the stopping state, then we've already sent the stop signal and this is the kill signal
|
7478
7576
|
if self._state == ProcessState.STOPPING:
|
7479
|
-
|
7577
|
+
kill_as_group = self._config.kill_as_group
|
7480
7578
|
else:
|
7481
|
-
|
7579
|
+
kill_as_group = self._config.stop_as_group
|
7482
7580
|
|
7483
7581
|
as_group = ''
|
7484
|
-
if
|
7582
|
+
if kill_as_group:
|
7485
7583
|
as_group = 'process group '
|
7486
7584
|
|
7487
7585
|
log.debug('killing %s (pid %s) %s with signal %s', self.name, self.pid, as_group, sig_name(sig))
|
7488
7586
|
|
7489
7587
|
# RUNNING/STARTING/STOPPING -> STOPPING
|
7490
7588
|
self._killing = True
|
7491
|
-
self._delay = now + self._config.
|
7492
|
-
# we will already be in the STOPPING state if we're doing a SIGKILL as a result of overrunning
|
7589
|
+
self._delay = now + self._config.stop_wait_secs
|
7590
|
+
# we will already be in the STOPPING state if we're doing a SIGKILL as a result of overrunning stop_wait_secs
|
7493
7591
|
self.check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
7494
7592
|
self.change_state(ProcessState.STOPPING)
|
7495
7593
|
|
7496
7594
|
kpid = int(self.pid)
|
7497
|
-
if
|
7595
|
+
if kill_as_group:
|
7498
7596
|
# send to the whole process group instead
|
7499
7597
|
kpid = -kpid
|
7500
7598
|
|
@@ -7504,7 +7602,7 @@ class ProcessImpl(Process):
|
|
7504
7602
|
except OSError as exc:
|
7505
7603
|
if exc.errno == errno.ESRCH:
|
7506
7604
|
log.debug('unable to signal %s (pid %s), it probably just exited on its own: %s', self.name, self.pid, str(exc)) # noqa
|
7507
|
-
# we could change the state here but we intentionally do not.
|
7605
|
+
# we could change the state here but we intentionally do not. we will do it during normal SIGCHLD
|
7508
7606
|
# processing.
|
7509
7607
|
return None
|
7510
7608
|
raise
|
@@ -7547,7 +7645,7 @@ class ProcessImpl(Process):
|
|
7547
7645
|
self.pid,
|
7548
7646
|
str(exc),
|
7549
7647
|
)
|
7550
|
-
# we could change the state here but we intentionally do not.
|
7648
|
+
# we could change the state here but we intentionally do not. we will do it during normal SIGCHLD
|
7551
7649
|
# processing.
|
7552
7650
|
return None
|
7553
7651
|
raise
|
@@ -7574,8 +7672,7 @@ class ProcessImpl(Process):
|
|
7574
7672
|
self._last_stop = now
|
7575
7673
|
|
7576
7674
|
if now > self._last_start:
|
7577
|
-
|
7578
|
-
too_quickly = now - self._last_start < self._config.startsecs
|
7675
|
+
too_quickly = now - self._last_start < self._config.start_secs
|
7579
7676
|
else:
|
7580
7677
|
too_quickly = False
|
7581
7678
|
log.warning(
|
@@ -7647,8 +7744,8 @@ class ProcessImpl(Process):
|
|
7647
7744
|
if self._supervisor_states.state > SupervisorState.RESTARTING:
|
7648
7745
|
# dont start any processes if supervisor is shutting down
|
7649
7746
|
if state == ProcessState.EXITED:
|
7650
|
-
if self._config.
|
7651
|
-
if self._config.
|
7747
|
+
if self._config.auto_restart:
|
7748
|
+
if self._config.auto_restart is RestartUnconditionally:
|
7652
7749
|
# EXITED -> STARTING
|
7653
7750
|
self.spawn()
|
7654
7751
|
elif self._exitstatus not in self._config.exitcodes:
|
@@ -7656,29 +7753,29 @@ class ProcessImpl(Process):
|
|
7656
7753
|
self.spawn()
|
7657
7754
|
|
7658
7755
|
elif state == ProcessState.STOPPED and not self._last_start:
|
7659
|
-
if self._config.
|
7756
|
+
if self._config.auto_start:
|
7660
7757
|
# STOPPED -> STARTING
|
7661
7758
|
self.spawn()
|
7662
7759
|
|
7663
7760
|
elif state == ProcessState.BACKOFF:
|
7664
|
-
if self._backoff <= self._config.
|
7761
|
+
if self._backoff <= self._config.start_retries:
|
7665
7762
|
if now > self._delay:
|
7666
7763
|
# BACKOFF -> STARTING
|
7667
7764
|
self.spawn()
|
7668
7765
|
|
7669
7766
|
if state == ProcessState.STARTING:
|
7670
|
-
if now - self._last_start > self._config.
|
7767
|
+
if now - self._last_start > self._config.start_secs:
|
7671
7768
|
# STARTING -> RUNNING if the proc has started successfully and it has stayed up for at least
|
7672
|
-
# proc.config.
|
7769
|
+
# proc.config.start_secs,
|
7673
7770
|
self._delay = 0
|
7674
7771
|
self._backoff = 0
|
7675
7772
|
self.check_in_state(ProcessState.STARTING)
|
7676
7773
|
self.change_state(ProcessState.RUNNING)
|
7677
|
-
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (
|
7774
|
+
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (start_secs)' % self._config.start_secs) # noqa
|
7678
7775
|
log.info('success: %s %s', self.name, msg)
|
7679
7776
|
|
7680
7777
|
if state == ProcessState.BACKOFF:
|
7681
|
-
if self._backoff > self._config.
|
7778
|
+
if self._backoff > self._config.start_retries:
|
7682
7779
|
# BACKOFF -> FATAL if the proc has exceeded its number of retries
|
7683
7780
|
self.give_up()
|
7684
7781
|
msg = ('entered FATAL state, too many start retries too quickly')
|
@@ -7687,7 +7784,7 @@ class ProcessImpl(Process):
|
|
7687
7784
|
elif state == ProcessState.STOPPING:
|
7688
7785
|
time_left = self._delay - now
|
7689
7786
|
if time_left <= 0:
|
7690
|
-
# kill processes which are taking too long to stop with a final sigkill.
|
7787
|
+
# kill processes which are taking too long to stop with a final sigkill. if this doesn't kill it, the
|
7691
7788
|
# process will be stuck in the STOPPING state forever.
|
7692
7789
|
log.warning('killing \'%s\' (%s) with SIGKILL', self.name, self.pid)
|
7693
7790
|
self.kill(signal.SIGKILL)
|
@@ -8039,7 +8136,7 @@ class ProcessSpawningImpl(ProcessSpawning):
|
|
8039
8136
|
else:
|
8040
8137
|
os.dup2(check_not_none(pipes.child_stderr), 2)
|
8041
8138
|
|
8042
|
-
for i in range(3, self._server_config.
|
8139
|
+
for i in range(3, self._server_config.min_fds):
|
8043
8140
|
if i in self._inherited_fds:
|
8044
8141
|
continue
|
8045
8142
|
close_fd(Fd(i))
|
@@ -8229,11 +8326,29 @@ class Supervisor:
|
|
8229
8326
|
#
|
8230
8327
|
|
8231
8328
|
def _run_once(self) -> None:
|
8232
|
-
|
8233
|
-
|
8234
|
-
|
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
|
+
|
8235
8348
|
self._reap()
|
8349
|
+
|
8236
8350
|
self._signal_handler.handle_signals()
|
8351
|
+
|
8237
8352
|
self._tick()
|
8238
8353
|
|
8239
8354
|
if self._states.state < SupervisorState.RUNNING:
|
@@ -8255,38 +8370,18 @@ class Supervisor:
|
|
8255
8370
|
# down, so push it back on to the end of the stop group queue
|
8256
8371
|
self._stop_groups.append(group)
|
8257
8372
|
|
8258
|
-
|
8259
|
-
sorted_groups = list(self._process_groups)
|
8260
|
-
sorted_groups.sort()
|
8261
|
-
|
8262
|
-
if self._states.state < SupervisorState.RUNNING:
|
8263
|
-
if not self._stopping:
|
8264
|
-
# first time, set the stopping flag, do a notification and set stop_groups
|
8265
|
-
self._stopping = True
|
8266
|
-
self._stop_groups = sorted_groups[:]
|
8267
|
-
self._event_callbacks.notify(SupervisorStoppingEvent())
|
8268
|
-
|
8269
|
-
self._ordered_stop_groups_phase_1()
|
8270
|
-
|
8271
|
-
if not self.shutdown_report():
|
8272
|
-
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
8273
|
-
raise ExitNow
|
8274
|
-
|
8275
|
-
self._io.poll()
|
8276
|
-
|
8277
|
-
for group in sorted_groups:
|
8278
|
-
for process in group:
|
8279
|
-
process.transition()
|
8373
|
+
#
|
8280
8374
|
|
8281
8375
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
8282
8376
|
if depth >= 100:
|
8283
8377
|
return
|
8284
8378
|
|
8285
8379
|
wp = waitpid()
|
8286
|
-
|
8380
|
+
|
8287
8381
|
if wp is None or not wp.pid:
|
8288
8382
|
return
|
8289
8383
|
|
8384
|
+
log.info(f'Waited pid: {wp}') # noqa
|
8290
8385
|
process = self._pid_history.get(wp.pid, None)
|
8291
8386
|
if process is None:
|
8292
8387
|
_, msg = decode_wait_status(wp.sts)
|
@@ -8299,6 +8394,8 @@ class Supervisor:
|
|
8299
8394
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
8300
8395
|
self._reap(once=False, depth=depth + 1)
|
8301
8396
|
|
8397
|
+
#
|
8398
|
+
|
8302
8399
|
def _tick(self, now: ta.Optional[float] = None) -> None:
|
8303
8400
|
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
8304
8401
|
|
@@ -8329,7 +8426,7 @@ class WaitedPid(ta.NamedTuple):
|
|
8329
8426
|
|
8330
8427
|
|
8331
8428
|
def waitpid() -> ta.Optional[WaitedPid]:
|
8332
|
-
# Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4.
|
8429
|
+
# Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
|
8333
8430
|
# still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
|
8334
8431
|
# waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
|
8335
8432
|
# normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
|
@@ -8364,6 +8461,7 @@ class _FdIoPollerDaemonizeListener(DaemonizeListener):
|
|
8364
8461
|
|
8365
8462
|
|
8366
8463
|
def bind_server(
|
8464
|
+
exit_stack: contextlib.ExitStack,
|
8367
8465
|
config: ServerConfig,
|
8368
8466
|
*,
|
8369
8467
|
server_epoch: ta.Optional[ServerEpoch] = None,
|
@@ -8372,6 +8470,8 @@ def bind_server(
|
|
8372
8470
|
lst: ta.List[InjectorBindingOrBindings] = [
|
8373
8471
|
inj.bind(config),
|
8374
8472
|
|
8473
|
+
inj.bind(exit_stack),
|
8474
|
+
|
8375
8475
|
inj.bind_array(DaemonizeListener),
|
8376
8476
|
inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
|
8377
8477
|
|
@@ -8427,8 +8527,10 @@ def bind_server(
|
|
8427
8527
|
PollFdIoPoller,
|
8428
8528
|
SelectFdIoPoller,
|
8429
8529
|
]))
|
8430
|
-
lst.
|
8431
|
-
|
8530
|
+
lst.extend([
|
8531
|
+
inj.bind(poller_impl, key=FdIoPoller, singleton=True),
|
8532
|
+
inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True),
|
8533
|
+
])
|
8432
8534
|
|
8433
8535
|
#
|
8434
8536
|
|
@@ -8497,18 +8599,20 @@ def main(
|
|
8497
8599
|
prepare=prepare_server_config,
|
8498
8600
|
)
|
8499
8601
|
|
8500
|
-
|
8501
|
-
|
8502
|
-
|
8503
|
-
|
8504
|
-
|
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
|
+
))
|
8505
8609
|
|
8506
|
-
|
8610
|
+
supervisor = injector[Supervisor]
|
8507
8611
|
|
8508
|
-
|
8509
|
-
|
8510
|
-
|
8511
|
-
|
8612
|
+
try:
|
8613
|
+
supervisor.main()
|
8614
|
+
except ExitNow:
|
8615
|
+
pass
|
8512
8616
|
|
8513
8617
|
if supervisor.state < SupervisorState.RESTARTING:
|
8514
8618
|
break
|