|
"""A parent poller for unix.""" |
|
|
|
|
|
|
|
try: |
|
import ctypes |
|
except ImportError: |
|
ctypes = None |
|
import os |
|
import platform |
|
import signal |
|
import time |
|
import warnings |
|
from _thread import interrupt_main |
|
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): |
|
"""Initialize the poller.""" |
|
super().__init__() |
|
self.daemon = True |
|
|
|
def run(self): |
|
"""Run the poller.""" |
|
|
|
from errno import EINTR |
|
|
|
while True: |
|
try: |
|
if os.getppid() == 1: |
|
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" |
|
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 |
|
except ImportError: |
|
from _subprocess import INFINITE, WAIT_OBJECT_0 |
|
|
|
|
|
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 |
|
|
|
|
|
while True: |
|
result = ctypes.windll.kernel32.WaitForMultipleObjects( |
|
len(handles), |
|
(c_int * len(handles))(*handles), |
|
False, |
|
INFINITE, |
|
) |
|
|
|
if WAIT_OBJECT_0 <= result < len(handles): |
|
handle = handles[result - WAIT_OBJECT_0] |
|
|
|
if handle == self.interrupt_handle: |
|
|
|
|
|
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: |
|
|
|
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 |
|
[email protected]""", |
|
stacklevel=2, |
|
) |
|
return |
|
|