##// END OF EJS Templates
Fix use of sys.executable in Windows kernel launcher.
epatters -
Show More
@@ -1,265 +1,265 b''
1 1 """ Defines helper functions for creating kernel entry points and process
2 2 launchers.
3 3 """
4 4
5 5 # Standard library imports.
6 6 import atexit
7 7 import os
8 8 import socket
9 9 from subprocess import Popen, PIPE
10 10 import sys
11 11
12 12 # System library imports.
13 13 import zmq
14 14
15 15 # Local imports.
16 16 from IPython.core.ultratb import FormattedTB
17 17 from IPython.external.argparse import ArgumentParser
18 18 from IPython.utils import io
19 19 from IPython.utils.localinterfaces import LOCALHOST
20 20 from displayhook import DisplayHook
21 21 from heartbeat import Heartbeat
22 22 from iostream import OutStream
23 23 from parentpoller import ParentPollerUnix, ParentPollerWindows
24 24 from session import Session
25 25
26 26 def bind_port(socket, ip, port):
27 27 """ Binds the specified ZMQ socket. If the port is zero, a random port is
28 28 chosen. Returns the port that was bound.
29 29 """
30 30 connection = 'tcp://%s' % ip
31 31 if port <= 0:
32 32 port = socket.bind_to_random_port(connection)
33 33 else:
34 34 connection += ':%i' % port
35 35 socket.bind(connection)
36 36 return port
37 37
38 38
39 39 def make_argument_parser():
40 40 """ Creates an ArgumentParser for the generic arguments supported by all
41 41 kernel entry points.
42 42 """
43 43 parser = ArgumentParser()
44 44 parser.add_argument('--ip', type=str, default=LOCALHOST,
45 45 help='set the kernel\'s IP address [default: local]')
46 46 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
47 47 help='set the XREP channel port [default: random]')
48 48 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
49 49 help='set the PUB channel port [default: random]')
50 50 parser.add_argument('--req', type=int, metavar='PORT', default=0,
51 51 help='set the REQ channel port [default: random]')
52 52 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
53 53 help='set the heartbeat port [default: random]')
54 54
55 55 if sys.platform == 'win32':
56 56 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
57 57 default=0, help='interrupt this process when '
58 58 'HANDLE is signaled')
59 59 parser.add_argument('--parent', type=int, metavar='HANDLE',
60 60 default=0, help='kill this process if the process '
61 61 'with HANDLE dies')
62 62 else:
63 63 parser.add_argument('--parent', action='store_true',
64 64 help='kill this process if its parent dies')
65 65
66 66 return parser
67 67
68 68
69 69 def make_kernel(namespace, kernel_factory,
70 70 out_stream_factory=None, display_hook_factory=None):
71 71 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
72 72 and exception handler.
73 73 """
74 74 # If running under pythonw.exe, the interpreter will crash if more than 4KB
75 75 # of data is written to stdout or stderr. This is a bug that has been with
76 76 # Python for a very long time; see http://bugs.python.org/issue706263.
77 77 if sys.executable.endswith('pythonw.exe'):
78 78 blackhole = file(os.devnull, 'w')
79 79 sys.stdout = sys.stderr = blackhole
80 80 sys.__stdout__ = sys.__stderr__ = blackhole
81 81
82 82 # Install minimal exception handling
83 83 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
84 84 ostream=sys.__stdout__)
85 85
86 86 # Create a context, a session, and the kernel sockets.
87 87 io.raw_print("Starting the kernel at pid:", os.getpid())
88 88 context = zmq.Context()
89 89 # Uncomment this to try closing the context.
90 90 # atexit.register(context.close)
91 91 session = Session(username=u'kernel')
92 92
93 93 reply_socket = context.socket(zmq.XREP)
94 94 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
95 95 io.raw_print("XREP Channel on port", xrep_port)
96 96
97 97 pub_socket = context.socket(zmq.PUB)
98 98 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
99 99 io.raw_print("PUB Channel on port", pub_port)
100 100
101 101 req_socket = context.socket(zmq.XREQ)
102 102 req_port = bind_port(req_socket, namespace.ip, namespace.req)
103 103 io.raw_print("REQ Channel on port", req_port)
104 104
105 105 hb = Heartbeat(context, (namespace.ip, namespace.hb))
106 106 hb.start()
107 107 hb_port = hb.port
108 108 io.raw_print("Heartbeat REP Channel on port", hb_port)
109 109
110 110 # Helper to make it easier to connect to an existing kernel, until we have
111 111 # single-port connection negotiation fully implemented.
112 112 io.raw_print("To connect another client to this kernel, use:")
113 113 io.raw_print("-e --xreq {0} --sub {1} --rep {2} --hb {3}".format(
114 114 xrep_port, pub_port, req_port, hb_port))
115 115
116 116 # Redirect input streams and set a display hook.
117 117 if out_stream_factory:
118 118 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
119 119 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
120 120 if display_hook_factory:
121 121 sys.displayhook = display_hook_factory(session, pub_socket)
122 122
123 123 # Create the kernel.
124 124 kernel = kernel_factory(session=session, reply_socket=reply_socket,
125 125 pub_socket=pub_socket, req_socket=req_socket)
126 126 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
127 127 req_port=req_port, hb_port=hb_port)
128 128 return kernel
129 129
130 130
131 131 def start_kernel(namespace, kernel):
132 132 """ Starts a kernel.
133 133 """
134 134 # Configure this kernel process to poll the parent process, if necessary.
135 135 if sys.platform == 'win32':
136 136 if namespace.interrupt or namespace.parent:
137 137 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
138 138 poller.start()
139 139 elif namespace.parent:
140 140 poller = ParentPollerUnix()
141 141 poller.start()
142 142
143 143 # Start the kernel mainloop.
144 144 kernel.start()
145 145
146 146
147 147 def make_default_main(kernel_factory):
148 148 """ Creates the simplest possible kernel entry point.
149 149 """
150 150 def main():
151 151 namespace = make_argument_parser().parse_args()
152 152 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
153 153 start_kernel(namespace, kernel)
154 154 return main
155 155
156 156
157 157 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
158 158 executable=None, independent=False, extra_arguments=[]):
159 159 """ Launches a localhost kernel, binding to the specified ports.
160 160
161 161 Parameters
162 162 ----------
163 163 code : str,
164 164 A string of Python code that imports and executes a kernel entry point.
165 165
166 166 xrep_port : int, optional
167 167 The port to use for XREP channel.
168 168
169 169 pub_port : int, optional
170 170 The port to use for the SUB channel.
171 171
172 172 req_port : int, optional
173 173 The port to use for the REQ (raw input) channel.
174 174
175 175 hb_port : int, optional
176 176 The port to use for the hearbeat REP channel.
177 177
178 178 executable : str, optional (default sys.executable)
179 179 The Python executable to use for the kernel process.
180 180
181 181 independent : bool, optional (default False)
182 182 If set, the kernel process is guaranteed to survive if this process
183 183 dies. If not set, an effort is made to ensure that the kernel is killed
184 184 when this process dies. Note that in this case it is still good practice
185 185 to kill kernels manually before exiting.
186 186
187 187 extra_arguments = list, optional
188 188 A list of extra arguments to pass when executing the launch code.
189 189
190 190 Returns
191 191 -------
192 192 A tuple of form:
193 193 (kernel_process, xrep_port, pub_port, req_port)
194 194 where kernel_process is a Popen object and the ports are integers.
195 195 """
196 196 # Find open ports as necessary.
197 197 ports = []
198 198 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
199 199 int(req_port <= 0) + int(hb_port <= 0)
200 200 for i in xrange(ports_needed):
201 201 sock = socket.socket()
202 202 sock.bind(('', 0))
203 203 ports.append(sock)
204 204 for i, sock in enumerate(ports):
205 205 port = sock.getsockname()[1]
206 206 sock.close()
207 207 ports[i] = port
208 208 if xrep_port <= 0:
209 209 xrep_port = ports.pop(0)
210 210 if pub_port <= 0:
211 211 pub_port = ports.pop(0)
212 212 if req_port <= 0:
213 213 req_port = ports.pop(0)
214 214 if hb_port <= 0:
215 215 hb_port = ports.pop(0)
216 216
217 217 # Build the kernel launch command.
218 218 if executable is None:
219 219 executable = sys.executable
220 220 arguments = [ executable, '-c', code, '--xrep', str(xrep_port),
221 221 '--pub', str(pub_port), '--req', str(req_port),
222 222 '--hb', str(hb_port) ]
223 223 arguments.extend(extra_arguments)
224 224
225 225 # Spawn a kernel.
226 226 if sys.platform == 'win32':
227 227 # Create a Win32 event for interrupting the kernel.
228 228 interrupt_event = ParentPollerWindows.create_interrupt_event()
229 229 arguments += [ '--interrupt', str(int(interrupt_event)) ]
230 230
231 231 # If using pythonw, stdin, stdout, and stderr are invalid. Popen will
232 232 # fail unless they are suitably redirected. We don't read from the
233 233 # pipes, but they must exist.
234 redirect = PIPE if sys.executable.endswith('pythonw.exe') else None
234 redirect = PIPE if executable.endswith('pythonw.exe') else None
235 235
236 236 if independent:
237 237 proc = Popen(arguments,
238 238 creationflags=512, # CREATE_NEW_PROCESS_GROUP
239 239 stdout=redirect, stderr=redirect, stdin=redirect)
240 240 else:
241 241 from _subprocess import DuplicateHandle, GetCurrentProcess, \
242 242 DUPLICATE_SAME_ACCESS
243 243 pid = GetCurrentProcess()
244 244 handle = DuplicateHandle(pid, pid, pid, 0,
245 245 True, # Inheritable by new processes.
246 246 DUPLICATE_SAME_ACCESS)
247 247 proc = Popen(arguments + ['--parent', str(int(handle))],
248 248 stdout=redirect, stderr=redirect, stdin=redirect)
249 249
250 250 # Attach the interrupt event to the Popen objet so it can be used later.
251 251 proc.win32_interrupt_event = interrupt_event
252 252
253 253 # Clean up pipes created to work around Popen bug.
254 254 if redirect is not None:
255 255 proc.stdout.close()
256 256 proc.stderr.close()
257 257 proc.stdin.close()
258 258
259 259 else:
260 260 if independent:
261 261 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
262 262 else:
263 263 proc = Popen(arguments + ['--parent'])
264 264
265 265 return proc, xrep_port, pub_port, req_port, hb_port
General Comments 0
You need to be logged in to leave comments. Login now