ominfra 0.0.0.dev119__py3-none-any.whl → 0.0.0.dev120__py3-none-any.whl

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