##// END OF EJS Templates
Merge pull request #6236 from takluyver/fix-sys-executable-for-native-kernel...
Thomas Kluyver -
r17462:54ce2c62 merge
parent child Browse files
Show More
@@ -1,416 +1,416 b''
1 1 """Base class to manage a running kernel"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import absolute_import
7 7
8 8 import os
9 9 import re
10 10 import signal
11 11 import sys
12 12 import time
13 13 import warnings
14 14
15 15 import zmq
16 16
17 17 from IPython.utils.importstring import import_item
18 18 from IPython.utils.localinterfaces import is_local_ip, local_ips
19 19 from IPython.utils.path import get_ipython_dir
20 20 from IPython.utils.traitlets import (
21 21 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
22 22 )
23 23 from IPython.kernel import (
24 24 make_ipkernel_cmd,
25 25 launch_kernel,
26 26 kernelspec,
27 27 )
28 28 from .connect import ConnectionFileMixin
29 29 from .zmq.session import Session
30 30 from .managerabc import (
31 31 KernelManagerABC
32 32 )
33 33
34 34
35 35 class KernelManager(ConnectionFileMixin):
36 36 """Manages a single kernel in a subprocess on this host.
37 37
38 38 This version starts kernels with Popen.
39 39 """
40 40
41 41 # The PyZMQ Context to use for communication with the kernel.
42 42 context = Instance(zmq.Context)
43 43 def _context_default(self):
44 44 return zmq.Context.instance()
45 45
46 46 # the class to create with our `client` method
47 47 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
48 48 client_factory = Type()
49 49 def _client_class_changed(self, name, old, new):
50 50 self.client_factory = import_item(str(new))
51 51
52 52 # The kernel process with which the KernelManager is communicating.
53 53 # generally a Popen instance
54 54 kernel = Any()
55 55
56 56 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
57 57
58 58 def _kernel_spec_manager_default(self):
59 59 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
60 60
61 61 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
62 62
63 63 kernel_spec = Instance(kernelspec.KernelSpec)
64 64
65 65 def _kernel_spec_default(self):
66 66 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
67 67
68 68 def _kernel_name_changed(self, name, old, new):
69 69 if new == 'python':
70 70 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
71 71 # This triggered another run of this function, so we can exit now
72 72 return
73 73 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
74 74 self.ipython_kernel = new in {'python', 'python2', 'python3'}
75 75
76 76 kernel_cmd = List(Unicode, config=True,
77 77 help="""DEPRECATED: Use kernel_name instead.
78 78
79 79 The Popen Command to launch the kernel.
80 80 Override this if you have a custom kernel.
81 81 If kernel_cmd is specified in a configuration file,
82 82 IPython does not pass any arguments to the kernel,
83 83 because it cannot make any assumptions about the
84 84 arguments that the kernel understands. In particular,
85 85 this means that the kernel does not receive the
86 86 option --debug if it given on the IPython command line.
87 87 """
88 88 )
89 89
90 90 def _kernel_cmd_changed(self, name, old, new):
91 91 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
92 92 "start different kernels.")
93 93 self.ipython_kernel = False
94 94
95 95 ipython_kernel = Bool(True)
96 96
97 97 ipython_dir = Unicode()
98 98 def _ipython_dir_default(self):
99 99 return get_ipython_dir()
100 100
101 101 # Protected traits
102 102 _launch_args = Any()
103 103 _control_socket = Any()
104 104
105 105 _restarter = Any()
106 106
107 107 autorestart = Bool(False, config=True,
108 108 help="""Should we autorestart the kernel if it dies."""
109 109 )
110 110
111 111 def __del__(self):
112 112 self._close_control_socket()
113 113 self.cleanup_connection_file()
114 114
115 115 #--------------------------------------------------------------------------
116 116 # Kernel restarter
117 117 #--------------------------------------------------------------------------
118 118
119 119 def start_restarter(self):
120 120 pass
121 121
122 122 def stop_restarter(self):
123 123 pass
124 124
125 125 def add_restart_callback(self, callback, event='restart'):
126 126 """register a callback to be called when a kernel is restarted"""
127 127 if self._restarter is None:
128 128 return
129 129 self._restarter.add_callback(callback, event)
130 130
131 131 def remove_restart_callback(self, callback, event='restart'):
132 132 """unregister a callback to be called when a kernel is restarted"""
133 133 if self._restarter is None:
134 134 return
135 135 self._restarter.remove_callback(callback, event)
136 136
137 137 #--------------------------------------------------------------------------
138 138 # create a Client connected to our Kernel
139 139 #--------------------------------------------------------------------------
140 140
141 141 def client(self, **kwargs):
142 142 """Create a client configured to connect to our kernel"""
143 143 if self.client_factory is None:
144 144 self.client_factory = import_item(self.client_class)
145 145
146 146 kw = {}
147 147 kw.update(self.get_connection_info())
148 148 kw.update(dict(
149 149 connection_file=self.connection_file,
150 150 session=self.session,
151 151 parent=self,
152 152 ))
153 153
154 154 # add kwargs last, for manual overrides
155 155 kw.update(kwargs)
156 156 return self.client_factory(**kw)
157 157
158 158 #--------------------------------------------------------------------------
159 159 # Kernel management
160 160 #--------------------------------------------------------------------------
161 161
162 162 def format_kernel_cmd(self, **kw):
163 163 """replace templated args (e.g. {connection_file})"""
164 164 if self.kernel_cmd:
165 165 cmd = self.kernel_cmd
166 elif self.kernel_name == 'python':
166 elif self.kernel_name == kernelspec.NATIVE_KERNEL_NAME:
167 167 # The native kernel gets special handling
168 168 cmd = make_ipkernel_cmd(
169 169 'from IPython.kernel.zmq.kernelapp import main; main()',
170 170 **kw
171 171 )
172 172 else:
173 173 cmd = self.kernel_spec.argv
174 174
175 175 ns = dict(connection_file=self.connection_file)
176 176 ns.update(self._launch_args)
177 177
178 178 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
179 179 def from_ns(match):
180 180 """Get the key out of ns if it's there, otherwise no change."""
181 181 return ns.get(match.group(1), match.group())
182 182
183 183 return [ pat.sub(from_ns, arg) for arg in cmd ]
184 184
185 185 def _launch_kernel(self, kernel_cmd, **kw):
186 186 """actually launch the kernel
187 187
188 188 override in a subclass to launch kernel subprocesses differently
189 189 """
190 190 return launch_kernel(kernel_cmd, **kw)
191 191
192 192 # Control socket used for polite kernel shutdown
193 193
194 194 def _connect_control_socket(self):
195 195 if self._control_socket is None:
196 196 self._control_socket = self.connect_control()
197 197 self._control_socket.linger = 100
198 198
199 199 def _close_control_socket(self):
200 200 if self._control_socket is None:
201 201 return
202 202 self._control_socket.close()
203 203 self._control_socket = None
204 204
205 205 def start_kernel(self, **kw):
206 206 """Starts a kernel on this host in a separate process.
207 207
208 208 If random ports (port=0) are being used, this method must be called
209 209 before the channels are created.
210 210
211 211 Parameters
212 212 ----------
213 213 **kw : optional
214 214 keyword arguments that are passed down to build the kernel_cmd
215 215 and launching the kernel (e.g. Popen kwargs).
216 216 """
217 217 if self.transport == 'tcp' and not is_local_ip(self.ip):
218 218 raise RuntimeError("Can only launch a kernel on a local interface. "
219 219 "Make sure that the '*_address' attributes are "
220 220 "configured properly. "
221 221 "Currently valid addresses are: %s" % local_ips()
222 222 )
223 223
224 224 # write connection file / get default ports
225 225 self.write_connection_file()
226 226
227 227 # save kwargs for use in restart
228 228 self._launch_args = kw.copy()
229 229 # build the Popen cmd
230 230 kernel_cmd = self.format_kernel_cmd(**kw)
231 231 if self.kernel_cmd:
232 232 # If kernel_cmd has been set manually, don't refer to a kernel spec
233 233 env = os.environ
234 234 else:
235 235 # Environment variables from kernel spec are added to os.environ
236 236 env = os.environ.copy()
237 237 env.update(self.kernel_spec.env or {})
238 238 # launch the kernel subprocess
239 239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
240 240 ipython_kernel=self.ipython_kernel,
241 241 **kw)
242 242 self.start_restarter()
243 243 self._connect_control_socket()
244 244
245 245 def request_shutdown(self, restart=False):
246 246 """Send a shutdown request via control channel
247 247
248 248 On Windows, this just kills kernels instead, because the shutdown
249 249 messages don't work.
250 250 """
251 251 content = dict(restart=restart)
252 252 msg = self.session.msg("shutdown_request", content=content)
253 253 self.session.send(self._control_socket, msg)
254 254
255 255 def finish_shutdown(self, waittime=1, pollinterval=0.1):
256 256 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
257 257
258 258 This does not send shutdown requests - use :meth:`request_shutdown`
259 259 first.
260 260 """
261 261 for i in range(int(waittime/pollinterval)):
262 262 if self.is_alive():
263 263 time.sleep(pollinterval)
264 264 else:
265 265 break
266 266 else:
267 267 # OK, we've waited long enough.
268 268 if self.has_kernel:
269 269 self._kill_kernel()
270 270
271 271 def cleanup(self, connection_file=True):
272 272 """Clean up resources when the kernel is shut down"""
273 273 if connection_file:
274 274 self.cleanup_connection_file()
275 275
276 276 self.cleanup_ipc_files()
277 277 self._close_control_socket()
278 278
279 279 def shutdown_kernel(self, now=False, restart=False):
280 280 """Attempts to the stop the kernel process cleanly.
281 281
282 282 This attempts to shutdown the kernels cleanly by:
283 283
284 284 1. Sending it a shutdown message over the shell channel.
285 285 2. If that fails, the kernel is shutdown forcibly by sending it
286 286 a signal.
287 287
288 288 Parameters
289 289 ----------
290 290 now : bool
291 291 Should the kernel be forcible killed *now*. This skips the
292 292 first, nice shutdown attempt.
293 293 restart: bool
294 294 Will this kernel be restarted after it is shutdown. When this
295 295 is True, connection files will not be cleaned up.
296 296 """
297 297 # Stop monitoring for restarting while we shutdown.
298 298 self.stop_restarter()
299 299
300 300 if now:
301 301 self._kill_kernel()
302 302 else:
303 303 self.request_shutdown(restart=restart)
304 304 # Don't send any additional kernel kill messages immediately, to give
305 305 # the kernel a chance to properly execute shutdown actions. Wait for at
306 306 # most 1s, checking every 0.1s.
307 307 self.finish_shutdown()
308 308
309 309 self.cleanup(connection_file=not restart)
310 310
311 311 def restart_kernel(self, now=False, **kw):
312 312 """Restarts a kernel with the arguments that were used to launch it.
313 313
314 314 If the old kernel was launched with random ports, the same ports will be
315 315 used for the new kernel. The same connection file is used again.
316 316
317 317 Parameters
318 318 ----------
319 319 now : bool, optional
320 320 If True, the kernel is forcefully restarted *immediately*, without
321 321 having a chance to do any cleanup action. Otherwise the kernel is
322 322 given 1s to clean up before a forceful restart is issued.
323 323
324 324 In all cases the kernel is restarted, the only difference is whether
325 325 it is given a chance to perform a clean shutdown or not.
326 326
327 327 **kw : optional
328 328 Any options specified here will overwrite those used to launch the
329 329 kernel.
330 330 """
331 331 if self._launch_args is None:
332 332 raise RuntimeError("Cannot restart the kernel. "
333 333 "No previous call to 'start_kernel'.")
334 334 else:
335 335 # Stop currently running kernel.
336 336 self.shutdown_kernel(now=now, restart=True)
337 337
338 338 # Start new kernel.
339 339 self._launch_args.update(kw)
340 340 self.start_kernel(**self._launch_args)
341 341
342 342 @property
343 343 def has_kernel(self):
344 344 """Has a kernel been started that we are managing."""
345 345 return self.kernel is not None
346 346
347 347 def _kill_kernel(self):
348 348 """Kill the running kernel.
349 349
350 350 This is a private method, callers should use shutdown_kernel(now=True).
351 351 """
352 352 if self.has_kernel:
353 353
354 354 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
355 355 # TerminateProcess() on Win32).
356 356 try:
357 357 self.kernel.kill()
358 358 except OSError as e:
359 359 # In Windows, we will get an Access Denied error if the process
360 360 # has already terminated. Ignore it.
361 361 if sys.platform == 'win32':
362 362 if e.winerror != 5:
363 363 raise
364 364 # On Unix, we may get an ESRCH error if the process has already
365 365 # terminated. Ignore it.
366 366 else:
367 367 from errno import ESRCH
368 368 if e.errno != ESRCH:
369 369 raise
370 370
371 371 # Block until the kernel terminates.
372 372 self.kernel.wait()
373 373 self.kernel = None
374 374 else:
375 375 raise RuntimeError("Cannot kill kernel. No kernel is running!")
376 376
377 377 def interrupt_kernel(self):
378 378 """Interrupts the kernel by sending it a signal.
379 379
380 380 Unlike ``signal_kernel``, this operation is well supported on all
381 381 platforms.
382 382 """
383 383 if self.has_kernel:
384 384 if sys.platform == 'win32':
385 385 from .zmq.parentpoller import ParentPollerWindows as Poller
386 386 Poller.send_interrupt(self.kernel.win32_interrupt_event)
387 387 else:
388 388 self.kernel.send_signal(signal.SIGINT)
389 389 else:
390 390 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
391 391
392 392 def signal_kernel(self, signum):
393 393 """Sends a signal to the kernel.
394 394
395 395 Note that since only SIGTERM is supported on Windows, this function is
396 396 only useful on Unix systems.
397 397 """
398 398 if self.has_kernel:
399 399 self.kernel.send_signal(signum)
400 400 else:
401 401 raise RuntimeError("Cannot signal kernel. No kernel is running!")
402 402
403 403 def is_alive(self):
404 404 """Is the kernel process still running?"""
405 405 if self.has_kernel:
406 406 if self.kernel.poll() is None:
407 407 return True
408 408 else:
409 409 return False
410 410 else:
411 411 # we don't have a kernel
412 412 return False
413 413
414 414
415 415 KernelManagerABC.register(KernelManager)
416 416
General Comments 0
You need to be logged in to leave comments. Login now