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