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