##// 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 3 # Standard library imports
4 4 from collections import namedtuple
5 import signal
6 5 import sys
7 6
8 7 # System library imports
@@ -391,7 +390,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
391 390 if self.custom_interrupt:
392 391 self.custom_interrupt_requested.emit()
393 392 elif self.kernel_manager.has_kernel:
394 self.kernel_manager.signal_kernel(signal.SIGINT)
393 self.kernel_manager.interrupt_kernel()
395 394 else:
396 395 self._append_plain_text('Kernel process is either remote or '
397 396 'unspecified. Cannot interrupt.\n')
@@ -15,11 +15,11 b' import zmq'
15 15 from IPython.core.ultratb import FormattedTB
16 16 from IPython.external.argparse import ArgumentParser
17 17 from IPython.utils import io
18 from exitpoller import ExitPollerUnix, ExitPollerWindows
19 18 from displayhook import DisplayHook
19 from heartbeat import Heartbeat
20 20 from iostream import OutStream
21 from parentpoller import ParentPollerUnix, ParentPollerWindows
21 22 from session import Session
22 from heartbeat import Heartbeat
23 23
24 24 def bind_port(socket, ip, port):
25 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 51 help='set the heartbeat port [default: random]')
52 52
53 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 57 parser.add_argument('--parent', type=int, metavar='HANDLE',
55 58 default=0, help='kill this process if the process '
56 59 'with HANDLE dies')
@@ -118,12 +121,13 b' def make_kernel(namespace, kernel_factory,'
118 121 def start_kernel(namespace, kernel):
119 122 """ Starts a kernel.
120 123 """
121 # Configure this kernel/process to die on parent termination, if necessary.
122 if namespace.parent:
123 if sys.platform == 'win32':
124 poller = ExitPollerWindows(namespace.parent)
125 else:
126 poller = ExitPollerUnix()
124 # Configure this kernel process to poll the parent process, if necessary.
125 if sys.platform == 'win32':
126 if namespace.interrupt or namespace.parent:
127 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
128 poller.start()
129 elif namespace.parent:
130 poller = ParentPollerUnix()
127 131 poller.start()
128 132
129 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 210 # Spawn a kernel.
207 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 216 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
210 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 219 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
213 220
214 221 if independent:
215 proc = Popen(['start', '/b'] + arguments, shell=True,
222 proc = Popen(arguments,
223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
216 224 stdout=redirect, stderr=redirect, stdin=redirect)
217 225 else:
218 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 232 proc = Popen(arguments + ['--parent', str(int(handle))],
225 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 238 # Clean up pipes created to work around Popen bug.
228 239 if redirect is not None:
229 240 proc.stdout.close()
@@ -1,8 +1,6 b''
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 Todo
4 ====
5
3 TODO
6 4 * Create logger to handle debugging and console messages.
7 5 """
8 6
@@ -20,6 +18,7 b' Todo'
20 18 # Standard library imports.
21 19 from Queue import Queue, Empty
22 20 from subprocess import Popen
21 import signal
23 22 import sys
24 23 from threading import Thread
25 24 import time
@@ -804,8 +803,23 b' class KernelManager(HasTraits):'
804 803 else:
805 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 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 823 if self.has_kernel:
810 824 self.kernel.send_signal(signum)
811 825 else:
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now