##// END OF EJS Templates
use pyzmq tools where appropriate...
MinRK -
Show More
@@ -1,333 +1,332 b''
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import signal
23 import signal
24 import socket
24 import socket
25 import sys
25 import sys
26 import threading
26 import threading
27 import webbrowser
27 import webbrowser
28
28
29 # Third party
29 # Third party
30 import zmq
30 import zmq
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 import tornado.ioloop
35 ioloop.install()
36 tornado.ioloop.IOLoop = ioloop.IOLoop
37
36
38 from tornado import httpserver
37 from tornado import httpserver
39 from tornado import web
38 from tornado import web
40
39
41 # Our own libraries
40 # Our own libraries
42 from .kernelmanager import MappingKernelManager
41 from .kernelmanager import MappingKernelManager
43 from .handlers import (LoginHandler, LogoutHandler,
42 from .handlers import (LoginHandler, LogoutHandler,
44 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
43 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
45 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
44 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
46 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
45 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
47 )
46 )
48 from .notebookmanager import NotebookManager
47 from .notebookmanager import NotebookManager
49
48
50 from IPython.config.application import catch_config_error
49 from IPython.config.application import catch_config_error
51 from IPython.core.application import BaseIPythonApplication
50 from IPython.core.application import BaseIPythonApplication
52 from IPython.core.profiledir import ProfileDir
51 from IPython.core.profiledir import ProfileDir
53 from IPython.zmq.session import Session, default_secure
52 from IPython.zmq.session import Session, default_secure
54 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
55 from IPython.zmq.ipkernel import (
54 from IPython.zmq.ipkernel import (
56 flags as ipkernel_flags,
55 flags as ipkernel_flags,
57 aliases as ipkernel_aliases,
56 aliases as ipkernel_aliases,
58 IPKernelApp
57 IPKernelApp
59 )
58 )
60 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
59 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
61
60
62 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
63 # Module globals
62 # Module globals
64 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
65
64
66 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
65 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
67 _kernel_action_regex = r"(?P<action>restart|interrupt)"
66 _kernel_action_regex = r"(?P<action>restart|interrupt)"
68 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
67 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
69
68
70 LOCALHOST = '127.0.0.1'
69 LOCALHOST = '127.0.0.1'
71
70
72 _examples = """
71 _examples = """
73 ipython notebook # start the notebook
72 ipython notebook # start the notebook
74 ipython notebook --profile=sympy # use the sympy profile
73 ipython notebook --profile=sympy # use the sympy profile
75 ipython notebook --pylab=inline # pylab in inline plotting mode
74 ipython notebook --pylab=inline # pylab in inline plotting mode
76 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
75 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
77 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
76 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
78 """
77 """
79
78
80 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
81 # The Tornado web application
80 # The Tornado web application
82 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
83
82
84 class NotebookWebApplication(web.Application):
83 class NotebookWebApplication(web.Application):
85
84
86 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
85 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
87 handlers = [
86 handlers = [
88 (r"/", ProjectDashboardHandler),
87 (r"/", ProjectDashboardHandler),
89 (r"/login", LoginHandler),
88 (r"/login", LoginHandler),
90 (r"/logout", LogoutHandler),
89 (r"/logout", LogoutHandler),
91 (r"/new", NewHandler),
90 (r"/new", NewHandler),
92 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
91 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
93 (r"/kernels", MainKernelHandler),
92 (r"/kernels", MainKernelHandler),
94 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
93 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
95 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
94 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
96 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
95 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
97 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
96 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
98 (r"/notebooks", NotebookRootHandler),
97 (r"/notebooks", NotebookRootHandler),
99 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
98 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
100 (r"/rstservice/render", RSTHandler)
99 (r"/rstservice/render", RSTHandler)
101 ]
100 ]
102 settings = dict(
101 settings = dict(
103 template_path=os.path.join(os.path.dirname(__file__), "templates"),
102 template_path=os.path.join(os.path.dirname(__file__), "templates"),
104 static_path=os.path.join(os.path.dirname(__file__), "static"),
103 static_path=os.path.join(os.path.dirname(__file__), "static"),
105 cookie_secret=os.urandom(1024),
104 cookie_secret=os.urandom(1024),
106 login_url="/login",
105 login_url="/login",
107 )
106 )
108 web.Application.__init__(self, handlers, **settings)
107 web.Application.__init__(self, handlers, **settings)
109
108
110 self.kernel_manager = kernel_manager
109 self.kernel_manager = kernel_manager
111 self.log = log
110 self.log = log
112 self.notebook_manager = notebook_manager
111 self.notebook_manager = notebook_manager
113 self.ipython_app = ipython_app
112 self.ipython_app = ipython_app
114 self.read_only = self.ipython_app.read_only
113 self.read_only = self.ipython_app.read_only
115
114
116
115
117 #-----------------------------------------------------------------------------
116 #-----------------------------------------------------------------------------
118 # Aliases and Flags
117 # Aliases and Flags
119 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
120
119
121 flags = dict(ipkernel_flags)
120 flags = dict(ipkernel_flags)
122 flags['no-browser']=(
121 flags['no-browser']=(
123 {'NotebookApp' : {'open_browser' : False}},
122 {'NotebookApp' : {'open_browser' : False}},
124 "Don't open the notebook in a browser after startup."
123 "Don't open the notebook in a browser after startup."
125 )
124 )
126 flags['read-only'] = (
125 flags['read-only'] = (
127 {'NotebookApp' : {'read_only' : True}},
126 {'NotebookApp' : {'read_only' : True}},
128 """Allow read-only access to notebooks.
127 """Allow read-only access to notebooks.
129
128
130 When using a password to protect the notebook server, this flag
129 When using a password to protect the notebook server, this flag
131 allows unauthenticated clients to view the notebook list, and
130 allows unauthenticated clients to view the notebook list, and
132 individual notebooks, but not edit them, start kernels, or run
131 individual notebooks, but not edit them, start kernels, or run
133 code.
132 code.
134
133
135 If no password is set, the server will be entirely read-only.
134 If no password is set, the server will be entirely read-only.
136 """
135 """
137 )
136 )
138
137
139 # the flags that are specific to the frontend
138 # the flags that are specific to the frontend
140 # these must be scrubbed before being passed to the kernel,
139 # these must be scrubbed before being passed to the kernel,
141 # or it will raise an error on unrecognized flags
140 # or it will raise an error on unrecognized flags
142 notebook_flags = ['no-browser', 'read-only']
141 notebook_flags = ['no-browser', 'read-only']
143
142
144 aliases = dict(ipkernel_aliases)
143 aliases = dict(ipkernel_aliases)
145
144
146 aliases.update({
145 aliases.update({
147 'ip': 'NotebookApp.ip',
146 'ip': 'NotebookApp.ip',
148 'port': 'NotebookApp.port',
147 'port': 'NotebookApp.port',
149 'keyfile': 'NotebookApp.keyfile',
148 'keyfile': 'NotebookApp.keyfile',
150 'certfile': 'NotebookApp.certfile',
149 'certfile': 'NotebookApp.certfile',
151 'notebook-dir': 'NotebookManager.notebook_dir',
150 'notebook-dir': 'NotebookManager.notebook_dir',
152 })
151 })
153
152
154 # remove ipkernel flags that are singletons, and don't make sense in
153 # remove ipkernel flags that are singletons, and don't make sense in
155 # multi-kernel evironment:
154 # multi-kernel evironment:
156 aliases.pop('f', None)
155 aliases.pop('f', None)
157
156
158 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
157 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
159 u'notebook-dir']
158 u'notebook-dir']
160
159
161 #-----------------------------------------------------------------------------
160 #-----------------------------------------------------------------------------
162 # NotebookApp
161 # NotebookApp
163 #-----------------------------------------------------------------------------
162 #-----------------------------------------------------------------------------
164
163
165 class NotebookApp(BaseIPythonApplication):
164 class NotebookApp(BaseIPythonApplication):
166
165
167 name = 'ipython-notebook'
166 name = 'ipython-notebook'
168 default_config_file_name='ipython_notebook_config.py'
167 default_config_file_name='ipython_notebook_config.py'
169
168
170 description = """
169 description = """
171 The IPython HTML Notebook.
170 The IPython HTML Notebook.
172
171
173 This launches a Tornado based HTML Notebook Server that serves up an
172 This launches a Tornado based HTML Notebook Server that serves up an
174 HTML5/Javascript Notebook client.
173 HTML5/Javascript Notebook client.
175 """
174 """
176 examples = _examples
175 examples = _examples
177
176
178 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
177 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
179 MappingKernelManager, NotebookManager]
178 MappingKernelManager, NotebookManager]
180 flags = Dict(flags)
179 flags = Dict(flags)
181 aliases = Dict(aliases)
180 aliases = Dict(aliases)
182
181
183 kernel_argv = List(Unicode)
182 kernel_argv = List(Unicode)
184
183
185 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
184 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
186 default_value=logging.INFO,
185 default_value=logging.INFO,
187 config=True,
186 config=True,
188 help="Set the log level by value or name.")
187 help="Set the log level by value or name.")
189
188
190 # Network related information.
189 # Network related information.
191
190
192 ip = Unicode(LOCALHOST, config=True,
191 ip = Unicode(LOCALHOST, config=True,
193 help="The IP address the notebook server will listen on."
192 help="The IP address the notebook server will listen on."
194 )
193 )
195
194
196 def _ip_changed(self, name, old, new):
195 def _ip_changed(self, name, old, new):
197 if new == u'*': self.ip = u''
196 if new == u'*': self.ip = u''
198
197
199 port = Integer(8888, config=True,
198 port = Integer(8888, config=True,
200 help="The port the notebook server will listen on."
199 help="The port the notebook server will listen on."
201 )
200 )
202
201
203 certfile = Unicode(u'', config=True,
202 certfile = Unicode(u'', config=True,
204 help="""The full path to an SSL/TLS certificate file."""
203 help="""The full path to an SSL/TLS certificate file."""
205 )
204 )
206
205
207 keyfile = Unicode(u'', config=True,
206 keyfile = Unicode(u'', config=True,
208 help="""The full path to a private key file for usage with SSL/TLS."""
207 help="""The full path to a private key file for usage with SSL/TLS."""
209 )
208 )
210
209
211 password = Unicode(u'', config=True,
210 password = Unicode(u'', config=True,
212 help="""Hashed password to use for web authentication.
211 help="""Hashed password to use for web authentication.
213
212
214 To generate, type in a python/IPython shell:
213 To generate, type in a python/IPython shell:
215
214
216 from IPython.lib import passwd; passwd()
215 from IPython.lib import passwd; passwd()
217
216
218 The string should be of the form type:salt:hashed-password.
217 The string should be of the form type:salt:hashed-password.
219 """
218 """
220 )
219 )
221
220
222 open_browser = Bool(True, config=True,
221 open_browser = Bool(True, config=True,
223 help="Whether to open in a browser after starting.")
222 help="Whether to open in a browser after starting.")
224
223
225 read_only = Bool(False, config=True,
224 read_only = Bool(False, config=True,
226 help="Whether to prevent editing/execution of notebooks."
225 help="Whether to prevent editing/execution of notebooks."
227 )
226 )
228
227
229 def parse_command_line(self, argv=None):
228 def parse_command_line(self, argv=None):
230 super(NotebookApp, self).parse_command_line(argv)
229 super(NotebookApp, self).parse_command_line(argv)
231 if argv is None:
230 if argv is None:
232 argv = sys.argv[1:]
231 argv = sys.argv[1:]
233
232
234 self.kernel_argv = list(argv) # copy
233 self.kernel_argv = list(argv) # copy
235 # Kernel should inherit default config file from frontend
234 # Kernel should inherit default config file from frontend
236 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
235 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
237 # Scrub frontend-specific flags
236 # Scrub frontend-specific flags
238 for a in argv:
237 for a in argv:
239 if a.startswith('-') and a.lstrip('-') in notebook_flags:
238 if a.startswith('-') and a.lstrip('-') in notebook_flags:
240 self.kernel_argv.remove(a)
239 self.kernel_argv.remove(a)
241 swallow_next = False
240 swallow_next = False
242 for a in argv:
241 for a in argv:
243 if swallow_next:
242 if swallow_next:
244 self.kernel_argv.remove(a)
243 self.kernel_argv.remove(a)
245 swallow_next = False
244 swallow_next = False
246 continue
245 continue
247 if a.startswith('-'):
246 if a.startswith('-'):
248 split = a.lstrip('-').split('=')
247 split = a.lstrip('-').split('=')
249 alias = split[0]
248 alias = split[0]
250 if alias in notebook_aliases:
249 if alias in notebook_aliases:
251 self.kernel_argv.remove(a)
250 self.kernel_argv.remove(a)
252 if len(split) == 1:
251 if len(split) == 1:
253 # alias passed with arg via space
252 # alias passed with arg via space
254 swallow_next = True
253 swallow_next = True
255
254
256 def init_configurables(self):
255 def init_configurables(self):
257 # Don't let Qt or ZMQ swallow KeyboardInterupts.
256 # Don't let Qt or ZMQ swallow KeyboardInterupts.
258 signal.signal(signal.SIGINT, signal.SIG_DFL)
257 signal.signal(signal.SIGINT, signal.SIG_DFL)
259
258
260 # force Session default to be secure
259 # force Session default to be secure
261 default_secure(self.config)
260 default_secure(self.config)
262 # Create a KernelManager and start a kernel.
261 # Create a KernelManager and start a kernel.
263 self.kernel_manager = MappingKernelManager(
262 self.kernel_manager = MappingKernelManager(
264 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
263 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
265 connection_dir = self.profile_dir.security_dir,
264 connection_dir = self.profile_dir.security_dir,
266 )
265 )
267 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
266 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
268 self.notebook_manager.list_notebooks()
267 self.notebook_manager.list_notebooks()
269
268
270 def init_logging(self):
269 def init_logging(self):
271 super(NotebookApp, self).init_logging()
270 super(NotebookApp, self).init_logging()
272 # This prevents double log messages because tornado use a root logger that
271 # This prevents double log messages because tornado use a root logger that
273 # self.log is a child of. The logging module dipatches log messages to a log
272 # self.log is a child of. The logging module dipatches log messages to a log
274 # and all of its ancenstors until propagate is set to False.
273 # and all of its ancenstors until propagate is set to False.
275 self.log.propagate = False
274 self.log.propagate = False
276
275
277 @catch_config_error
276 @catch_config_error
278 def initialize(self, argv=None):
277 def initialize(self, argv=None):
279 super(NotebookApp, self).initialize(argv)
278 super(NotebookApp, self).initialize(argv)
280 self.init_configurables()
279 self.init_configurables()
281 self.web_app = NotebookWebApplication(
280 self.web_app = NotebookWebApplication(
282 self, self.kernel_manager, self.notebook_manager, self.log
281 self, self.kernel_manager, self.notebook_manager, self.log
283 )
282 )
284 if self.certfile:
283 if self.certfile:
285 ssl_options = dict(certfile=self.certfile)
284 ssl_options = dict(certfile=self.certfile)
286 if self.keyfile:
285 if self.keyfile:
287 ssl_options['keyfile'] = self.keyfile
286 ssl_options['keyfile'] = self.keyfile
288 else:
287 else:
289 ssl_options = None
288 ssl_options = None
290 self.web_app.password = self.password
289 self.web_app.password = self.password
291 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
290 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
292 if ssl_options is None and not self.ip:
291 if ssl_options is None and not self.ip:
293 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
292 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
294 'but not using any encryption or authentication. This is highly '
293 'but not using any encryption or authentication. This is highly '
295 'insecure and not recommended.')
294 'insecure and not recommended.')
296
295
297 # Try random ports centered around the default.
296 # Try random ports centered around the default.
298 from random import randint
297 from random import randint
299 n = 50 # Max number of attempts, keep reasonably large.
298 n = 50 # Max number of attempts, keep reasonably large.
300 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
299 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
301 try:
300 try:
302 self.http_server.listen(port, self.ip)
301 self.http_server.listen(port, self.ip)
303 except socket.error, e:
302 except socket.error, e:
304 if e.errno != errno.EADDRINUSE:
303 if e.errno != errno.EADDRINUSE:
305 raise
304 raise
306 self.log.info('The port %i is already in use, trying another random port.' % port)
305 self.log.info('The port %i is already in use, trying another random port.' % port)
307 else:
306 else:
308 self.port = port
307 self.port = port
309 break
308 break
310
309
311 def start(self):
310 def start(self):
312 ip = self.ip if self.ip else '[all ip addresses on your system]'
311 ip = self.ip if self.ip else '[all ip addresses on your system]'
313 proto = 'https' if self.certfile else 'http'
312 proto = 'https' if self.certfile else 'http'
314 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
313 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
315 ip,
314 ip,
316 self.port))
315 self.port))
317 if self.open_browser:
316 if self.open_browser:
318 ip = self.ip or '127.0.0.1'
317 ip = self.ip or '127.0.0.1'
319 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
318 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
320 new=2)
319 new=2)
321 threading.Thread(target=b).start()
320 threading.Thread(target=b).start()
322
321
323 ioloop.IOLoop.instance().start()
322 ioloop.IOLoop.instance().start()
324
323
325 #-----------------------------------------------------------------------------
324 #-----------------------------------------------------------------------------
326 # Main entry point
325 # Main entry point
327 #-----------------------------------------------------------------------------
326 #-----------------------------------------------------------------------------
328
327
329 def launch_new_instance():
328 def launch_new_instance():
330 app = NotebookApp()
329 app = NotebookApp()
331 app.initialize()
330 app.initialize()
332 app.start()
331 app.start()
333
332
@@ -1,1046 +1,947 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import errno
19 import errno
20 import json
20 import json
21 from Queue import Queue, Empty
22 from subprocess import Popen
21 from subprocess import Popen
23 import os
22 import os
24 import signal
23 import signal
25 import sys
24 import sys
26 from threading import Thread
25 from threading import Thread
27 import time
26 import time
28
27
29 # System library imports.
28 # System library imports.
30 import zmq
29 import zmq
31 from zmq import POLLIN, POLLOUT, POLLERR
30 from zmq.eventloop import ioloop, zmqstream
32 from zmq.eventloop import ioloop
33
31
34 # Local imports.
32 # Local imports.
35 from IPython.config.loader import Config
33 from IPython.config.loader import Config
36 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
38 HasTraits, Any, Instance, Type, Unicode, Integer, Bool
36 HasTraits, Any, Instance, Type, Unicode, Integer, Bool
39 )
37 )
40 from IPython.utils.py3compat import str_to_bytes
38 from IPython.utils.py3compat import str_to_bytes
41 from IPython.zmq.entry_point import write_connection_file
39 from IPython.zmq.entry_point import write_connection_file
42 from session import Session
40 from session import Session
43
41
44 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
45 # Constants and exceptions
43 # Constants and exceptions
46 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
47
45
48 class InvalidPortNumber(Exception):
46 class InvalidPortNumber(Exception):
49 pass
47 pass
50
48
51 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
52 # Utility functions
50 # Utility functions
53 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
54
52
55 # some utilities to validate message structure, these might get moved elsewhere
53 # some utilities to validate message structure, these might get moved elsewhere
56 # if they prove to have more generic utility
54 # if they prove to have more generic utility
57
55
58 def validate_string_list(lst):
56 def validate_string_list(lst):
59 """Validate that the input is a list of strings.
57 """Validate that the input is a list of strings.
60
58
61 Raises ValueError if not."""
59 Raises ValueError if not."""
62 if not isinstance(lst, list):
60 if not isinstance(lst, list):
63 raise ValueError('input %r must be a list' % lst)
61 raise ValueError('input %r must be a list' % lst)
64 for x in lst:
62 for x in lst:
65 if not isinstance(x, basestring):
63 if not isinstance(x, basestring):
66 raise ValueError('element %r in list must be a string' % x)
64 raise ValueError('element %r in list must be a string' % x)
67
65
68
66
69 def validate_string_dict(dct):
67 def validate_string_dict(dct):
70 """Validate that the input is a dict with string keys and values.
68 """Validate that the input is a dict with string keys and values.
71
69
72 Raises ValueError if not."""
70 Raises ValueError if not."""
73 for k,v in dct.iteritems():
71 for k,v in dct.iteritems():
74 if not isinstance(k, basestring):
72 if not isinstance(k, basestring):
75 raise ValueError('key %r in dict must be a string' % k)
73 raise ValueError('key %r in dict must be a string' % k)
76 if not isinstance(v, basestring):
74 if not isinstance(v, basestring):
77 raise ValueError('value %r in dict must be a string' % v)
75 raise ValueError('value %r in dict must be a string' % v)
78
76
79
77
80 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
81 # ZMQ Socket Channel classes
79 # ZMQ Socket Channel classes
82 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
83
81
84 class ZMQSocketChannel(Thread):
82 class ZMQSocketChannel(Thread):
85 """The base class for the channels that use ZMQ sockets.
83 """The base class for the channels that use ZMQ sockets.
86 """
84 """
87 context = None
85 context = None
88 session = None
86 session = None
89 socket = None
87 socket = None
90 ioloop = None
88 ioloop = None
91 iostate = None
89 stream = None
92 _address = None
90 _address = None
93
91
94 def __init__(self, context, session, address):
92 def __init__(self, context, session, address):
95 """Create a channel
93 """Create a channel
96
94
97 Parameters
95 Parameters
98 ----------
96 ----------
99 context : :class:`zmq.Context`
97 context : :class:`zmq.Context`
100 The ZMQ context to use.
98 The ZMQ context to use.
101 session : :class:`session.Session`
99 session : :class:`session.Session`
102 The session to use.
100 The session to use.
103 address : tuple
101 address : tuple
104 Standard (ip, port) tuple that the kernel is listening on.
102 Standard (ip, port) tuple that the kernel is listening on.
105 """
103 """
106 super(ZMQSocketChannel, self).__init__()
104 super(ZMQSocketChannel, self).__init__()
107 self.daemon = True
105 self.daemon = True
108
106
109 self.context = context
107 self.context = context
110 self.session = session
108 self.session = session
111 if address[1] == 0:
109 if address[1] == 0:
112 message = 'The port number for a channel cannot be 0.'
110 message = 'The port number for a channel cannot be 0.'
113 raise InvalidPortNumber(message)
111 raise InvalidPortNumber(message)
114 self._address = address
112 self._address = address
115
113
116 def _run_loop(self):
114 def _run_loop(self):
117 """Run my loop, ignoring EINTR events in the poller"""
115 """Run my loop, ignoring EINTR events in the poller"""
118 while True:
116 while True:
119 try:
117 try:
120 self.ioloop.start()
118 self.ioloop.start()
121 except zmq.ZMQError as e:
119 except zmq.ZMQError as e:
122 if e.errno == errno.EINTR:
120 if e.errno == errno.EINTR:
123 continue
121 continue
124 else:
122 else:
125 raise
123 raise
126 else:
124 else:
127 break
125 break
128
126
129 def stop(self):
127 def stop(self):
130 """Stop the channel's activity.
128 """Stop the channel's activity.
131
129
132 This calls :method:`Thread.join` and returns when the thread
130 This calls :method:`Thread.join` and returns when the thread
133 terminates. :class:`RuntimeError` will be raised if
131 terminates. :class:`RuntimeError` will be raised if
134 :method:`self.start` is called again.
132 :method:`self.start` is called again.
135 """
133 """
136 self.join()
134 self.join()
137
135
138 @property
136 @property
139 def address(self):
137 def address(self):
140 """Get the channel's address as an (ip, port) tuple.
138 """Get the channel's address as an (ip, port) tuple.
141
139
142 By the default, the address is (localhost, 0), where 0 means a random
140 By the default, the address is (localhost, 0), where 0 means a random
143 port.
141 port.
144 """
142 """
145 return self._address
143 return self._address
146
144
147 def add_io_state(self, state):
145 def _queue_send(self, msg):
148 """Add IO state to the eventloop.
146 """Queue a message to be sent from the IOLoop's thread.
149
147
150 Parameters
148 Parameters
151 ----------
149 ----------
152 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 msg : message to send
153 The IO state flag to set.
151
154
152 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
155 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 thread control of the action.
156 """
154 """
157 def add_io_state_callback():
155 def thread_send():
158 if not self.iostate & state:
156 self.session.send(self.stream, msg)
159 self.iostate = self.iostate | state
157 self.ioloop.add_callback(thread_send)
160 self.ioloop.update_handler(self.socket, self.iostate)
161 self.ioloop.add_callback(add_io_state_callback)
162
163 def drop_io_state(self, state):
164 """Drop IO state from the eventloop.
165
166 Parameters
167 ----------
168 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
169 The IO state flag to set.
170
158
171 This is thread safe as it uses the thread safe IOLoop.add_callback.
159 def _handle_recv(self, msg):
160 """callback for stream.on_recv
161
162 unpacks message, and calls handlers with it.
172 """
163 """
173 def drop_io_state_callback():
164 ident,smsg = self.session.feed_identities(msg)
174 if self.iostate & state:
165 self.call_handlers(self.session.unserialize(smsg))
175 self.iostate = self.iostate & (~state)
166
176 self.ioloop.update_handler(self.socket, self.iostate)
177 self.ioloop.add_callback(drop_io_state_callback)
178
167
179
168
180 class ShellSocketChannel(ZMQSocketChannel):
169 class ShellSocketChannel(ZMQSocketChannel):
181 """The XREQ channel for issues request/replies to the kernel.
170 """The XREQ channel for issues request/replies to the kernel.
182 """
171 """
183
172
184 command_queue = None
173 command_queue = None
185 # flag for whether execute requests should be allowed to call raw_input:
174 # flag for whether execute requests should be allowed to call raw_input:
186 allow_stdin = True
175 allow_stdin = True
187
176
188 def __init__(self, context, session, address):
177 def __init__(self, context, session, address):
189 super(ShellSocketChannel, self).__init__(context, session, address)
178 super(ShellSocketChannel, self).__init__(context, session, address)
190 self.command_queue = Queue()
191 self.ioloop = ioloop.IOLoop()
179 self.ioloop = ioloop.IOLoop()
192
180
193 def run(self):
181 def run(self):
194 """The thread's main activity. Call start() instead."""
182 """The thread's main activity. Call start() instead."""
195 self.socket = self.context.socket(zmq.DEALER)
183 self.socket = self.context.socket(zmq.DEALER)
196 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
184 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
197 self.socket.connect('tcp://%s:%i' % self.address)
185 self.socket.connect('tcp://%s:%i' % self.address)
198 self.iostate = POLLERR|POLLIN
186 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
199 self.ioloop.add_handler(self.socket, self._handle_events,
187 self.stream.on_recv(self._handle_recv)
200 self.iostate)
201 self._run_loop()
188 self._run_loop()
202
189
203 def stop(self):
190 def stop(self):
204 self.ioloop.stop()
191 self.ioloop.stop()
205 super(ShellSocketChannel, self).stop()
192 super(ShellSocketChannel, self).stop()
206
193
207 def call_handlers(self, msg):
194 def call_handlers(self, msg):
208 """This method is called in the ioloop thread when a message arrives.
195 """This method is called in the ioloop thread when a message arrives.
209
196
210 Subclasses should override this method to handle incoming messages.
197 Subclasses should override this method to handle incoming messages.
211 It is important to remember that this method is called in the thread
198 It is important to remember that this method is called in the thread
212 so that some logic must be done to ensure that the application leve
199 so that some logic must be done to ensure that the application leve
213 handlers are called in the application thread.
200 handlers are called in the application thread.
214 """
201 """
215 raise NotImplementedError('call_handlers must be defined in a subclass.')
202 raise NotImplementedError('call_handlers must be defined in a subclass.')
216
203
217 def execute(self, code, silent=False,
204 def execute(self, code, silent=False,
218 user_variables=None, user_expressions=None, allow_stdin=None):
205 user_variables=None, user_expressions=None, allow_stdin=None):
219 """Execute code in the kernel.
206 """Execute code in the kernel.
220
207
221 Parameters
208 Parameters
222 ----------
209 ----------
223 code : str
210 code : str
224 A string of Python code.
211 A string of Python code.
225
212
226 silent : bool, optional (default False)
213 silent : bool, optional (default False)
227 If set, the kernel will execute the code as quietly possible.
214 If set, the kernel will execute the code as quietly possible.
228
215
229 user_variables : list, optional
216 user_variables : list, optional
230 A list of variable names to pull from the user's namespace. They
217 A list of variable names to pull from the user's namespace. They
231 will come back as a dict with these names as keys and their
218 will come back as a dict with these names as keys and their
232 :func:`repr` as values.
219 :func:`repr` as values.
233
220
234 user_expressions : dict, optional
221 user_expressions : dict, optional
235 A dict with string keys and to pull from the user's
222 A dict with string keys and to pull from the user's
236 namespace. They will come back as a dict with these names as keys
223 namespace. They will come back as a dict with these names as keys
237 and their :func:`repr` as values.
224 and their :func:`repr` as values.
238
225
239 allow_stdin : bool, optional
226 allow_stdin : bool, optional
240 Flag for
227 Flag for
241 A dict with string keys and to pull from the user's
228 A dict with string keys and to pull from the user's
242 namespace. They will come back as a dict with these names as keys
229 namespace. They will come back as a dict with these names as keys
243 and their :func:`repr` as values.
230 and their :func:`repr` as values.
244
231
245 Returns
232 Returns
246 -------
233 -------
247 The msg_id of the message sent.
234 The msg_id of the message sent.
248 """
235 """
249 if user_variables is None:
236 if user_variables is None:
250 user_variables = []
237 user_variables = []
251 if user_expressions is None:
238 if user_expressions is None:
252 user_expressions = {}
239 user_expressions = {}
253 if allow_stdin is None:
240 if allow_stdin is None:
254 allow_stdin = self.allow_stdin
241 allow_stdin = self.allow_stdin
255
242
256
243
257 # Don't waste network traffic if inputs are invalid
244 # Don't waste network traffic if inputs are invalid
258 if not isinstance(code, basestring):
245 if not isinstance(code, basestring):
259 raise ValueError('code %r must be a string' % code)
246 raise ValueError('code %r must be a string' % code)
260 validate_string_list(user_variables)
247 validate_string_list(user_variables)
261 validate_string_dict(user_expressions)
248 validate_string_dict(user_expressions)
262
249
263 # Create class for content/msg creation. Related to, but possibly
250 # Create class for content/msg creation. Related to, but possibly
264 # not in Session.
251 # not in Session.
265 content = dict(code=code, silent=silent,
252 content = dict(code=code, silent=silent,
266 user_variables=user_variables,
253 user_variables=user_variables,
267 user_expressions=user_expressions,
254 user_expressions=user_expressions,
268 allow_stdin=allow_stdin,
255 allow_stdin=allow_stdin,
269 )
256 )
270 msg = self.session.msg('execute_request', content)
257 msg = self.session.msg('execute_request', content)
271 self._queue_request(msg)
258 self._queue_send(msg)
272 return msg['header']['msg_id']
259 return msg['header']['msg_id']
273
260
274 def complete(self, text, line, cursor_pos, block=None):
261 def complete(self, text, line, cursor_pos, block=None):
275 """Tab complete text in the kernel's namespace.
262 """Tab complete text in the kernel's namespace.
276
263
277 Parameters
264 Parameters
278 ----------
265 ----------
279 text : str
266 text : str
280 The text to complete.
267 The text to complete.
281 line : str
268 line : str
282 The full line of text that is the surrounding context for the
269 The full line of text that is the surrounding context for the
283 text to complete.
270 text to complete.
284 cursor_pos : int
271 cursor_pos : int
285 The position of the cursor in the line where the completion was
272 The position of the cursor in the line where the completion was
286 requested.
273 requested.
287 block : str, optional
274 block : str, optional
288 The full block of code in which the completion is being requested.
275 The full block of code in which the completion is being requested.
289
276
290 Returns
277 Returns
291 -------
278 -------
292 The msg_id of the message sent.
279 The msg_id of the message sent.
293 """
280 """
294 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
281 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
295 msg = self.session.msg('complete_request', content)
282 msg = self.session.msg('complete_request', content)
296 self._queue_request(msg)
283 self._queue_send(msg)
297 return msg['header']['msg_id']
284 return msg['header']['msg_id']
298
285
299 def object_info(self, oname):
286 def object_info(self, oname):
300 """Get metadata information about an object.
287 """Get metadata information about an object.
301
288
302 Parameters
289 Parameters
303 ----------
290 ----------
304 oname : str
291 oname : str
305 A string specifying the object name.
292 A string specifying the object name.
306
293
307 Returns
294 Returns
308 -------
295 -------
309 The msg_id of the message sent.
296 The msg_id of the message sent.
310 """
297 """
311 content = dict(oname=oname)
298 content = dict(oname=oname)
312 msg = self.session.msg('object_info_request', content)
299 msg = self.session.msg('object_info_request', content)
313 self._queue_request(msg)
300 self._queue_send(msg)
314 return msg['header']['msg_id']
301 return msg['header']['msg_id']
315
302
316 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
303 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
317 """Get entries from the history list.
304 """Get entries from the history list.
318
305
319 Parameters
306 Parameters
320 ----------
307 ----------
321 raw : bool
308 raw : bool
322 If True, return the raw input.
309 If True, return the raw input.
323 output : bool
310 output : bool
324 If True, then return the output as well.
311 If True, then return the output as well.
325 hist_access_type : str
312 hist_access_type : str
326 'range' (fill in session, start and stop params), 'tail' (fill in n)
313 'range' (fill in session, start and stop params), 'tail' (fill in n)
327 or 'search' (fill in pattern param).
314 or 'search' (fill in pattern param).
328
315
329 session : int
316 session : int
330 For a range request, the session from which to get lines. Session
317 For a range request, the session from which to get lines. Session
331 numbers are positive integers; negative ones count back from the
318 numbers are positive integers; negative ones count back from the
332 current session.
319 current session.
333 start : int
320 start : int
334 The first line number of a history range.
321 The first line number of a history range.
335 stop : int
322 stop : int
336 The final (excluded) line number of a history range.
323 The final (excluded) line number of a history range.
337
324
338 n : int
325 n : int
339 The number of lines of history to get for a tail request.
326 The number of lines of history to get for a tail request.
340
327
341 pattern : str
328 pattern : str
342 The glob-syntax pattern for a search request.
329 The glob-syntax pattern for a search request.
343
330
344 Returns
331 Returns
345 -------
332 -------
346 The msg_id of the message sent.
333 The msg_id of the message sent.
347 """
334 """
348 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
335 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
349 **kwargs)
336 **kwargs)
350 msg = self.session.msg('history_request', content)
337 msg = self.session.msg('history_request', content)
351 self._queue_request(msg)
338 self._queue_send(msg)
352 return msg['header']['msg_id']
339 return msg['header']['msg_id']
353
340
354 def shutdown(self, restart=False):
341 def shutdown(self, restart=False):
355 """Request an immediate kernel shutdown.
342 """Request an immediate kernel shutdown.
356
343
357 Upon receipt of the (empty) reply, client code can safely assume that
344 Upon receipt of the (empty) reply, client code can safely assume that
358 the kernel has shut down and it's safe to forcefully terminate it if
345 the kernel has shut down and it's safe to forcefully terminate it if
359 it's still alive.
346 it's still alive.
360
347
361 The kernel will send the reply via a function registered with Python's
348 The kernel will send the reply via a function registered with Python's
362 atexit module, ensuring it's truly done as the kernel is done with all
349 atexit module, ensuring it's truly done as the kernel is done with all
363 normal operation.
350 normal operation.
364 """
351 """
365 # Send quit message to kernel. Once we implement kernel-side setattr,
352 # Send quit message to kernel. Once we implement kernel-side setattr,
366 # this should probably be done that way, but for now this will do.
353 # this should probably be done that way, but for now this will do.
367 msg = self.session.msg('shutdown_request', {'restart':restart})
354 msg = self.session.msg('shutdown_request', {'restart':restart})
368 self._queue_request(msg)
355 self._queue_send(msg)
369 return msg['header']['msg_id']
356 return msg['header']['msg_id']
370
357
371 def _handle_events(self, socket, events):
372 if events & POLLERR:
373 self._handle_err()
374 if events & POLLOUT:
375 self._handle_send()
376 if events & POLLIN:
377 self._handle_recv()
378
379 def _handle_recv(self):
380 ident,msg = self.session.recv(self.socket, 0)
381 self.call_handlers(msg)
382
383 def _handle_send(self):
384 try:
385 msg = self.command_queue.get(False)
386 except Empty:
387 pass
388 else:
389 self.session.send(self.socket,msg)
390 if self.command_queue.empty():
391 self.drop_io_state(POLLOUT)
392
393 def _handle_err(self):
394 # We don't want to let this go silently, so eventually we should log.
395 raise zmq.ZMQError()
396
397 def _queue_request(self, msg):
398 self.command_queue.put(msg)
399 self.add_io_state(POLLOUT)
400
358
401
359
402 class SubSocketChannel(ZMQSocketChannel):
360 class SubSocketChannel(ZMQSocketChannel):
403 """The SUB channel which listens for messages that the kernel publishes.
361 """The SUB channel which listens for messages that the kernel publishes.
404 """
362 """
405
363
406 def __init__(self, context, session, address):
364 def __init__(self, context, session, address):
407 super(SubSocketChannel, self).__init__(context, session, address)
365 super(SubSocketChannel, self).__init__(context, session, address)
408 self.ioloop = ioloop.IOLoop()
366 self.ioloop = ioloop.IOLoop()
409
367
410 def run(self):
368 def run(self):
411 """The thread's main activity. Call start() instead."""
369 """The thread's main activity. Call start() instead."""
412 self.socket = self.context.socket(zmq.SUB)
370 self.socket = self.context.socket(zmq.SUB)
413 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
371 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
414 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
372 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
415 self.socket.connect('tcp://%s:%i' % self.address)
373 self.socket.connect('tcp://%s:%i' % self.address)
416 self.iostate = POLLIN|POLLERR
374 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
417 self.ioloop.add_handler(self.socket, self._handle_events,
375 self.stream.on_recv(self._handle_recv)
418 self.iostate)
419 self._run_loop()
376 self._run_loop()
420
377
421 def stop(self):
378 def stop(self):
422 self.ioloop.stop()
379 self.ioloop.stop()
423 super(SubSocketChannel, self).stop()
380 super(SubSocketChannel, self).stop()
424
381
425 def call_handlers(self, msg):
382 def call_handlers(self, msg):
426 """This method is called in the ioloop thread when a message arrives.
383 """This method is called in the ioloop thread when a message arrives.
427
384
428 Subclasses should override this method to handle incoming messages.
385 Subclasses should override this method to handle incoming messages.
429 It is important to remember that this method is called in the thread
386 It is important to remember that this method is called in the thread
430 so that some logic must be done to ensure that the application leve
387 so that some logic must be done to ensure that the application leve
431 handlers are called in the application thread.
388 handlers are called in the application thread.
432 """
389 """
433 raise NotImplementedError('call_handlers must be defined in a subclass.')
390 raise NotImplementedError('call_handlers must be defined in a subclass.')
434
391
435 def flush(self, timeout=1.0):
392 def flush(self, timeout=1.0):
436 """Immediately processes all pending messages on the SUB channel.
393 """Immediately processes all pending messages on the SUB channel.
437
394
438 Callers should use this method to ensure that :method:`call_handlers`
395 Callers should use this method to ensure that :method:`call_handlers`
439 has been called for all messages that have been received on the
396 has been called for all messages that have been received on the
440 0MQ SUB socket of this channel.
397 0MQ SUB socket of this channel.
441
398
442 This method is thread safe.
399 This method is thread safe.
443
400
444 Parameters
401 Parameters
445 ----------
402 ----------
446 timeout : float, optional
403 timeout : float, optional
447 The maximum amount of time to spend flushing, in seconds. The
404 The maximum amount of time to spend flushing, in seconds. The
448 default is one second.
405 default is one second.
449 """
406 """
450 # We do the IOLoop callback process twice to ensure that the IOLoop
407 # We do the IOLoop callback process twice to ensure that the IOLoop
451 # gets to perform at least one full poll.
408 # gets to perform at least one full poll.
452 stop_time = time.time() + timeout
409 stop_time = time.time() + timeout
453 for i in xrange(2):
410 for i in xrange(2):
454 self._flushed = False
411 self._flushed = False
455 self.ioloop.add_callback(self._flush)
412 self.ioloop.add_callback(self._flush)
456 while not self._flushed and time.time() < stop_time:
413 while not self._flushed and time.time() < stop_time:
457 time.sleep(0.01)
414 time.sleep(0.01)
458
415
459 def _handle_events(self, socket, events):
460 # Turn on and off POLLOUT depending on if we have made a request
461 if events & POLLERR:
462 self._handle_err()
463 if events & POLLIN:
464 self._handle_recv()
465
466 def _handle_err(self):
467 # We don't want to let this go silently, so eventually we should log.
468 raise zmq.ZMQError()
469
470 def _handle_recv(self):
471 # Get all of the messages we can
472 while True:
473 try:
474 ident,msg = self.session.recv(self.socket)
475 except zmq.ZMQError:
476 # Check the errno?
477 # Will this trigger POLLERR?
478 break
479 else:
480 if msg is None:
481 break
482 self.call_handlers(msg)
483
484 def _flush(self):
416 def _flush(self):
485 """Callback for :method:`self.flush`."""
417 """Callback for :method:`self.flush`."""
418 self.stream.flush()
486 self._flushed = True
419 self._flushed = True
487
420
488
421
489 class StdInSocketChannel(ZMQSocketChannel):
422 class StdInSocketChannel(ZMQSocketChannel):
490 """A reply channel to handle raw_input requests that the kernel makes."""
423 """A reply channel to handle raw_input requests that the kernel makes."""
491
424
492 msg_queue = None
425 msg_queue = None
493
426
494 def __init__(self, context, session, address):
427 def __init__(self, context, session, address):
495 super(StdInSocketChannel, self).__init__(context, session, address)
428 super(StdInSocketChannel, self).__init__(context, session, address)
496 self.ioloop = ioloop.IOLoop()
429 self.ioloop = ioloop.IOLoop()
497 self.msg_queue = Queue()
498
430
499 def run(self):
431 def run(self):
500 """The thread's main activity. Call start() instead."""
432 """The thread's main activity. Call start() instead."""
501 self.socket = self.context.socket(zmq.DEALER)
433 self.socket = self.context.socket(zmq.DEALER)
502 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
434 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
503 self.socket.connect('tcp://%s:%i' % self.address)
435 self.socket.connect('tcp://%s:%i' % self.address)
504 self.iostate = POLLERR|POLLIN
436 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
505 self.ioloop.add_handler(self.socket, self._handle_events,
437 self.stream.on_recv(self._handle_recv)
506 self.iostate)
507 self._run_loop()
438 self._run_loop()
508
439
509 def stop(self):
440 def stop(self):
510 self.ioloop.stop()
441 self.ioloop.stop()
511 super(StdInSocketChannel, self).stop()
442 super(StdInSocketChannel, self).stop()
512
443
513 def call_handlers(self, msg):
444 def call_handlers(self, msg):
514 """This method is called in the ioloop thread when a message arrives.
445 """This method is called in the ioloop thread when a message arrives.
515
446
516 Subclasses should override this method to handle incoming messages.
447 Subclasses should override this method to handle incoming messages.
517 It is important to remember that this method is called in the thread
448 It is important to remember that this method is called in the thread
518 so that some logic must be done to ensure that the application leve
449 so that some logic must be done to ensure that the application leve
519 handlers are called in the application thread.
450 handlers are called in the application thread.
520 """
451 """
521 raise NotImplementedError('call_handlers must be defined in a subclass.')
452 raise NotImplementedError('call_handlers must be defined in a subclass.')
522
453
523 def input(self, string):
454 def input(self, string):
524 """Send a string of raw input to the kernel."""
455 """Send a string of raw input to the kernel."""
525 content = dict(value=string)
456 content = dict(value=string)
526 msg = self.session.msg('input_reply', content)
457 msg = self.session.msg('input_reply', content)
527 self._queue_reply(msg)
458 self._queue_send(msg)
528
529 def _handle_events(self, socket, events):
530 if events & POLLERR:
531 self._handle_err()
532 if events & POLLOUT:
533 self._handle_send()
534 if events & POLLIN:
535 self._handle_recv()
536
537 def _handle_recv(self):
538 ident,msg = self.session.recv(self.socket, 0)
539 self.call_handlers(msg)
540
541 def _handle_send(self):
542 try:
543 msg = self.msg_queue.get(False)
544 except Empty:
545 pass
546 else:
547 self.session.send(self.socket,msg)
548 if self.msg_queue.empty():
549 self.drop_io_state(POLLOUT)
550
551 def _handle_err(self):
552 # We don't want to let this go silently, so eventually we should log.
553 raise zmq.ZMQError()
554
555 def _queue_reply(self, msg):
556 self.msg_queue.put(msg)
557 self.add_io_state(POLLOUT)
558
459
559
460
560 class HBSocketChannel(ZMQSocketChannel):
461 class HBSocketChannel(ZMQSocketChannel):
561 """The heartbeat channel which monitors the kernel heartbeat.
462 """The heartbeat channel which monitors the kernel heartbeat.
562
463
563 Note that the heartbeat channel is paused by default. As long as you start
464 Note that the heartbeat channel is paused by default. As long as you start
564 this channel, the kernel manager will ensure that it is paused and un-paused
465 this channel, the kernel manager will ensure that it is paused and un-paused
565 as appropriate.
466 as appropriate.
566 """
467 """
567
468
568 time_to_dead = 3.0
469 time_to_dead = 3.0
569 socket = None
470 socket = None
570 poller = None
471 poller = None
571 _running = None
472 _running = None
572 _pause = None
473 _pause = None
573
474
574 def __init__(self, context, session, address):
475 def __init__(self, context, session, address):
575 super(HBSocketChannel, self).__init__(context, session, address)
476 super(HBSocketChannel, self).__init__(context, session, address)
576 self._running = False
477 self._running = False
577 self._pause = True
478 self._pause = True
578
479
579 def _create_socket(self):
480 def _create_socket(self):
580 self.socket = self.context.socket(zmq.REQ)
481 self.socket = self.context.socket(zmq.REQ)
581 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
482 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
582 self.socket.connect('tcp://%s:%i' % self.address)
483 self.socket.connect('tcp://%s:%i' % self.address)
583 self.poller = zmq.Poller()
484 self.poller = zmq.Poller()
584 self.poller.register(self.socket, zmq.POLLIN)
485 self.poller.register(self.socket, zmq.POLLIN)
585
486
586 def run(self):
487 def run(self):
587 """The thread's main activity. Call start() instead."""
488 """The thread's main activity. Call start() instead."""
588 self._create_socket()
489 self._create_socket()
589 self._running = True
490 self._running = True
590 while self._running:
491 while self._running:
591 if self._pause:
492 if self._pause:
592 time.sleep(self.time_to_dead)
493 time.sleep(self.time_to_dead)
593 else:
494 else:
594 since_last_heartbeat = 0.0
495 since_last_heartbeat = 0.0
595 request_time = time.time()
496 request_time = time.time()
596 try:
497 try:
597 #io.rprint('Ping from HB channel') # dbg
498 #io.rprint('Ping from HB channel') # dbg
598 self.socket.send(b'ping')
499 self.socket.send(b'ping')
599 except zmq.ZMQError, e:
500 except zmq.ZMQError, e:
600 #io.rprint('*** HB Error:', e) # dbg
501 #io.rprint('*** HB Error:', e) # dbg
601 if e.errno == zmq.EFSM:
502 if e.errno == zmq.EFSM:
602 #io.rprint('sleep...', self.time_to_dead) # dbg
503 #io.rprint('sleep...', self.time_to_dead) # dbg
603 time.sleep(self.time_to_dead)
504 time.sleep(self.time_to_dead)
604 self._create_socket()
505 self._create_socket()
605 else:
506 else:
606 raise
507 raise
607 else:
508 else:
608 while True:
509 while True:
609 try:
510 try:
610 self.socket.recv(zmq.NOBLOCK)
511 self.socket.recv(zmq.NOBLOCK)
611 except zmq.ZMQError, e:
512 except zmq.ZMQError, e:
612 #io.rprint('*** HB Error 2:', e) # dbg
513 #io.rprint('*** HB Error 2:', e) # dbg
613 if e.errno == zmq.EAGAIN:
514 if e.errno == zmq.EAGAIN:
614 before_poll = time.time()
515 before_poll = time.time()
615 until_dead = self.time_to_dead - (before_poll -
516 until_dead = self.time_to_dead - (before_poll -
616 request_time)
517 request_time)
617
518
618 # When the return value of poll() is an empty
519 # When the return value of poll() is an empty
619 # list, that is when things have gone wrong
520 # list, that is when things have gone wrong
620 # (zeromq bug). As long as it is not an empty
521 # (zeromq bug). As long as it is not an empty
621 # list, poll is working correctly even if it
522 # list, poll is working correctly even if it
622 # returns quickly. Note: poll timeout is in
523 # returns quickly. Note: poll timeout is in
623 # milliseconds.
524 # milliseconds.
624 if until_dead > 0.0:
525 if until_dead > 0.0:
625 while True:
526 while True:
626 try:
527 try:
627 self.poller.poll(1000 * until_dead)
528 self.poller.poll(1000 * until_dead)
628 except zmq.ZMQError as e:
529 except zmq.ZMQError as e:
629 if e.errno == errno.EINTR:
530 if e.errno == errno.EINTR:
630 continue
531 continue
631 else:
532 else:
632 raise
533 raise
633 else:
534 else:
634 break
535 break
635
536
636 since_last_heartbeat = time.time()-request_time
537 since_last_heartbeat = time.time()-request_time
637 if since_last_heartbeat > self.time_to_dead:
538 if since_last_heartbeat > self.time_to_dead:
638 self.call_handlers(since_last_heartbeat)
539 self.call_handlers(since_last_heartbeat)
639 break
540 break
640 else:
541 else:
641 # FIXME: We should probably log this instead.
542 # FIXME: We should probably log this instead.
642 raise
543 raise
643 else:
544 else:
644 until_dead = self.time_to_dead - (time.time() -
545 until_dead = self.time_to_dead - (time.time() -
645 request_time)
546 request_time)
646 if until_dead > 0.0:
547 if until_dead > 0.0:
647 #io.rprint('sleep...', self.time_to_dead) # dbg
548 #io.rprint('sleep...', self.time_to_dead) # dbg
648 time.sleep(until_dead)
549 time.sleep(until_dead)
649 break
550 break
650
551
651 def pause(self):
552 def pause(self):
652 """Pause the heartbeat."""
553 """Pause the heartbeat."""
653 self._pause = True
554 self._pause = True
654
555
655 def unpause(self):
556 def unpause(self):
656 """Unpause the heartbeat."""
557 """Unpause the heartbeat."""
657 self._pause = False
558 self._pause = False
658
559
659 def is_beating(self):
560 def is_beating(self):
660 """Is the heartbeat running and not paused."""
561 """Is the heartbeat running and not paused."""
661 if self.is_alive() and not self._pause:
562 if self.is_alive() and not self._pause:
662 return True
563 return True
663 else:
564 else:
664 return False
565 return False
665
566
666 def stop(self):
567 def stop(self):
667 self._running = False
568 self._running = False
668 super(HBSocketChannel, self).stop()
569 super(HBSocketChannel, self).stop()
669
570
670 def call_handlers(self, since_last_heartbeat):
571 def call_handlers(self, since_last_heartbeat):
671 """This method is called in the ioloop thread when a message arrives.
572 """This method is called in the ioloop thread when a message arrives.
672
573
673 Subclasses should override this method to handle incoming messages.
574 Subclasses should override this method to handle incoming messages.
674 It is important to remember that this method is called in the thread
575 It is important to remember that this method is called in the thread
675 so that some logic must be done to ensure that the application leve
576 so that some logic must be done to ensure that the application leve
676 handlers are called in the application thread.
577 handlers are called in the application thread.
677 """
578 """
678 raise NotImplementedError('call_handlers must be defined in a subclass.')
579 raise NotImplementedError('call_handlers must be defined in a subclass.')
679
580
680
581
681 #-----------------------------------------------------------------------------
582 #-----------------------------------------------------------------------------
682 # Main kernel manager class
583 # Main kernel manager class
683 #-----------------------------------------------------------------------------
584 #-----------------------------------------------------------------------------
684
585
685 class KernelManager(HasTraits):
586 class KernelManager(HasTraits):
686 """ Manages a kernel for a frontend.
587 """ Manages a kernel for a frontend.
687
588
688 The SUB channel is for the frontend to receive messages published by the
589 The SUB channel is for the frontend to receive messages published by the
689 kernel.
590 kernel.
690
591
691 The REQ channel is for the frontend to make requests of the kernel.
592 The REQ channel is for the frontend to make requests of the kernel.
692
593
693 The REP channel is for the kernel to request stdin (raw_input) from the
594 The REP channel is for the kernel to request stdin (raw_input) from the
694 frontend.
595 frontend.
695 """
596 """
696 # config object for passing to child configurables
597 # config object for passing to child configurables
697 config = Instance(Config)
598 config = Instance(Config)
698
599
699 # The PyZMQ Context to use for communication with the kernel.
600 # The PyZMQ Context to use for communication with the kernel.
700 context = Instance(zmq.Context)
601 context = Instance(zmq.Context)
701 def _context_default(self):
602 def _context_default(self):
702 return zmq.Context.instance()
603 return zmq.Context.instance()
703
604
704 # The Session to use for communication with the kernel.
605 # The Session to use for communication with the kernel.
705 session = Instance(Session)
606 session = Instance(Session)
706
607
707 # The kernel process with which the KernelManager is communicating.
608 # The kernel process with which the KernelManager is communicating.
708 kernel = Instance(Popen)
609 kernel = Instance(Popen)
709
610
710 # The addresses for the communication channels.
611 # The addresses for the communication channels.
711 connection_file = Unicode('')
612 connection_file = Unicode('')
712 ip = Unicode(LOCALHOST)
613 ip = Unicode(LOCALHOST)
713 def _ip_changed(self, name, old, new):
614 def _ip_changed(self, name, old, new):
714 if new == '*':
615 if new == '*':
715 self.ip = '0.0.0.0'
616 self.ip = '0.0.0.0'
716 shell_port = Integer(0)
617 shell_port = Integer(0)
717 iopub_port = Integer(0)
618 iopub_port = Integer(0)
718 stdin_port = Integer(0)
619 stdin_port = Integer(0)
719 hb_port = Integer(0)
620 hb_port = Integer(0)
720
621
721 # The classes to use for the various channels.
622 # The classes to use for the various channels.
722 shell_channel_class = Type(ShellSocketChannel)
623 shell_channel_class = Type(ShellSocketChannel)
723 sub_channel_class = Type(SubSocketChannel)
624 sub_channel_class = Type(SubSocketChannel)
724 stdin_channel_class = Type(StdInSocketChannel)
625 stdin_channel_class = Type(StdInSocketChannel)
725 hb_channel_class = Type(HBSocketChannel)
626 hb_channel_class = Type(HBSocketChannel)
726
627
727 # Protected traits.
628 # Protected traits.
728 _launch_args = Any
629 _launch_args = Any
729 _shell_channel = Any
630 _shell_channel = Any
730 _sub_channel = Any
631 _sub_channel = Any
731 _stdin_channel = Any
632 _stdin_channel = Any
732 _hb_channel = Any
633 _hb_channel = Any
733 _connection_file_written=Bool(False)
634 _connection_file_written=Bool(False)
734
635
735 def __init__(self, **kwargs):
636 def __init__(self, **kwargs):
736 super(KernelManager, self).__init__(**kwargs)
637 super(KernelManager, self).__init__(**kwargs)
737 if self.session is None:
638 if self.session is None:
738 self.session = Session(config=self.config)
639 self.session = Session(config=self.config)
739
640
740 def __del__(self):
641 def __del__(self):
741 if self._connection_file_written:
642 if self._connection_file_written:
742 # cleanup connection files on full shutdown of kernel we started
643 # cleanup connection files on full shutdown of kernel we started
743 self._connection_file_written = False
644 self._connection_file_written = False
744 try:
645 try:
745 os.remove(self.connection_file)
646 os.remove(self.connection_file)
746 except IOError:
647 except IOError:
747 pass
648 pass
748
649
749
650
750 #--------------------------------------------------------------------------
651 #--------------------------------------------------------------------------
751 # Channel management methods:
652 # Channel management methods:
752 #--------------------------------------------------------------------------
653 #--------------------------------------------------------------------------
753
654
754 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
655 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
755 """Starts the channels for this kernel.
656 """Starts the channels for this kernel.
756
657
757 This will create the channels if they do not exist and then start
658 This will create the channels if they do not exist and then start
758 them. If port numbers of 0 are being used (random ports) then you
659 them. If port numbers of 0 are being used (random ports) then you
759 must first call :method:`start_kernel`. If the channels have been
660 must first call :method:`start_kernel`. If the channels have been
760 stopped and you call this, :class:`RuntimeError` will be raised.
661 stopped and you call this, :class:`RuntimeError` will be raised.
761 """
662 """
762 if shell:
663 if shell:
763 self.shell_channel.start()
664 self.shell_channel.start()
764 if sub:
665 if sub:
765 self.sub_channel.start()
666 self.sub_channel.start()
766 if stdin:
667 if stdin:
767 self.stdin_channel.start()
668 self.stdin_channel.start()
768 self.shell_channel.allow_stdin = True
669 self.shell_channel.allow_stdin = True
769 else:
670 else:
770 self.shell_channel.allow_stdin = False
671 self.shell_channel.allow_stdin = False
771 if hb:
672 if hb:
772 self.hb_channel.start()
673 self.hb_channel.start()
773
674
774 def stop_channels(self):
675 def stop_channels(self):
775 """Stops all the running channels for this kernel.
676 """Stops all the running channels for this kernel.
776 """
677 """
777 if self.shell_channel.is_alive():
678 if self.shell_channel.is_alive():
778 self.shell_channel.stop()
679 self.shell_channel.stop()
779 if self.sub_channel.is_alive():
680 if self.sub_channel.is_alive():
780 self.sub_channel.stop()
681 self.sub_channel.stop()
781 if self.stdin_channel.is_alive():
682 if self.stdin_channel.is_alive():
782 self.stdin_channel.stop()
683 self.stdin_channel.stop()
783 if self.hb_channel.is_alive():
684 if self.hb_channel.is_alive():
784 self.hb_channel.stop()
685 self.hb_channel.stop()
785
686
786 @property
687 @property
787 def channels_running(self):
688 def channels_running(self):
788 """Are any of the channels created and running?"""
689 """Are any of the channels created and running?"""
789 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
690 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
790 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
691 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
791
692
792 #--------------------------------------------------------------------------
693 #--------------------------------------------------------------------------
793 # Kernel process management methods:
694 # Kernel process management methods:
794 #--------------------------------------------------------------------------
695 #--------------------------------------------------------------------------
795
696
796 def load_connection_file(self):
697 def load_connection_file(self):
797 """load connection info from JSON dict in self.connection_file"""
698 """load connection info from JSON dict in self.connection_file"""
798 with open(self.connection_file) as f:
699 with open(self.connection_file) as f:
799 cfg = json.loads(f.read())
700 cfg = json.loads(f.read())
800
701
801 self.ip = cfg['ip']
702 self.ip = cfg['ip']
802 self.shell_port = cfg['shell_port']
703 self.shell_port = cfg['shell_port']
803 self.stdin_port = cfg['stdin_port']
704 self.stdin_port = cfg['stdin_port']
804 self.iopub_port = cfg['iopub_port']
705 self.iopub_port = cfg['iopub_port']
805 self.hb_port = cfg['hb_port']
706 self.hb_port = cfg['hb_port']
806 self.session.key = str_to_bytes(cfg['key'])
707 self.session.key = str_to_bytes(cfg['key'])
807
708
808 def write_connection_file(self):
709 def write_connection_file(self):
809 """write connection info to JSON dict in self.connection_file"""
710 """write connection info to JSON dict in self.connection_file"""
810 if self._connection_file_written:
711 if self._connection_file_written:
811 return
712 return
812 self.connection_file,cfg = write_connection_file(self.connection_file,
713 self.connection_file,cfg = write_connection_file(self.connection_file,
813 ip=self.ip, key=self.session.key,
714 ip=self.ip, key=self.session.key,
814 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
715 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
815 shell_port=self.shell_port, hb_port=self.hb_port)
716 shell_port=self.shell_port, hb_port=self.hb_port)
816 # write_connection_file also sets default ports:
717 # write_connection_file also sets default ports:
817 self.shell_port = cfg['shell_port']
718 self.shell_port = cfg['shell_port']
818 self.stdin_port = cfg['stdin_port']
719 self.stdin_port = cfg['stdin_port']
819 self.iopub_port = cfg['iopub_port']
720 self.iopub_port = cfg['iopub_port']
820 self.hb_port = cfg['hb_port']
721 self.hb_port = cfg['hb_port']
821
722
822 self._connection_file_written = True
723 self._connection_file_written = True
823
724
824 def start_kernel(self, **kw):
725 def start_kernel(self, **kw):
825 """Starts a kernel process and configures the manager to use it.
726 """Starts a kernel process and configures the manager to use it.
826
727
827 If random ports (port=0) are being used, this method must be called
728 If random ports (port=0) are being used, this method must be called
828 before the channels are created.
729 before the channels are created.
829
730
830 Parameters:
731 Parameters:
831 -----------
732 -----------
832 ipython : bool, optional (default True)
733 ipython : bool, optional (default True)
833 Whether to use an IPython kernel instead of a plain Python kernel.
734 Whether to use an IPython kernel instead of a plain Python kernel.
834
735
835 launcher : callable, optional (default None)
736 launcher : callable, optional (default None)
836 A custom function for launching the kernel process (generally a
737 A custom function for launching the kernel process (generally a
837 wrapper around ``entry_point.base_launch_kernel``). In most cases,
738 wrapper around ``entry_point.base_launch_kernel``). In most cases,
838 it should not be necessary to use this parameter.
739 it should not be necessary to use this parameter.
839
740
840 **kw : optional
741 **kw : optional
841 See respective options for IPython and Python kernels.
742 See respective options for IPython and Python kernels.
842 """
743 """
843 if self.ip not in LOCAL_IPS:
744 if self.ip not in LOCAL_IPS:
844 raise RuntimeError("Can only launch a kernel on a local interface. "
745 raise RuntimeError("Can only launch a kernel on a local interface. "
845 "Make sure that the '*_address' attributes are "
746 "Make sure that the '*_address' attributes are "
846 "configured properly. "
747 "configured properly. "
847 "Currently valid addresses are: %s"%LOCAL_IPS
748 "Currently valid addresses are: %s"%LOCAL_IPS
848 )
749 )
849
750
850 # write connection file / get default ports
751 # write connection file / get default ports
851 self.write_connection_file()
752 self.write_connection_file()
852
753
853 self._launch_args = kw.copy()
754 self._launch_args = kw.copy()
854 launch_kernel = kw.pop('launcher', None)
755 launch_kernel = kw.pop('launcher', None)
855 if launch_kernel is None:
756 if launch_kernel is None:
856 if kw.pop('ipython', True):
757 if kw.pop('ipython', True):
857 from ipkernel import launch_kernel
758 from ipkernel import launch_kernel
858 else:
759 else:
859 from pykernel import launch_kernel
760 from pykernel import launch_kernel
860 self.kernel = launch_kernel(fname=self.connection_file, **kw)
761 self.kernel = launch_kernel(fname=self.connection_file, **kw)
861
762
862 def shutdown_kernel(self, restart=False):
763 def shutdown_kernel(self, restart=False):
863 """ Attempts to the stop the kernel process cleanly. If the kernel
764 """ Attempts to the stop the kernel process cleanly. If the kernel
864 cannot be stopped, it is killed, if possible.
765 cannot be stopped, it is killed, if possible.
865 """
766 """
866 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
767 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
867 if sys.platform == 'win32':
768 if sys.platform == 'win32':
868 self.kill_kernel()
769 self.kill_kernel()
869 return
770 return
870
771
871 # Pause the heart beat channel if it exists.
772 # Pause the heart beat channel if it exists.
872 if self._hb_channel is not None:
773 if self._hb_channel is not None:
873 self._hb_channel.pause()
774 self._hb_channel.pause()
874
775
875 # Don't send any additional kernel kill messages immediately, to give
776 # Don't send any additional kernel kill messages immediately, to give
876 # the kernel a chance to properly execute shutdown actions. Wait for at
777 # the kernel a chance to properly execute shutdown actions. Wait for at
877 # most 1s, checking every 0.1s.
778 # most 1s, checking every 0.1s.
878 self.shell_channel.shutdown(restart=restart)
779 self.shell_channel.shutdown(restart=restart)
879 for i in range(10):
780 for i in range(10):
880 if self.is_alive:
781 if self.is_alive:
881 time.sleep(0.1)
782 time.sleep(0.1)
882 else:
783 else:
883 break
784 break
884 else:
785 else:
885 # OK, we've waited long enough.
786 # OK, we've waited long enough.
886 if self.has_kernel:
787 if self.has_kernel:
887 self.kill_kernel()
788 self.kill_kernel()
888
789
889 if not restart and self._connection_file_written:
790 if not restart and self._connection_file_written:
890 # cleanup connection files on full shutdown of kernel we started
791 # cleanup connection files on full shutdown of kernel we started
891 self._connection_file_written = False
792 self._connection_file_written = False
892 try:
793 try:
893 os.remove(self.connection_file)
794 os.remove(self.connection_file)
894 except IOError:
795 except IOError:
895 pass
796 pass
896
797
897 def restart_kernel(self, now=False, **kw):
798 def restart_kernel(self, now=False, **kw):
898 """Restarts a kernel with the arguments that were used to launch it.
799 """Restarts a kernel with the arguments that were used to launch it.
899
800
900 If the old kernel was launched with random ports, the same ports will be
801 If the old kernel was launched with random ports, the same ports will be
901 used for the new kernel.
802 used for the new kernel.
902
803
903 Parameters
804 Parameters
904 ----------
805 ----------
905 now : bool, optional
806 now : bool, optional
906 If True, the kernel is forcefully restarted *immediately*, without
807 If True, the kernel is forcefully restarted *immediately*, without
907 having a chance to do any cleanup action. Otherwise the kernel is
808 having a chance to do any cleanup action. Otherwise the kernel is
908 given 1s to clean up before a forceful restart is issued.
809 given 1s to clean up before a forceful restart is issued.
909
810
910 In all cases the kernel is restarted, the only difference is whether
811 In all cases the kernel is restarted, the only difference is whether
911 it is given a chance to perform a clean shutdown or not.
812 it is given a chance to perform a clean shutdown or not.
912
813
913 **kw : optional
814 **kw : optional
914 Any options specified here will replace those used to launch the
815 Any options specified here will replace those used to launch the
915 kernel.
816 kernel.
916 """
817 """
917 if self._launch_args is None:
818 if self._launch_args is None:
918 raise RuntimeError("Cannot restart the kernel. "
819 raise RuntimeError("Cannot restart the kernel. "
919 "No previous call to 'start_kernel'.")
820 "No previous call to 'start_kernel'.")
920 else:
821 else:
921 # Stop currently running kernel.
822 # Stop currently running kernel.
922 if self.has_kernel:
823 if self.has_kernel:
923 if now:
824 if now:
924 self.kill_kernel()
825 self.kill_kernel()
925 else:
826 else:
926 self.shutdown_kernel(restart=True)
827 self.shutdown_kernel(restart=True)
927
828
928 # Start new kernel.
829 # Start new kernel.
929 self._launch_args.update(kw)
830 self._launch_args.update(kw)
930 self.start_kernel(**self._launch_args)
831 self.start_kernel(**self._launch_args)
931
832
932 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
833 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
933 # unless there is some delay here.
834 # unless there is some delay here.
934 if sys.platform == 'win32':
835 if sys.platform == 'win32':
935 time.sleep(0.2)
836 time.sleep(0.2)
936
837
937 @property
838 @property
938 def has_kernel(self):
839 def has_kernel(self):
939 """Returns whether a kernel process has been specified for the kernel
840 """Returns whether a kernel process has been specified for the kernel
940 manager.
841 manager.
941 """
842 """
942 return self.kernel is not None
843 return self.kernel is not None
943
844
944 def kill_kernel(self):
845 def kill_kernel(self):
945 """ Kill the running kernel. """
846 """ Kill the running kernel. """
946 if self.has_kernel:
847 if self.has_kernel:
947 # Pause the heart beat channel if it exists.
848 # Pause the heart beat channel if it exists.
948 if self._hb_channel is not None:
849 if self._hb_channel is not None:
949 self._hb_channel.pause()
850 self._hb_channel.pause()
950
851
951 # Attempt to kill the kernel.
852 # Attempt to kill the kernel.
952 try:
853 try:
953 self.kernel.kill()
854 self.kernel.kill()
954 except OSError, e:
855 except OSError, e:
955 # In Windows, we will get an Access Denied error if the process
856 # In Windows, we will get an Access Denied error if the process
956 # has already terminated. Ignore it.
857 # has already terminated. Ignore it.
957 if sys.platform == 'win32':
858 if sys.platform == 'win32':
958 if e.winerror != 5:
859 if e.winerror != 5:
959 raise
860 raise
960 # On Unix, we may get an ESRCH error if the process has already
861 # On Unix, we may get an ESRCH error if the process has already
961 # terminated. Ignore it.
862 # terminated. Ignore it.
962 else:
863 else:
963 from errno import ESRCH
864 from errno import ESRCH
964 if e.errno != ESRCH:
865 if e.errno != ESRCH:
965 raise
866 raise
966 self.kernel = None
867 self.kernel = None
967 else:
868 else:
968 raise RuntimeError("Cannot kill kernel. No kernel is running!")
869 raise RuntimeError("Cannot kill kernel. No kernel is running!")
969
870
970 def interrupt_kernel(self):
871 def interrupt_kernel(self):
971 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
872 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
972 well supported on all platforms.
873 well supported on all platforms.
973 """
874 """
974 if self.has_kernel:
875 if self.has_kernel:
975 if sys.platform == 'win32':
876 if sys.platform == 'win32':
976 from parentpoller import ParentPollerWindows as Poller
877 from parentpoller import ParentPollerWindows as Poller
977 Poller.send_interrupt(self.kernel.win32_interrupt_event)
878 Poller.send_interrupt(self.kernel.win32_interrupt_event)
978 else:
879 else:
979 self.kernel.send_signal(signal.SIGINT)
880 self.kernel.send_signal(signal.SIGINT)
980 else:
881 else:
981 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
882 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
982
883
983 def signal_kernel(self, signum):
884 def signal_kernel(self, signum):
984 """ Sends a signal to the kernel. Note that since only SIGTERM is
885 """ Sends a signal to the kernel. Note that since only SIGTERM is
985 supported on Windows, this function is only useful on Unix systems.
886 supported on Windows, this function is only useful on Unix systems.
986 """
887 """
987 if self.has_kernel:
888 if self.has_kernel:
988 self.kernel.send_signal(signum)
889 self.kernel.send_signal(signum)
989 else:
890 else:
990 raise RuntimeError("Cannot signal kernel. No kernel is running!")
891 raise RuntimeError("Cannot signal kernel. No kernel is running!")
991
892
992 @property
893 @property
993 def is_alive(self):
894 def is_alive(self):
994 """Is the kernel process still running?"""
895 """Is the kernel process still running?"""
995 # FIXME: not using a heartbeat means this method is broken for any
896 # FIXME: not using a heartbeat means this method is broken for any
996 # remote kernel, it's only capable of handling local kernels.
897 # remote kernel, it's only capable of handling local kernels.
997 if self.has_kernel:
898 if self.has_kernel:
998 if self.kernel.poll() is None:
899 if self.kernel.poll() is None:
999 return True
900 return True
1000 else:
901 else:
1001 return False
902 return False
1002 else:
903 else:
1003 # We didn't start the kernel with this KernelManager so we don't
904 # We didn't start the kernel with this KernelManager so we don't
1004 # know if it is running. We should use a heartbeat for this case.
905 # know if it is running. We should use a heartbeat for this case.
1005 return True
906 return True
1006
907
1007 #--------------------------------------------------------------------------
908 #--------------------------------------------------------------------------
1008 # Channels used for communication with the kernel:
909 # Channels used for communication with the kernel:
1009 #--------------------------------------------------------------------------
910 #--------------------------------------------------------------------------
1010
911
1011 @property
912 @property
1012 def shell_channel(self):
913 def shell_channel(self):
1013 """Get the REQ socket channel object to make requests of the kernel."""
914 """Get the REQ socket channel object to make requests of the kernel."""
1014 if self._shell_channel is None:
915 if self._shell_channel is None:
1015 self._shell_channel = self.shell_channel_class(self.context,
916 self._shell_channel = self.shell_channel_class(self.context,
1016 self.session,
917 self.session,
1017 (self.ip, self.shell_port))
918 (self.ip, self.shell_port))
1018 return self._shell_channel
919 return self._shell_channel
1019
920
1020 @property
921 @property
1021 def sub_channel(self):
922 def sub_channel(self):
1022 """Get the SUB socket channel object."""
923 """Get the SUB socket channel object."""
1023 if self._sub_channel is None:
924 if self._sub_channel is None:
1024 self._sub_channel = self.sub_channel_class(self.context,
925 self._sub_channel = self.sub_channel_class(self.context,
1025 self.session,
926 self.session,
1026 (self.ip, self.iopub_port))
927 (self.ip, self.iopub_port))
1027 return self._sub_channel
928 return self._sub_channel
1028
929
1029 @property
930 @property
1030 def stdin_channel(self):
931 def stdin_channel(self):
1031 """Get the REP socket channel object to handle stdin (raw_input)."""
932 """Get the REP socket channel object to handle stdin (raw_input)."""
1032 if self._stdin_channel is None:
933 if self._stdin_channel is None:
1033 self._stdin_channel = self.stdin_channel_class(self.context,
934 self._stdin_channel = self.stdin_channel_class(self.context,
1034 self.session,
935 self.session,
1035 (self.ip, self.stdin_port))
936 (self.ip, self.stdin_port))
1036 return self._stdin_channel
937 return self._stdin_channel
1037
938
1038 @property
939 @property
1039 def hb_channel(self):
940 def hb_channel(self):
1040 """Get the heartbeat socket channel object to check that the
941 """Get the heartbeat socket channel object to check that the
1041 kernel is alive."""
942 kernel is alive."""
1042 if self._hb_channel is None:
943 if self._hb_channel is None:
1043 self._hb_channel = self.hb_channel_class(self.context,
944 self._hb_channel = self.hb_channel_class(self.context,
1044 self.session,
945 self.session,
1045 (self.ip, self.hb_port))
946 (self.ip, self.hb_port))
1046 return self._hb_channel
947 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now