ominfra 0.0.0.dev119__py3-none-any.whl → 0.0.0.dev120__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/scripts/supervisor.py +108 -99
- ominfra/supervisor/dispatchers.py +4 -4
- ominfra/supervisor/events.py +4 -7
- ominfra/supervisor/process.py +8 -7
- ominfra/supervisor/supervisor.py +95 -86
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/RECORD +11 -11
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev119.dist-info → ominfra-0.0.0.dev120.dist-info}/top_level.txt +0 -0
ominfra/scripts/supervisor.py
CHANGED
@@ -22,6 +22,7 @@ import itertools
|
|
22
22
|
import json
|
23
23
|
import logging
|
24
24
|
import os
|
25
|
+
import os.path
|
25
26
|
import pwd
|
26
27
|
import re
|
27
28
|
import resource
|
@@ -1602,12 +1603,9 @@ class EventCallbacks:
|
|
1602
1603
|
|
1603
1604
|
EVENT_CALLBACKS = EventCallbacks()
|
1604
1605
|
|
1605
|
-
notify_event = EVENT_CALLBACKS.notify
|
1606
|
-
clear_events = EVENT_CALLBACKS.clear
|
1607
|
-
|
1608
1606
|
|
1609
1607
|
class Event(abc.ABC): # noqa
|
1610
|
-
"""Abstract event type
|
1608
|
+
"""Abstract event type."""
|
1611
1609
|
|
1612
1610
|
|
1613
1611
|
class ProcessLogEvent(Event, abc.ABC):
|
@@ -1687,7 +1685,7 @@ class RemoteCommunicationEvent(Event):
|
|
1687
1685
|
|
1688
1686
|
|
1689
1687
|
class SupervisorStateChangeEvent(Event):
|
1690
|
-
"""
|
1688
|
+
"""Abstract class."""
|
1691
1689
|
|
1692
1690
|
def payload(self):
|
1693
1691
|
return ''
|
@@ -1709,7 +1707,7 @@ class EventRejectedEvent: # purposely does not subclass Event
|
|
1709
1707
|
|
1710
1708
|
|
1711
1709
|
class ProcessStateEvent(Event):
|
1712
|
-
"""
|
1710
|
+
"""Abstract class, never raised directly."""
|
1713
1711
|
frm = None
|
1714
1712
|
to = None
|
1715
1713
|
|
@@ -1798,7 +1796,7 @@ class ProcessGroupRemovedEvent(ProcessGroupEvent):
|
|
1798
1796
|
|
1799
1797
|
|
1800
1798
|
class TickEvent(Event):
|
1801
|
-
"""
|
1799
|
+
"""Abstract."""
|
1802
1800
|
|
1803
1801
|
def __init__(self, when, supervisord):
|
1804
1802
|
super().__init__()
|
@@ -2752,10 +2750,10 @@ class OutputDispatcher(Dispatcher):
|
|
2752
2750
|
|
2753
2751
|
if self._channel == 'stdout':
|
2754
2752
|
if self._stdout_events_enabled:
|
2755
|
-
|
2753
|
+
EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
|
2756
2754
|
|
2757
2755
|
elif self._stderr_events_enabled:
|
2758
|
-
|
2756
|
+
EVENT_CALLBACKS.notify(ProcessLogStderrEvent(self._process, self._process.pid, data))
|
2759
2757
|
|
2760
2758
|
def record_output(self):
|
2761
2759
|
if self._capture_log is None:
|
@@ -2806,7 +2804,7 @@ class OutputDispatcher(Dispatcher):
|
|
2806
2804
|
channel = self._channel
|
2807
2805
|
procname = self._process.config.name
|
2808
2806
|
event = self.event_type(self._process, self._process.pid, data)
|
2809
|
-
|
2807
|
+
EVENT_CALLBACKS.notify(event)
|
2810
2808
|
|
2811
2809
|
log.debug('%r %s emitted a comm event', procname, channel)
|
2812
2810
|
for handler in self._capture_log.handlers:
|
@@ -3019,7 +3017,7 @@ class Subprocess(AbstractSubprocess):
|
|
3019
3017
|
event_class = self.event_map.get(new_state)
|
3020
3018
|
if event_class is not None:
|
3021
3019
|
event = event_class(self, old_state, expected)
|
3022
|
-
|
3020
|
+
EVENT_CALLBACKS.notify(event)
|
3023
3021
|
|
3024
3022
|
return True
|
3025
3023
|
|
@@ -3136,6 +3134,7 @@ class Subprocess(AbstractSubprocess):
|
|
3136
3134
|
os.dup2(self._pipes['child_stdout'], 2)
|
3137
3135
|
else:
|
3138
3136
|
os.dup2(self._pipes['child_stderr'], 2)
|
3137
|
+
# FIXME: leave debugger fds
|
3139
3138
|
for i in range(3, self.context.config.minfds):
|
3140
3139
|
close_fd(i)
|
3141
3140
|
|
@@ -3171,7 +3170,7 @@ class Subprocess(AbstractSubprocess):
|
|
3171
3170
|
cwd = self.config.directory
|
3172
3171
|
try:
|
3173
3172
|
if cwd is not None:
|
3174
|
-
os.chdir(cwd)
|
3173
|
+
os.chdir(os.path.expanduser(cwd))
|
3175
3174
|
except OSError as why:
|
3176
3175
|
code = errno.errorcode.get(why.args[0], why.args[0])
|
3177
3176
|
msg = f"couldn't chdir to {cwd}: {code}\n"
|
@@ -3227,7 +3226,7 @@ class Subprocess(AbstractSubprocess):
|
|
3227
3226
|
return self.kill(self.config.stopsignal)
|
3228
3227
|
|
3229
3228
|
def stop_report(self) -> None:
|
3230
|
-
"""
|
3229
|
+
"""Log a 'waiting for x to stop' message with throttling."""
|
3231
3230
|
if self.state == ProcessStates.STOPPING:
|
3232
3231
|
now = time.time()
|
3233
3232
|
|
@@ -3357,7 +3356,7 @@ class Subprocess(AbstractSubprocess):
|
|
3357
3356
|
return None
|
3358
3357
|
|
3359
3358
|
def finish(self, sts: int) -> None:
|
3360
|
-
"""
|
3359
|
+
"""The process was reaped and we need to report and manage its state."""
|
3361
3360
|
|
3362
3361
|
self.drain()
|
3363
3362
|
|
@@ -3438,7 +3437,7 @@ class Subprocess(AbstractSubprocess):
|
|
3438
3437
|
# system that this event was rejected so it can be processed again.
|
3439
3438
|
if self.event is not None:
|
3440
3439
|
# Note: this should only be true if we were in the BUSY state when finish() was called.
|
3441
|
-
|
3440
|
+
EVENT_CALLBACKS.notify(EventRejectedEvent(self, self.event)) # type: ignore
|
3442
3441
|
self.event = None
|
3443
3442
|
|
3444
3443
|
def set_uid(self) -> ta.Optional[str]:
|
@@ -3604,7 +3603,7 @@ class ProcessGroup:
|
|
3604
3603
|
# ../supervisor.py
|
3605
3604
|
|
3606
3605
|
|
3607
|
-
def timeslice(period, when):
|
3606
|
+
def timeslice(period: int, when: float) -> int:
|
3608
3607
|
return int(when - (when % period))
|
3609
3608
|
|
3610
3609
|
|
@@ -3620,6 +3619,8 @@ class Supervisor:
|
|
3620
3619
|
self._stopping = False # set after we detect that we are handling a stop request
|
3621
3620
|
self._last_shutdown_report = 0. # throttle for delayed process error reports at stop
|
3622
3621
|
|
3622
|
+
#
|
3623
|
+
|
3623
3624
|
@property
|
3624
3625
|
def context(self) -> ServerContext:
|
3625
3626
|
return self._context
|
@@ -3627,58 +3628,7 @@ class Supervisor:
|
|
3627
3628
|
def get_state(self) -> SupervisorState:
|
3628
3629
|
return self._context.state
|
3629
3630
|
|
3630
|
-
|
3631
|
-
self.setup()
|
3632
|
-
self.run()
|
3633
|
-
|
3634
|
-
@cached_nullary
|
3635
|
-
def setup(self) -> None:
|
3636
|
-
if not self._context.first:
|
3637
|
-
# prevent crash on libdispatch-based systems, at least for the first request
|
3638
|
-
self._context.cleanup_fds()
|
3639
|
-
|
3640
|
-
self._context.set_uid_or_exit()
|
3641
|
-
|
3642
|
-
if self._context.first:
|
3643
|
-
self._context.set_rlimits_or_exit()
|
3644
|
-
|
3645
|
-
# this sets the options.logger object delay logger instantiation until after setuid
|
3646
|
-
if not self._context.config.nocleanup:
|
3647
|
-
# clean up old automatic logs
|
3648
|
-
self._context.clear_auto_child_logdir()
|
3649
|
-
|
3650
|
-
def run(
|
3651
|
-
self,
|
3652
|
-
*,
|
3653
|
-
callback: ta.Optional[ta.Callable[['Supervisor'], bool]] = None,
|
3654
|
-
) -> None:
|
3655
|
-
self._process_groups = {} # clear
|
3656
|
-
self._stop_groups = None # clear
|
3657
|
-
|
3658
|
-
clear_events()
|
3659
|
-
|
3660
|
-
try:
|
3661
|
-
for config in self._context.config.groups or []:
|
3662
|
-
self.add_process_group(config)
|
3663
|
-
|
3664
|
-
self._context.set_signals()
|
3665
|
-
|
3666
|
-
if not self._context.config.nodaemon and self._context.first:
|
3667
|
-
self._context.daemonize()
|
3668
|
-
|
3669
|
-
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
3670
|
-
self._context.write_pidfile()
|
3671
|
-
|
3672
|
-
notify_event(SupervisorRunningEvent())
|
3673
|
-
|
3674
|
-
while True:
|
3675
|
-
if callback is not None and not callback(self):
|
3676
|
-
break
|
3677
|
-
|
3678
|
-
self._run_once()
|
3679
|
-
|
3680
|
-
finally:
|
3681
|
-
self._context.cleanup()
|
3631
|
+
#
|
3682
3632
|
|
3683
3633
|
class DiffToActive(ta.NamedTuple):
|
3684
3634
|
added: ta.List[ProcessGroupConfig]
|
@@ -3707,7 +3657,7 @@ class Supervisor:
|
|
3707
3657
|
group = self._process_groups[name] = ProcessGroup(config, self._context)
|
3708
3658
|
group.after_setuid()
|
3709
3659
|
|
3710
|
-
|
3660
|
+
EVENT_CALLBACKS.notify(ProcessGroupAddedEvent(name))
|
3711
3661
|
return True
|
3712
3662
|
|
3713
3663
|
def remove_process_group(self, name: str) -> bool:
|
@@ -3718,7 +3668,7 @@ class Supervisor:
|
|
3718
3668
|
|
3719
3669
|
del self._process_groups[name]
|
3720
3670
|
|
3721
|
-
|
3671
|
+
EVENT_CALLBACKS.notify(ProcessGroupRemovedEvent(name))
|
3722
3672
|
return True
|
3723
3673
|
|
3724
3674
|
def get_process_map(self) -> ta.Dict[int, Dispatcher]:
|
@@ -3747,6 +3697,72 @@ class Supervisor:
|
|
3747
3697
|
|
3748
3698
|
return unstopped
|
3749
3699
|
|
3700
|
+
#
|
3701
|
+
|
3702
|
+
def main(self) -> None:
|
3703
|
+
self.setup()
|
3704
|
+
self.run()
|
3705
|
+
|
3706
|
+
@cached_nullary
|
3707
|
+
def setup(self) -> None:
|
3708
|
+
if not self._context.first:
|
3709
|
+
# prevent crash on libdispatch-based systems, at least for the first request
|
3710
|
+
self._context.cleanup_fds()
|
3711
|
+
|
3712
|
+
self._context.set_uid_or_exit()
|
3713
|
+
|
3714
|
+
if self._context.first:
|
3715
|
+
self._context.set_rlimits_or_exit()
|
3716
|
+
|
3717
|
+
# this sets the options.logger object delay logger instantiation until after setuid
|
3718
|
+
if not self._context.config.nocleanup:
|
3719
|
+
# clean up old automatic logs
|
3720
|
+
self._context.clear_auto_child_logdir()
|
3721
|
+
|
3722
|
+
def run(
|
3723
|
+
self,
|
3724
|
+
*,
|
3725
|
+
callback: ta.Optional[ta.Callable[['Supervisor'], bool]] = None,
|
3726
|
+
) -> None:
|
3727
|
+
self._process_groups = {} # clear
|
3728
|
+
self._stop_groups = None # clear
|
3729
|
+
|
3730
|
+
EVENT_CALLBACKS.clear()
|
3731
|
+
|
3732
|
+
try:
|
3733
|
+
for config in self._context.config.groups or []:
|
3734
|
+
self.add_process_group(config)
|
3735
|
+
|
3736
|
+
self._context.set_signals()
|
3737
|
+
|
3738
|
+
if not self._context.config.nodaemon and self._context.first:
|
3739
|
+
self._context.daemonize()
|
3740
|
+
|
3741
|
+
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
3742
|
+
self._context.write_pidfile()
|
3743
|
+
|
3744
|
+
EVENT_CALLBACKS.notify(SupervisorRunningEvent())
|
3745
|
+
|
3746
|
+
while True:
|
3747
|
+
if callback is not None and not callback(self):
|
3748
|
+
break
|
3749
|
+
|
3750
|
+
self._run_once()
|
3751
|
+
|
3752
|
+
finally:
|
3753
|
+
self._context.cleanup()
|
3754
|
+
|
3755
|
+
#
|
3756
|
+
|
3757
|
+
def _run_once(self) -> None:
|
3758
|
+
self._poll()
|
3759
|
+
self._reap()
|
3760
|
+
self._handle_signal()
|
3761
|
+
self._tick()
|
3762
|
+
|
3763
|
+
if self._context.state < SupervisorStates.RUNNING:
|
3764
|
+
self._ordered_stop_groups_phase_2()
|
3765
|
+
|
3750
3766
|
def _ordered_stop_groups_phase_1(self) -> None:
|
3751
3767
|
if self._stop_groups:
|
3752
3768
|
# stop the last group (the one with the "highest" priority)
|
@@ -3763,7 +3779,7 @@ class Supervisor:
|
|
3763
3779
|
# down, so push it back on to the end of the stop group queue
|
3764
3780
|
self._stop_groups.append(group)
|
3765
3781
|
|
3766
|
-
def
|
3782
|
+
def _poll(self) -> None:
|
3767
3783
|
combined_map = {}
|
3768
3784
|
combined_map.update(self.get_process_map())
|
3769
3785
|
|
@@ -3775,7 +3791,7 @@ class Supervisor:
|
|
3775
3791
|
# first time, set the stopping flag, do a notification and set stop_groups
|
3776
3792
|
self._stopping = True
|
3777
3793
|
self._stop_groups = pgroups[:]
|
3778
|
-
|
3794
|
+
EVENT_CALLBACKS.notify(SupervisorStoppingEvent())
|
3779
3795
|
|
3780
3796
|
self._ordered_stop_groups_phase_1()
|
3781
3797
|
|
@@ -3835,33 +3851,6 @@ class Supervisor:
|
|
3835
3851
|
for group in pgroups:
|
3836
3852
|
group.transition()
|
3837
3853
|
|
3838
|
-
self._reap()
|
3839
|
-
self._handle_signal()
|
3840
|
-
self._tick()
|
3841
|
-
|
3842
|
-
if self._context.state < SupervisorStates.RUNNING:
|
3843
|
-
self._ordered_stop_groups_phase_2()
|
3844
|
-
|
3845
|
-
def _tick(self, now: ta.Optional[float] = None) -> None:
|
3846
|
-
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
3847
|
-
|
3848
|
-
if now is None:
|
3849
|
-
# now won't be None in unit tests
|
3850
|
-
now = time.time()
|
3851
|
-
|
3852
|
-
for event in TICK_EVENTS:
|
3853
|
-
period = event.period # type: ignore
|
3854
|
-
|
3855
|
-
last_tick = self._ticks.get(period)
|
3856
|
-
if last_tick is None:
|
3857
|
-
# we just started up
|
3858
|
-
last_tick = self._ticks[period] = timeslice(period, now)
|
3859
|
-
|
3860
|
-
this_tick = timeslice(period, now)
|
3861
|
-
if this_tick != last_tick:
|
3862
|
-
self._ticks[period] = this_tick
|
3863
|
-
notify_event(event(this_tick, self))
|
3864
|
-
|
3865
3854
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
3866
3855
|
if depth >= 100:
|
3867
3856
|
return
|
@@ -3910,6 +3899,26 @@ class Supervisor:
|
|
3910
3899
|
else:
|
3911
3900
|
log.debug('received %s indicating nothing', signame(sig))
|
3912
3901
|
|
3902
|
+
def _tick(self, now: ta.Optional[float] = None) -> None:
|
3903
|
+
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
3904
|
+
|
3905
|
+
if now is None:
|
3906
|
+
# now won't be None in unit tests
|
3907
|
+
now = time.time()
|
3908
|
+
|
3909
|
+
for event in TICK_EVENTS:
|
3910
|
+
period = event.period # type: ignore
|
3911
|
+
|
3912
|
+
last_tick = self._ticks.get(period)
|
3913
|
+
if last_tick is None:
|
3914
|
+
# we just started up
|
3915
|
+
last_tick = self._ticks[period] = timeslice(period, now)
|
3916
|
+
|
3917
|
+
this_tick = timeslice(period, now)
|
3918
|
+
if this_tick != last_tick:
|
3919
|
+
self._ticks[period] = this_tick
|
3920
|
+
EVENT_CALLBACKS.notify(event(this_tick, self))
|
3921
|
+
|
3913
3922
|
|
3914
3923
|
########################################
|
3915
3924
|
# main.py
|
@@ -13,9 +13,9 @@ from .compat import find_prefix_at_end
|
|
13
13
|
from .compat import readfd
|
14
14
|
from .compat import strip_escapes
|
15
15
|
from .configs import ProcessConfig
|
16
|
+
from .events import EVENT_CALLBACKS
|
16
17
|
from .events import ProcessLogStderrEvent
|
17
18
|
from .events import ProcessLogStdoutEvent
|
18
|
-
from .events import notify_event
|
19
19
|
from .types import AbstractSubprocess
|
20
20
|
|
21
21
|
|
@@ -207,10 +207,10 @@ class OutputDispatcher(Dispatcher):
|
|
207
207
|
|
208
208
|
if self._channel == 'stdout':
|
209
209
|
if self._stdout_events_enabled:
|
210
|
-
|
210
|
+
EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
|
211
211
|
|
212
212
|
elif self._stderr_events_enabled:
|
213
|
-
|
213
|
+
EVENT_CALLBACKS.notify(ProcessLogStderrEvent(self._process, self._process.pid, data))
|
214
214
|
|
215
215
|
def record_output(self):
|
216
216
|
if self._capture_log is None:
|
@@ -261,7 +261,7 @@ class OutputDispatcher(Dispatcher):
|
|
261
261
|
channel = self._channel
|
262
262
|
procname = self._process.config.name
|
263
263
|
event = self.event_type(self._process, self._process.pid, data)
|
264
|
-
|
264
|
+
EVENT_CALLBACKS.notify(event)
|
265
265
|
|
266
266
|
log.debug('%r %s emitted a comm event', procname, channel)
|
267
267
|
for handler in self._capture_log.handlers:
|
ominfra/supervisor/events.py
CHANGED
@@ -29,12 +29,9 @@ class EventCallbacks:
|
|
29
29
|
|
30
30
|
EVENT_CALLBACKS = EventCallbacks()
|
31
31
|
|
32
|
-
notify_event = EVENT_CALLBACKS.notify
|
33
|
-
clear_events = EVENT_CALLBACKS.clear
|
34
|
-
|
35
32
|
|
36
33
|
class Event(abc.ABC): # noqa
|
37
|
-
"""Abstract event type
|
34
|
+
"""Abstract event type."""
|
38
35
|
|
39
36
|
|
40
37
|
class ProcessLogEvent(Event, abc.ABC):
|
@@ -114,7 +111,7 @@ class RemoteCommunicationEvent(Event):
|
|
114
111
|
|
115
112
|
|
116
113
|
class SupervisorStateChangeEvent(Event):
|
117
|
-
"""
|
114
|
+
"""Abstract class."""
|
118
115
|
|
119
116
|
def payload(self):
|
120
117
|
return ''
|
@@ -136,7 +133,7 @@ class EventRejectedEvent: # purposely does not subclass Event
|
|
136
133
|
|
137
134
|
|
138
135
|
class ProcessStateEvent(Event):
|
139
|
-
"""
|
136
|
+
"""Abstract class, never raised directly."""
|
140
137
|
frm = None
|
141
138
|
to = None
|
142
139
|
|
@@ -225,7 +222,7 @@ class ProcessGroupRemovedEvent(ProcessGroupEvent):
|
|
225
222
|
|
226
223
|
|
227
224
|
class TickEvent(Event):
|
228
|
-
"""
|
225
|
+
"""Abstract."""
|
229
226
|
|
230
227
|
def __init__(self, when, supervisord):
|
231
228
|
super().__init__()
|
ominfra/supervisor/process.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import errno
|
3
3
|
import functools
|
4
|
-
import os
|
4
|
+
import os.path
|
5
5
|
import shlex
|
6
6
|
import signal
|
7
7
|
import time
|
@@ -30,6 +30,7 @@ from .datatypes import RestartUnconditionally
|
|
30
30
|
from .dispatchers import Dispatcher
|
31
31
|
from .dispatchers import InputDispatcher
|
32
32
|
from .dispatchers import OutputDispatcher
|
33
|
+
from .events import EVENT_CALLBACKS
|
33
34
|
from .events import EventRejectedEvent
|
34
35
|
from .events import ProcessCommunicationEvent
|
35
36
|
from .events import ProcessCommunicationStderrEvent
|
@@ -43,7 +44,6 @@ from .events import ProcessStateStartingEvent
|
|
43
44
|
from .events import ProcessStateStoppedEvent
|
44
45
|
from .events import ProcessStateStoppingEvent
|
45
46
|
from .events import ProcessStateUnknownEvent
|
46
|
-
from .events import notify_event
|
47
47
|
from .exceptions import BadCommandError
|
48
48
|
from .exceptions import ProcessError
|
49
49
|
from .states import STOPPED_STATES
|
@@ -207,7 +207,7 @@ class Subprocess(AbstractSubprocess):
|
|
207
207
|
event_class = self.event_map.get(new_state)
|
208
208
|
if event_class is not None:
|
209
209
|
event = event_class(self, old_state, expected)
|
210
|
-
|
210
|
+
EVENT_CALLBACKS.notify(event)
|
211
211
|
|
212
212
|
return True
|
213
213
|
|
@@ -324,6 +324,7 @@ class Subprocess(AbstractSubprocess):
|
|
324
324
|
os.dup2(self._pipes['child_stdout'], 2)
|
325
325
|
else:
|
326
326
|
os.dup2(self._pipes['child_stderr'], 2)
|
327
|
+
# FIXME: leave debugger fds
|
327
328
|
for i in range(3, self.context.config.minfds):
|
328
329
|
close_fd(i)
|
329
330
|
|
@@ -359,7 +360,7 @@ class Subprocess(AbstractSubprocess):
|
|
359
360
|
cwd = self.config.directory
|
360
361
|
try:
|
361
362
|
if cwd is not None:
|
362
|
-
os.chdir(cwd)
|
363
|
+
os.chdir(os.path.expanduser(cwd))
|
363
364
|
except OSError as why:
|
364
365
|
code = errno.errorcode.get(why.args[0], why.args[0])
|
365
366
|
msg = f"couldn't chdir to {cwd}: {code}\n"
|
@@ -415,7 +416,7 @@ class Subprocess(AbstractSubprocess):
|
|
415
416
|
return self.kill(self.config.stopsignal)
|
416
417
|
|
417
418
|
def stop_report(self) -> None:
|
418
|
-
"""
|
419
|
+
"""Log a 'waiting for x to stop' message with throttling."""
|
419
420
|
if self.state == ProcessStates.STOPPING:
|
420
421
|
now = time.time()
|
421
422
|
|
@@ -545,7 +546,7 @@ class Subprocess(AbstractSubprocess):
|
|
545
546
|
return None
|
546
547
|
|
547
548
|
def finish(self, sts: int) -> None:
|
548
|
-
"""
|
549
|
+
"""The process was reaped and we need to report and manage its state."""
|
549
550
|
|
550
551
|
self.drain()
|
551
552
|
|
@@ -626,7 +627,7 @@ class Subprocess(AbstractSubprocess):
|
|
626
627
|
# system that this event was rejected so it can be processed again.
|
627
628
|
if self.event is not None:
|
628
629
|
# Note: this should only be true if we were in the BUSY state when finish() was called.
|
629
|
-
|
630
|
+
EVENT_CALLBACKS.notify(EventRejectedEvent(self, self.event)) # type: ignore
|
630
631
|
self.event = None
|
631
632
|
|
632
633
|
def set_uid(self) -> ta.Optional[str]:
|
ominfra/supervisor/supervisor.py
CHANGED
@@ -14,13 +14,12 @@ from .compat import signame
|
|
14
14
|
from .configs import ProcessGroupConfig
|
15
15
|
from .context import ServerContext
|
16
16
|
from .dispatchers import Dispatcher
|
17
|
+
from .events import EVENT_CALLBACKS
|
17
18
|
from .events import TICK_EVENTS
|
18
19
|
from .events import ProcessGroupAddedEvent
|
19
20
|
from .events import ProcessGroupRemovedEvent
|
20
21
|
from .events import SupervisorRunningEvent
|
21
22
|
from .events import SupervisorStoppingEvent
|
22
|
-
from .events import clear_events
|
23
|
-
from .events import notify_event
|
24
23
|
from .process import ProcessGroup
|
25
24
|
from .process import Subprocess
|
26
25
|
from .states import SupervisorState
|
@@ -28,7 +27,7 @@ from .states import SupervisorStates
|
|
28
27
|
from .states import get_process_state_description
|
29
28
|
|
30
29
|
|
31
|
-
def timeslice(period, when):
|
30
|
+
def timeslice(period: int, when: float) -> int:
|
32
31
|
return int(when - (when % period))
|
33
32
|
|
34
33
|
|
@@ -44,6 +43,8 @@ class Supervisor:
|
|
44
43
|
self._stopping = False # set after we detect that we are handling a stop request
|
45
44
|
self._last_shutdown_report = 0. # throttle for delayed process error reports at stop
|
46
45
|
|
46
|
+
#
|
47
|
+
|
47
48
|
@property
|
48
49
|
def context(self) -> ServerContext:
|
49
50
|
return self._context
|
@@ -51,58 +52,7 @@ class Supervisor:
|
|
51
52
|
def get_state(self) -> SupervisorState:
|
52
53
|
return self._context.state
|
53
54
|
|
54
|
-
|
55
|
-
self.setup()
|
56
|
-
self.run()
|
57
|
-
|
58
|
-
@cached_nullary
|
59
|
-
def setup(self) -> None:
|
60
|
-
if not self._context.first:
|
61
|
-
# prevent crash on libdispatch-based systems, at least for the first request
|
62
|
-
self._context.cleanup_fds()
|
63
|
-
|
64
|
-
self._context.set_uid_or_exit()
|
65
|
-
|
66
|
-
if self._context.first:
|
67
|
-
self._context.set_rlimits_or_exit()
|
68
|
-
|
69
|
-
# this sets the options.logger object delay logger instantiation until after setuid
|
70
|
-
if not self._context.config.nocleanup:
|
71
|
-
# clean up old automatic logs
|
72
|
-
self._context.clear_auto_child_logdir()
|
73
|
-
|
74
|
-
def run(
|
75
|
-
self,
|
76
|
-
*,
|
77
|
-
callback: ta.Optional[ta.Callable[['Supervisor'], bool]] = None,
|
78
|
-
) -> None:
|
79
|
-
self._process_groups = {} # clear
|
80
|
-
self._stop_groups = None # clear
|
81
|
-
|
82
|
-
clear_events()
|
83
|
-
|
84
|
-
try:
|
85
|
-
for config in self._context.config.groups or []:
|
86
|
-
self.add_process_group(config)
|
87
|
-
|
88
|
-
self._context.set_signals()
|
89
|
-
|
90
|
-
if not self._context.config.nodaemon and self._context.first:
|
91
|
-
self._context.daemonize()
|
92
|
-
|
93
|
-
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
94
|
-
self._context.write_pidfile()
|
95
|
-
|
96
|
-
notify_event(SupervisorRunningEvent())
|
97
|
-
|
98
|
-
while True:
|
99
|
-
if callback is not None and not callback(self):
|
100
|
-
break
|
101
|
-
|
102
|
-
self._run_once()
|
103
|
-
|
104
|
-
finally:
|
105
|
-
self._context.cleanup()
|
55
|
+
#
|
106
56
|
|
107
57
|
class DiffToActive(ta.NamedTuple):
|
108
58
|
added: ta.List[ProcessGroupConfig]
|
@@ -131,7 +81,7 @@ class Supervisor:
|
|
131
81
|
group = self._process_groups[name] = ProcessGroup(config, self._context)
|
132
82
|
group.after_setuid()
|
133
83
|
|
134
|
-
|
84
|
+
EVENT_CALLBACKS.notify(ProcessGroupAddedEvent(name))
|
135
85
|
return True
|
136
86
|
|
137
87
|
def remove_process_group(self, name: str) -> bool:
|
@@ -142,7 +92,7 @@ class Supervisor:
|
|
142
92
|
|
143
93
|
del self._process_groups[name]
|
144
94
|
|
145
|
-
|
95
|
+
EVENT_CALLBACKS.notify(ProcessGroupRemovedEvent(name))
|
146
96
|
return True
|
147
97
|
|
148
98
|
def get_process_map(self) -> ta.Dict[int, Dispatcher]:
|
@@ -171,6 +121,72 @@ class Supervisor:
|
|
171
121
|
|
172
122
|
return unstopped
|
173
123
|
|
124
|
+
#
|
125
|
+
|
126
|
+
def main(self) -> None:
|
127
|
+
self.setup()
|
128
|
+
self.run()
|
129
|
+
|
130
|
+
@cached_nullary
|
131
|
+
def setup(self) -> None:
|
132
|
+
if not self._context.first:
|
133
|
+
# prevent crash on libdispatch-based systems, at least for the first request
|
134
|
+
self._context.cleanup_fds()
|
135
|
+
|
136
|
+
self._context.set_uid_or_exit()
|
137
|
+
|
138
|
+
if self._context.first:
|
139
|
+
self._context.set_rlimits_or_exit()
|
140
|
+
|
141
|
+
# this sets the options.logger object delay logger instantiation until after setuid
|
142
|
+
if not self._context.config.nocleanup:
|
143
|
+
# clean up old automatic logs
|
144
|
+
self._context.clear_auto_child_logdir()
|
145
|
+
|
146
|
+
def run(
|
147
|
+
self,
|
148
|
+
*,
|
149
|
+
callback: ta.Optional[ta.Callable[['Supervisor'], bool]] = None,
|
150
|
+
) -> None:
|
151
|
+
self._process_groups = {} # clear
|
152
|
+
self._stop_groups = None # clear
|
153
|
+
|
154
|
+
EVENT_CALLBACKS.clear()
|
155
|
+
|
156
|
+
try:
|
157
|
+
for config in self._context.config.groups or []:
|
158
|
+
self.add_process_group(config)
|
159
|
+
|
160
|
+
self._context.set_signals()
|
161
|
+
|
162
|
+
if not self._context.config.nodaemon and self._context.first:
|
163
|
+
self._context.daemonize()
|
164
|
+
|
165
|
+
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
166
|
+
self._context.write_pidfile()
|
167
|
+
|
168
|
+
EVENT_CALLBACKS.notify(SupervisorRunningEvent())
|
169
|
+
|
170
|
+
while True:
|
171
|
+
if callback is not None and not callback(self):
|
172
|
+
break
|
173
|
+
|
174
|
+
self._run_once()
|
175
|
+
|
176
|
+
finally:
|
177
|
+
self._context.cleanup()
|
178
|
+
|
179
|
+
#
|
180
|
+
|
181
|
+
def _run_once(self) -> None:
|
182
|
+
self._poll()
|
183
|
+
self._reap()
|
184
|
+
self._handle_signal()
|
185
|
+
self._tick()
|
186
|
+
|
187
|
+
if self._context.state < SupervisorStates.RUNNING:
|
188
|
+
self._ordered_stop_groups_phase_2()
|
189
|
+
|
174
190
|
def _ordered_stop_groups_phase_1(self) -> None:
|
175
191
|
if self._stop_groups:
|
176
192
|
# stop the last group (the one with the "highest" priority)
|
@@ -187,7 +203,7 @@ class Supervisor:
|
|
187
203
|
# down, so push it back on to the end of the stop group queue
|
188
204
|
self._stop_groups.append(group)
|
189
205
|
|
190
|
-
def
|
206
|
+
def _poll(self) -> None:
|
191
207
|
combined_map = {}
|
192
208
|
combined_map.update(self.get_process_map())
|
193
209
|
|
@@ -199,7 +215,7 @@ class Supervisor:
|
|
199
215
|
# first time, set the stopping flag, do a notification and set stop_groups
|
200
216
|
self._stopping = True
|
201
217
|
self._stop_groups = pgroups[:]
|
202
|
-
|
218
|
+
EVENT_CALLBACKS.notify(SupervisorStoppingEvent())
|
203
219
|
|
204
220
|
self._ordered_stop_groups_phase_1()
|
205
221
|
|
@@ -259,33 +275,6 @@ class Supervisor:
|
|
259
275
|
for group in pgroups:
|
260
276
|
group.transition()
|
261
277
|
|
262
|
-
self._reap()
|
263
|
-
self._handle_signal()
|
264
|
-
self._tick()
|
265
|
-
|
266
|
-
if self._context.state < SupervisorStates.RUNNING:
|
267
|
-
self._ordered_stop_groups_phase_2()
|
268
|
-
|
269
|
-
def _tick(self, now: ta.Optional[float] = None) -> None:
|
270
|
-
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
271
|
-
|
272
|
-
if now is None:
|
273
|
-
# now won't be None in unit tests
|
274
|
-
now = time.time()
|
275
|
-
|
276
|
-
for event in TICK_EVENTS:
|
277
|
-
period = event.period # type: ignore
|
278
|
-
|
279
|
-
last_tick = self._ticks.get(period)
|
280
|
-
if last_tick is None:
|
281
|
-
# we just started up
|
282
|
-
last_tick = self._ticks[period] = timeslice(period, now)
|
283
|
-
|
284
|
-
this_tick = timeslice(period, now)
|
285
|
-
if this_tick != last_tick:
|
286
|
-
self._ticks[period] = this_tick
|
287
|
-
notify_event(event(this_tick, self))
|
288
|
-
|
289
278
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
290
279
|
if depth >= 100:
|
291
280
|
return
|
@@ -333,3 +322,23 @@ class Supervisor:
|
|
333
322
|
|
334
323
|
else:
|
335
324
|
log.debug('received %s indicating nothing', signame(sig))
|
325
|
+
|
326
|
+
def _tick(self, now: ta.Optional[float] = None) -> None:
|
327
|
+
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
328
|
+
|
329
|
+
if now is None:
|
330
|
+
# now won't be None in unit tests
|
331
|
+
now = time.time()
|
332
|
+
|
333
|
+
for event in TICK_EVENTS:
|
334
|
+
period = event.period # type: ignore
|
335
|
+
|
336
|
+
last_tick = self._ticks.get(period)
|
337
|
+
if last_tick is None:
|
338
|
+
# we just started up
|
339
|
+
last_tick = self._ticks[period] = timeslice(period, now)
|
340
|
+
|
341
|
+
this_tick = timeslice(period, now)
|
342
|
+
if this_tick != last_tick:
|
343
|
+
self._ticks[period] = this_tick
|
344
|
+
EVENT_CALLBACKS.notify(event(this_tick, self))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev120
|
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.
|
16
|
-
Requires-Dist: omlish ==0.0.0.
|
15
|
+
Requires-Dist: omdev ==0.0.0.dev120
|
16
|
+
Requires-Dist: omlish ==0.0.0.dev120
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko ~=3.5 ; extra == 'all'
|
19
19
|
Requires-Dist: asyncssh ~=2.18 ; extra == 'all'
|
@@ -59,30 +59,30 @@ ominfra/pyremote/bootstrap.py,sha256=RvMO3YGaN1E4sgUi1JEtiPak8cjvqtc_vRCq1yqbeZg
|
|
59
59
|
ominfra/pyremote/runcommands.py,sha256=bviS0_TDIoZVAe4h-_iavbvJtVSFu8lnk7fQ5iasCWE,1571
|
60
60
|
ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
61
|
ominfra/scripts/journald2aws.py,sha256=0HHYi_uBV1t2KefVrExs3IZ6Zy-mQa7xN_ka9W9Obb8,94910
|
62
|
-
ominfra/scripts/supervisor.py,sha256=
|
62
|
+
ominfra/scripts/supervisor.py,sha256=C1eT7pIqPRJgN4U87tOjR_SuOSUwd5aUswvPeTy5Xlw,121831
|
63
63
|
ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
64
64
|
ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
|
65
65
|
ominfra/supervisor/compat.py,sha256=Y1d_pk4eN18AbVYjDHAXMMnPwOKTFpc7JDb1uClYMsQ,5064
|
66
66
|
ominfra/supervisor/configs.py,sha256=KpibZJ-V-4UpoJM2fnjXOXJLvDbwRJzNLXLGESUljV4,2966
|
67
67
|
ominfra/supervisor/context.py,sha256=Fg_wD6oUR8vxe3JMM14mR-5ssIrTNwxRr-AXfoGbzJQ,15795
|
68
68
|
ominfra/supervisor/datatypes.py,sha256=UnXO_UlCyJD9u0uvea1wvnk_UZCzxNMeFvPK83gv530,4432
|
69
|
-
ominfra/supervisor/dispatchers.py,sha256=
|
70
|
-
ominfra/supervisor/events.py,sha256=
|
69
|
+
ominfra/supervisor/dispatchers.py,sha256=sJ61yTo9EEbxHwe2NbzOTAFFFCuuyIhYli_xJioQBoo,10423
|
70
|
+
ominfra/supervisor/events.py,sha256=IhdL7Fj-hEvTvZ5WF6aIa2YjSPQhuUoasoJSMmRLQkU,7650
|
71
71
|
ominfra/supervisor/exceptions.py,sha256=Qbu211H3CLlSmi9LsSikOwrcL5HgJP9ugvcKWlGTAoI,750
|
72
72
|
ominfra/supervisor/main.py,sha256=0bj_9AzIfDlB1BB8zcX9npQIknamN7FGoVEYgLMLuP0,1701
|
73
73
|
ominfra/supervisor/poller.py,sha256=VCBxLItfA4Vj69jet2XFbFScPbmdD9JA1evaofk_AnY,7709
|
74
|
-
ominfra/supervisor/process.py,sha256=
|
74
|
+
ominfra/supervisor/process.py,sha256=94cglin7qBwxTNXjOBxqec4qsACu-VfeboW-JfzvvbE,31454
|
75
75
|
ominfra/supervisor/states.py,sha256=JMxXYTZhJkMNQZ2tTV6wId7wrvnWgiZteskACprKskM,1374
|
76
|
-
ominfra/supervisor/supervisor.py,sha256=
|
76
|
+
ominfra/supervisor/supervisor.py,sha256=p612yph5aKzkVLH6CfOMcRHEbNfs_TMlmjfLwtG8Jo0,12324
|
77
77
|
ominfra/supervisor/types.py,sha256=ec62QG0CDJc0XNxCnf3lXxhsxrr4CCScLPI-1SpQjlc,1141
|
78
78
|
ominfra/tailscale/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
79
79
|
ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
|
80
80
|
ominfra/tailscale/cli.py,sha256=DSGp4hn5xwOW-l_u_InKlSF6kIobxtUtVssf_73STs0,3567
|
81
81
|
ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
82
|
ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
|
83
|
-
ominfra-0.0.0.
|
84
|
-
ominfra-0.0.0.
|
85
|
-
ominfra-0.0.0.
|
86
|
-
ominfra-0.0.0.
|
87
|
-
ominfra-0.0.0.
|
88
|
-
ominfra-0.0.0.
|
83
|
+
ominfra-0.0.0.dev120.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
84
|
+
ominfra-0.0.0.dev120.dist-info/METADATA,sha256=xuFwZN7LnvBCDHvhqgdKONRKkDYUYJ6SZqTjaE4k2rI,742
|
85
|
+
ominfra-0.0.0.dev120.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
86
|
+
ominfra-0.0.0.dev120.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
|
87
|
+
ominfra-0.0.0.dev120.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
|
88
|
+
ominfra-0.0.0.dev120.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|