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