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