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