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