##// END OF EJS Templates
Actually kill old kernels upon restart.
Brian E. Granger -
Show More
@@ -1,263 +1,264
1 1 """A tornado based IPython notebook server."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import logging
15 15 import os
16 16 import signal
17 17 import sys
18 18
19 19 import zmq
20 20
21 21 # Install the pyzmq ioloop. This has to be done before anything else from
22 22 # tornado is imported.
23 23 from zmq.eventloop import ioloop
24 24 import tornado.ioloop
25 25 tornado.ioloop = ioloop
26 26
27 27 from tornado import httpserver
28 28 from tornado import web
29 29
30 30 from kernelmanager import KernelManager
31 31 from sessionmanager import SessionManager
32 32 from handlers import (
33 33 MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 34 NotebookRootHandler, NotebookHandler
35 35 )
36 36 from routers import IOPubStreamRouter, ShellStreamRouter
37 37
38 38 from IPython.core.application import BaseIPythonApplication
39 39 from IPython.core.profiledir import ProfileDir
40 40 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
41 41 from IPython.zmq.session import Session
42 42 from IPython.zmq.zmqshell import ZMQInteractiveShell
43 43 from IPython.zmq.ipkernel import (
44 44 flags as ipkernel_flags,
45 45 aliases as ipkernel_aliases,
46 46 IPKernelApp
47 47 )
48 48 from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Module globals
52 52 #-----------------------------------------------------------------------------
53 53
54 54 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
55 55 _kernel_action_regex = r"(?P<action>restart|interrupt)"
56 56
57 57 LOCALHOST = '127.0.0.1'
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # The Tornado web application
61 61 #-----------------------------------------------------------------------------
62 62
63 63 class NotebookWebApplication(web.Application):
64 64
65 65 def __init__(self, kernel_manager, log, kernel_argv, config):
66 66 handlers = [
67 67 (r"/", MainHandler),
68 68 (r"/kernels", KernelHandler),
69 69 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
70 70 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
71 71 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
72 72 (r"/notebooks", NotebookRootHandler),
73 73 (r"/notebooks/([^/]+)", NotebookHandler)
74 74 ]
75 75 settings = dict(
76 76 template_path=os.path.join(os.path.dirname(__file__), "templates"),
77 77 static_path=os.path.join(os.path.dirname(__file__), "static"),
78 78 )
79 79 web.Application.__init__(self, handlers, **settings)
80 80
81 81 self.kernel_manager = kernel_manager
82 82 self.log = log
83 83 self.kernel_argv = kernel_argv
84 84 self.config = config
85 85 self._routers = {}
86 86 self._session_dict = {}
87 87
88 88 #-------------------------------------------------------------------------
89 89 # Methods for managing kernels and sessions
90 90 #-------------------------------------------------------------------------
91 91
92 92 @property
93 93 def kernel_ids(self):
94 94 return self.kernel_manager.kernel_ids
95 95
96 96 def start_kernel(self):
97 97 kwargs = dict()
98 98 kwargs['extra_arguments'] = self.kernel_argv
99 99 kernel_id = self.kernel_manager.start_kernel(**kwargs)
100 100 self.log.info("Kernel started: %s" % kernel_id)
101 101 self.log.debug("Kernel args: %r" % kwargs)
102 102 self.start_session_manager(kernel_id)
103 103 return kernel_id
104 104
105 105 def start_session_manager(self, kernel_id):
106 106 sm = self.kernel_manager.create_session_manager(kernel_id)
107 107 self._session_dict[kernel_id] = sm
108 108 iopub_stream = sm.get_iopub_stream()
109 109 shell_stream = sm.get_shell_stream()
110 110 iopub_router = IOPubStreamRouter(
111 111 zmq_stream=iopub_stream, session=sm.session, config=self.config
112 112 )
113 113 shell_router = ShellStreamRouter(
114 114 zmq_stream=shell_stream, session=sm.session, config=self.config
115 115 )
116 116 self._routers[(kernel_id, 'iopub')] = iopub_router
117 117 self._routers[(kernel_id, 'shell')] = shell_router
118 118
119 119 def kill_kernel(self, kernel_id):
120 120 sm = self._session_dict.pop(kernel_id)
121 121 sm.stop()
122 122 self.kernel_manager.kill_kernel(kernel_id)
123 123 self.log.info("Kernel killed: %s" % kernel_id)
124 124
125 125 def interrupt_kernel(self, kernel_id):
126 126 self.kernel_manager.interrupt_kernel(kernel_id)
127 127 self.log.debug("Kernel interrupted: %s" % kernel_id)
128 128
129 129 def restart_kernel(self, kernel_id):
130 130 # Create the new kernel first so we can move the clients over.
131 131 new_kernel_id = self.start_kernel()
132 132
133 133 # Copy the clients over to the new routers.
134 134 old_iopub_router = self.get_router(kernel_id, 'iopub')
135 135 old_shell_router = self.get_router(kernel_id, 'shell')
136 136 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
137 137 new_shell_router = self.get_router(new_kernel_id, 'shell')
138 138 new_iopub_router.copy_clients(old_iopub_router)
139 139 new_shell_router.copy_clients(old_shell_router)
140 140
141 141 # Now shutdown the old session and the kernel.
142 142 # TODO: This causes a hard crash in ZMQStream.close, which sets
143 143 # self.socket to None to hastily. We will need to fix this in PyZMQ
144 144 # itself. For now, we just leave the old kernel running :(
145 # self.kill_kernel(kernel_id)
145 # Maybe this is fixed now, but nothing was changed really.
146 self.kill_kernel(kernel_id)
146 147
147 148 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
148 149 return new_kernel_id
149 150
150 151 def get_router(self, kernel_id, stream_name):
151 152 router = self._routers[(kernel_id, stream_name)]
152 153 return router
153 154
154 155
155 156
156 157 #-----------------------------------------------------------------------------
157 158 # Aliases and Flags
158 159 #-----------------------------------------------------------------------------
159 160
160 161 flags = dict(ipkernel_flags)
161 162
162 163 # the flags that are specific to the frontend
163 164 # these must be scrubbed before being passed to the kernel,
164 165 # or it will raise an error on unrecognized flags
165 166 notebook_flags = []
166 167
167 168 aliases = dict(ipkernel_aliases)
168 169
169 170 aliases.update(dict(
170 171 ip = 'IPythonNotebookApp.ip',
171 172 port = 'IPythonNotebookApp.port',
172 173 colors = 'ZMQInteractiveShell.colors',
173 174 editor = 'RichIPythonWidget.editor',
174 175 ))
175 176
176 177 #-----------------------------------------------------------------------------
177 178 # IPythonNotebookApp
178 179 #-----------------------------------------------------------------------------
179 180
180 181 class IPythonNotebookApp(BaseIPythonApplication):
181 182 name = 'ipython-notebook'
182 183 default_config_file_name='ipython_notebook_config.py'
183 184
184 185 description = """
185 186 The IPython HTML Notebook.
186 187
187 188 This launches a Tornado based HTML Notebook Server that serves up an
188 189 HTML5/Javascript Notebook client.
189 190 """
190 191
191 192 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
192 193 KernelManager, SessionManager, RichIPythonWidget]
193 194 flags = Dict(flags)
194 195 aliases = Dict(aliases)
195 196
196 197 kernel_argv = List(Unicode)
197 198
198 199 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
199 200 default_value=logging.INFO,
200 201 config=True,
201 202 help="Set the log level by value or name.")
202 203
203 204 # connection info:
204 205 ip = Unicode(LOCALHOST, config=True,
205 206 help="The IP address the notebook server will listen on."
206 207 )
207 208
208 209 port = Int(8888, config=True,
209 210 help="The port the notebook server will listen on."
210 211 )
211 212
212 213 # the factory for creating a widget
213 214 widget_factory = Any(RichIPythonWidget)
214 215
215 216 def parse_command_line(self, argv=None):
216 217 super(IPythonNotebookApp, self).parse_command_line(argv)
217 218 if argv is None:
218 219 argv = sys.argv[1:]
219 220
220 221 self.kernel_argv = list(argv) # copy
221 222 # kernel should inherit default config file from frontend
222 223 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
223 224 # scrub frontend-specific flags
224 225 for a in argv:
225 226 if a.startswith('-') and a.lstrip('-') in notebook_flags:
226 227 self.kernel_argv.remove(a)
227 228
228 229 def init_kernel_manager(self):
229 230 # Don't let Qt or ZMQ swallow KeyboardInterupts.
230 231 signal.signal(signal.SIGINT, signal.SIG_DFL)
231 232
232 233 # Create a KernelManager and start a kernel.
233 234 self.kernel_manager = KernelManager(config=self.config, log=self.log)
234 235
235 236 def init_logging(self):
236 237 super(IPythonNotebookApp, self).init_logging()
237 238 # This prevents double log messages because tornado use a root logger that
238 239 # self.log is a child of. The logging module dipatches log messages to a log
239 240 # and all of its ancenstors until propagate is set to False.
240 241 self.log.propagate = False
241 242
242 243 def initialize(self, argv=None):
243 244 super(IPythonNotebookApp, self).initialize(argv)
244 245 self.init_kernel_manager()
245 246 self.web_app = NotebookWebApplication(
246 247 self.kernel_manager, self.log, self.kernel_argv, self.config
247 248 )
248 249 self.http_server = httpserver.HTTPServer(self.web_app)
249 250 self.http_server.listen(self.port)
250 251
251 252 def start(self):
252 253 self.log.info("The IPython Notebook is running at: http://%s:%i" % (self.ip, self.port))
253 254 ioloop.IOLoop.instance().start()
254 255
255 256 #-----------------------------------------------------------------------------
256 257 # Main entry point
257 258 #-----------------------------------------------------------------------------
258 259
259 260 def launch_new_instance():
260 261 app = IPythonNotebookApp()
261 262 app.initialize()
262 263 app.start()
263 264
General Comments 0
You need to be logged in to leave comments. Login now