ominfra 0.0.0.dev118__py3-none-any.whl → 0.0.0.dev119__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.
@@ -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__':