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