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