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

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,13 @@
2
2
  # noinspection DuplicatedCode
3
3
  # @omlish-lite
4
4
  # @omlish-script
5
- # @omlish-amalg-output ../supervisor/supervisor.py
6
- # ruff: noqa: N802 UP006 UP007 UP036
5
+ # @omlish-amalg-output ../supervisor/main.py
6
+ # ruff: noqa: N802 UP006 UP007 UP012 UP036
7
7
  import abc
8
8
  import base64
9
9
  import collections.abc
10
10
  import contextlib
11
+ import ctypes as ct
11
12
  import dataclasses as dc
12
13
  import datetime
13
14
  import decimal
@@ -17,6 +18,7 @@ import fcntl
17
18
  import fractions
18
19
  import functools
19
20
  import grp
21
+ import itertools
20
22
  import json
21
23
  import logging
22
24
  import os
@@ -28,6 +30,7 @@ import shlex
28
30
  import signal
29
31
  import stat
30
32
  import sys
33
+ import syslog
31
34
  import tempfile
32
35
  import threading
33
36
  import time
@@ -466,240 +469,28 @@ class NoPermissionError(ProcessError):
466
469
 
467
470
 
468
471
  ########################################
469
- # ../poller.py
470
-
471
-
472
- log = logging.getLogger(__name__)
472
+ # ../../../omlish/lite/cached.py
473
473
 
474
474
 
475
- class BasePoller(abc.ABC):
476
-
477
- def __init__(self) -> None:
475
+ class _cached_nullary: # noqa
476
+ def __init__(self, fn):
478
477
  super().__init__()
478
+ self._fn = fn
479
+ self._value = self._missing = object()
480
+ functools.update_wrapper(self, fn)
479
481
 
480
- @abc.abstractmethod
481
- def register_readable(self, fd: int) -> None:
482
- raise NotImplementedError
483
-
484
- @abc.abstractmethod
485
- def register_writable(self, fd: int) -> None:
486
- raise NotImplementedError
487
-
488
- @abc.abstractmethod
489
- def unregister_readable(self, fd: int) -> None:
490
- raise NotImplementedError
482
+ def __call__(self, *args, **kwargs): # noqa
483
+ if self._value is self._missing:
484
+ self._value = self._fn()
485
+ return self._value
491
486
 
492
- @abc.abstractmethod
493
- def unregister_writable(self, fd: int) -> None:
494
- raise NotImplementedError
487
+ def __get__(self, instance, owner): # noqa
488
+ bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
489
+ return bound
495
490
 
496
- @abc.abstractmethod
497
- def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
498
- raise NotImplementedError
499
491
 
500
- def before_daemonize(self) -> None: # noqa
501
- pass
502
-
503
- def after_daemonize(self) -> None: # noqa
504
- pass
505
-
506
- def close(self) -> None: # noqa
507
- pass
508
-
509
-
510
- class SelectPoller(BasePoller):
511
-
512
- def __init__(self) -> None:
513
- super().__init__()
514
-
515
- self._readables: ta.Set[int] = set()
516
- self._writables: ta.Set[int] = set()
517
-
518
- def register_readable(self, fd: int) -> None:
519
- self._readables.add(fd)
520
-
521
- def register_writable(self, fd: int) -> None:
522
- self._writables.add(fd)
523
-
524
- def unregister_readable(self, fd: int) -> None:
525
- self._readables.discard(fd)
526
-
527
- def unregister_writable(self, fd: int) -> None:
528
- self._writables.discard(fd)
529
-
530
- def unregister_all(self) -> None:
531
- self._readables.clear()
532
- self._writables.clear()
533
-
534
- def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
535
- try:
536
- r, w, x = select.select(
537
- self._readables,
538
- self._writables,
539
- [], timeout,
540
- )
541
- except OSError as err:
542
- if err.args[0] == errno.EINTR:
543
- log.debug('EINTR encountered in poll')
544
- return [], []
545
- if err.args[0] == errno.EBADF:
546
- log.debug('EBADF encountered in poll')
547
- self.unregister_all()
548
- return [], []
549
- raise
550
- return r, w
551
-
552
-
553
- class PollPoller(BasePoller):
554
- _READ = select.POLLIN | select.POLLPRI | select.POLLHUP
555
- _WRITE = select.POLLOUT
556
-
557
- def __init__(self) -> None:
558
- super().__init__()
559
-
560
- self._poller = select.poll()
561
- self._readables: set[int] = set()
562
- self._writables: set[int] = set()
563
-
564
- def register_readable(self, fd: int) -> None:
565
- self._poller.register(fd, self._READ)
566
- self._readables.add(fd)
567
-
568
- def register_writable(self, fd: int) -> None:
569
- self._poller.register(fd, self._WRITE)
570
- self._writables.add(fd)
571
-
572
- def unregister_readable(self, fd: int) -> None:
573
- self._readables.discard(fd)
574
- self._poller.unregister(fd)
575
- if fd in self._writables:
576
- self._poller.register(fd, self._WRITE)
577
-
578
- def unregister_writable(self, fd: int) -> None:
579
- self._writables.discard(fd)
580
- self._poller.unregister(fd)
581
- if fd in self._readables:
582
- self._poller.register(fd, self._READ)
583
-
584
- def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
585
- fds = self._poll_fds(timeout) # type: ignore
586
- readables, writables = [], []
587
- for fd, eventmask in fds:
588
- if self._ignore_invalid(fd, eventmask):
589
- continue
590
- if eventmask & self._READ:
591
- readables.append(fd)
592
- if eventmask & self._WRITE:
593
- writables.append(fd)
594
- return readables, writables
595
-
596
- def _poll_fds(self, timeout: float) -> ta.List[ta.Tuple[int, int]]:
597
- try:
598
- return self._poller.poll(timeout * 1000)
599
- except OSError as err:
600
- if err.args[0] == errno.EINTR:
601
- log.debug('EINTR encountered in poll')
602
- return []
603
- raise
604
-
605
- def _ignore_invalid(self, fd: int, eventmask: int) -> bool:
606
- if eventmask & select.POLLNVAL:
607
- # POLLNVAL means `fd` value is invalid, not open. When a process quits it's `fd`s are closed so there is no
608
- # more reason to keep this `fd` registered If the process restarts it's `fd`s are registered again.
609
- self._poller.unregister(fd)
610
- self._readables.discard(fd)
611
- self._writables.discard(fd)
612
- return True
613
- return False
614
-
615
-
616
- if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
617
- class KqueuePoller(BasePoller):
618
- max_events = 1000
619
-
620
- def __init__(self) -> None:
621
- super().__init__()
622
-
623
- self._kqueue: ta.Optional[ta.Any] = select.kqueue()
624
- self._readables: set[int] = set()
625
- self._writables: set[int] = set()
626
-
627
- def register_readable(self, fd: int) -> None:
628
- self._readables.add(fd)
629
- kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD)
630
- self._kqueue_control(fd, kevent)
631
-
632
- def register_writable(self, fd: int) -> None:
633
- self._writables.add(fd)
634
- kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_ADD)
635
- self._kqueue_control(fd, kevent)
636
-
637
- def unregister_readable(self, fd: int) -> None:
638
- kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_DELETE)
639
- self._readables.discard(fd)
640
- self._kqueue_control(fd, kevent)
641
-
642
- def unregister_writable(self, fd: int) -> None:
643
- kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_DELETE)
644
- self._writables.discard(fd)
645
- self._kqueue_control(fd, kevent)
646
-
647
- def _kqueue_control(self, fd: int, kevent: 'select.kevent') -> None:
648
- try:
649
- self._kqueue.control([kevent], 0) # type: ignore
650
- except OSError as error:
651
- if error.errno == errno.EBADF:
652
- log.debug('EBADF encountered in kqueue. Invalid file descriptor %s', fd)
653
- else:
654
- raise
655
-
656
- def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
657
- readables, writables = [], [] # type: ignore
658
-
659
- try:
660
- kevents = self._kqueue.control(None, self.max_events, timeout) # type: ignore
661
- except OSError as error:
662
- if error.errno == errno.EINTR:
663
- log.debug('EINTR encountered in poll')
664
- return readables, writables
665
- raise
666
-
667
- for kevent in kevents:
668
- if kevent.filter == select.KQ_FILTER_READ:
669
- readables.append(kevent.ident)
670
- if kevent.filter == select.KQ_FILTER_WRITE:
671
- writables.append(kevent.ident)
672
-
673
- return readables, writables
674
-
675
- def before_daemonize(self) -> None:
676
- self.close()
677
-
678
- def after_daemonize(self) -> None:
679
- self._kqueue = select.kqueue()
680
- for fd in self._readables:
681
- self.register_readable(fd)
682
- for fd in self._writables:
683
- self.register_writable(fd)
684
-
685
- def close(self) -> None:
686
- self._kqueue.close() # type: ignore
687
- self._kqueue = None
688
-
689
- else:
690
- KqueuePoller = None
691
-
692
-
693
- Poller: ta.Type[BasePoller]
694
- if (
695
- sys.platform == 'darwin' or sys.platform.startswith('freebsd') and
696
- hasattr(select, 'kqueue') and KqueuePoller is not None
697
- ):
698
- Poller = KqueuePoller
699
- elif hasattr(select, 'poll'):
700
- Poller = PollPoller
701
- else:
702
- Poller = SelectPoller
492
+ def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
493
+ return _cached_nullary(fn)
703
494
 
704
495
 
705
496
  ########################################
@@ -1012,6 +803,164 @@ def get_supervisor_state_description(code: SupervisorState) -> str:
1012
803
  return check_not_none(_supervisor_states_by_code.get(code))
1013
804
 
1014
805
 
806
+ ########################################
807
+ # ../../../omlish/lite/journald.py
808
+
809
+
810
+ ##
811
+
812
+
813
+ class sd_iovec(ct.Structure): # noqa
814
+ pass
815
+
816
+
817
+ sd_iovec._fields_ = [
818
+ ('iov_base', ct.c_void_p), # Pointer to data.
819
+ ('iov_len', ct.c_size_t), # Length of data.
820
+ ]
821
+
822
+
823
+ ##
824
+
825
+
826
+ @cached_nullary
827
+ def sd_libsystemd() -> ta.Any:
828
+ lib = ct.CDLL('libsystemd.so.0')
829
+
830
+ lib.sd_journal_sendv = lib['sd_journal_sendv'] # type: ignore
831
+ lib.sd_journal_sendv.restype = ct.c_int
832
+ lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
833
+
834
+ return lib
835
+
836
+
837
+ @cached_nullary
838
+ def sd_try_libsystemd() -> ta.Optional[ta.Any]:
839
+ try:
840
+ return sd_libsystemd()
841
+ except OSError: # noqa
842
+ return None
843
+
844
+
845
+ ##
846
+
847
+
848
+ def sd_journald_send(**fields: str) -> int:
849
+ lib = sd_libsystemd()
850
+
851
+ msgs = [
852
+ f'{k.upper()}={v}\0'.encode('utf-8')
853
+ for k, v in fields.items()
854
+ ]
855
+
856
+ vec = (sd_iovec * len(msgs))()
857
+ cl = (ct.c_char_p * len(msgs))() # noqa
858
+ for i in range(len(msgs)):
859
+ vec[i].iov_base = ct.cast(ct.c_char_p(msgs[i]), ct.c_void_p)
860
+ vec[i].iov_len = len(msgs[i]) - 1
861
+
862
+ return lib.sd_journal_sendv(vec, len(msgs))
863
+
864
+
865
+ ##
866
+
867
+
868
+ SD_LOG_LEVEL_MAP: ta.Mapping[int, int] = {
869
+ logging.FATAL: syslog.LOG_EMERG, # system is unusable
870
+ # LOG_ALERT ? # action must be taken immediately
871
+ logging.CRITICAL: syslog.LOG_CRIT,
872
+ logging.ERROR: syslog.LOG_ERR,
873
+ logging.WARNING: syslog.LOG_WARNING,
874
+ # LOG_NOTICE ? # normal but significant condition
875
+ logging.INFO: syslog.LOG_INFO,
876
+ logging.DEBUG: syslog.LOG_DEBUG,
877
+ }
878
+
879
+
880
+ class JournaldLogHandler(logging.Handler):
881
+ """
882
+ TODO:
883
+ - fallback handler for when this barfs
884
+ """
885
+
886
+ def __init__(
887
+ self,
888
+ *,
889
+ use_formatter_output: bool = False,
890
+ ) -> None:
891
+ super().__init__()
892
+
893
+ sd_libsystemd()
894
+
895
+ self._use_formatter_output = use_formatter_output
896
+
897
+ #
898
+
899
+ EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD: ta.ClassVar[ta.Mapping[str, str]] = {
900
+ 'name': 'name',
901
+ 'module': 'module',
902
+ 'exception': 'exc_text',
903
+ 'thread_name': 'threadName',
904
+ 'task_name': 'taskName',
905
+ }
906
+
907
+ def make_fields(self, record: logging.LogRecord) -> ta.Mapping[str, str]:
908
+ formatter_message = self.format(record)
909
+ if self._use_formatter_output:
910
+ message = formatter_message
911
+ else:
912
+ message = record.message
913
+
914
+ fields: dict[str, str] = {
915
+ 'message': message,
916
+ 'priority': str(SD_LOG_LEVEL_MAP[record.levelno]),
917
+ 'tid': str(threading.get_ident()),
918
+ }
919
+
920
+ if (pathname := record.pathname) is not None:
921
+ fields['code_file'] = pathname
922
+ if (lineno := record.lineno) is not None:
923
+ fields['code_lineno'] = str(lineno)
924
+ if (func_name := record.funcName) is not None:
925
+ fields['code_func'] = func_name
926
+
927
+ for f, a in self.EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD.items():
928
+ if (v := getattr(record, a, None)) is not None:
929
+ fields[f] = str(v)
930
+
931
+ return fields
932
+
933
+ #
934
+
935
+ def emit(self, record: logging.LogRecord) -> None:
936
+ try:
937
+ fields = self.make_fields(record)
938
+
939
+ if rc := sd_journald_send(**fields):
940
+ raise RuntimeError(f'{self.__class__.__name__}.emit failed: {rc=}') # noqa
941
+
942
+ except RecursionError: # See issue 36272
943
+ raise
944
+
945
+ except Exception: # noqa
946
+ self.handleError(record)
947
+
948
+
949
+ def journald_log_handler_factory(
950
+ *,
951
+ no_tty_check: bool = False,
952
+ no_fallback: bool = False,
953
+ ) -> logging.Handler:
954
+ if (
955
+ sys.platform == 'linux' and
956
+ (no_tty_check or not sys.stderr.isatty()) and
957
+ (no_fallback or sd_try_libsystemd() is not None)
958
+ ):
959
+ return JournaldLogHandler()
960
+
961
+ return logging.StreamHandler()
962
+
963
+
1015
964
  ########################################
1016
965
  # ../../../omlish/lite/logs.py
1017
966
  """
@@ -1099,7 +1048,7 @@ class StandardLogFormatter(logging.Formatter):
1099
1048
  if datefmt:
1100
1049
  return ct.strftime(datefmt) # noqa
1101
1050
  else:
1102
- t = ct.strftime("%Y-%m-%d %H:%M:%S") # noqa
1051
+ t = ct.strftime('%Y-%m-%d %H:%M:%S')
1103
1052
  return '%s.%03d' % (t, record.msecs)
1104
1053
 
1105
1054
 
@@ -1236,6 +1185,7 @@ def configure_standard_logging(
1236
1185
  json: bool = False,
1237
1186
  target: ta.Optional[logging.Logger] = None,
1238
1187
  force: bool = False,
1188
+ handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
1239
1189
  ) -> ta.Optional[StandardLogHandler]:
1240
1190
  with _locking_logging_module_lock():
1241
1191
  if target is None:
@@ -1249,7 +1199,10 @@ def configure_standard_logging(
1249
1199
 
1250
1200
  #
1251
1201
 
1252
- handler = logging.StreamHandler()
1202
+ if handler_factory is not None:
1203
+ handler = handler_factory()
1204
+ else:
1205
+ handler = logging.StreamHandler()
1253
1206
 
1254
1207
  #
1255
1208
 
@@ -1836,91 +1789,325 @@ class ProcessGroupEvent(Event):
1836
1789
  return f'groupname:{self.group}\n'
1837
1790
 
1838
1791
 
1839
- class ProcessGroupAddedEvent(ProcessGroupEvent):
1840
- pass
1792
+ class ProcessGroupAddedEvent(ProcessGroupEvent):
1793
+ pass
1794
+
1795
+
1796
+ class ProcessGroupRemovedEvent(ProcessGroupEvent):
1797
+ pass
1798
+
1799
+
1800
+ class TickEvent(Event):
1801
+ """ Abstract """
1802
+
1803
+ def __init__(self, when, supervisord):
1804
+ super().__init__()
1805
+ self.when = when
1806
+ self.supervisord = supervisord
1807
+
1808
+ def payload(self):
1809
+ return f'when:{self.when}'
1810
+
1811
+
1812
+ class Tick5Event(TickEvent):
1813
+ period = 5
1814
+
1815
+
1816
+ class Tick60Event(TickEvent):
1817
+ period = 60
1818
+
1819
+
1820
+ class Tick3600Event(TickEvent):
1821
+ period = 3600
1822
+
1823
+
1824
+ TICK_EVENTS = [ # imported elsewhere
1825
+ Tick5Event,
1826
+ Tick60Event,
1827
+ Tick3600Event,
1828
+ ]
1829
+
1830
+
1831
+ class EventTypes:
1832
+ EVENT = Event # abstract
1833
+
1834
+ PROCESS_STATE = ProcessStateEvent # abstract
1835
+ PROCESS_STATE_STOPPED = ProcessStateStoppedEvent
1836
+ PROCESS_STATE_EXITED = ProcessStateExitedEvent
1837
+ PROCESS_STATE_STARTING = ProcessStateStartingEvent
1838
+ PROCESS_STATE_STOPPING = ProcessStateStoppingEvent
1839
+ PROCESS_STATE_BACKOFF = ProcessStateBackoffEvent
1840
+ PROCESS_STATE_FATAL = ProcessStateFatalEvent
1841
+ PROCESS_STATE_RUNNING = ProcessStateRunningEvent
1842
+ PROCESS_STATE_UNKNOWN = ProcessStateUnknownEvent
1843
+
1844
+ PROCESS_COMMUNICATION = ProcessCommunicationEvent # abstract
1845
+ PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent
1846
+ PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent
1847
+
1848
+ PROCESS_LOG = ProcessLogEvent
1849
+ PROCESS_LOG_STDOUT = ProcessLogStdoutEvent
1850
+ PROCESS_LOG_STDERR = ProcessLogStderrEvent
1851
+
1852
+ REMOTE_COMMUNICATION = RemoteCommunicationEvent
1853
+
1854
+ SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent # abstract
1855
+ SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent
1856
+ SUPERVISOR_STATE_CHANGE_STOPPING = SupervisorStoppingEvent
1857
+
1858
+ TICK = TickEvent # abstract
1859
+ TICK_5 = Tick5Event
1860
+ TICK_60 = Tick60Event
1861
+ TICK_3600 = Tick3600Event
1862
+
1863
+ PROCESS_GROUP = ProcessGroupEvent # abstract
1864
+ PROCESS_GROUP_ADDED = ProcessGroupAddedEvent
1865
+ PROCESS_GROUP_REMOVED = ProcessGroupRemovedEvent
1866
+
1867
+
1868
+ def get_event_name_by_type(requested):
1869
+ for name, typ in EventTypes.__dict__.items():
1870
+ if typ is requested:
1871
+ return name
1872
+ return None
1873
+
1874
+
1875
+ def register(name, event):
1876
+ setattr(EventTypes, name, event)
1877
+
1878
+
1879
+ ########################################
1880
+ # ../poller.py
1881
+
1882
+
1883
+ class BasePoller(abc.ABC):
1884
+
1885
+ def __init__(self) -> None:
1886
+ super().__init__()
1887
+
1888
+ @abc.abstractmethod
1889
+ def register_readable(self, fd: int) -> None:
1890
+ raise NotImplementedError
1891
+
1892
+ @abc.abstractmethod
1893
+ def register_writable(self, fd: int) -> None:
1894
+ raise NotImplementedError
1895
+
1896
+ @abc.abstractmethod
1897
+ def unregister_readable(self, fd: int) -> None:
1898
+ raise NotImplementedError
1899
+
1900
+ @abc.abstractmethod
1901
+ def unregister_writable(self, fd: int) -> None:
1902
+ raise NotImplementedError
1903
+
1904
+ @abc.abstractmethod
1905
+ def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
1906
+ raise NotImplementedError
1907
+
1908
+ def before_daemonize(self) -> None: # noqa
1909
+ pass
1910
+
1911
+ def after_daemonize(self) -> None: # noqa
1912
+ pass
1913
+
1914
+ def close(self) -> None: # noqa
1915
+ pass
1916
+
1917
+
1918
+ class SelectPoller(BasePoller):
1919
+
1920
+ def __init__(self) -> None:
1921
+ super().__init__()
1922
+
1923
+ self._readables: ta.Set[int] = set()
1924
+ self._writables: ta.Set[int] = set()
1925
+
1926
+ def register_readable(self, fd: int) -> None:
1927
+ self._readables.add(fd)
1928
+
1929
+ def register_writable(self, fd: int) -> None:
1930
+ self._writables.add(fd)
1931
+
1932
+ def unregister_readable(self, fd: int) -> None:
1933
+ self._readables.discard(fd)
1934
+
1935
+ def unregister_writable(self, fd: int) -> None:
1936
+ self._writables.discard(fd)
1937
+
1938
+ def unregister_all(self) -> None:
1939
+ self._readables.clear()
1940
+ self._writables.clear()
1941
+
1942
+ def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
1943
+ try:
1944
+ r, w, x = select.select(
1945
+ self._readables,
1946
+ self._writables,
1947
+ [], timeout,
1948
+ )
1949
+ except OSError as err:
1950
+ if err.args[0] == errno.EINTR:
1951
+ log.debug('EINTR encountered in poll')
1952
+ return [], []
1953
+ if err.args[0] == errno.EBADF:
1954
+ log.debug('EBADF encountered in poll')
1955
+ self.unregister_all()
1956
+ return [], []
1957
+ raise
1958
+ return r, w
1959
+
1960
+
1961
+ class PollPoller(BasePoller):
1962
+ _READ = select.POLLIN | select.POLLPRI | select.POLLHUP
1963
+ _WRITE = select.POLLOUT
1964
+
1965
+ def __init__(self) -> None:
1966
+ super().__init__()
1841
1967
 
1968
+ self._poller = select.poll()
1969
+ self._readables: set[int] = set()
1970
+ self._writables: set[int] = set()
1842
1971
 
1843
- class ProcessGroupRemovedEvent(ProcessGroupEvent):
1844
- pass
1972
+ def register_readable(self, fd: int) -> None:
1973
+ self._poller.register(fd, self._READ)
1974
+ self._readables.add(fd)
1845
1975
 
1976
+ def register_writable(self, fd: int) -> None:
1977
+ self._poller.register(fd, self._WRITE)
1978
+ self._writables.add(fd)
1846
1979
 
1847
- class TickEvent(Event):
1848
- """ Abstract """
1980
+ def unregister_readable(self, fd: int) -> None:
1981
+ self._readables.discard(fd)
1982
+ self._poller.unregister(fd)
1983
+ if fd in self._writables:
1984
+ self._poller.register(fd, self._WRITE)
1849
1985
 
1850
- def __init__(self, when, supervisord):
1851
- super().__init__()
1852
- self.when = when
1853
- self.supervisord = supervisord
1986
+ def unregister_writable(self, fd: int) -> None:
1987
+ self._writables.discard(fd)
1988
+ self._poller.unregister(fd)
1989
+ if fd in self._readables:
1990
+ self._poller.register(fd, self._READ)
1854
1991
 
1855
- def payload(self):
1856
- return f'when:{self.when}'
1992
+ def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
1993
+ fds = self._poll_fds(timeout) # type: ignore
1994
+ readables, writables = [], []
1995
+ for fd, eventmask in fds:
1996
+ if self._ignore_invalid(fd, eventmask):
1997
+ continue
1998
+ if eventmask & self._READ:
1999
+ readables.append(fd)
2000
+ if eventmask & self._WRITE:
2001
+ writables.append(fd)
2002
+ return readables, writables
1857
2003
 
2004
+ def _poll_fds(self, timeout: float) -> ta.List[ta.Tuple[int, int]]:
2005
+ try:
2006
+ return self._poller.poll(timeout * 1000)
2007
+ except OSError as err:
2008
+ if err.args[0] == errno.EINTR:
2009
+ log.debug('EINTR encountered in poll')
2010
+ return []
2011
+ raise
1858
2012
 
1859
- class Tick5Event(TickEvent):
1860
- period = 5
2013
+ def _ignore_invalid(self, fd: int, eventmask: int) -> bool:
2014
+ if eventmask & select.POLLNVAL:
2015
+ # POLLNVAL means `fd` value is invalid, not open. When a process quits it's `fd`s are closed so there is no
2016
+ # more reason to keep this `fd` registered If the process restarts it's `fd`s are registered again.
2017
+ self._poller.unregister(fd)
2018
+ self._readables.discard(fd)
2019
+ self._writables.discard(fd)
2020
+ return True
2021
+ return False
1861
2022
 
1862
2023
 
1863
- class Tick60Event(TickEvent):
1864
- period = 60
2024
+ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
2025
+ class KqueuePoller(BasePoller):
2026
+ max_events = 1000
1865
2027
 
2028
+ def __init__(self) -> None:
2029
+ super().__init__()
1866
2030
 
1867
- class Tick3600Event(TickEvent):
1868
- period = 3600
2031
+ self._kqueue: ta.Optional[ta.Any] = select.kqueue()
2032
+ self._readables: set[int] = set()
2033
+ self._writables: set[int] = set()
1869
2034
 
2035
+ def register_readable(self, fd: int) -> None:
2036
+ self._readables.add(fd)
2037
+ kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD)
2038
+ self._kqueue_control(fd, kevent)
1870
2039
 
1871
- TICK_EVENTS = [ # imported elsewhere
1872
- Tick5Event,
1873
- Tick60Event,
1874
- Tick3600Event,
1875
- ]
2040
+ def register_writable(self, fd: int) -> None:
2041
+ self._writables.add(fd)
2042
+ kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_ADD)
2043
+ self._kqueue_control(fd, kevent)
1876
2044
 
2045
+ def unregister_readable(self, fd: int) -> None:
2046
+ kevent = select.kevent(fd, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_DELETE)
2047
+ self._readables.discard(fd)
2048
+ self._kqueue_control(fd, kevent)
1877
2049
 
1878
- class EventTypes:
1879
- EVENT = Event # abstract
2050
+ def unregister_writable(self, fd: int) -> None:
2051
+ kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=select.KQ_EV_DELETE)
2052
+ self._writables.discard(fd)
2053
+ self._kqueue_control(fd, kevent)
1880
2054
 
1881
- PROCESS_STATE = ProcessStateEvent # abstract
1882
- PROCESS_STATE_STOPPED = ProcessStateStoppedEvent
1883
- PROCESS_STATE_EXITED = ProcessStateExitedEvent
1884
- PROCESS_STATE_STARTING = ProcessStateStartingEvent
1885
- PROCESS_STATE_STOPPING = ProcessStateStoppingEvent
1886
- PROCESS_STATE_BACKOFF = ProcessStateBackoffEvent
1887
- PROCESS_STATE_FATAL = ProcessStateFatalEvent
1888
- PROCESS_STATE_RUNNING = ProcessStateRunningEvent
1889
- PROCESS_STATE_UNKNOWN = ProcessStateUnknownEvent
2055
+ def _kqueue_control(self, fd: int, kevent: 'select.kevent') -> None:
2056
+ try:
2057
+ self._kqueue.control([kevent], 0) # type: ignore
2058
+ except OSError as error:
2059
+ if error.errno == errno.EBADF:
2060
+ log.debug('EBADF encountered in kqueue. Invalid file descriptor %s', fd)
2061
+ else:
2062
+ raise
1890
2063
 
1891
- PROCESS_COMMUNICATION = ProcessCommunicationEvent # abstract
1892
- PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent
1893
- PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent
2064
+ def poll(self, timeout: ta.Optional[float]) -> ta.Tuple[ta.List[int], ta.List[int]]:
2065
+ readables, writables = [], [] # type: ignore
1894
2066
 
1895
- PROCESS_LOG = ProcessLogEvent
1896
- PROCESS_LOG_STDOUT = ProcessLogStdoutEvent
1897
- PROCESS_LOG_STDERR = ProcessLogStderrEvent
2067
+ try:
2068
+ kevents = self._kqueue.control(None, self.max_events, timeout) # type: ignore
2069
+ except OSError as error:
2070
+ if error.errno == errno.EINTR:
2071
+ log.debug('EINTR encountered in poll')
2072
+ return readables, writables
2073
+ raise
1898
2074
 
1899
- REMOTE_COMMUNICATION = RemoteCommunicationEvent
2075
+ for kevent in kevents:
2076
+ if kevent.filter == select.KQ_FILTER_READ:
2077
+ readables.append(kevent.ident)
2078
+ if kevent.filter == select.KQ_FILTER_WRITE:
2079
+ writables.append(kevent.ident)
1900
2080
 
1901
- SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent # abstract
1902
- SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent
1903
- SUPERVISOR_STATE_CHANGE_STOPPING = SupervisorStoppingEvent
2081
+ return readables, writables
1904
2082
 
1905
- TICK = TickEvent # abstract
1906
- TICK_5 = Tick5Event
1907
- TICK_60 = Tick60Event
1908
- TICK_3600 = Tick3600Event
2083
+ def before_daemonize(self) -> None:
2084
+ self.close()
1909
2085
 
1910
- PROCESS_GROUP = ProcessGroupEvent # abstract
1911
- PROCESS_GROUP_ADDED = ProcessGroupAddedEvent
1912
- PROCESS_GROUP_REMOVED = ProcessGroupRemovedEvent
2086
+ def after_daemonize(self) -> None:
2087
+ self._kqueue = select.kqueue()
2088
+ for fd in self._readables:
2089
+ self.register_readable(fd)
2090
+ for fd in self._writables:
2091
+ self.register_writable(fd)
1913
2092
 
2093
+ def close(self) -> None:
2094
+ self._kqueue.close() # type: ignore
2095
+ self._kqueue = None
1914
2096
 
1915
- def get_event_name_by_type(requested):
1916
- for name, typ in EventTypes.__dict__.items():
1917
- if typ is requested:
1918
- return name
1919
- return None
2097
+ else:
2098
+ KqueuePoller = None
1920
2099
 
1921
2100
 
1922
- def register(name, event):
1923
- setattr(EventTypes, name, event)
2101
+ Poller: ta.Type[BasePoller]
2102
+ if (
2103
+ sys.platform == 'darwin' or sys.platform.startswith('freebsd') and
2104
+ hasattr(select, 'kqueue') and KqueuePoller is not None
2105
+ ):
2106
+ Poller = KqueuePoller
2107
+ elif hasattr(select, 'poll'):
2108
+ Poller = PollPoller
2109
+ else:
2110
+ Poller = SelectPoller
1924
2111
 
1925
2112
 
1926
2113
  ########################################
@@ -1973,41 +2160,47 @@ class AbstractSubprocess(abc.ABC):
1973
2160
  # ../context.py
1974
2161
 
1975
2162
 
1976
- log = logging.getLogger(__name__)
1977
-
1978
-
1979
2163
  class ServerContext(AbstractServerContext):
1980
- first = False
1981
- test = False
1982
-
1983
- ##
1984
-
1985
- def __init__(self, config: ServerConfig) -> None:
2164
+ def __init__(
2165
+ self,
2166
+ config: ServerConfig,
2167
+ *,
2168
+ epoch: int = 0,
2169
+ ) -> None:
1986
2170
  super().__init__()
1987
2171
 
1988
2172
  self._config = config
2173
+ self._epoch = epoch
1989
2174
 
1990
2175
  self._pid_history: ta.Dict[int, AbstractSubprocess] = {}
1991
2176
  self._state: SupervisorState = SupervisorStates.RUNNING
1992
2177
 
1993
- self.signal_receiver = SignalReceiver()
2178
+ self._signal_receiver = SignalReceiver()
1994
2179
 
1995
- self.poller = Poller()
2180
+ self._poller: BasePoller = Poller()
1996
2181
 
1997
- if self.config.user is not None:
1998
- uid = name_to_uid(self.config.user)
1999
- self.uid = uid
2000
- self.gid = gid_for_uid(uid)
2182
+ if config.user is not None:
2183
+ uid = name_to_uid(config.user)
2184
+ self._uid: ta.Optional[int] = uid
2185
+ self._gid: ta.Optional[int] = gid_for_uid(uid)
2001
2186
  else:
2002
- self.uid = None
2003
- self.gid = None
2187
+ self._uid = None
2188
+ self._gid = None
2004
2189
 
2005
- self.unlink_pidfile = False
2190
+ self._unlink_pidfile = False
2006
2191
 
2007
2192
  @property
2008
2193
  def config(self) -> ServerConfig:
2009
2194
  return self._config
2010
2195
 
2196
+ @property
2197
+ def epoch(self) -> int:
2198
+ return self._epoch
2199
+
2200
+ @property
2201
+ def first(self) -> bool:
2202
+ return not self._epoch
2203
+
2011
2204
  @property
2012
2205
  def state(self) -> SupervisorState:
2013
2206
  return self._state
@@ -2015,17 +2208,26 @@ class ServerContext(AbstractServerContext):
2015
2208
  def set_state(self, state: SupervisorState) -> None:
2016
2209
  self._state = state
2017
2210
 
2211
+ @property
2212
+ def poller(self) -> BasePoller:
2213
+ return self._poller
2214
+
2018
2215
  @property
2019
2216
  def pid_history(self) -> ta.Dict[int, AbstractSubprocess]:
2020
2217
  return self._pid_history
2021
2218
 
2022
- uid: ta.Optional[int]
2023
- gid: ta.Optional[int]
2219
+ @property
2220
+ def uid(self) -> ta.Optional[int]:
2221
+ return self._uid
2222
+
2223
+ @property
2224
+ def gid(self) -> ta.Optional[int]:
2225
+ return self._gid
2024
2226
 
2025
2227
  ##
2026
2228
 
2027
2229
  def set_signals(self) -> None:
2028
- self.signal_receiver.install(
2230
+ self._signal_receiver.install(
2029
2231
  signal.SIGTERM,
2030
2232
  signal.SIGINT,
2031
2233
  signal.SIGQUIT,
@@ -2136,7 +2338,7 @@ class ServerContext(AbstractServerContext):
2136
2338
  ))
2137
2339
 
2138
2340
  def cleanup(self) -> None:
2139
- if self.unlink_pidfile:
2341
+ if self._unlink_pidfile:
2140
2342
  try_unlink(self.config.pidfile)
2141
2343
  self.poller.close()
2142
2344
 
@@ -2189,6 +2391,7 @@ class ServerContext(AbstractServerContext):
2189
2391
  # Parent
2190
2392
  log.debug('supervisord forked; parent exiting')
2191
2393
  real_exit(0)
2394
+
2192
2395
  # Child
2193
2396
  log.info('daemonizing the supervisord process')
2194
2397
  if self.config.directory:
@@ -2198,11 +2401,15 @@ class ServerContext(AbstractServerContext):
2198
2401
  log.critical("can't chdir into %r: %s", self.config.directory, err)
2199
2402
  else:
2200
2403
  log.info('set current directory: %r', self.config.directory)
2404
+
2201
2405
  os.dup2(0, os.open('/dev/null', os.O_RDONLY))
2202
2406
  os.dup2(1, os.open('/dev/null', os.O_WRONLY))
2203
2407
  os.dup2(2, os.open('/dev/null', os.O_WRONLY))
2408
+
2204
2409
  os.setsid()
2410
+
2205
2411
  os.umask(self.config.umask)
2412
+
2206
2413
  # XXX Stevens, in his Advanced Unix book, section 13.3 (page 417) recommends calling umask(0) and closing unused
2207
2414
  # file descriptors. In his Network Programming book, he additionally recommends ignoring SIGHUP and forking
2208
2415
  # again after the setsid() call, for obscure SVR4 reasons.
@@ -2217,7 +2424,7 @@ class ServerContext(AbstractServerContext):
2217
2424
  return logfile
2218
2425
 
2219
2426
  def get_signal(self) -> ta.Optional[int]:
2220
- return self.signal_receiver.get_signal()
2427
+ return self._signal_receiver.get_signal()
2221
2428
 
2222
2429
  def write_pidfile(self) -> None:
2223
2430
  pid = os.getpid()
@@ -2227,7 +2434,7 @@ class ServerContext(AbstractServerContext):
2227
2434
  except OSError:
2228
2435
  log.critical('could not write pidfile %s', self.config.pidfile)
2229
2436
  else:
2230
- self.unlink_pidfile = True
2437
+ self._unlink_pidfile = True
2231
2438
  log.info('supervisord started with pid %s', pid)
2232
2439
 
2233
2440
 
@@ -2280,11 +2487,14 @@ def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
2280
2487
  os.setgroups(groups)
2281
2488
  except OSError:
2282
2489
  return 'Could not set groups of effective user'
2490
+
2283
2491
  try:
2284
2492
  os.setgid(gid)
2285
2493
  except OSError:
2286
2494
  return 'Could not set group id of effective user'
2495
+
2287
2496
  os.setuid(uid)
2497
+
2288
2498
  return None
2289
2499
 
2290
2500
 
@@ -2354,9 +2564,6 @@ def check_execv_args(filename, argv, st) -> None:
2354
2564
  # ../dispatchers.py
2355
2565
 
2356
2566
 
2357
- log = logging.getLogger(__name__)
2358
-
2359
-
2360
2567
  class Dispatcher(abc.ABC):
2361
2568
 
2362
2569
  def __init__(self, process: AbstractSubprocess, channel: str, fd: int) -> None:
@@ -2513,16 +2720,16 @@ class OutputDispatcher(Dispatcher):
2513
2720
  # )
2514
2721
 
2515
2722
  def remove_logs(self):
2516
- for log in (self._normal_log, self._capture_log):
2517
- if log is not None:
2518
- for handler in log.handlers:
2723
+ for l in (self._normal_log, self._capture_log):
2724
+ if l is not None:
2725
+ for handler in l.handlers:
2519
2726
  handler.remove() # type: ignore
2520
2727
  handler.reopen() # type: ignore
2521
2728
 
2522
2729
  def reopen_logs(self):
2523
- for log in (self._normal_log, self._capture_log):
2524
- if log is not None:
2525
- for handler in log.handlers:
2730
+ for l in (self._normal_log, self._capture_log):
2731
+ if l is not None:
2732
+ for handler in l.handlers:
2526
2733
  handler.reopen() # type: ignore
2527
2734
 
2528
2735
  def _log(self, data):
@@ -2660,9 +2867,6 @@ class InputDispatcher(Dispatcher):
2660
2867
  # ../process.py
2661
2868
 
2662
2869
 
2663
- log = logging.getLogger(__name__)
2664
-
2665
-
2666
2870
  @functools.total_ordering
2667
2871
  class Subprocess(AbstractSubprocess):
2668
2872
  """A class to manage a subprocess."""
@@ -3397,10 +3601,11 @@ class ProcessGroup:
3397
3601
 
3398
3602
 
3399
3603
  ########################################
3400
- # supervisor.py
3604
+ # ../supervisor.py
3401
3605
 
3402
3606
 
3403
- log = logging.getLogger(__name__)
3607
+ def timeslice(period, when):
3608
+ return int(when - (when % period))
3404
3609
 
3405
3610
 
3406
3611
  class Supervisor:
@@ -3423,6 +3628,11 @@ class Supervisor:
3423
3628
  return self._context.state
3424
3629
 
3425
3630
  def main(self) -> None:
3631
+ self.setup()
3632
+ self.run()
3633
+
3634
+ @cached_nullary
3635
+ def setup(self) -> None:
3426
3636
  if not self._context.first:
3427
3637
  # prevent crash on libdispatch-based systems, at least for the first request
3428
3638
  self._context.cleanup_fds()
@@ -3437,9 +3647,11 @@ class Supervisor:
3437
3647
  # clean up old automatic logs
3438
3648
  self._context.clear_auto_child_logdir()
3439
3649
 
3440
- self.run()
3441
-
3442
- def run(self) -> None:
3650
+ def run(
3651
+ self,
3652
+ *,
3653
+ callback: ta.Optional[ta.Callable[['Supervisor'], bool]] = None,
3654
+ ) -> None:
3443
3655
  self._process_groups = {} # clear
3444
3656
  self._stop_groups = None # clear
3445
3657
 
@@ -3457,12 +3669,23 @@ class Supervisor:
3457
3669
  # writing pid file needs to come *after* daemonizing or pid will be wrong
3458
3670
  self._context.write_pidfile()
3459
3671
 
3460
- self.runforever()
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()
3461
3679
 
3462
3680
  finally:
3463
3681
  self._context.cleanup()
3464
3682
 
3465
- def diff_to_active(self):
3683
+ class DiffToActive(ta.NamedTuple):
3684
+ added: ta.List[ProcessGroupConfig]
3685
+ changed: ta.List[ProcessGroupConfig]
3686
+ removed: ta.List[ProcessGroupConfig]
3687
+
3688
+ def diff_to_active(self) -> DiffToActive:
3466
3689
  new = self._context.config.groups or []
3467
3690
  cur = [group.config for group in self._process_groups.values()]
3468
3691
 
@@ -3474,7 +3697,7 @@ class Supervisor:
3474
3697
 
3475
3698
  changed = [cand for cand in new if cand != curdict.get(cand.name, cand)]
3476
3699
 
3477
- return added, changed, removed
3700
+ return Supervisor.DiffToActive(added, changed, removed)
3478
3701
 
3479
3702
  def add_process_group(self, config: ProcessGroupConfig) -> bool:
3480
3703
  name = config.name
@@ -3540,90 +3763,84 @@ class Supervisor:
3540
3763
  # down, so push it back on to the end of the stop group queue
3541
3764
  self._stop_groups.append(group)
3542
3765
 
3543
- def runforever(self) -> None:
3544
- notify_event(SupervisorRunningEvent())
3545
- timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
3546
-
3547
- while True:
3548
- combined_map = {}
3549
- combined_map.update(self.get_process_map())
3766
+ def _run_once(self) -> None:
3767
+ combined_map = {}
3768
+ combined_map.update(self.get_process_map())
3550
3769
 
3551
- pgroups = list(self._process_groups.values())
3552
- pgroups.sort()
3770
+ pgroups = list(self._process_groups.values())
3771
+ pgroups.sort()
3553
3772
 
3554
- if self._context.state < SupervisorStates.RUNNING:
3555
- if not self._stopping:
3556
- # first time, set the stopping flag, do a notification and set stop_groups
3557
- self._stopping = True
3558
- self._stop_groups = pgroups[:]
3559
- notify_event(SupervisorStoppingEvent())
3773
+ if self._context.state < SupervisorStates.RUNNING:
3774
+ if not self._stopping:
3775
+ # first time, set the stopping flag, do a notification and set stop_groups
3776
+ self._stopping = True
3777
+ self._stop_groups = pgroups[:]
3778
+ notify_event(SupervisorStoppingEvent())
3560
3779
 
3561
- self._ordered_stop_groups_phase_1()
3780
+ self._ordered_stop_groups_phase_1()
3562
3781
 
3563
- if not self.shutdown_report():
3564
- # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
3565
- raise ExitNow
3782
+ if not self.shutdown_report():
3783
+ # if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
3784
+ raise ExitNow
3566
3785
 
3567
- for fd, dispatcher in combined_map.items():
3568
- if dispatcher.readable():
3569
- self._context.poller.register_readable(fd)
3570
- if dispatcher.writable():
3571
- self._context.poller.register_writable(fd)
3786
+ for fd, dispatcher in combined_map.items():
3787
+ if dispatcher.readable():
3788
+ self._context.poller.register_readable(fd)
3789
+ if dispatcher.writable():
3790
+ self._context.poller.register_writable(fd)
3572
3791
 
3573
- r, w = self._context.poller.poll(timeout)
3792
+ timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
3793
+ r, w = self._context.poller.poll(timeout)
3574
3794
 
3575
- for fd in r:
3576
- if fd in combined_map:
3577
- try:
3578
- dispatcher = combined_map[fd]
3579
- log.debug('read event caused by %r', dispatcher)
3580
- dispatcher.handle_read_event()
3581
- if not dispatcher.readable():
3582
- self._context.poller.unregister_readable(fd)
3583
- except ExitNow:
3584
- raise
3585
- except Exception: # noqa
3586
- combined_map[fd].handle_error()
3587
- else:
3588
- # if the fd is not in combined_map, we should unregister it. otherwise, it will be polled every
3589
- # time, which may cause 100% cpu usage
3590
- log.debug('unexpected read event from fd %r', fd)
3591
- try:
3795
+ for fd in r:
3796
+ if fd in combined_map:
3797
+ try:
3798
+ dispatcher = combined_map[fd]
3799
+ log.debug('read event caused by %r', dispatcher)
3800
+ dispatcher.handle_read_event()
3801
+ if not dispatcher.readable():
3592
3802
  self._context.poller.unregister_readable(fd)
3593
- except Exception: # noqa
3594
- pass
3803
+ except ExitNow:
3804
+ raise
3805
+ except Exception: # noqa
3806
+ combined_map[fd].handle_error()
3807
+ else:
3808
+ # if the fd is not in combined_map, we should unregister it. otherwise, it will be polled every
3809
+ # time, which may cause 100% cpu usage
3810
+ log.debug('unexpected read event from fd %r', fd)
3811
+ try:
3812
+ self._context.poller.unregister_readable(fd)
3813
+ except Exception: # noqa
3814
+ pass
3595
3815
 
3596
- for fd in w:
3597
- if fd in combined_map:
3598
- try:
3599
- dispatcher = combined_map[fd]
3600
- log.debug('write event caused by %r', dispatcher)
3601
- dispatcher.handle_write_event()
3602
- if not dispatcher.writable():
3603
- self._context.poller.unregister_writable(fd)
3604
- except ExitNow:
3605
- raise
3606
- except Exception: # noqa
3607
- combined_map[fd].handle_error()
3608
- else:
3609
- log.debug('unexpected write event from fd %r', fd)
3610
- try:
3816
+ for fd in w:
3817
+ if fd in combined_map:
3818
+ try:
3819
+ dispatcher = combined_map[fd]
3820
+ log.debug('write event caused by %r', dispatcher)
3821
+ dispatcher.handle_write_event()
3822
+ if not dispatcher.writable():
3611
3823
  self._context.poller.unregister_writable(fd)
3612
- except Exception: # noqa
3613
- pass
3614
-
3615
- for group in pgroups:
3616
- group.transition()
3824
+ except ExitNow:
3825
+ raise
3826
+ except Exception: # noqa
3827
+ combined_map[fd].handle_error()
3828
+ else:
3829
+ log.debug('unexpected write event from fd %r', fd)
3830
+ try:
3831
+ self._context.poller.unregister_writable(fd)
3832
+ except Exception: # noqa
3833
+ pass
3617
3834
 
3618
- self._reap()
3619
- self._handle_signal()
3620
- self._tick()
3835
+ for group in pgroups:
3836
+ group.transition()
3621
3837
 
3622
- if self._context.state < SupervisorStates.RUNNING:
3623
- self._ordered_stop_groups_phase_2()
3838
+ self._reap()
3839
+ self._handle_signal()
3840
+ self._tick()
3624
3841
 
3625
- if self._context.test:
3626
- break
3842
+ if self._context.state < SupervisorStates.RUNNING:
3843
+ self._ordered_stop_groups_phase_2()
3627
3844
 
3628
3845
  def _tick(self, now: ta.Optional[float] = None) -> None:
3629
3846
  """Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
@@ -3694,49 +3911,56 @@ class Supervisor:
3694
3911
  log.debug('received %s indicating nothing', signame(sig))
3695
3912
 
3696
3913
 
3697
- def timeslice(period, when):
3698
- return int(when - (when % period))
3914
+ ########################################
3915
+ # main.py
3699
3916
 
3700
3917
 
3701
- def main(args=None, test=False):
3918
+ def main(
3919
+ argv: ta.Optional[ta.Sequence[str]] = None,
3920
+ *,
3921
+ no_logging: bool = False,
3922
+ ) -> None:
3702
3923
  import argparse
3703
3924
 
3704
3925
  parser = argparse.ArgumentParser()
3705
3926
  parser.add_argument('config_file', metavar='config-file')
3706
- args = parser.parse_args()
3927
+ parser.add_argument('--no-journald', action='store_true')
3928
+ args = parser.parse_args(argv)
3707
3929
 
3708
- configure_standard_logging('INFO')
3930
+ #
3709
3931
 
3710
3932
  if not (cf := args.config_file):
3711
3933
  raise RuntimeError('No config file specified')
3712
3934
 
3935
+ if not no_logging:
3936
+ configure_standard_logging(
3937
+ 'INFO',
3938
+ handler_factory=journald_log_handler_factory if not args.no_journald else None,
3939
+ )
3940
+
3941
+ #
3942
+
3713
3943
  # if we hup, restart by making a new Supervisor()
3714
- first = True
3715
- while True:
3944
+ for epoch in itertools.count():
3716
3945
  with open(cf) as f:
3717
3946
  config_src = f.read()
3947
+
3718
3948
  config_dct = json.loads(config_src)
3719
3949
  config: ServerConfig = unmarshal_obj(config_dct, ServerConfig)
3720
3950
 
3721
3951
  context = ServerContext(
3722
3952
  config,
3953
+ epoch=epoch,
3723
3954
  )
3724
3955
 
3725
- context.first = first
3726
- context.test = test
3727
- go(context)
3728
- # options.close_logger()
3729
- first = False
3730
- if test or (context.state < SupervisorStates.RESTARTING):
3731
- break
3732
-
3956
+ supervisor = Supervisor(context)
3957
+ try:
3958
+ supervisor.main()
3959
+ except ExitNow:
3960
+ pass
3733
3961
 
3734
- def go(context): # pragma: no cover
3735
- d = Supervisor(context)
3736
- try:
3737
- d.main()
3738
- except ExitNow:
3739
- pass
3962
+ if context.state < SupervisorStates.RESTARTING:
3963
+ break
3740
3964
 
3741
3965
 
3742
3966
  if __name__ == '__main__':