##// END OF EJS Templates
Merge pull request #7688 from minrk/callable-interrupt...
Thomas Kluyver -
r20349:a8827f0e merge
parent child Browse files
Show More
@@ -1,142 +1,148
1 # Standard library imports.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
3
2 4 try:
3 5 import ctypes
4 6 except:
5 7 ctypes = None
6 8 import os
7 9 import platform
10 import signal
8 11 import time
9 12 try:
10 13 from _thread import interrupt_main # Py 3
11 14 except ImportError:
12 15 from thread import interrupt_main # Py 2
13 16 from threading import Thread
14 17
15 18 from IPython.utils.warn import warn
16 19
17 20
18 21 class ParentPollerUnix(Thread):
19 22 """ A Unix-specific daemon thread that terminates the program immediately
20 23 when the parent process no longer exists.
21 24 """
22 25
23 26 def __init__(self):
24 27 super(ParentPollerUnix, self).__init__()
25 28 self.daemon = True
26 29
27 30 def run(self):
28 31 # We cannot use os.waitpid because it works only for child processes.
29 32 from errno import EINTR
30 33 while True:
31 34 try:
32 35 if os.getppid() == 1:
33 36 os._exit(1)
34 37 time.sleep(1.0)
35 38 except OSError as e:
36 39 if e.errno == EINTR:
37 40 continue
38 41 raise
39 42
40 43
41 44 class ParentPollerWindows(Thread):
42 45 """ A Windows-specific daemon thread that listens for a special event that
43 46 signals an interrupt and, optionally, terminates the program immediately
44 47 when the parent process no longer exists.
45 48 """
46 49
47 50 def __init__(self, interrupt_handle=None, parent_handle=None):
48 51 """ Create the poller. At least one of the optional parameters must be
49 52 provided.
50 53
51 54 Parameters
52 55 ----------
53 56 interrupt_handle : HANDLE (int), optional
54 57 If provided, the program will generate a Ctrl+C event when this
55 58 handle is signaled.
56 59
57 60 parent_handle : HANDLE (int), optional
58 61 If provided, the program will terminate immediately when this
59 62 handle is signaled.
60 63 """
61 64 assert(interrupt_handle or parent_handle)
62 65 super(ParentPollerWindows, self).__init__()
63 66 if ctypes is None:
64 67 raise ImportError("ParentPollerWindows requires ctypes")
65 68 self.daemon = True
66 69 self.interrupt_handle = interrupt_handle
67 70 self.parent_handle = parent_handle
68 71
69 72 @staticmethod
70 73 def create_interrupt_event():
71 74 """ Create an interrupt event handle.
72 75
73 76 The parent process should use this static method for creating the
74 77 interrupt event that is passed to the child process. It should store
75 78 this handle and use it with ``send_interrupt`` to interrupt the child
76 79 process.
77 80 """
78 81 # Create a security attributes struct that permits inheritance of the
79 82 # handle by new processes.
80 83 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
81 84 class SECURITY_ATTRIBUTES(ctypes.Structure):
82 85 _fields_ = [ ("nLength", ctypes.c_int),
83 86 ("lpSecurityDescriptor", ctypes.c_void_p),
84 87 ("bInheritHandle", ctypes.c_int) ]
85 88 sa = SECURITY_ATTRIBUTES()
86 89 sa_p = ctypes.pointer(sa)
87 90 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
88 91 sa.lpSecurityDescriptor = 0
89 92 sa.bInheritHandle = 1
90 93
91 94 return ctypes.windll.kernel32.CreateEventA(
92 95 sa_p, # lpEventAttributes
93 96 False, # bManualReset
94 97 False, # bInitialState
95 98 '') # lpName
96 99
97 100 @staticmethod
98 101 def send_interrupt(interrupt_handle):
99 102 """ Sends an interrupt event using the specified handle.
100 103 """
101 104 ctypes.windll.kernel32.SetEvent(interrupt_handle)
102 105
103 106 def run(self):
104 107 """ Run the poll loop. This method never returns.
105 108 """
106 109 try:
107 110 from _winapi import WAIT_OBJECT_0, INFINITE
108 111 except ImportError:
109 112 from _subprocess import WAIT_OBJECT_0, INFINITE
110 113
111 114 # Build the list of handle to listen on.
112 115 handles = []
113 116 if self.interrupt_handle:
114 117 handles.append(self.interrupt_handle)
115 118 if self.parent_handle:
116 119 handles.append(self.parent_handle)
117 120 arch = platform.architecture()[0]
118 121 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
119 122
120 123 # Listen forever.
121 124 while True:
122 125 result = ctypes.windll.kernel32.WaitForMultipleObjects(
123 126 len(handles), # nCount
124 127 (c_int * len(handles))(*handles), # lpHandles
125 128 False, # bWaitAll
126 129 INFINITE) # dwMilliseconds
127 130
128 131 if WAIT_OBJECT_0 <= result < len(handles):
129 132 handle = handles[result - WAIT_OBJECT_0]
130 133
131 134 if handle == self.interrupt_handle:
135 # check if signal handler is callable
136 # to avoid 'int not callable' error (Python issue #23395)
137 if callable(signal.getsignal(signal.SIGINT)):
132 138 interrupt_main()
133 139
134 140 elif handle == self.parent_handle:
135 141 os._exit(1)
136 142 elif result < 0:
137 143 # wait failed, just give up and stop polling.
138 144 warn("""Parent poll failed. If the frontend dies,
139 145 the kernel may be left running. Please let us know
140 146 about your system (bitness, Python, etc.) at
141 147 ipython-dev@scipy.org""")
142 148 return
General Comments 0
You need to be logged in to leave comments. Login now