##// END OF EJS Templates
Implemented kernel interrupts for Windows.
epatters -
Show More
@@ -0,0 +1,125 b''
1 # Standard library imports.
2 import ctypes
3 import os
4 import time
5 from thread import interrupt_main
6 from threading import Thread
7
8 # Local imports.
9 from IPython.utils.io import raw_print
10
11
12 class ParentPollerUnix(Thread):
13 """ A Unix-specific daemon thread that terminates the program immediately
14 when the parent process no longer exists.
15 """
16
17 def __init__(self):
18 super(ParentPollerUnix, self).__init__()
19 self.daemon = True
20
21 def run(self):
22 # We cannot use os.waitpid because it works only for child processes.
23 from errno import EINTR
24 while True:
25 try:
26 if os.getppid() == 1:
27 raw_print('Killed by parent poller!')
28 os._exit(1)
29 time.sleep(1.0)
30 except OSError, e:
31 if e.errno == EINTR:
32 continue
33 raise
34
35
36 class ParentPollerWindows(Thread):
37 """ A Windows-specific daemon thread that listens for a special event that
38 signals an interrupt and, optionally, terminates the program immediately
39 when the parent process no longer exists.
40 """
41
42 def __init__(self, interrupt_handle=None, parent_handle=None):
43 """ Create the poller. At least one of the optional parameters must be
44 provided.
45
46 Parameters:
47 -----------
48 interrupt_handle : HANDLE (int), optional
49 If provided, the program will generate a Ctrl+C event when this
50 handle is signaled.
51
52 parent_handle : HANDLE (int), optional
53 If provided, the program will terminate immediately when this
54 handle is signaled.
55 """
56 assert(interrupt_handle or parent_handle)
57 super(ParentPollerWindows, self).__init__()
58 self.daemon = True
59 self.interrupt_handle = interrupt_handle
60 self.parent_handle = parent_handle
61
62 @staticmethod
63 def create_interrupt_event():
64 """ Create an interrupt event handle.
65
66 The parent process should use this static method for creating the
67 interrupt event that is passed to the child process. It should store
68 this handle and use it with ``send_interrupt`` to interrupt the child
69 process.
70 """
71 # Create a security attributes struct that permits inheritance of the
72 # handle by new processes.
73 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
74 class SECURITY_ATTRIBUTES(ctypes.Structure):
75 _fields_ = [ ("nLength", ctypes.c_int),
76 ("lpSecurityDescriptor", ctypes.c_void_p),
77 ("bInheritHandle", ctypes.c_int) ]
78 sa = SECURITY_ATTRIBUTES()
79 sa_p = ctypes.pointer(sa)
80 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
81 sa.lpSecurityDescriptor = 0
82 sa.bInheritHandle = 1
83
84 return ctypes.windll.kernel32.CreateEventA(
85 sa_p, # lpEventAttributes
86 False, # bManualReset
87 False, # bInitialState
88 '') # lpName
89
90 @staticmethod
91 def send_interrupt(interrupt_handle):
92 """ Sends an interrupt event using the specified handle.
93 """
94 ctypes.windll.kernel32.SetEvent(interrupt_handle)
95
96 def run(self):
97 """ Run the poll loop. This method never returns.
98 """
99 from _subprocess import WAIT_OBJECT_0, INFINITE
100
101 # Build the list of handle to listen on.
102 handles = []
103 if self.interrupt_handle:
104 handles.append(self.interrupt_handle)
105 if self.parent_handle:
106 handles.append(self.parent_handle)
107
108 # Listen forever.
109 while True:
110 result = ctypes.windll.kernel32.WaitForMultipleObjects(
111 len(handles), # nCount
112 (ctypes.c_int * len(handles))(*handles), # lpHandles
113 False, # bWaitAll
114 INFINITE) # dwMilliseconds
115
116 if WAIT_OBJECT_0 <= result < len(handles):
117 handle = handles[result - WAIT_OBJECT_0]
118
119 if handle == self.interrupt_handle:
120 raw_print('Interrupted by parent poller!')
121 interrupt_main()
122
123 elif handle == self.parent_handle:
124 raw_print('Killed by parent poller!')
125 os._exit(1)
@@ -2,7 +2,6 b' from __future__ import print_function'
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import signal
6 import sys
5 import sys
7
6
8 # System library imports
7 # System library imports
@@ -391,7 +390,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
391 if self.custom_interrupt:
390 if self.custom_interrupt:
392 self.custom_interrupt_requested.emit()
391 self.custom_interrupt_requested.emit()
393 elif self.kernel_manager.has_kernel:
392 elif self.kernel_manager.has_kernel:
394 self.kernel_manager.signal_kernel(signal.SIGINT)
393 self.kernel_manager.interrupt_kernel()
395 else:
394 else:
396 self._append_plain_text('Kernel process is either remote or '
395 self._append_plain_text('Kernel process is either remote or '
397 'unspecified. Cannot interrupt.\n')
396 'unspecified. Cannot interrupt.\n')
@@ -15,11 +15,11 b' import zmq'
15 from IPython.core.ultratb import FormattedTB
15 from IPython.core.ultratb import FormattedTB
16 from IPython.external.argparse import ArgumentParser
16 from IPython.external.argparse import ArgumentParser
17 from IPython.utils import io
17 from IPython.utils import io
18 from exitpoller import ExitPollerUnix, ExitPollerWindows
19 from displayhook import DisplayHook
18 from displayhook import DisplayHook
19 from heartbeat import Heartbeat
20 from iostream import OutStream
20 from iostream import OutStream
21 from parentpoller import ParentPollerUnix, ParentPollerWindows
21 from session import Session
22 from session import Session
22 from heartbeat import Heartbeat
23
23
24 def bind_port(socket, ip, port):
24 def bind_port(socket, ip, port):
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
@@ -51,6 +51,9 b' def make_argument_parser():'
51 help='set the heartbeat port [default: random]')
51 help='set the heartbeat port [default: random]')
52
52
53 if sys.platform == 'win32':
53 if sys.platform == 'win32':
54 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
55 default=0, help='interrupt this process when '
56 'HANDLE is signaled')
54 parser.add_argument('--parent', type=int, metavar='HANDLE',
57 parser.add_argument('--parent', type=int, metavar='HANDLE',
55 default=0, help='kill this process if the process '
58 default=0, help='kill this process if the process '
56 'with HANDLE dies')
59 'with HANDLE dies')
@@ -118,12 +121,13 b' def make_kernel(namespace, kernel_factory,'
118 def start_kernel(namespace, kernel):
121 def start_kernel(namespace, kernel):
119 """ Starts a kernel.
122 """ Starts a kernel.
120 """
123 """
121 # Configure this kernel/process to die on parent termination, if necessary.
124 # Configure this kernel process to poll the parent process, if necessary.
122 if namespace.parent:
125 if sys.platform == 'win32':
123 if sys.platform == 'win32':
126 if namespace.interrupt or namespace.parent:
124 poller = ExitPollerWindows(namespace.parent)
127 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
125 else:
128 poller.start()
126 poller = ExitPollerUnix()
129 elif namespace.parent:
130 poller = ParentPollerUnix()
127 poller.start()
131 poller.start()
128
132
129 # Start the kernel mainloop.
133 # Start the kernel mainloop.
@@ -205,6 +209,9 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
205
209
206 # Spawn a kernel.
210 # Spawn a kernel.
207 if sys.platform == 'win32':
211 if sys.platform == 'win32':
212 # Create a Win32 event for interrupting the kernel.
213 interrupt_event = ParentPollerWindows.create_interrupt_event()
214 arguments += [ '--interrupt', str(int(interrupt_event)) ]
208
215
209 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
216 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
210 # fail unless they are suitably redirected. We don't read from the
217 # fail unless they are suitably redirected. We don't read from the
@@ -212,7 +219,8 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
212 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
219 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
213
220
214 if independent:
221 if independent:
215 proc = Popen(['start', '/b'] + arguments, shell=True,
222 proc = Popen(arguments,
223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
216 stdout=redirect, stderr=redirect, stdin=redirect)
224 stdout=redirect, stderr=redirect, stdin=redirect)
217 else:
225 else:
218 from _subprocess import DuplicateHandle, GetCurrentProcess, \
226 from _subprocess import DuplicateHandle, GetCurrentProcess, \
@@ -224,6 +232,9 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
224 proc = Popen(arguments + ['--parent', str(int(handle))],
232 proc = Popen(arguments + ['--parent', str(int(handle))],
225 stdout=redirect, stderr=redirect, stdin=redirect)
233 stdout=redirect, stderr=redirect, stdin=redirect)
226
234
235 # Attach the interrupt event to the Popen objet so it can be used later.
236 proc.win32_interrupt_event = interrupt_event
237
227 # Clean up pipes created to work around Popen bug.
238 # Clean up pipes created to work around Popen bug.
228 if redirect is not None:
239 if redirect is not None:
229 proc.stdout.close()
240 proc.stdout.close()
@@ -1,8 +1,6 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 Todo
3 TODO
4 ====
5
6 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
7 """
5 """
8
6
@@ -20,6 +18,7 b' Todo'
20 # Standard library imports.
18 # Standard library imports.
21 from Queue import Queue, Empty
19 from Queue import Queue, Empty
22 from subprocess import Popen
20 from subprocess import Popen
21 import signal
23 import sys
22 import sys
24 from threading import Thread
23 from threading import Thread
25 import time
24 import time
@@ -804,8 +803,23 b' class KernelManager(HasTraits):'
804 else:
803 else:
805 raise RuntimeError("Cannot kill kernel. No kernel is running!")
804 raise RuntimeError("Cannot kill kernel. No kernel is running!")
806
805
806 def interrupt_kernel(self):
807 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
808 well supported on all platforms.
809 """
810 if self.has_kernel:
811 if sys.platform == 'win32':
812 from parentpoller import ParentPollerWindows as Poller
813 Poller.send_interrupt(self.kernel.win32_interrupt_event)
814 else:
815 self.kernel.send_signal(signal.SIGINT)
816 else:
817 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
818
807 def signal_kernel(self, signum):
819 def signal_kernel(self, signum):
808 """ Sends a signal to the kernel. """
820 """ Sends a signal to the kernel. Note that since only SIGTERM is
821 supported on Windows, this function is only useful on Unix systems.
822 """
809 if self.has_kernel:
823 if self.has_kernel:
810 self.kernel.send_signal(signum)
824 self.kernel.send_signal(signum)
811 else:
825 else:
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now