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.
@@ -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
- """ Abstract class """
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
- """ Abstract class, never raised directly """
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
- """ Abstract """
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
- notify_event(ProcessLogStdoutEvent(self._process, self._process.pid, data))
2753
+ EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
2756
2754
 
2757
2755
  elif self._stderr_events_enabled:
2758
- notify_event(ProcessLogStderrEvent(self._process, self._process.pid, data))
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
- notify_event(event)
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
- notify_event(event)
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
- """ Log a 'waiting for x to stop' message with throttling. """
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
- """ The process was reaped and we need to report and manage its state """
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
- notify_event(EventRejectedEvent(self, self.event)) # type: ignore
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
- def main(self) -> None:
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
- notify_event(ProcessGroupAddedEvent(name))
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
- notify_event(ProcessGroupRemovedEvent(name))
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 _run_once(self) -> None:
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
- notify_event(SupervisorStoppingEvent())
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
- notify_event(ProcessLogStdoutEvent(self._process, self._process.pid, data))
210
+ EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
211
211
 
212
212
  elif self._stderr_events_enabled:
213
- notify_event(ProcessLogStderrEvent(self._process, self._process.pid, data))
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
- notify_event(event)
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:
@@ -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
- """ Abstract class """
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
- """ Abstract class, never raised directly """
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
- """ Abstract """
225
+ """Abstract."""
229
226
 
230
227
  def __init__(self, when, supervisord):
231
228
  super().__init__()
@@ -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
- notify_event(event)
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
- """ Log a 'waiting for x to stop' message with throttling. """
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
- """ The process was reaped and we need to report and manage its state """
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
- notify_event(EventRejectedEvent(self, self.event)) # type: ignore
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]:
@@ -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
- def main(self) -> None:
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
- notify_event(ProcessGroupAddedEvent(name))
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
- notify_event(ProcessGroupRemovedEvent(name))
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 _run_once(self) -> None:
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
- notify_event(SupervisorStoppingEvent())
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.dev119
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.dev119
16
- Requires-Dist: omlish ==0.0.0.dev119
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=31TqubTvWBa3FXNew9hM4aDUqyw_5Pl4DmJZOckiIGc,121636
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=Xoor4MrYSV4sKUsXKe8RLWsz2tSc2v4T2OWPw2qXizc,10390
70
- ominfra/supervisor/events.py,sha256=OGGCuf1RWobFPDecksHJO3I4GmzBPgFvPce4-DOaZ3s,7729
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=6Ut2QFRnCS4NP6xje-KiR519gNxGA6uNzoyiZjhrOM8,31373
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=CfYK3fgwNA-Z5g7ZeWxf1yg6ubVCe0rl_AXELu85o34,12198
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.dev119.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
84
- ominfra-0.0.0.dev119.dist-info/METADATA,sha256=XLOYfTWCuS2Ny4MBd0vDgy6hmHZw1WupGdpxfKBrWVQ,742
85
- ominfra-0.0.0.dev119.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
86
- ominfra-0.0.0.dev119.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
87
- ominfra-0.0.0.dev119.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
88
- ominfra-0.0.0.dev119.dist-info/RECORD,,
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,,