##// END OF EJS Templates
Allow IPython directory to be passed down to kernel selection from App...
Thomas Kluyver -
Show More
@@ -1,392 +1,393 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import signal
27 import signal
28 import sys
28 import sys
29 import uuid
29 import uuid
30
30
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.kernel.blocking import BlockingKernelClient
35 from IPython.kernel.blocking import BlockingKernelClient
36 from IPython.kernel import KernelManager
36 from IPython.kernel import KernelManager
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 from IPython.utils.path import filefind
38 from IPython.utils.path import filefind
39 from IPython.utils.py3compat import str_to_bytes
39 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.traitlets import (
40 from IPython.utils.traitlets import (
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 )
42 )
43 from IPython.kernel.zmq.kernelapp import (
43 from IPython.kernel.zmq.kernelapp import (
44 kernel_flags,
44 kernel_flags,
45 kernel_aliases,
45 kernel_aliases,
46 IPKernelApp
46 IPKernelApp
47 )
47 )
48 from IPython.kernel.zmq.pylab.config import InlineBackend
48 from IPython.kernel.zmq.pylab.config import InlineBackend
49 from IPython.kernel.zmq.session import Session, default_secure
49 from IPython.kernel.zmq.session import Session, default_secure
50 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.kernel.connect import ConnectionFileMixin
51 from IPython.kernel.connect import ConnectionFileMixin
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Network Constants
54 # Network Constants
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 from IPython.utils.localinterfaces import localhost
57 from IPython.utils.localinterfaces import localhost
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Aliases and Flags
65 # Aliases and Flags
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 flags = dict(kernel_flags)
68 flags = dict(kernel_flags)
69
69
70 # the flags that are specific to the frontend
70 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
71 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
72 # or it will raise an error on unrecognized flags
73 app_flags = {
73 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
76 }
77 app_flags.update(boolean_flag(
77 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
80 to force a direct exit without any confirmation.
81 """,
81 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
82 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
83 if it is owned by the frontend, and leave it alive if it is external.
84 """
84 """
85 ))
85 ))
86 flags.update(app_flags)
86 flags.update(app_flags)
87
87
88 aliases = dict(kernel_aliases)
88 aliases = dict(kernel_aliases)
89
89
90 # also scrub aliases from the frontend
90 # also scrub aliases from the frontend
91 app_aliases = dict(
91 app_aliases = dict(
92 ip = 'IPythonConsoleApp.ip',
92 ip = 'IPythonConsoleApp.ip',
93 transport = 'IPythonConsoleApp.transport',
93 transport = 'IPythonConsoleApp.transport',
94 hb = 'IPythonConsoleApp.hb_port',
94 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
98 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
99 f = 'IPythonConsoleApp.connection_file',
100
100
101 kernel = 'IPythonConsoleApp.kernel_name',
101 kernel = 'IPythonConsoleApp.kernel_name',
102
102
103 ssh = 'IPythonConsoleApp.sshserver',
103 ssh = 'IPythonConsoleApp.sshserver',
104 )
104 )
105 aliases.update(app_aliases)
105 aliases.update(app_aliases)
106
106
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108 # Classes
108 # Classes
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110
110
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 # IPythonConsole
112 # IPythonConsole
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114
114
115 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
115 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
116
116
117 class IPythonConsoleApp(ConnectionFileMixin):
117 class IPythonConsoleApp(ConnectionFileMixin):
118 name = 'ipython-console-mixin'
118 name = 'ipython-console-mixin'
119
119
120 description = """
120 description = """
121 The IPython Mixin Console.
121 The IPython Mixin Console.
122
122
123 This class contains the common portions of console client (QtConsole,
123 This class contains the common portions of console client (QtConsole,
124 ZMQ-based terminal console, etc). It is not a full console, in that
124 ZMQ-based terminal console, etc). It is not a full console, in that
125 launched terminal subprocesses will not be able to accept input.
125 launched terminal subprocesses will not be able to accept input.
126
126
127 The Console using this mixing supports various extra features beyond
127 The Console using this mixing supports various extra features beyond
128 the single-process Terminal IPython shell, such as connecting to
128 the single-process Terminal IPython shell, such as connecting to
129 existing kernel, via:
129 existing kernel, via:
130
130
131 ipython <appname> --existing
131 ipython <appname> --existing
132
132
133 as well as tunnel via SSH
133 as well as tunnel via SSH
134
134
135 """
135 """
136
136
137 classes = classes
137 classes = classes
138 flags = Dict(flags)
138 flags = Dict(flags)
139 aliases = Dict(aliases)
139 aliases = Dict(aliases)
140 kernel_manager_class = KernelManager
140 kernel_manager_class = KernelManager
141 kernel_client_class = BlockingKernelClient
141 kernel_client_class = BlockingKernelClient
142
142
143 kernel_argv = List(Unicode)
143 kernel_argv = List(Unicode)
144 # frontend flags&aliases to be stripped when building kernel_argv
144 # frontend flags&aliases to be stripped when building kernel_argv
145 frontend_flags = Any(app_flags)
145 frontend_flags = Any(app_flags)
146 frontend_aliases = Any(app_aliases)
146 frontend_aliases = Any(app_aliases)
147
147
148 # create requested profiles by default, if they don't exist:
148 # create requested profiles by default, if they don't exist:
149 auto_create = CBool(True)
149 auto_create = CBool(True)
150 # connection info:
150 # connection info:
151
151
152 sshserver = Unicode('', config=True,
152 sshserver = Unicode('', config=True,
153 help="""The SSH server to use to connect to the kernel.""")
153 help="""The SSH server to use to connect to the kernel.""")
154 sshkey = Unicode('', config=True,
154 sshkey = Unicode('', config=True,
155 help="""Path to the ssh key to use for logging in to the ssh server.""")
155 help="""Path to the ssh key to use for logging in to the ssh server.""")
156
156
157 hb_port = Int(0, config=True,
157 hb_port = Int(0, config=True,
158 help="set the heartbeat port [default: random]")
158 help="set the heartbeat port [default: random]")
159 shell_port = Int(0, config=True,
159 shell_port = Int(0, config=True,
160 help="set the shell (ROUTER) port [default: random]")
160 help="set the shell (ROUTER) port [default: random]")
161 iopub_port = Int(0, config=True,
161 iopub_port = Int(0, config=True,
162 help="set the iopub (PUB) port [default: random]")
162 help="set the iopub (PUB) port [default: random]")
163 stdin_port = Int(0, config=True,
163 stdin_port = Int(0, config=True,
164 help="set the stdin (DEALER) port [default: random]")
164 help="set the stdin (DEALER) port [default: random]")
165 connection_file = Unicode('', config=True,
165 connection_file = Unicode('', config=True,
166 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
166 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
167
167
168 This file will contain the IP, ports, and authentication key needed to connect
168 This file will contain the IP, ports, and authentication key needed to connect
169 clients to this kernel. By default, this file will be created in the security-dir
169 clients to this kernel. By default, this file will be created in the security-dir
170 of the current profile, but can be specified by absolute path.
170 of the current profile, but can be specified by absolute path.
171 """)
171 """)
172 def _connection_file_default(self):
172 def _connection_file_default(self):
173 return 'kernel-%i.json' % os.getpid()
173 return 'kernel-%i.json' % os.getpid()
174
174
175 existing = CUnicode('', config=True,
175 existing = CUnicode('', config=True,
176 help="""Connect to an already running kernel""")
176 help="""Connect to an already running kernel""")
177
177
178 kernel_name = Unicode('python', config=True,
178 kernel_name = Unicode('python', config=True,
179 help="""The name of the default kernel to start.""")
179 help="""The name of the default kernel to start.""")
180
180
181 confirm_exit = CBool(True, config=True,
181 confirm_exit = CBool(True, config=True,
182 help="""
182 help="""
183 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
183 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
184 to force a direct exit without any confirmation.""",
184 to force a direct exit without any confirmation.""",
185 )
185 )
186
186
187
187
188 def build_kernel_argv(self, argv=None):
188 def build_kernel_argv(self, argv=None):
189 """build argv to be passed to kernel subprocess"""
189 """build argv to be passed to kernel subprocess"""
190 if argv is None:
190 if argv is None:
191 argv = sys.argv[1:]
191 argv = sys.argv[1:]
192 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
192 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
193 # kernel should inherit default config file from frontend
193 # kernel should inherit default config file from frontend
194 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
194 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
195
195
196 def init_connection_file(self):
196 def init_connection_file(self):
197 """find the connection file, and load the info if found.
197 """find the connection file, and load the info if found.
198
198
199 The current working directory and the current profile's security
199 The current working directory and the current profile's security
200 directory will be searched for the file if it is not given by
200 directory will be searched for the file if it is not given by
201 absolute path.
201 absolute path.
202
202
203 When attempting to connect to an existing kernel and the `--existing`
203 When attempting to connect to an existing kernel and the `--existing`
204 argument does not match an existing file, it will be interpreted as a
204 argument does not match an existing file, it will be interpreted as a
205 fileglob, and the matching file in the current profile's security dir
205 fileglob, and the matching file in the current profile's security dir
206 with the latest access time will be used.
206 with the latest access time will be used.
207
207
208 After this method is called, self.connection_file contains the *full path*
208 After this method is called, self.connection_file contains the *full path*
209 to the connection file, never just its name.
209 to the connection file, never just its name.
210 """
210 """
211 if self.existing:
211 if self.existing:
212 try:
212 try:
213 cf = find_connection_file(self.existing)
213 cf = find_connection_file(self.existing)
214 except Exception:
214 except Exception:
215 self.log.critical("Could not find existing kernel connection file %s", self.existing)
215 self.log.critical("Could not find existing kernel connection file %s", self.existing)
216 self.exit(1)
216 self.exit(1)
217 self.log.debug("Connecting to existing kernel: %s" % cf)
217 self.log.debug("Connecting to existing kernel: %s" % cf)
218 self.connection_file = cf
218 self.connection_file = cf
219 else:
219 else:
220 # not existing, check if we are going to write the file
220 # not existing, check if we are going to write the file
221 # and ensure that self.connection_file is a full path, not just the shortname
221 # and ensure that self.connection_file is a full path, not just the shortname
222 try:
222 try:
223 cf = find_connection_file(self.connection_file)
223 cf = find_connection_file(self.connection_file)
224 except Exception:
224 except Exception:
225 # file might not exist
225 # file might not exist
226 if self.connection_file == os.path.basename(self.connection_file):
226 if self.connection_file == os.path.basename(self.connection_file):
227 # just shortname, put it in security dir
227 # just shortname, put it in security dir
228 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
228 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
229 else:
229 else:
230 cf = self.connection_file
230 cf = self.connection_file
231 self.connection_file = cf
231 self.connection_file = cf
232
232
233 # should load_connection_file only be used for existing?
233 # should load_connection_file only be used for existing?
234 # as it is now, this allows reusing ports if an existing
234 # as it is now, this allows reusing ports if an existing
235 # file is requested
235 # file is requested
236 try:
236 try:
237 self.load_connection_file()
237 self.load_connection_file()
238 except Exception:
238 except Exception:
239 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
239 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
240 self.exit(1)
240 self.exit(1)
241
241
242 def load_connection_file(self):
242 def load_connection_file(self):
243 """load ip/port/hmac config from JSON connection file"""
243 """load ip/port/hmac config from JSON connection file"""
244 # this is identical to IPKernelApp.load_connection_file
244 # this is identical to IPKernelApp.load_connection_file
245 # perhaps it can be centralized somewhere?
245 # perhaps it can be centralized somewhere?
246 try:
246 try:
247 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
247 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
248 except IOError:
248 except IOError:
249 self.log.debug("Connection File not found: %s", self.connection_file)
249 self.log.debug("Connection File not found: %s", self.connection_file)
250 return
250 return
251 self.log.debug(u"Loading connection file %s", fname)
251 self.log.debug(u"Loading connection file %s", fname)
252 with open(fname) as f:
252 with open(fname) as f:
253 cfg = json.load(f)
253 cfg = json.load(f)
254 self.transport = cfg.get('transport', 'tcp')
254 self.transport = cfg.get('transport', 'tcp')
255 self.ip = cfg.get('ip', localhost())
255 self.ip = cfg.get('ip', localhost())
256
256
257 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
257 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
258 name = channel + '_port'
258 name = channel + '_port'
259 if getattr(self, name) == 0 and name in cfg:
259 if getattr(self, name) == 0 and name in cfg:
260 # not overridden by config or cl_args
260 # not overridden by config or cl_args
261 setattr(self, name, cfg[name])
261 setattr(self, name, cfg[name])
262 if 'key' in cfg:
262 if 'key' in cfg:
263 self.config.Session.key = str_to_bytes(cfg['key'])
263 self.config.Session.key = str_to_bytes(cfg['key'])
264 if 'signature_scheme' in cfg:
264 if 'signature_scheme' in cfg:
265 self.config.Session.signature_scheme = cfg['signature_scheme']
265 self.config.Session.signature_scheme = cfg['signature_scheme']
266
266
267 def init_ssh(self):
267 def init_ssh(self):
268 """set up ssh tunnels, if needed."""
268 """set up ssh tunnels, if needed."""
269 if not self.existing or (not self.sshserver and not self.sshkey):
269 if not self.existing or (not self.sshserver and not self.sshkey):
270 return
270 return
271 self.load_connection_file()
271 self.load_connection_file()
272
272
273 transport = self.transport
273 transport = self.transport
274 ip = self.ip
274 ip = self.ip
275
275
276 if transport != 'tcp':
276 if transport != 'tcp':
277 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
277 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
278 sys.exit(-1)
278 sys.exit(-1)
279
279
280 if self.sshkey and not self.sshserver:
280 if self.sshkey and not self.sshserver:
281 # specifying just the key implies that we are connecting directly
281 # specifying just the key implies that we are connecting directly
282 self.sshserver = ip
282 self.sshserver = ip
283 ip = localhost()
283 ip = localhost()
284
284
285 # build connection dict for tunnels:
285 # build connection dict for tunnels:
286 info = dict(ip=ip,
286 info = dict(ip=ip,
287 shell_port=self.shell_port,
287 shell_port=self.shell_port,
288 iopub_port=self.iopub_port,
288 iopub_port=self.iopub_port,
289 stdin_port=self.stdin_port,
289 stdin_port=self.stdin_port,
290 hb_port=self.hb_port
290 hb_port=self.hb_port
291 )
291 )
292
292
293 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
293 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
294
294
295 # tunnels return a new set of ports, which will be on localhost:
295 # tunnels return a new set of ports, which will be on localhost:
296 self.ip = localhost()
296 self.ip = localhost()
297 try:
297 try:
298 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
298 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
299 except:
299 except:
300 # even catch KeyboardInterrupt
300 # even catch KeyboardInterrupt
301 self.log.error("Could not setup tunnels", exc_info=True)
301 self.log.error("Could not setup tunnels", exc_info=True)
302 self.exit(1)
302 self.exit(1)
303
303
304 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
304 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
305
305
306 cf = self.connection_file
306 cf = self.connection_file
307 base,ext = os.path.splitext(cf)
307 base,ext = os.path.splitext(cf)
308 base = os.path.basename(base)
308 base = os.path.basename(base)
309 self.connection_file = os.path.basename(base)+'-ssh'+ext
309 self.connection_file = os.path.basename(base)+'-ssh'+ext
310 self.log.info("To connect another client via this tunnel, use:")
310 self.log.info("To connect another client via this tunnel, use:")
311 self.log.info("--existing %s" % self.connection_file)
311 self.log.info("--existing %s" % self.connection_file)
312
312
313 def _new_connection_file(self):
313 def _new_connection_file(self):
314 cf = ''
314 cf = ''
315 while not cf:
315 while not cf:
316 # we don't need a 128b id to distinguish kernels, use more readable
316 # we don't need a 128b id to distinguish kernels, use more readable
317 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
317 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
318 # kernels can subclass.
318 # kernels can subclass.
319 ident = str(uuid.uuid4()).split('-')[-1]
319 ident = str(uuid.uuid4()).split('-')[-1]
320 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
320 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
321 # only keep if it's actually new. Protect against unlikely collision
321 # only keep if it's actually new. Protect against unlikely collision
322 # in 48b random search space
322 # in 48b random search space
323 cf = cf if not os.path.exists(cf) else ''
323 cf = cf if not os.path.exists(cf) else ''
324 return cf
324 return cf
325
325
326 def init_kernel_manager(self):
326 def init_kernel_manager(self):
327 # Don't let Qt or ZMQ swallow KeyboardInterupts.
327 # Don't let Qt or ZMQ swallow KeyboardInterupts.
328 if self.existing:
328 if self.existing:
329 self.kernel_manager = None
329 self.kernel_manager = None
330 return
330 return
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
332
332
333 # Create a KernelManager and start a kernel.
333 # Create a KernelManager and start a kernel.
334 self.kernel_manager = self.kernel_manager_class(
334 self.kernel_manager = self.kernel_manager_class(
335 ip=self.ip,
335 ip=self.ip,
336 transport=self.transport,
336 transport=self.transport,
337 shell_port=self.shell_port,
337 shell_port=self.shell_port,
338 iopub_port=self.iopub_port,
338 iopub_port=self.iopub_port,
339 stdin_port=self.stdin_port,
339 stdin_port=self.stdin_port,
340 hb_port=self.hb_port,
340 hb_port=self.hb_port,
341 connection_file=self.connection_file,
341 connection_file=self.connection_file,
342 kernel_name=self.kernel_name,
342 kernel_name=self.kernel_name,
343 parent=self,
343 parent=self,
344 ipython_dir=self.ipython_dir,
344 )
345 )
345 self.kernel_manager.client_factory = self.kernel_client_class
346 self.kernel_manager.client_factory = self.kernel_client_class
346 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
347 atexit.register(self.kernel_manager.cleanup_ipc_files)
348 atexit.register(self.kernel_manager.cleanup_ipc_files)
348
349
349 if self.sshserver:
350 if self.sshserver:
350 # ssh, write new connection file
351 # ssh, write new connection file
351 self.kernel_manager.write_connection_file()
352 self.kernel_manager.write_connection_file()
352
353
353 # in case KM defaults / ssh writing changes things:
354 # in case KM defaults / ssh writing changes things:
354 km = self.kernel_manager
355 km = self.kernel_manager
355 self.shell_port=km.shell_port
356 self.shell_port=km.shell_port
356 self.iopub_port=km.iopub_port
357 self.iopub_port=km.iopub_port
357 self.stdin_port=km.stdin_port
358 self.stdin_port=km.stdin_port
358 self.hb_port=km.hb_port
359 self.hb_port=km.hb_port
359 self.connection_file = km.connection_file
360 self.connection_file = km.connection_file
360
361
361 atexit.register(self.kernel_manager.cleanup_connection_file)
362 atexit.register(self.kernel_manager.cleanup_connection_file)
362
363
363 def init_kernel_client(self):
364 def init_kernel_client(self):
364 if self.kernel_manager is not None:
365 if self.kernel_manager is not None:
365 self.kernel_client = self.kernel_manager.client()
366 self.kernel_client = self.kernel_manager.client()
366 else:
367 else:
367 self.kernel_client = self.kernel_client_class(
368 self.kernel_client = self.kernel_client_class(
368 ip=self.ip,
369 ip=self.ip,
369 transport=self.transport,
370 transport=self.transport,
370 shell_port=self.shell_port,
371 shell_port=self.shell_port,
371 iopub_port=self.iopub_port,
372 iopub_port=self.iopub_port,
372 stdin_port=self.stdin_port,
373 stdin_port=self.stdin_port,
373 hb_port=self.hb_port,
374 hb_port=self.hb_port,
374 connection_file=self.connection_file,
375 connection_file=self.connection_file,
375 parent=self,
376 parent=self,
376 )
377 )
377
378
378 self.kernel_client.start_channels()
379 self.kernel_client.start_channels()
379
380
380
381
381
382
382 def initialize(self, argv=None):
383 def initialize(self, argv=None):
383 """
384 """
384 Classes which mix this class in should call:
385 Classes which mix this class in should call:
385 IPythonConsoleApp.initialize(self,argv)
386 IPythonConsoleApp.initialize(self,argv)
386 """
387 """
387 self.init_connection_file()
388 self.init_connection_file()
388 default_secure(self.config)
389 default_secure(self.config)
389 self.init_ssh()
390 self.init_ssh()
390 self.init_kernel_manager()
391 self.init_kernel_manager()
391 self.init_kernel_client()
392 self.init_kernel_client()
392
393
@@ -1,113 +1,135 b''
1 import io
1 import io
2 import json
2 import json
3 import os
3 import os
4 import sys
4 import sys
5
5
6 pjoin = os.path.join
6 pjoin = os.path.join
7
7
8 from IPython.utils.path import get_ipython_dir
8 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.py3compat import PY3
9 from IPython.utils.py3compat import PY3
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
11
11
12 USER_KERNEL_DIR = pjoin(get_ipython_dir(), 'kernels')
13
14 if os.name == 'nt':
12 if os.name == 'nt':
15 programdata = os.environ.get('PROGRAMDATA', None)
13 programdata = os.environ.get('PROGRAMDATA', None)
16 if programdata:
14 if programdata:
17 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
15 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
18 else: # PROGRAMDATA is not defined by default on XP.
16 else: # PROGRAMDATA is not defined by default on XP.
19 SYSTEM_KERNEL_DIR = None
17 SYSTEM_KERNEL_DIR = None
20 else:
18 else:
21 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
19 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
22
23 # List of kernel directories to search. Later ones take priority over earlier.
24 kernel_dirs = [
25 SYSTEM_KERNEL_DIR,
26 USER_KERNEL_DIR,
27 ]
28
20
29 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
21 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
30
22
31 class KernelSpec(HasTraits):
23 class KernelSpec(HasTraits):
32 argv = List()
24 argv = List()
33 display_name = Unicode()
25 display_name = Unicode()
34 language = Unicode()
26 language = Unicode()
35 codemirror_mode = None
27 codemirror_mode = None
36 env = Dict()
28 env = Dict()
37
29
38 resource_dir = Unicode()
30 resource_dir = Unicode()
39
31
40 def __init__(self, resource_dir, argv, display_name, language,
32 def __init__(self, resource_dir, argv, display_name, language,
41 codemirror_mode=None):
33 codemirror_mode=None):
42 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
34 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
43 display_name=display_name, language=language,
35 display_name=display_name, language=language,
44 codemirror_mode=codemirror_mode)
36 codemirror_mode=codemirror_mode)
45 if not self.codemirror_mode:
37 if not self.codemirror_mode:
46 self.codemirror_mode = self.language
38 self.codemirror_mode = self.language
47
39
48 @classmethod
40 @classmethod
49 def from_resource_dir(cls, resource_dir):
41 def from_resource_dir(cls, resource_dir):
50 """Create a KernelSpec object by reading kernel.json
42 """Create a KernelSpec object by reading kernel.json
51
43
52 Pass the path to the *directory* containing kernel.json.
44 Pass the path to the *directory* containing kernel.json.
53 """
45 """
54 kernel_file = pjoin(resource_dir, 'kernel.json')
46 kernel_file = pjoin(resource_dir, 'kernel.json')
55 with io.open(kernel_file, 'r', encoding='utf-8') as f:
47 with io.open(kernel_file, 'r', encoding='utf-8') as f:
56 kernel_dict = json.load(f)
48 kernel_dict = json.load(f)
57 return cls(resource_dir=resource_dir, **kernel_dict)
49 return cls(resource_dir=resource_dir, **kernel_dict)
58
50
59 def _is_kernel_dir(path):
51 def _is_kernel_dir(path):
60 """Is ``path`` a kernel directory?"""
52 """Is ``path`` a kernel directory?"""
61 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
53 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
62
54
63 def _list_kernels_in(dir):
55 def _list_kernels_in(dir):
64 """Ensure dir exists, and return a mapping of kernel names to resource
56 """Return a mapping of kernel names to resource directories from dir.
65 directories from it.
57
58 If dir is None or does not exist, returns an empty dict.
66 """
59 """
67 if dir is None or not os.path.isdir(dir):
60 if dir is None or not os.path.isdir(dir):
68 return {}
61 return {}
69 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
62 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
70 if _is_kernel_dir(pjoin(dir, f))}
63 if _is_kernel_dir(pjoin(dir, f))}
71
64
72 def _make_native_kernel_dir():
65 class KernelSpecManager(HasTraits):
73 """Makes a kernel directory for the native kernel.
66 ipython_dir = Unicode()
67 def _ipython_dir_default(self):
68 return get_ipython_dir()
69
70 user_kernel_dir = Unicode()
71 def _user_kernel_dir_default(self):
72 return pjoin(self.ipython_dir, 'kernels')
74
73
75 The native kernel is the kernel using the same Python runtime as this
74 kernel_dirs = List(
76 process. This will put its informatino in the user kernels directory.
75 help="List of kernel directories to search. Later ones take priority over earlier."
77 """
76 )
78 path = pjoin(USER_KERNEL_DIR, NATIVE_KERNEL_NAME)
77 def _kernel_dirs_default(self):
79 os.makedirs(path, mode=0o755)
78 return [
80 with open(pjoin(path, 'kernel.json'), 'w') as f:
79 SYSTEM_KERNEL_DIR,
81 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
80 self.user_kernel_dir,
82 'from IPython.kernel.zmq.kernelapp import main; main()',
81 ]
83 '-f', '{connection_file}'],
82
84 'display_name': 'Python 3' if PY3 else 'Python 2',
83 def _make_native_kernel_dir(self):
85 'language': 'python',
84 """Makes a kernel directory for the native kernel.
86 'codemirror_mode': {'name': 'python',
85
87 'version': sys.version_info[0]},
86 The native kernel is the kernel using the same Python runtime as this
88 },
87 process. This will put its informatino in the user kernels directory.
89 f, indent=1)
88 """
90 # TODO: Copy icons into directory
89 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
91 return path
90 os.makedirs(path, mode=0o755)
91 with open(pjoin(path, 'kernel.json'), 'w') as f:
92 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
93 'from IPython.kernel.zmq.kernelapp import main; main()',
94 '-f', '{connection_file}'],
95 'display_name': 'Python 3' if PY3 else 'Python 2',
96 'language': 'python',
97 'codemirror_mode': {'name': 'python',
98 'version': sys.version_info[0]},
99 },
100 f, indent=1)
101 # TODO: Copy icons into directory
102 return path
103
104 def find_kernel_specs(self):
105 """Returns a dict mapping kernel names to resource directories."""
106 d = {}
107 for kernel_dir in self.kernel_dirs:
108 d.update(_list_kernels_in(kernel_dir))
109
110 if NATIVE_KERNEL_NAME not in d:
111 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
112 return d
113 # TODO: Caching?
114
115 def get_kernel_spec(self, kernel_name):
116 """Returns a :class:`KernelSpec` instance for the given kernel_name.
117
118 Raises KeyError if the given kernel name is not found.
119 """
120 if kernel_name == 'python':
121 kernel_name = NATIVE_KERNEL_NAME
122 d = self.find_kernel_specs()
123 resource_dir = d[kernel_name.lower()]
124 return KernelSpec.from_resource_dir(resource_dir)
92
125
93 def find_kernel_specs():
126 def find_kernel_specs():
94 """Returns a dict mapping kernel names to resource directories."""
127 """Returns a dict mapping kernel names to resource directories."""
95 d = {}
128 return KernelSpecManager().find_kernel_specs()
96 for kernel_dir in kernel_dirs:
97 d.update(_list_kernels_in(kernel_dir))
98
99 if NATIVE_KERNEL_NAME not in d:
100 d[NATIVE_KERNEL_NAME] = _make_native_kernel_dir()
101 return d
102 # TODO: Caching?
103
129
104 def get_kernel_spec(kernel_name):
130 def get_kernel_spec(kernel_name):
105 """Returns a :class:`KernelSpec` instance for the given kernel_name.
131 """Returns a :class:`KernelSpec` instance for the given kernel_name.
106
132
107 Raises KeyError if the given kernel name is not found.
133 Raises KeyError if the given kernel name is not found.
108 """
134 """
109 if kernel_name == 'python':
135 return KernelSpecManager().get_kernel_spec(kernel_name) No newline at end of file
110 kernel_name = NATIVE_KERNEL_NAME
111 d = find_kernel_specs()
112 resource_dir = d[kernel_name.lower()]
113 return KernelSpec.from_resource_dir(resource_dir) No newline at end of file
@@ -1,403 +1,413 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 # Standard library imports
8 # Standard library imports
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15
15
16 import zmq
16 import zmq
17
17
18 # Local imports
18 # Local imports
19 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
20 from IPython.utils.importstring import import_item
20 from IPython.utils.importstring import import_item
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
22 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
23 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
24 )
25 )
25 from IPython.kernel import (
26 from IPython.kernel import (
26 make_ipkernel_cmd,
27 make_ipkernel_cmd,
27 launch_kernel,
28 launch_kernel,
28 kernelspec,
29 kernelspec,
29 )
30 )
30 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
31 from .zmq.session import Session
32 from .zmq.session import Session
32 from .managerabc import (
33 from .managerabc import (
33 KernelManagerABC
34 KernelManagerABC
34 )
35 )
35
36
36
37
37 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
38 """Manages a single kernel in a subprocess on this host.
39 """Manages a single kernel in a subprocess on this host.
39
40
40 This version starts kernels with Popen.
41 This version starts kernels with Popen.
41 """
42 """
42
43
43 # The PyZMQ Context to use for communication with the kernel.
44 # The PyZMQ Context to use for communication with the kernel.
44 context = Instance(zmq.Context)
45 context = Instance(zmq.Context)
45 def _context_default(self):
46 def _context_default(self):
46 return zmq.Context.instance()
47 return zmq.Context.instance()
47
48
48 # The Session to use for communication with the kernel.
49 # The Session to use for communication with the kernel.
49 session = Instance(Session)
50 session = Instance(Session)
50 def _session_default(self):
51 def _session_default(self):
51 return Session(parent=self)
52 return Session(parent=self)
52
53
53 # the class to create with our `client` method
54 # the class to create with our `client` method
54 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
55 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
55 client_factory = Type()
56 client_factory = Type()
56 def _client_class_changed(self, name, old, new):
57 def _client_class_changed(self, name, old, new):
57 self.client_factory = import_item(str(new))
58 self.client_factory = import_item(str(new))
58
59
59 # The kernel process with which the KernelManager is communicating.
60 # The kernel process with which the KernelManager is communicating.
60 # generally a Popen instance
61 # generally a Popen instance
61 kernel = Any()
62 kernel = Any()
62
63
64 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
65
66 def _kernel_spec_manager_default(self):
67 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
68
63 kernel_name = Unicode('python')
69 kernel_name = Unicode('python')
64
70
65 kernel_spec = Instance(kernelspec.KernelSpec)
71 kernel_spec = Instance(kernelspec.KernelSpec)
66
72
67 def _kernel_spec_default(self):
73 def _kernel_spec_default(self):
68 return kernelspec.get_kernel_spec(self.kernel_name)
74 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
69
75
70 def _kernel_name_changed(self, name, old, new):
76 def _kernel_name_changed(self, name, old, new):
71 self.kernel_spec = kernelspec.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
72 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
73
79
74 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
75 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
76
82
77 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
78 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
79 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
80 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
81 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
82 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
83 this means that the kernel does not receive the
89 this means that the kernel does not receive the
84 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
85 """
91 """
86 )
92 )
87
93
88 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
89 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
90 "start different kernels.")
96 "start different kernels.")
91 self.ipython_kernel = False
97 self.ipython_kernel = False
92
98
93 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
94
104
95 # Protected traits
105 # Protected traits
96 _launch_args = Any()
106 _launch_args = Any()
97 _control_socket = Any()
107 _control_socket = Any()
98
108
99 _restarter = Any()
109 _restarter = Any()
100
110
101 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
102 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
103 )
113 )
104
114
105 def __del__(self):
115 def __del__(self):
106 self._close_control_socket()
116 self._close_control_socket()
107 self.cleanup_connection_file()
117 self.cleanup_connection_file()
108
118
109 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
110 # Kernel restarter
120 # Kernel restarter
111 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
112
122
113 def start_restarter(self):
123 def start_restarter(self):
114 pass
124 pass
115
125
116 def stop_restarter(self):
126 def stop_restarter(self):
117 pass
127 pass
118
128
119 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
120 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
121 if self._restarter is None:
131 if self._restarter is None:
122 return
132 return
123 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
124
134
125 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
126 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
127 if self._restarter is None:
137 if self._restarter is None:
128 return
138 return
129 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
130
140
131 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
132 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
133 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
134
144
135 def client(self, **kwargs):
145 def client(self, **kwargs):
136 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
137 if self.client_factory is None:
147 if self.client_factory is None:
138 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
139
149
140 kw = {}
150 kw = {}
141 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
142 kw.update(dict(
152 kw.update(dict(
143 connection_file=self.connection_file,
153 connection_file=self.connection_file,
144 session=self.session,
154 session=self.session,
145 parent=self,
155 parent=self,
146 ))
156 ))
147
157
148 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
149 kw.update(kwargs)
159 kw.update(kwargs)
150 return self.client_factory(**kw)
160 return self.client_factory(**kw)
151
161
152 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
153 # Kernel management
163 # Kernel management
154 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
155
165
156 def format_kernel_cmd(self, **kw):
166 def format_kernel_cmd(self, **kw):
157 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
158 if self.kernel_cmd:
168 if self.kernel_cmd:
159 cmd = self.kernel_cmd
169 cmd = self.kernel_cmd
160 elif self.kernel_name == 'python':
170 elif self.kernel_name == 'python':
161 # The native kernel gets special handling
171 # The native kernel gets special handling
162 cmd = make_ipkernel_cmd(
172 cmd = make_ipkernel_cmd(
163 'from IPython.kernel.zmq.kernelapp import main; main()',
173 'from IPython.kernel.zmq.kernelapp import main; main()',
164 **kw
174 **kw
165 )
175 )
166 else:
176 else:
167 cmd = self.kernel_spec.argv
177 cmd = self.kernel_spec.argv
168
178
169 ns = dict(connection_file=self.connection_file)
179 ns = dict(connection_file=self.connection_file)
170 ns.update(self._launch_args)
180 ns.update(self._launch_args)
171
181
172 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
182 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
173 def from_ns(match):
183 def from_ns(match):
174 """Get the key out of ns if it's there, otherwise no change."""
184 """Get the key out of ns if it's there, otherwise no change."""
175 return ns.get(match.group(1), match.group())
185 return ns.get(match.group(1), match.group())
176
186
177 return [ pat.sub(from_ns, arg) for arg in cmd ]
187 return [ pat.sub(from_ns, arg) for arg in cmd ]
178
188
179 def _launch_kernel(self, kernel_cmd, **kw):
189 def _launch_kernel(self, kernel_cmd, **kw):
180 """actually launch the kernel
190 """actually launch the kernel
181
191
182 override in a subclass to launch kernel subprocesses differently
192 override in a subclass to launch kernel subprocesses differently
183 """
193 """
184 return launch_kernel(kernel_cmd, **kw)
194 return launch_kernel(kernel_cmd, **kw)
185
195
186 # Control socket used for polite kernel shutdown
196 # Control socket used for polite kernel shutdown
187
197
188 def _connect_control_socket(self):
198 def _connect_control_socket(self):
189 if self._control_socket is None:
199 if self._control_socket is None:
190 self._control_socket = self.connect_control()
200 self._control_socket = self.connect_control()
191 self._control_socket.linger = 100
201 self._control_socket.linger = 100
192
202
193 def _close_control_socket(self):
203 def _close_control_socket(self):
194 if self._control_socket is None:
204 if self._control_socket is None:
195 return
205 return
196 self._control_socket.close()
206 self._control_socket.close()
197 self._control_socket = None
207 self._control_socket = None
198
208
199 def start_kernel(self, **kw):
209 def start_kernel(self, **kw):
200 """Starts a kernel on this host in a separate process.
210 """Starts a kernel on this host in a separate process.
201
211
202 If random ports (port=0) are being used, this method must be called
212 If random ports (port=0) are being used, this method must be called
203 before the channels are created.
213 before the channels are created.
204
214
205 Parameters
215 Parameters
206 ----------
216 ----------
207 **kw : optional
217 **kw : optional
208 keyword arguments that are passed down to build the kernel_cmd
218 keyword arguments that are passed down to build the kernel_cmd
209 and launching the kernel (e.g. Popen kwargs).
219 and launching the kernel (e.g. Popen kwargs).
210 """
220 """
211 if self.transport == 'tcp' and not is_local_ip(self.ip):
221 if self.transport == 'tcp' and not is_local_ip(self.ip):
212 raise RuntimeError("Can only launch a kernel on a local interface. "
222 raise RuntimeError("Can only launch a kernel on a local interface. "
213 "Make sure that the '*_address' attributes are "
223 "Make sure that the '*_address' attributes are "
214 "configured properly. "
224 "configured properly. "
215 "Currently valid addresses are: %s" % local_ips()
225 "Currently valid addresses are: %s" % local_ips()
216 )
226 )
217
227
218 # write connection file / get default ports
228 # write connection file / get default ports
219 self.write_connection_file()
229 self.write_connection_file()
220
230
221 # save kwargs for use in restart
231 # save kwargs for use in restart
222 self._launch_args = kw.copy()
232 self._launch_args = kw.copy()
223 # build the Popen cmd
233 # build the Popen cmd
224 kernel_cmd = self.format_kernel_cmd(**kw)
234 kernel_cmd = self.format_kernel_cmd(**kw)
225 if self.kernel_cmd:
235 if self.kernel_cmd:
226 # If kernel_cmd has been set manually, don't refer to a kernel spec
236 # If kernel_cmd has been set manually, don't refer to a kernel spec
227 env = os.environ
237 env = os.environ
228 else:
238 else:
229 # Environment variables from kernel spec are added to os.environ
239 # Environment variables from kernel spec are added to os.environ
230 env = os.environ.copy()
240 env = os.environ.copy()
231 env.update(self.kernel_spec.env or {})
241 env.update(self.kernel_spec.env or {})
232 # launch the kernel subprocess
242 # launch the kernel subprocess
233 self.kernel = self._launch_kernel(kernel_cmd, env=env,
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
234 ipython_kernel=self.ipython_kernel,
244 ipython_kernel=self.ipython_kernel,
235 **kw)
245 **kw)
236 self.start_restarter()
246 self.start_restarter()
237 self._connect_control_socket()
247 self._connect_control_socket()
238
248
239 def _send_shutdown_request(self, restart=False):
249 def _send_shutdown_request(self, restart=False):
240 """TODO: send a shutdown request via control channel"""
250 """TODO: send a shutdown request via control channel"""
241 content = dict(restart=restart)
251 content = dict(restart=restart)
242 msg = self.session.msg("shutdown_request", content=content)
252 msg = self.session.msg("shutdown_request", content=content)
243 self.session.send(self._control_socket, msg)
253 self.session.send(self._control_socket, msg)
244
254
245 def shutdown_kernel(self, now=False, restart=False):
255 def shutdown_kernel(self, now=False, restart=False):
246 """Attempts to the stop the kernel process cleanly.
256 """Attempts to the stop the kernel process cleanly.
247
257
248 This attempts to shutdown the kernels cleanly by:
258 This attempts to shutdown the kernels cleanly by:
249
259
250 1. Sending it a shutdown message over the shell channel.
260 1. Sending it a shutdown message over the shell channel.
251 2. If that fails, the kernel is shutdown forcibly by sending it
261 2. If that fails, the kernel is shutdown forcibly by sending it
252 a signal.
262 a signal.
253
263
254 Parameters
264 Parameters
255 ----------
265 ----------
256 now : bool
266 now : bool
257 Should the kernel be forcible killed *now*. This skips the
267 Should the kernel be forcible killed *now*. This skips the
258 first, nice shutdown attempt.
268 first, nice shutdown attempt.
259 restart: bool
269 restart: bool
260 Will this kernel be restarted after it is shutdown. When this
270 Will this kernel be restarted after it is shutdown. When this
261 is True, connection files will not be cleaned up.
271 is True, connection files will not be cleaned up.
262 """
272 """
263 # Stop monitoring for restarting while we shutdown.
273 # Stop monitoring for restarting while we shutdown.
264 self.stop_restarter()
274 self.stop_restarter()
265
275
266 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
276 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
267 if now or sys.platform == 'win32':
277 if now or sys.platform == 'win32':
268 if self.has_kernel:
278 if self.has_kernel:
269 self._kill_kernel()
279 self._kill_kernel()
270 else:
280 else:
271 # Don't send any additional kernel kill messages immediately, to give
281 # Don't send any additional kernel kill messages immediately, to give
272 # the kernel a chance to properly execute shutdown actions. Wait for at
282 # the kernel a chance to properly execute shutdown actions. Wait for at
273 # most 1s, checking every 0.1s.
283 # most 1s, checking every 0.1s.
274 self._send_shutdown_request(restart=restart)
284 self._send_shutdown_request(restart=restart)
275 for i in range(10):
285 for i in range(10):
276 if self.is_alive():
286 if self.is_alive():
277 time.sleep(0.1)
287 time.sleep(0.1)
278 else:
288 else:
279 break
289 break
280 else:
290 else:
281 # OK, we've waited long enough.
291 # OK, we've waited long enough.
282 if self.has_kernel:
292 if self.has_kernel:
283 self._kill_kernel()
293 self._kill_kernel()
284
294
285 if not restart:
295 if not restart:
286 self.cleanup_connection_file()
296 self.cleanup_connection_file()
287 self.cleanup_ipc_files()
297 self.cleanup_ipc_files()
288 else:
298 else:
289 self.cleanup_ipc_files()
299 self.cleanup_ipc_files()
290
300
291 self._close_control_socket()
301 self._close_control_socket()
292
302
293 def restart_kernel(self, now=False, **kw):
303 def restart_kernel(self, now=False, **kw):
294 """Restarts a kernel with the arguments that were used to launch it.
304 """Restarts a kernel with the arguments that were used to launch it.
295
305
296 If the old kernel was launched with random ports, the same ports will be
306 If the old kernel was launched with random ports, the same ports will be
297 used for the new kernel. The same connection file is used again.
307 used for the new kernel. The same connection file is used again.
298
308
299 Parameters
309 Parameters
300 ----------
310 ----------
301 now : bool, optional
311 now : bool, optional
302 If True, the kernel is forcefully restarted *immediately*, without
312 If True, the kernel is forcefully restarted *immediately*, without
303 having a chance to do any cleanup action. Otherwise the kernel is
313 having a chance to do any cleanup action. Otherwise the kernel is
304 given 1s to clean up before a forceful restart is issued.
314 given 1s to clean up before a forceful restart is issued.
305
315
306 In all cases the kernel is restarted, the only difference is whether
316 In all cases the kernel is restarted, the only difference is whether
307 it is given a chance to perform a clean shutdown or not.
317 it is given a chance to perform a clean shutdown or not.
308
318
309 **kw : optional
319 **kw : optional
310 Any options specified here will overwrite those used to launch the
320 Any options specified here will overwrite those used to launch the
311 kernel.
321 kernel.
312 """
322 """
313 if self._launch_args is None:
323 if self._launch_args is None:
314 raise RuntimeError("Cannot restart the kernel. "
324 raise RuntimeError("Cannot restart the kernel. "
315 "No previous call to 'start_kernel'.")
325 "No previous call to 'start_kernel'.")
316 else:
326 else:
317 # Stop currently running kernel.
327 # Stop currently running kernel.
318 self.shutdown_kernel(now=now, restart=True)
328 self.shutdown_kernel(now=now, restart=True)
319
329
320 # Start new kernel.
330 # Start new kernel.
321 self._launch_args.update(kw)
331 self._launch_args.update(kw)
322 self.start_kernel(**self._launch_args)
332 self.start_kernel(**self._launch_args)
323
333
324 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
334 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
325 # unless there is some delay here.
335 # unless there is some delay here.
326 if sys.platform == 'win32':
336 if sys.platform == 'win32':
327 time.sleep(0.2)
337 time.sleep(0.2)
328
338
329 @property
339 @property
330 def has_kernel(self):
340 def has_kernel(self):
331 """Has a kernel been started that we are managing."""
341 """Has a kernel been started that we are managing."""
332 return self.kernel is not None
342 return self.kernel is not None
333
343
334 def _kill_kernel(self):
344 def _kill_kernel(self):
335 """Kill the running kernel.
345 """Kill the running kernel.
336
346
337 This is a private method, callers should use shutdown_kernel(now=True).
347 This is a private method, callers should use shutdown_kernel(now=True).
338 """
348 """
339 if self.has_kernel:
349 if self.has_kernel:
340
350
341 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
351 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
342 # TerminateProcess() on Win32).
352 # TerminateProcess() on Win32).
343 try:
353 try:
344 self.kernel.kill()
354 self.kernel.kill()
345 except OSError as e:
355 except OSError as e:
346 # In Windows, we will get an Access Denied error if the process
356 # In Windows, we will get an Access Denied error if the process
347 # has already terminated. Ignore it.
357 # has already terminated. Ignore it.
348 if sys.platform == 'win32':
358 if sys.platform == 'win32':
349 if e.winerror != 5:
359 if e.winerror != 5:
350 raise
360 raise
351 # On Unix, we may get an ESRCH error if the process has already
361 # On Unix, we may get an ESRCH error if the process has already
352 # terminated. Ignore it.
362 # terminated. Ignore it.
353 else:
363 else:
354 from errno import ESRCH
364 from errno import ESRCH
355 if e.errno != ESRCH:
365 if e.errno != ESRCH:
356 raise
366 raise
357
367
358 # Block until the kernel terminates.
368 # Block until the kernel terminates.
359 self.kernel.wait()
369 self.kernel.wait()
360 self.kernel = None
370 self.kernel = None
361 else:
371 else:
362 raise RuntimeError("Cannot kill kernel. No kernel is running!")
372 raise RuntimeError("Cannot kill kernel. No kernel is running!")
363
373
364 def interrupt_kernel(self):
374 def interrupt_kernel(self):
365 """Interrupts the kernel by sending it a signal.
375 """Interrupts the kernel by sending it a signal.
366
376
367 Unlike ``signal_kernel``, this operation is well supported on all
377 Unlike ``signal_kernel``, this operation is well supported on all
368 platforms.
378 platforms.
369 """
379 """
370 if self.has_kernel:
380 if self.has_kernel:
371 if sys.platform == 'win32':
381 if sys.platform == 'win32':
372 from .zmq.parentpoller import ParentPollerWindows as Poller
382 from .zmq.parentpoller import ParentPollerWindows as Poller
373 Poller.send_interrupt(self.kernel.win32_interrupt_event)
383 Poller.send_interrupt(self.kernel.win32_interrupt_event)
374 else:
384 else:
375 self.kernel.send_signal(signal.SIGINT)
385 self.kernel.send_signal(signal.SIGINT)
376 else:
386 else:
377 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
387 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
378
388
379 def signal_kernel(self, signum):
389 def signal_kernel(self, signum):
380 """Sends a signal to the kernel.
390 """Sends a signal to the kernel.
381
391
382 Note that since only SIGTERM is supported on Windows, this function is
392 Note that since only SIGTERM is supported on Windows, this function is
383 only useful on Unix systems.
393 only useful on Unix systems.
384 """
394 """
385 if self.has_kernel:
395 if self.has_kernel:
386 self.kernel.send_signal(signum)
396 self.kernel.send_signal(signum)
387 else:
397 else:
388 raise RuntimeError("Cannot signal kernel. No kernel is running!")
398 raise RuntimeError("Cannot signal kernel. No kernel is running!")
389
399
390 def is_alive(self):
400 def is_alive(self):
391 """Is the kernel process still running?"""
401 """Is the kernel process still running?"""
392 if self.has_kernel:
402 if self.has_kernel:
393 if self.kernel.poll() is None:
403 if self.kernel.poll() is None:
394 return True
404 return True
395 else:
405 else:
396 return False
406 return False
397 else:
407 else:
398 # we don't have a kernel
408 # we don't have a kernel
399 return False
409 return False
400
410
401
411
402 KernelManagerABC.register(KernelManager)
412 KernelManagerABC.register(KernelManager)
403
413
General Comments 0
You need to be logged in to leave comments. Login now