| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- """A parent poller for unix."""
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- try:
- import ctypes
- except ImportError:
- ctypes = None # type:ignore[assignment]
- import os
- import platform
- import signal
- import time
- import warnings
- from _thread import interrupt_main # Py 3
- from threading import Thread
- from traitlets.log import get_logger
- class ParentPollerUnix(Thread):
- """A Unix-specific daemon thread that terminates the program immediately
- when the parent process no longer exists.
- """
- def __init__(self, parent_pid=0):
- """Initialize the poller.
- Parameters
- ----------
- parent_handle : int, optional
- If provided, the program will terminate immediately when
- process parent is no longer this original parent.
- """
- super().__init__()
- self.parent_pid = parent_pid
- self.daemon = True
- def run(self):
- """Run the poller."""
- # We cannot use os.waitpid because it works only for child processes.
- from errno import EINTR
- # before start, check if the passed-in parent pid is valid
- original_ppid = os.getppid()
- if original_ppid != self.parent_pid:
- self.parent_pid = 0
- get_logger().debug(
- "%s: poll for parent change with original parent pid=%d",
- type(self).__name__,
- self.parent_pid,
- )
- while True:
- try:
- ppid = os.getppid()
- parent_is_init = not self.parent_pid and ppid == 1
- parent_has_changed = self.parent_pid and ppid != self.parent_pid
- if parent_is_init or parent_has_changed:
- get_logger().warning("Parent appears to have exited, shutting down.")
- os._exit(1)
- time.sleep(1.0)
- except OSError as e:
- if e.errno == EINTR:
- continue
- raise
- class ParentPollerWindows(Thread):
- """A Windows-specific daemon thread that listens for a special event that
- signals an interrupt and, optionally, terminates the program immediately
- when the parent process no longer exists.
- """
- def __init__(self, interrupt_handle=None, parent_handle=None):
- """Create the poller. At least one of the optional parameters must be
- provided.
- Parameters
- ----------
- interrupt_handle : HANDLE (int), optional
- If provided, the program will generate a Ctrl+C event when this
- handle is signaled.
- parent_handle : HANDLE (int), optional
- If provided, the program will terminate immediately when this
- handle is signaled.
- """
- assert interrupt_handle or parent_handle
- super().__init__()
- if ctypes is None:
- msg = "ParentPollerWindows requires ctypes" # type:ignore[unreachable]
- raise ImportError(msg)
- self.daemon = True
- self.interrupt_handle = interrupt_handle
- self.parent_handle = parent_handle
- def run(self):
- """Run the poll loop. This method never returns."""
- try:
- from _winapi import INFINITE, WAIT_OBJECT_0 # type:ignore[attr-defined]
- except ImportError:
- from _subprocess import INFINITE, WAIT_OBJECT_0
- # Build the list of handle to listen on.
- handles = []
- if self.interrupt_handle:
- handles.append(self.interrupt_handle)
- if self.parent_handle:
- handles.append(self.parent_handle)
- arch = platform.architecture()[0]
- c_int = ctypes.c_int64 if arch.startswith("64") else ctypes.c_int
- # Listen forever.
- while True:
- result = ctypes.windll.kernel32.WaitForMultipleObjects( # type:ignore[attr-defined]
- len(handles), # nCount
- (c_int * len(handles))(*handles), # lpHandles
- False, # bWaitAll
- INFINITE,
- ) # dwMilliseconds
- if WAIT_OBJECT_0 <= result < len(handles):
- handle = handles[result - WAIT_OBJECT_0]
- ctypes.windll.kernel32.ResetEvent(handle) # type:ignore[attr-defined]
- if handle == self.interrupt_handle:
- # check if signal handler is callable
- # to avoid 'int not callable' error (Python issue #23395)
- if callable(signal.getsignal(signal.SIGINT)):
- interrupt_main()
- elif handle == self.parent_handle:
- get_logger().warning("Parent appears to have exited, shutting down.")
- os._exit(1)
- elif result < 0:
- # wait failed, just give up and stop polling.
- warnings.warn(
- """Parent poll failed. If the frontend dies,
- the kernel may be left running. Please let us know
- about your system (bitness, Python, etc.) at
- ipython-dev@scipy.org""",
- stacklevel=2,
- )
- return
|