Format: text/x-python
⬇ Download File (4.2 KB)
   1  """A parent poller for unix."""
   2  # Copyright (c) IPython Development Team.
   3  # Distributed under the terms of the Modified BSD License.
   4  
   5  try:
   6      import ctypes
   7  except ImportError:
   8      ctypes = None  # type:ignore[assignment]
   9  import os
  10  import platform
  11  import signal
  12  import time
  13  import warnings
  14  from _thread import interrupt_main  # Py 3
  15  from threading import Thread
  16  
  17  from traitlets.log import get_logger
  18  
  19  
  20  class ParentPollerUnix(Thread):
  21      """A Unix-specific daemon thread that terminates the program immediately
  22      when the parent process no longer exists.
  23      """
  24  
  25      def __init__(self):
  26          """Initialize the poller."""
  27          super().__init__()
  28          self.daemon = True
  29  
  30      def run(self):
  31          """Run the poller."""
  32          # We cannot use os.waitpid because it works only for child processes.
  33          from errno import EINTR
  34  
  35          while True:
  36              try:
  37                  if os.getppid() == 1:
  38                      get_logger().warning("Parent appears to have exited, shutting down.")
  39                      os._exit(1)
  40                  time.sleep(1.0)
  41              except OSError as e:
  42                  if e.errno == EINTR:
  43                      continue
  44                  raise
  45  
  46  
  47  class ParentPollerWindows(Thread):
  48      """A Windows-specific daemon thread that listens for a special event that
  49      signals an interrupt and, optionally, terminates the program immediately
  50      when the parent process no longer exists.
  51      """
  52  
  53      def __init__(self, interrupt_handle=None, parent_handle=None):
  54          """Create the poller. At least one of the optional parameters must be
  55          provided.
  56  
  57          Parameters
  58          ----------
  59          interrupt_handle : HANDLE (int), optional
  60              If provided, the program will generate a Ctrl+C event when this
  61              handle is signaled.
  62          parent_handle : HANDLE (int), optional
  63              If provided, the program will terminate immediately when this
  64              handle is signaled.
  65          """
  66          assert interrupt_handle or parent_handle
  67          super().__init__()
  68          if ctypes is None:
  69              msg = "ParentPollerWindows requires ctypes"  # type:ignore[unreachable]
  70              raise ImportError(msg)
  71          self.daemon = True
  72          self.interrupt_handle = interrupt_handle
  73          self.parent_handle = parent_handle
  74  
  75      def run(self):
  76          """Run the poll loop. This method never returns."""
  77          try:
  78              from _winapi import INFINITE, WAIT_OBJECT_0  # type:ignore[attr-defined]
  79          except ImportError:
  80              from _subprocess import INFINITE, WAIT_OBJECT_0
  81  
  82          # Build the list of handle to listen on.
  83          handles = []
  84          if self.interrupt_handle:
  85              handles.append(self.interrupt_handle)
  86          if self.parent_handle:
  87              handles.append(self.parent_handle)
  88          arch = platform.architecture()[0]
  89          c_int = ctypes.c_int64 if arch.startswith("64") else ctypes.c_int
  90  
  91          # Listen forever.
  92          while True:
  93              result = ctypes.windll.kernel32.WaitForMultipleObjects(  # type:ignore[attr-defined]
  94                  len(handles),  # nCount
  95                  (c_int * len(handles))(*handles),  # lpHandles
  96                  False,  # bWaitAll
  97                  INFINITE,
  98              )  # dwMilliseconds
  99  
 100              if WAIT_OBJECT_0 <= result < len(handles):
 101                  handle = handles[result - WAIT_OBJECT_0]
 102  
 103                  if handle == self.interrupt_handle:
 104                      # check if signal handler is callable
 105                      # to avoid 'int not callable' error (Python issue #23395)
 106                      if callable(signal.getsignal(signal.SIGINT)):
 107                          interrupt_main()
 108  
 109                  elif handle == self.parent_handle:
 110                      get_logger().warning("Parent appears to have exited, shutting down.")
 111                      os._exit(1)
 112              elif result < 0:
 113                  # wait failed, just give up and stop polling.
 114                  warnings.warn(
 115                      """Parent poll failed.  If the frontend dies,
 116                  the kernel may be left running.  Please let us know
 117                  about your system (bitness, Python, etc.) at
 118                  ipython-dev@scipy.org""",
 119                      stacklevel=2,
 120                  )
 121                  return
 122