##// END OF EJS Templates
Merge pull request #7099 from minrk/parent-env...
Thomas Kluyver -
r19268:9c06d407 merge
parent child Browse files
Show More
@@ -1,258 +1,226 b''
1 """Utilities for launching kernels
1 """Utilities for launching kernels
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import sys
8 import sys
9 from subprocess import Popen, PIPE
9 from subprocess import Popen, PIPE
10
10
11 from IPython.utils.encoding import getdefaultencoding
11 from IPython.utils.encoding import getdefaultencoding
12 from IPython.utils.py3compat import cast_bytes_py2
12 from IPython.utils.py3compat import cast_bytes_py2
13
13
14
14
15 def swallow_argv(argv, aliases=None, flags=None):
15 def swallow_argv(argv, aliases=None, flags=None):
16 """strip frontend-specific aliases and flags from an argument list
16 """strip frontend-specific aliases and flags from an argument list
17
17
18 For use primarily in frontend apps that want to pass a subset of command-line
18 For use primarily in frontend apps that want to pass a subset of command-line
19 arguments through to a subprocess, where frontend-specific flags and aliases
19 arguments through to a subprocess, where frontend-specific flags and aliases
20 should be removed from the list.
20 should be removed from the list.
21
21
22 Parameters
22 Parameters
23 ----------
23 ----------
24
24
25 argv : list(str)
25 argv : list(str)
26 The starting argv, to be filtered
26 The starting argv, to be filtered
27 aliases : container of aliases (dict, list, set, etc.)
27 aliases : container of aliases (dict, list, set, etc.)
28 The frontend-specific aliases to be removed
28 The frontend-specific aliases to be removed
29 flags : container of flags (dict, list, set, etc.)
29 flags : container of flags (dict, list, set, etc.)
30 The frontend-specific flags to be removed
30 The frontend-specific flags to be removed
31
31
32 Returns
32 Returns
33 -------
33 -------
34
34
35 argv : list(str)
35 argv : list(str)
36 The argv list, excluding flags and aliases that have been stripped
36 The argv list, excluding flags and aliases that have been stripped
37 """
37 """
38
38
39 if aliases is None:
39 if aliases is None:
40 aliases = set()
40 aliases = set()
41 if flags is None:
41 if flags is None:
42 flags = set()
42 flags = set()
43
43
44 stripped = list(argv) # copy
44 stripped = list(argv) # copy
45
45
46 swallow_next = False
46 swallow_next = False
47 was_flag = False
47 was_flag = False
48 for a in argv:
48 for a in argv:
49 if a == '--':
49 if a == '--':
50 break
50 break
51 if swallow_next:
51 if swallow_next:
52 swallow_next = False
52 swallow_next = False
53 # last arg was an alias, remove the next one
53 # last arg was an alias, remove the next one
54 # *unless* the last alias has a no-arg flag version, in which
54 # *unless* the last alias has a no-arg flag version, in which
55 # case, don't swallow the next arg if it's also a flag:
55 # case, don't swallow the next arg if it's also a flag:
56 if not (was_flag and a.startswith('-')):
56 if not (was_flag and a.startswith('-')):
57 stripped.remove(a)
57 stripped.remove(a)
58 continue
58 continue
59 if a.startswith('-'):
59 if a.startswith('-'):
60 split = a.lstrip('-').split('=')
60 split = a.lstrip('-').split('=')
61 name = split[0]
61 name = split[0]
62 # we use startswith because argparse accepts any arg to be specified
62 # we use startswith because argparse accepts any arg to be specified
63 # by any leading section, as long as it is unique,
63 # by any leading section, as long as it is unique,
64 # so `--no-br` means `--no-browser` in the notebook, etc.
64 # so `--no-br` means `--no-browser` in the notebook, etc.
65 if any(alias.startswith(name) for alias in aliases):
65 if any(alias.startswith(name) for alias in aliases):
66 stripped.remove(a)
66 stripped.remove(a)
67 if len(split) == 1:
67 if len(split) == 1:
68 # alias passed with arg via space
68 # alias passed with arg via space
69 swallow_next = True
69 swallow_next = True
70 # could have been a flag that matches an alias, e.g. `existing`
70 # could have been a flag that matches an alias, e.g. `existing`
71 # in which case, we might not swallow the next arg
71 # in which case, we might not swallow the next arg
72 was_flag = name in flags
72 was_flag = name in flags
73 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
73 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
74 # strip flag, but don't swallow next, as flags don't take args
74 # strip flag, but don't swallow next, as flags don't take args
75 stripped.remove(a)
75 stripped.remove(a)
76
76
77 # return shortened list
77 # return shortened list
78 return stripped
78 return stripped
79
79
80
80
81 def make_ipkernel_cmd(mod='IPython.kernel', executable=None, extra_arguments=[], **kw):
81 def make_ipkernel_cmd(mod='IPython.kernel', executable=None, extra_arguments=[], **kw):
82 """Build Popen command list for launching an IPython kernel.
82 """Build Popen command list for launching an IPython kernel.
83
83
84 Parameters
84 Parameters
85 ----------
85 ----------
86 mod : str, optional (default 'IPython.kernel')
86 mod : str, optional (default 'IPython.kernel')
87 A string of an IPython module whose __main__ starts an IPython kernel
87 A string of an IPython module whose __main__ starts an IPython kernel
88
88
89 executable : str, optional (default sys.executable)
89 executable : str, optional (default sys.executable)
90 The Python executable to use for the kernel process.
90 The Python executable to use for the kernel process.
91
91
92 extra_arguments : list, optional
92 extra_arguments : list, optional
93 A list of extra arguments to pass when executing the launch code.
93 A list of extra arguments to pass when executing the launch code.
94
94
95 Returns
95 Returns
96 -------
96 -------
97
97
98 A Popen command list
98 A Popen command list
99 """
99 """
100 if executable is None:
100 if executable is None:
101 executable = sys.executable
101 executable = sys.executable
102 arguments = [ executable, '-m', mod, '-f', '{connection_file}' ]
102 arguments = [ executable, '-m', mod, '-f', '{connection_file}' ]
103 arguments.extend(extra_arguments)
103 arguments.extend(extra_arguments)
104
104
105 if sys.platform == 'win32':
106
107 # If the kernel is running on pythonw and stdout/stderr are not been
108 # re-directed, it will crash when more than 4KB of data is written to
109 # stdout or stderr. This is a bug that has been with Python for a very
110 # long time; see http://bugs.python.org/issue706263.
111 # A cleaner solution to this problem would be to pass os.devnull to
112 # Popen directly. Unfortunately, that does not work.
113 if executable.endswith('pythonw.exe'):
114 arguments.append('--no-stdout')
115 arguments.append('--no-stderr')
116
117 return arguments
105 return arguments
118
106
119
107
120 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
108 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
121 independent=False,
109 independent=False,
122 cwd=None, ipython_kernel=True,
110 cwd=None,
123 **kw
111 **kw
124 ):
112 ):
125 """ Launches a localhost kernel, binding to the specified ports.
113 """ Launches a localhost kernel, binding to the specified ports.
126
114
127 Parameters
115 Parameters
128 ----------
116 ----------
129 cmd : Popen list,
117 cmd : Popen list,
130 A string of Python code that imports and executes a kernel entry point.
118 A string of Python code that imports and executes a kernel entry point.
131
119
132 stdin, stdout, stderr : optional (default None)
120 stdin, stdout, stderr : optional (default None)
133 Standards streams, as defined in subprocess.Popen.
121 Standards streams, as defined in subprocess.Popen.
134
122
135 independent : bool, optional (default False)
123 independent : bool, optional (default False)
136 If set, the kernel process is guaranteed to survive if this process
124 If set, the kernel process is guaranteed to survive if this process
137 dies. If not set, an effort is made to ensure that the kernel is killed
125 dies. If not set, an effort is made to ensure that the kernel is killed
138 when this process dies. Note that in this case it is still good practice
126 when this process dies. Note that in this case it is still good practice
139 to kill kernels manually before exiting.
127 to kill kernels manually before exiting.
140
128
141 cwd : path, optional
129 cwd : path, optional
142 The working dir of the kernel process (default: cwd of this process).
130 The working dir of the kernel process (default: cwd of this process).
143
131
144 ipython_kernel : bool, optional
145 Whether the kernel is an official IPython one,
146 and should get a bit of special treatment.
147
148 Returns
132 Returns
149 -------
133 -------
150
134
151 Popen instance for the kernel subprocess
135 Popen instance for the kernel subprocess
152 """
136 """
153
137
154 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
138 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
155 # are invalid. Unfortunately, there is in general no way to detect whether
139 # are invalid. Unfortunately, there is in general no way to detect whether
156 # they are valid. The following two blocks redirect them to (temporary)
140 # they are valid. The following two blocks redirect them to (temporary)
157 # pipes in certain important cases.
141 # pipes in certain important cases.
158
142
159 # If this process has been backgrounded, our stdin is invalid. Since there
143 # If this process has been backgrounded, our stdin is invalid. Since there
160 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
144 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
161 # place this one safe and always redirect.
145 # place this one safe and always redirect.
162 redirect_in = True
146 redirect_in = True
163 _stdin = PIPE if stdin is None else stdin
147 _stdin = PIPE if stdin is None else stdin
164
148
165 # If this process in running on pythonw, we know that stdin, stdout, and
149 # If this process in running on pythonw, we know that stdin, stdout, and
166 # stderr are all invalid.
150 # stderr are all invalid.
167 redirect_out = sys.executable.endswith('pythonw.exe')
151 redirect_out = sys.executable.endswith('pythonw.exe')
168 if redirect_out:
152 if redirect_out:
169 _stdout = PIPE if stdout is None else stdout
153 blackhole = open(os.devnull, 'w')
170 _stderr = PIPE if stderr is None else stderr
154 _stdout = blackhole if stdout is None else stdout
155 _stderr = blackhole if stderr is None else stderr
171 else:
156 else:
172 _stdout, _stderr = stdout, stderr
157 _stdout, _stderr = stdout, stderr
173
158
174 env = env if (env is not None) else os.environ.copy()
159 env = env if (env is not None) else os.environ.copy()
175
160
176 encoding = getdefaultencoding(prefer_stream=False)
161 encoding = getdefaultencoding(prefer_stream=False)
162 kwargs = dict(
163 stdin=_stdin,
164 stdout=_stdout,
165 stderr=_stderr,
166 cwd=cwd,
167 env=env,
168 )
177
169
178 # Spawn a kernel.
170 # Spawn a kernel.
179 if sys.platform == 'win32':
171 if sys.platform == 'win32':
180 # Popen on Python 2 on Windows cannot handle unicode args or cwd
172 # Popen on Python 2 on Windows cannot handle unicode args or cwd
181 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
173 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
182 if cwd:
174 if cwd:
183 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
175 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
176 kwargs['cwd'] = cwd
184
177
185 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
178 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
186 # Create a Win32 event for interrupting the kernel.
179 # Create a Win32 event for interrupting the kernel
180 # and store it in an environment variable.
187 interrupt_event = ParentPollerWindows.create_interrupt_event()
181 interrupt_event = ParentPollerWindows.create_interrupt_event()
188 # Store this in an environment variable for third party kernels, but at
182 env["JPY_INTERRUPT_EVENT"] = str(interrupt_event)
189 # present, our own kernel expects this as a command line argument.
183 # deprecated old env name:
190 env["IPY_INTERRUPT_EVENT"] = str(interrupt_event)
184 env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"]
191 if ipython_kernel:
185
192 cmd += [ '--interrupt=%i' % interrupt_event ]
186 try:
193
187 from _winapi import DuplicateHandle, GetCurrentProcess, \
194 # If the kernel is running on pythonw and stdout/stderr are not been
188 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
195 # re-directed, it will crash when more than 4KB of data is written to
189 except:
196 # stdout or stderr. This is a bug that has been with Python for a very
190 from _subprocess import DuplicateHandle, GetCurrentProcess, \
197 # long time; see http://bugs.python.org/issue706263.
191 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
198 # A cleaner solution to this problem would be to pass os.devnull to
192 # Launch the kernel process
199 # Popen directly. Unfortunately, that does not work.
200 if cmd[0].endswith('pythonw.exe'):
201 if stdout is None:
202 cmd.append('--no-stdout')
203 if stderr is None:
204 cmd.append('--no-stderr')
205
206 # Launch the kernel process.
207 if independent:
193 if independent:
208 proc = Popen(cmd,
194 kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
209 creationflags=512, # CREATE_NEW_PROCESS_GROUP
210 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
211 else:
195 else:
212 if ipython_kernel:
196 pid = GetCurrentProcess()
213 try:
197 handle = DuplicateHandle(pid, pid, pid, 0,
214 from _winapi import DuplicateHandle, GetCurrentProcess, \
198 True, # Inheritable by new processes.
215 DUPLICATE_SAME_ACCESS
199 DUPLICATE_SAME_ACCESS)
216 except:
200 env['JPY_PARENT_PID'] = str(handle)
217 from _subprocess import DuplicateHandle, GetCurrentProcess, \
201
218 DUPLICATE_SAME_ACCESS
202 proc = Popen(cmd, **kwargs)
219 pid = GetCurrentProcess()
220 handle = DuplicateHandle(pid, pid, pid, 0,
221 True, # Inheritable by new processes.
222 DUPLICATE_SAME_ACCESS)
223 cmd +=[ '--parent=%i' % handle ]
224
225
226 proc = Popen(cmd,
227 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
228
203
229 # Attach the interrupt event to the Popen objet so it can be used later.
204 # Attach the interrupt event to the Popen objet so it can be used later.
230 proc.win32_interrupt_event = interrupt_event
205 proc.win32_interrupt_event = interrupt_event
231
206
232 else:
207 else:
233 if independent:
208 if independent:
234 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
209 kwargs['preexec_fn'] = lambda: os.setsid()
235 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
236 else:
210 else:
237 if ipython_kernel:
211 env['JPY_PARENT_PID'] = str(os.getpid())
238 cmd += ['--parent=1']
212
239 proc = Popen(cmd,
213 proc = Popen(cmd, **kwargs)
240 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
241
214
242 # Clean up pipes created to work around Popen bug.
215 # Clean up pipes created to work around Popen bug.
243 if redirect_in:
216 if redirect_in:
244 if stdin is None:
217 if stdin is None:
245 proc.stdin.close()
218 proc.stdin.close()
246 if redirect_out:
247 if stdout is None:
248 proc.stdout.close()
249 if stderr is None:
250 proc.stderr.close()
251
219
252 return proc
220 return proc
253
221
254 __all__ = [
222 __all__ = [
255 'swallow_argv',
223 'swallow_argv',
256 'make_ipkernel_cmd',
224 'make_ipkernel_cmd',
257 'launch_kernel',
225 'launch_kernel',
258 ]
226 ]
@@ -1,452 +1,451 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15 try:
15 try:
16 from queue import Empty # Py 3
16 from queue import Empty # Py 3
17 except ImportError:
17 except ImportError:
18 from Queue import Empty # Py 2
18 from Queue import Empty # Py 2
19
19
20 import zmq
20 import zmq
21
21
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
24 from IPython.utils.path import get_ipython_dir
24 from IPython.utils.path import get_ipython_dir
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
27 )
27 )
28 from IPython.kernel import (
28 from IPython.kernel import (
29 launch_kernel,
29 launch_kernel,
30 kernelspec,
30 kernelspec,
31 )
31 )
32 from .connect import ConnectionFileMixin
32 from .connect import ConnectionFileMixin
33 from .zmq.session import Session
33 from .zmq.session import Session
34 from .managerabc import (
34 from .managerabc import (
35 KernelManagerABC
35 KernelManagerABC
36 )
36 )
37
37
38
38
39 class KernelManager(ConnectionFileMixin):
39 class KernelManager(ConnectionFileMixin):
40 """Manages a single kernel in a subprocess on this host.
40 """Manages a single kernel in a subprocess on this host.
41
41
42 This version starts kernels with Popen.
42 This version starts kernels with Popen.
43 """
43 """
44
44
45 # The PyZMQ Context to use for communication with the kernel.
45 # The PyZMQ Context to use for communication with the kernel.
46 context = Instance(zmq.Context)
46 context = Instance(zmq.Context)
47 def _context_default(self):
47 def _context_default(self):
48 return zmq.Context.instance()
48 return zmq.Context.instance()
49
49
50 # the class to create with our `client` method
50 # the class to create with our `client` method
51 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
51 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
52 client_factory = Type()
52 client_factory = Type()
53 def _client_class_changed(self, name, old, new):
53 def _client_class_changed(self, name, old, new):
54 self.client_factory = import_item(str(new))
54 self.client_factory = import_item(str(new))
55
55
56 # The kernel process with which the KernelManager is communicating.
56 # The kernel process with which the KernelManager is communicating.
57 # generally a Popen instance
57 # generally a Popen instance
58 kernel = Any()
58 kernel = Any()
59
59
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
61
61
62 def _kernel_spec_manager_default(self):
62 def _kernel_spec_manager_default(self):
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
64
64
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
66
66
67 kernel_spec = Instance(kernelspec.KernelSpec)
67 kernel_spec = Instance(kernelspec.KernelSpec)
68
68
69 def _kernel_spec_default(self):
69 def _kernel_spec_default(self):
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
71
71
72 def _kernel_name_changed(self, name, old, new):
72 def _kernel_name_changed(self, name, old, new):
73 if new == 'python':
73 if new == 'python':
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
75 # This triggered another run of this function, so we can exit now
75 # This triggered another run of this function, so we can exit now
76 return
76 return
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
79
79
80 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
81 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
82
82
83 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
84 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
85 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
86 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
87 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
88 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
89 this means that the kernel does not receive the
89 this means that the kernel does not receive the
90 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
91 """
91 """
92 )
92 )
93
93
94 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
96 "start different kernels.")
97 self.ipython_kernel = False
97 self.ipython_kernel = False
98
98
99 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
100
101 ipython_dir = Unicode()
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
103 return get_ipython_dir()
104
104
105 # Protected traits
105 # Protected traits
106 _launch_args = Any()
106 _launch_args = Any()
107 _control_socket = Any()
107 _control_socket = Any()
108
108
109 _restarter = Any()
109 _restarter = Any()
110
110
111 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
113 )
113 )
114
114
115 def __del__(self):
115 def __del__(self):
116 self._close_control_socket()
116 self._close_control_socket()
117 self.cleanup_connection_file()
117 self.cleanup_connection_file()
118
118
119 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
120 # Kernel restarter
120 # Kernel restarter
121 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
122
122
123 def start_restarter(self):
123 def start_restarter(self):
124 pass
124 pass
125
125
126 def stop_restarter(self):
126 def stop_restarter(self):
127 pass
127 pass
128
128
129 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
131 if self._restarter is None:
132 return
132 return
133 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
134
134
135 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
137 if self._restarter is None:
138 return
138 return
139 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
140
140
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
144
144
145 def client(self, **kwargs):
145 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
147 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
149
149
150 kw = {}
150 kw = {}
151 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
152 kw.update(dict(
152 kw.update(dict(
153 connection_file=self.connection_file,
153 connection_file=self.connection_file,
154 session=self.session,
154 session=self.session,
155 parent=self,
155 parent=self,
156 ))
156 ))
157
157
158 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
159 kw.update(kwargs)
159 kw.update(kwargs)
160 return self.client_factory(**kw)
160 return self.client_factory(**kw)
161
161
162 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
163 # Kernel management
163 # Kernel management
164 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
165
165
166 def format_kernel_cmd(self, extra_arguments=None):
166 def format_kernel_cmd(self, extra_arguments=None):
167 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
168 extra_arguments = extra_arguments or []
168 extra_arguments = extra_arguments or []
169 if self.kernel_cmd:
169 if self.kernel_cmd:
170 cmd = self.kernel_cmd + extra_arguments
170 cmd = self.kernel_cmd + extra_arguments
171 else:
171 else:
172 cmd = self.kernel_spec.argv + extra_arguments
172 cmd = self.kernel_spec.argv + extra_arguments
173
173
174 ns = dict(connection_file=self.connection_file)
174 ns = dict(connection_file=self.connection_file)
175 ns.update(self._launch_args)
175 ns.update(self._launch_args)
176
176
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
178 def from_ns(match):
178 def from_ns(match):
179 """Get the key out of ns if it's there, otherwise no change."""
179 """Get the key out of ns if it's there, otherwise no change."""
180 return ns.get(match.group(1), match.group())
180 return ns.get(match.group(1), match.group())
181
181
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
183
183
184 def _launch_kernel(self, kernel_cmd, **kw):
184 def _launch_kernel(self, kernel_cmd, **kw):
185 """actually launch the kernel
185 """actually launch the kernel
186
186
187 override in a subclass to launch kernel subprocesses differently
187 override in a subclass to launch kernel subprocesses differently
188 """
188 """
189 return launch_kernel(kernel_cmd, **kw)
189 return launch_kernel(kernel_cmd, **kw)
190
190
191 # Control socket used for polite kernel shutdown
191 # Control socket used for polite kernel shutdown
192
192
193 def _connect_control_socket(self):
193 def _connect_control_socket(self):
194 if self._control_socket is None:
194 if self._control_socket is None:
195 self._control_socket = self.connect_control()
195 self._control_socket = self.connect_control()
196 self._control_socket.linger = 100
196 self._control_socket.linger = 100
197
197
198 def _close_control_socket(self):
198 def _close_control_socket(self):
199 if self._control_socket is None:
199 if self._control_socket is None:
200 return
200 return
201 self._control_socket.close()
201 self._control_socket.close()
202 self._control_socket = None
202 self._control_socket = None
203
203
204 def start_kernel(self, **kw):
204 def start_kernel(self, **kw):
205 """Starts a kernel on this host in a separate process.
205 """Starts a kernel on this host in a separate process.
206
206
207 If random ports (port=0) are being used, this method must be called
207 If random ports (port=0) are being used, this method must be called
208 before the channels are created.
208 before the channels are created.
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 **kw : optional
212 **kw : optional
213 keyword arguments that are passed down to build the kernel_cmd
213 keyword arguments that are passed down to build the kernel_cmd
214 and launching the kernel (e.g. Popen kwargs).
214 and launching the kernel (e.g. Popen kwargs).
215 """
215 """
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
217 raise RuntimeError("Can only launch a kernel on a local interface. "
217 raise RuntimeError("Can only launch a kernel on a local interface. "
218 "Make sure that the '*_address' attributes are "
218 "Make sure that the '*_address' attributes are "
219 "configured properly. "
219 "configured properly. "
220 "Currently valid addresses are: %s" % local_ips()
220 "Currently valid addresses are: %s" % local_ips()
221 )
221 )
222
222
223 # write connection file / get default ports
223 # write connection file / get default ports
224 self.write_connection_file()
224 self.write_connection_file()
225
225
226 # save kwargs for use in restart
226 # save kwargs for use in restart
227 self._launch_args = kw.copy()
227 self._launch_args = kw.copy()
228 # build the Popen cmd
228 # build the Popen cmd
229 extra_arguments = kw.pop('extra_arguments', [])
229 extra_arguments = kw.pop('extra_arguments', [])
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
231 if self.kernel_cmd:
231 if self.kernel_cmd:
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
233 env = os.environ
233 env = os.environ
234 else:
234 else:
235 # Environment variables from kernel spec are added to os.environ
235 # Environment variables from kernel spec are added to os.environ
236 env = os.environ.copy()
236 env = os.environ.copy()
237 env.update(self.kernel_spec.env or {})
237 env.update(self.kernel_spec.env or {})
238 # launch the kernel subprocess
238 # launch the kernel subprocess
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
240 ipython_kernel=self.ipython_kernel,
241 **kw)
240 **kw)
242 self.start_restarter()
241 self.start_restarter()
243 self._connect_control_socket()
242 self._connect_control_socket()
244
243
245 def request_shutdown(self, restart=False):
244 def request_shutdown(self, restart=False):
246 """Send a shutdown request via control channel
245 """Send a shutdown request via control channel
247
246
248 On Windows, this just kills kernels instead, because the shutdown
247 On Windows, this just kills kernels instead, because the shutdown
249 messages don't work.
248 messages don't work.
250 """
249 """
251 content = dict(restart=restart)
250 content = dict(restart=restart)
252 msg = self.session.msg("shutdown_request", content=content)
251 msg = self.session.msg("shutdown_request", content=content)
253 self.session.send(self._control_socket, msg)
252 self.session.send(self._control_socket, msg)
254
253
255 def finish_shutdown(self, waittime=1, pollinterval=0.1):
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
256 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
257
256
258 This does not send shutdown requests - use :meth:`request_shutdown`
257 This does not send shutdown requests - use :meth:`request_shutdown`
259 first.
258 first.
260 """
259 """
261 for i in range(int(waittime/pollinterval)):
260 for i in range(int(waittime/pollinterval)):
262 if self.is_alive():
261 if self.is_alive():
263 time.sleep(pollinterval)
262 time.sleep(pollinterval)
264 else:
263 else:
265 break
264 break
266 else:
265 else:
267 # OK, we've waited long enough.
266 # OK, we've waited long enough.
268 if self.has_kernel:
267 if self.has_kernel:
269 self._kill_kernel()
268 self._kill_kernel()
270
269
271 def cleanup(self, connection_file=True):
270 def cleanup(self, connection_file=True):
272 """Clean up resources when the kernel is shut down"""
271 """Clean up resources when the kernel is shut down"""
273 if connection_file:
272 if connection_file:
274 self.cleanup_connection_file()
273 self.cleanup_connection_file()
275
274
276 self.cleanup_ipc_files()
275 self.cleanup_ipc_files()
277 self._close_control_socket()
276 self._close_control_socket()
278
277
279 def shutdown_kernel(self, now=False, restart=False):
278 def shutdown_kernel(self, now=False, restart=False):
280 """Attempts to the stop the kernel process cleanly.
279 """Attempts to the stop the kernel process cleanly.
281
280
282 This attempts to shutdown the kernels cleanly by:
281 This attempts to shutdown the kernels cleanly by:
283
282
284 1. Sending it a shutdown message over the shell channel.
283 1. Sending it a shutdown message over the shell channel.
285 2. If that fails, the kernel is shutdown forcibly by sending it
284 2. If that fails, the kernel is shutdown forcibly by sending it
286 a signal.
285 a signal.
287
286
288 Parameters
287 Parameters
289 ----------
288 ----------
290 now : bool
289 now : bool
291 Should the kernel be forcible killed *now*. This skips the
290 Should the kernel be forcible killed *now*. This skips the
292 first, nice shutdown attempt.
291 first, nice shutdown attempt.
293 restart: bool
292 restart: bool
294 Will this kernel be restarted after it is shutdown. When this
293 Will this kernel be restarted after it is shutdown. When this
295 is True, connection files will not be cleaned up.
294 is True, connection files will not be cleaned up.
296 """
295 """
297 # Stop monitoring for restarting while we shutdown.
296 # Stop monitoring for restarting while we shutdown.
298 self.stop_restarter()
297 self.stop_restarter()
299
298
300 if now:
299 if now:
301 self._kill_kernel()
300 self._kill_kernel()
302 else:
301 else:
303 self.request_shutdown(restart=restart)
302 self.request_shutdown(restart=restart)
304 # Don't send any additional kernel kill messages immediately, to give
303 # Don't send any additional kernel kill messages immediately, to give
305 # the kernel a chance to properly execute shutdown actions. Wait for at
304 # the kernel a chance to properly execute shutdown actions. Wait for at
306 # most 1s, checking every 0.1s.
305 # most 1s, checking every 0.1s.
307 self.finish_shutdown()
306 self.finish_shutdown()
308
307
309 self.cleanup(connection_file=not restart)
308 self.cleanup(connection_file=not restart)
310
309
311 def restart_kernel(self, now=False, **kw):
310 def restart_kernel(self, now=False, **kw):
312 """Restarts a kernel with the arguments that were used to launch it.
311 """Restarts a kernel with the arguments that were used to launch it.
313
312
314 If the old kernel was launched with random ports, the same ports will be
313 If the old kernel was launched with random ports, the same ports will be
315 used for the new kernel. The same connection file is used again.
314 used for the new kernel. The same connection file is used again.
316
315
317 Parameters
316 Parameters
318 ----------
317 ----------
319 now : bool, optional
318 now : bool, optional
320 If True, the kernel is forcefully restarted *immediately*, without
319 If True, the kernel is forcefully restarted *immediately*, without
321 having a chance to do any cleanup action. Otherwise the kernel is
320 having a chance to do any cleanup action. Otherwise the kernel is
322 given 1s to clean up before a forceful restart is issued.
321 given 1s to clean up before a forceful restart is issued.
323
322
324 In all cases the kernel is restarted, the only difference is whether
323 In all cases the kernel is restarted, the only difference is whether
325 it is given a chance to perform a clean shutdown or not.
324 it is given a chance to perform a clean shutdown or not.
326
325
327 **kw : optional
326 **kw : optional
328 Any options specified here will overwrite those used to launch the
327 Any options specified here will overwrite those used to launch the
329 kernel.
328 kernel.
330 """
329 """
331 if self._launch_args is None:
330 if self._launch_args is None:
332 raise RuntimeError("Cannot restart the kernel. "
331 raise RuntimeError("Cannot restart the kernel. "
333 "No previous call to 'start_kernel'.")
332 "No previous call to 'start_kernel'.")
334 else:
333 else:
335 # Stop currently running kernel.
334 # Stop currently running kernel.
336 self.shutdown_kernel(now=now, restart=True)
335 self.shutdown_kernel(now=now, restart=True)
337
336
338 # Start new kernel.
337 # Start new kernel.
339 self._launch_args.update(kw)
338 self._launch_args.update(kw)
340 self.start_kernel(**self._launch_args)
339 self.start_kernel(**self._launch_args)
341
340
342 @property
341 @property
343 def has_kernel(self):
342 def has_kernel(self):
344 """Has a kernel been started that we are managing."""
343 """Has a kernel been started that we are managing."""
345 return self.kernel is not None
344 return self.kernel is not None
346
345
347 def _kill_kernel(self):
346 def _kill_kernel(self):
348 """Kill the running kernel.
347 """Kill the running kernel.
349
348
350 This is a private method, callers should use shutdown_kernel(now=True).
349 This is a private method, callers should use shutdown_kernel(now=True).
351 """
350 """
352 if self.has_kernel:
351 if self.has_kernel:
353
352
354 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
355 # TerminateProcess() on Win32).
354 # TerminateProcess() on Win32).
356 try:
355 try:
357 self.kernel.kill()
356 self.kernel.kill()
358 except OSError as e:
357 except OSError as e:
359 # In Windows, we will get an Access Denied error if the process
358 # In Windows, we will get an Access Denied error if the process
360 # has already terminated. Ignore it.
359 # has already terminated. Ignore it.
361 if sys.platform == 'win32':
360 if sys.platform == 'win32':
362 if e.winerror != 5:
361 if e.winerror != 5:
363 raise
362 raise
364 # On Unix, we may get an ESRCH error if the process has already
363 # On Unix, we may get an ESRCH error if the process has already
365 # terminated. Ignore it.
364 # terminated. Ignore it.
366 else:
365 else:
367 from errno import ESRCH
366 from errno import ESRCH
368 if e.errno != ESRCH:
367 if e.errno != ESRCH:
369 raise
368 raise
370
369
371 # Block until the kernel terminates.
370 # Block until the kernel terminates.
372 self.kernel.wait()
371 self.kernel.wait()
373 self.kernel = None
372 self.kernel = None
374 else:
373 else:
375 raise RuntimeError("Cannot kill kernel. No kernel is running!")
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
376
375
377 def interrupt_kernel(self):
376 def interrupt_kernel(self):
378 """Interrupts the kernel by sending it a signal.
377 """Interrupts the kernel by sending it a signal.
379
378
380 Unlike ``signal_kernel``, this operation is well supported on all
379 Unlike ``signal_kernel``, this operation is well supported on all
381 platforms.
380 platforms.
382 """
381 """
383 if self.has_kernel:
382 if self.has_kernel:
384 if sys.platform == 'win32':
383 if sys.platform == 'win32':
385 from .zmq.parentpoller import ParentPollerWindows as Poller
384 from .zmq.parentpoller import ParentPollerWindows as Poller
386 Poller.send_interrupt(self.kernel.win32_interrupt_event)
385 Poller.send_interrupt(self.kernel.win32_interrupt_event)
387 else:
386 else:
388 self.kernel.send_signal(signal.SIGINT)
387 self.kernel.send_signal(signal.SIGINT)
389 else:
388 else:
390 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
391
390
392 def signal_kernel(self, signum):
391 def signal_kernel(self, signum):
393 """Sends a signal to the kernel.
392 """Sends a signal to the kernel.
394
393
395 Note that since only SIGTERM is supported on Windows, this function is
394 Note that since only SIGTERM is supported on Windows, this function is
396 only useful on Unix systems.
395 only useful on Unix systems.
397 """
396 """
398 if self.has_kernel:
397 if self.has_kernel:
399 self.kernel.send_signal(signum)
398 self.kernel.send_signal(signum)
400 else:
399 else:
401 raise RuntimeError("Cannot signal kernel. No kernel is running!")
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
402
401
403 def is_alive(self):
402 def is_alive(self):
404 """Is the kernel process still running?"""
403 """Is the kernel process still running?"""
405 if self.has_kernel:
404 if self.has_kernel:
406 if self.kernel.poll() is None:
405 if self.kernel.poll() is None:
407 return True
406 return True
408 else:
407 else:
409 return False
408 return False
410 else:
409 else:
411 # we don't have a kernel
410 # we don't have a kernel
412 return False
411 return False
413
412
414
413
415 KernelManagerABC.register(KernelManager)
414 KernelManagerABC.register(KernelManager)
416
415
417
416
418 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
419 """Start a new kernel, and return its Manager and Client"""
418 """Start a new kernel, and return its Manager and Client"""
420 km = KernelManager(kernel_name=kernel_name)
419 km = KernelManager(kernel_name=kernel_name)
421 km.start_kernel(**kwargs)
420 km.start_kernel(**kwargs)
422 kc = km.client()
421 kc = km.client()
423 kc.start_channels()
422 kc.start_channels()
424
423
425 kc.kernel_info()
424 kc.kernel_info()
426 kc.get_shell_msg(block=True, timeout=startup_timeout)
425 kc.get_shell_msg(block=True, timeout=startup_timeout)
427
426
428 # Flush channels
427 # Flush channels
429 for channel in (kc.shell_channel, kc.iopub_channel):
428 for channel in (kc.shell_channel, kc.iopub_channel):
430 while True:
429 while True:
431 try:
430 try:
432 channel.get_msg(block=True, timeout=0.1)
431 channel.get_msg(block=True, timeout=0.1)
433 except Empty:
432 except Empty:
434 break
433 break
435 return km, kc
434 return km, kc
436
435
437 @contextmanager
436 @contextmanager
438 def run_kernel(**kwargs):
437 def run_kernel(**kwargs):
439 """Context manager to create a kernel in a subprocess.
438 """Context manager to create a kernel in a subprocess.
440
439
441 The kernel is shut down when the context exits.
440 The kernel is shut down when the context exits.
442
441
443 Returns
442 Returns
444 -------
443 -------
445 kernel_client: connected KernelClient instance
444 kernel_client: connected KernelClient instance
446 """
445 """
447 km, kc = start_new_kernel(**kwargs)
446 km, kc = start_new_kernel(**kwargs)
448 try:
447 try:
449 yield kc
448 yield kc
450 finally:
449 finally:
451 kc.stop_channels()
450 kc.stop_channels()
452 km.shutdown_kernel(now=True)
451 km.shutdown_kernel(now=True)
@@ -1,391 +1,388 b''
1 """An Application for launching a kernel"""
1 """An Application for launching a kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import atexit
8 import atexit
9 import os
9 import os
10 import sys
10 import sys
11 import signal
11 import signal
12
12
13 import zmq
13 import zmq
14 from zmq.eventloop import ioloop
14 from zmq.eventloop import ioloop
15 from zmq.eventloop.zmqstream import ZMQStream
15 from zmq.eventloop.zmqstream import ZMQStream
16
16
17 from IPython.core.ultratb import FormattedTB
17 from IPython.core.ultratb import FormattedTB
18 from IPython.core.application import (
18 from IPython.core.application import (
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
20 )
20 )
21 from IPython.core.profiledir import ProfileDir
21 from IPython.core.profiledir import ProfileDir
22 from IPython.core.shellapp import (
22 from IPython.core.shellapp import (
23 InteractiveShellApp, shell_flags, shell_aliases
23 InteractiveShellApp, shell_flags, shell_aliases
24 )
24 )
25 from IPython.utils import io
25 from IPython.utils import io
26 from IPython.utils.path import filefind
26 from IPython.utils.path import filefind
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
29 )
29 )
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.kernel import write_connection_file
31 from IPython.kernel import write_connection_file
32 from IPython.kernel.connect import ConnectionFileMixin
32 from IPython.kernel.connect import ConnectionFileMixin
33
33
34 # local imports
34 # local imports
35 from .heartbeat import Heartbeat
35 from .heartbeat import Heartbeat
36 from .ipkernel import IPythonKernel
36 from .ipkernel import IPythonKernel
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
38 from .session import (
38 from .session import (
39 Session, session_flags, session_aliases, default_secure,
39 Session, session_flags, session_aliases, default_secure,
40 )
40 )
41 from .zmqshell import ZMQInteractiveShell
41 from .zmqshell import ZMQInteractiveShell
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Flags and Aliases
44 # Flags and Aliases
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 kernel_aliases = dict(base_aliases)
47 kernel_aliases = dict(base_aliases)
48 kernel_aliases.update({
48 kernel_aliases.update({
49 'ip' : 'IPKernelApp.ip',
49 'ip' : 'IPKernelApp.ip',
50 'hb' : 'IPKernelApp.hb_port',
50 'hb' : 'IPKernelApp.hb_port',
51 'shell' : 'IPKernelApp.shell_port',
51 'shell' : 'IPKernelApp.shell_port',
52 'iopub' : 'IPKernelApp.iopub_port',
52 'iopub' : 'IPKernelApp.iopub_port',
53 'stdin' : 'IPKernelApp.stdin_port',
53 'stdin' : 'IPKernelApp.stdin_port',
54 'control' : 'IPKernelApp.control_port',
54 'control' : 'IPKernelApp.control_port',
55 'f' : 'IPKernelApp.connection_file',
55 'f' : 'IPKernelApp.connection_file',
56 'parent': 'IPKernelApp.parent_handle',
57 'transport': 'IPKernelApp.transport',
56 'transport': 'IPKernelApp.transport',
58 })
57 })
59 if sys.platform.startswith('win'):
60 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
61
58
62 kernel_flags = dict(base_flags)
59 kernel_flags = dict(base_flags)
63 kernel_flags.update({
60 kernel_flags.update({
64 'no-stdout' : (
61 'no-stdout' : (
65 {'IPKernelApp' : {'no_stdout' : True}},
62 {'IPKernelApp' : {'no_stdout' : True}},
66 "redirect stdout to the null device"),
63 "redirect stdout to the null device"),
67 'no-stderr' : (
64 'no-stderr' : (
68 {'IPKernelApp' : {'no_stderr' : True}},
65 {'IPKernelApp' : {'no_stderr' : True}},
69 "redirect stderr to the null device"),
66 "redirect stderr to the null device"),
70 'pylab' : (
67 'pylab' : (
71 {'IPKernelApp' : {'pylab' : 'auto'}},
68 {'IPKernelApp' : {'pylab' : 'auto'}},
72 """Pre-load matplotlib and numpy for interactive use with
69 """Pre-load matplotlib and numpy for interactive use with
73 the default matplotlib backend."""),
70 the default matplotlib backend."""),
74 })
71 })
75
72
76 # inherit flags&aliases for any IPython shell apps
73 # inherit flags&aliases for any IPython shell apps
77 kernel_aliases.update(shell_aliases)
74 kernel_aliases.update(shell_aliases)
78 kernel_flags.update(shell_flags)
75 kernel_flags.update(shell_flags)
79
76
80 # inherit flags&aliases for Sessions
77 # inherit flags&aliases for Sessions
81 kernel_aliases.update(session_aliases)
78 kernel_aliases.update(session_aliases)
82 kernel_flags.update(session_flags)
79 kernel_flags.update(session_flags)
83
80
84 _ctrl_c_message = """\
81 _ctrl_c_message = """\
85 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
82 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
86
83
87 To exit, you will have to explicitly quit this process, by either sending
84 To exit, you will have to explicitly quit this process, by either sending
88 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
85 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
89
86
90 To read more about this, see https://github.com/ipython/ipython/issues/2049
87 To read more about this, see https://github.com/ipython/ipython/issues/2049
91
88
92 """
89 """
93
90
94 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
95 # Application class for starting an IPython Kernel
92 # Application class for starting an IPython Kernel
96 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
97
94
98 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
95 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
99 ConnectionFileMixin):
96 ConnectionFileMixin):
100 name='ipython-kernel'
97 name='ipython-kernel'
101 aliases = Dict(kernel_aliases)
98 aliases = Dict(kernel_aliases)
102 flags = Dict(kernel_flags)
99 flags = Dict(kernel_flags)
103 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
100 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
104 # the kernel class, as an importstring
101 # the kernel class, as an importstring
105 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
102 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
106 klass='IPython.kernel.zmq.kernelbase.Kernel',
103 klass='IPython.kernel.zmq.kernelbase.Kernel',
107 help="""The Kernel subclass to be used.
104 help="""The Kernel subclass to be used.
108
105
109 This should allow easy re-use of the IPKernelApp entry point
106 This should allow easy re-use of the IPKernelApp entry point
110 to configure and launch kernels other than IPython's own.
107 to configure and launch kernels other than IPython's own.
111 """)
108 """)
112 kernel = Any()
109 kernel = Any()
113 poller = Any() # don't restrict this even though current pollers are all Threads
110 poller = Any() # don't restrict this even though current pollers are all Threads
114 heartbeat = Instance(Heartbeat)
111 heartbeat = Instance(Heartbeat)
115 ports = Dict()
112 ports = Dict()
116
113
117 # connection info:
114 # connection info:
118
115
119 @property
116 @property
120 def abs_connection_file(self):
117 def abs_connection_file(self):
121 if os.path.basename(self.connection_file) == self.connection_file:
118 if os.path.basename(self.connection_file) == self.connection_file:
122 return os.path.join(self.profile_dir.security_dir, self.connection_file)
119 return os.path.join(self.profile_dir.security_dir, self.connection_file)
123 else:
120 else:
124 return self.connection_file
121 return self.connection_file
125
122
126
123
127 # streams, etc.
124 # streams, etc.
128 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
125 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
129 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
126 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
130 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
127 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
131 config=True, help="The importstring for the OutStream factory")
128 config=True, help="The importstring for the OutStream factory")
132 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
129 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
133 config=True, help="The importstring for the DisplayHook factory")
130 config=True, help="The importstring for the DisplayHook factory")
134
131
135 # polling
132 # polling
136 parent_handle = Integer(0, config=True,
133 parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), config=True,
137 help="""kill this process if its parent dies. On Windows, the argument
134 help="""kill this process if its parent dies. On Windows, the argument
138 specifies the HANDLE of the parent process, otherwise it is simply boolean.
135 specifies the HANDLE of the parent process, otherwise it is simply boolean.
139 """)
136 """)
140 interrupt = Integer(0, config=True,
137 interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), config=True,
141 help="""ONLY USED ON WINDOWS
138 help="""ONLY USED ON WINDOWS
142 Interrupt this process when the parent is signaled.
139 Interrupt this process when the parent is signaled.
143 """)
140 """)
144
141
145 def init_crash_handler(self):
142 def init_crash_handler(self):
146 # Install minimal exception handling
143 # Install minimal exception handling
147 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
144 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
148 ostream=sys.__stdout__)
145 ostream=sys.__stdout__)
149
146
150 def init_poller(self):
147 def init_poller(self):
151 if sys.platform == 'win32':
148 if sys.platform == 'win32':
152 if self.interrupt or self.parent_handle:
149 if self.interrupt or self.parent_handle:
153 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
150 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
154 elif self.parent_handle:
151 elif self.parent_handle:
155 self.poller = ParentPollerUnix()
152 self.poller = ParentPollerUnix()
156
153
157 def _bind_socket(self, s, port):
154 def _bind_socket(self, s, port):
158 iface = '%s://%s' % (self.transport, self.ip)
155 iface = '%s://%s' % (self.transport, self.ip)
159 if self.transport == 'tcp':
156 if self.transport == 'tcp':
160 if port <= 0:
157 if port <= 0:
161 port = s.bind_to_random_port(iface)
158 port = s.bind_to_random_port(iface)
162 else:
159 else:
163 s.bind("tcp://%s:%i" % (self.ip, port))
160 s.bind("tcp://%s:%i" % (self.ip, port))
164 elif self.transport == 'ipc':
161 elif self.transport == 'ipc':
165 if port <= 0:
162 if port <= 0:
166 port = 1
163 port = 1
167 path = "%s-%i" % (self.ip, port)
164 path = "%s-%i" % (self.ip, port)
168 while os.path.exists(path):
165 while os.path.exists(path):
169 port = port + 1
166 port = port + 1
170 path = "%s-%i" % (self.ip, port)
167 path = "%s-%i" % (self.ip, port)
171 else:
168 else:
172 path = "%s-%i" % (self.ip, port)
169 path = "%s-%i" % (self.ip, port)
173 s.bind("ipc://%s" % path)
170 s.bind("ipc://%s" % path)
174 return port
171 return port
175
172
176 def write_connection_file(self):
173 def write_connection_file(self):
177 """write connection info to JSON file"""
174 """write connection info to JSON file"""
178 cf = self.abs_connection_file
175 cf = self.abs_connection_file
179 self.log.debug("Writing connection file: %s", cf)
176 self.log.debug("Writing connection file: %s", cf)
180 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
177 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
181 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
178 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
182 iopub_port=self.iopub_port, control_port=self.control_port)
179 iopub_port=self.iopub_port, control_port=self.control_port)
183
180
184 def cleanup_connection_file(self):
181 def cleanup_connection_file(self):
185 cf = self.abs_connection_file
182 cf = self.abs_connection_file
186 self.log.debug("Cleaning up connection file: %s", cf)
183 self.log.debug("Cleaning up connection file: %s", cf)
187 try:
184 try:
188 os.remove(cf)
185 os.remove(cf)
189 except (IOError, OSError):
186 except (IOError, OSError):
190 pass
187 pass
191
188
192 self.cleanup_ipc_files()
189 self.cleanup_ipc_files()
193
190
194 def init_connection_file(self):
191 def init_connection_file(self):
195 if not self.connection_file:
192 if not self.connection_file:
196 self.connection_file = "kernel-%s.json"%os.getpid()
193 self.connection_file = "kernel-%s.json"%os.getpid()
197 try:
194 try:
198 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
195 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
199 except IOError:
196 except IOError:
200 self.log.debug("Connection file not found: %s", self.connection_file)
197 self.log.debug("Connection file not found: %s", self.connection_file)
201 # This means I own it, so I will clean it up:
198 # This means I own it, so I will clean it up:
202 atexit.register(self.cleanup_connection_file)
199 atexit.register(self.cleanup_connection_file)
203 return
200 return
204 try:
201 try:
205 self.load_connection_file()
202 self.load_connection_file()
206 except Exception:
203 except Exception:
207 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
204 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
208 self.exit(1)
205 self.exit(1)
209
206
210 def init_sockets(self):
207 def init_sockets(self):
211 # Create a context, a session, and the kernel sockets.
208 # Create a context, a session, and the kernel sockets.
212 self.log.info("Starting the kernel at pid: %i", os.getpid())
209 self.log.info("Starting the kernel at pid: %i", os.getpid())
213 context = zmq.Context.instance()
210 context = zmq.Context.instance()
214 # Uncomment this to try closing the context.
211 # Uncomment this to try closing the context.
215 # atexit.register(context.term)
212 # atexit.register(context.term)
216
213
217 self.shell_socket = context.socket(zmq.ROUTER)
214 self.shell_socket = context.socket(zmq.ROUTER)
218 self.shell_socket.linger = 1000
215 self.shell_socket.linger = 1000
219 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
216 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
220 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
217 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
221
218
222 self.iopub_socket = context.socket(zmq.PUB)
219 self.iopub_socket = context.socket(zmq.PUB)
223 self.iopub_socket.linger = 1000
220 self.iopub_socket.linger = 1000
224 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
221 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
225 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
222 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
226
223
227 self.stdin_socket = context.socket(zmq.ROUTER)
224 self.stdin_socket = context.socket(zmq.ROUTER)
228 self.stdin_socket.linger = 1000
225 self.stdin_socket.linger = 1000
229 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
226 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
230 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
227 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
231
228
232 self.control_socket = context.socket(zmq.ROUTER)
229 self.control_socket = context.socket(zmq.ROUTER)
233 self.control_socket.linger = 1000
230 self.control_socket.linger = 1000
234 self.control_port = self._bind_socket(self.control_socket, self.control_port)
231 self.control_port = self._bind_socket(self.control_socket, self.control_port)
235 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
232 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
236
233
237 def init_heartbeat(self):
234 def init_heartbeat(self):
238 """start the heart beating"""
235 """start the heart beating"""
239 # heartbeat doesn't share context, because it mustn't be blocked
236 # heartbeat doesn't share context, because it mustn't be blocked
240 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
237 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
241 hb_ctx = zmq.Context()
238 hb_ctx = zmq.Context()
242 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
239 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
243 self.hb_port = self.heartbeat.port
240 self.hb_port = self.heartbeat.port
244 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
241 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
245 self.heartbeat.start()
242 self.heartbeat.start()
246
243
247 def log_connection_info(self):
244 def log_connection_info(self):
248 """display connection info, and store ports"""
245 """display connection info, and store ports"""
249 basename = os.path.basename(self.connection_file)
246 basename = os.path.basename(self.connection_file)
250 if basename == self.connection_file or \
247 if basename == self.connection_file or \
251 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
248 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
252 # use shortname
249 # use shortname
253 tail = basename
250 tail = basename
254 if self.profile != 'default':
251 if self.profile != 'default':
255 tail += " --profile %s" % self.profile
252 tail += " --profile %s" % self.profile
256 else:
253 else:
257 tail = self.connection_file
254 tail = self.connection_file
258 lines = [
255 lines = [
259 "To connect another client to this kernel, use:",
256 "To connect another client to this kernel, use:",
260 " --existing %s" % tail,
257 " --existing %s" % tail,
261 ]
258 ]
262 # log connection info
259 # log connection info
263 # info-level, so often not shown.
260 # info-level, so often not shown.
264 # frontends should use the %connect_info magic
261 # frontends should use the %connect_info magic
265 # to see the connection info
262 # to see the connection info
266 for line in lines:
263 for line in lines:
267 self.log.info(line)
264 self.log.info(line)
268 # also raw print to the terminal if no parent_handle (`ipython kernel`)
265 # also raw print to the terminal if no parent_handle (`ipython kernel`)
269 if not self.parent_handle:
266 if not self.parent_handle:
270 io.rprint(_ctrl_c_message)
267 io.rprint(_ctrl_c_message)
271 for line in lines:
268 for line in lines:
272 io.rprint(line)
269 io.rprint(line)
273
270
274 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
271 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
275 stdin=self.stdin_port, hb=self.hb_port,
272 stdin=self.stdin_port, hb=self.hb_port,
276 control=self.control_port)
273 control=self.control_port)
277
274
278 def init_blackhole(self):
275 def init_blackhole(self):
279 """redirects stdout/stderr to devnull if necessary"""
276 """redirects stdout/stderr to devnull if necessary"""
280 if self.no_stdout or self.no_stderr:
277 if self.no_stdout or self.no_stderr:
281 blackhole = open(os.devnull, 'w')
278 blackhole = open(os.devnull, 'w')
282 if self.no_stdout:
279 if self.no_stdout:
283 sys.stdout = sys.__stdout__ = blackhole
280 sys.stdout = sys.__stdout__ = blackhole
284 if self.no_stderr:
281 if self.no_stderr:
285 sys.stderr = sys.__stderr__ = blackhole
282 sys.stderr = sys.__stderr__ = blackhole
286
283
287 def init_io(self):
284 def init_io(self):
288 """Redirect input streams and set a display hook."""
285 """Redirect input streams and set a display hook."""
289 if self.outstream_class:
286 if self.outstream_class:
290 outstream_factory = import_item(str(self.outstream_class))
287 outstream_factory = import_item(str(self.outstream_class))
291 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
288 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
292 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
289 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
293 if self.displayhook_class:
290 if self.displayhook_class:
294 displayhook_factory = import_item(str(self.displayhook_class))
291 displayhook_factory = import_item(str(self.displayhook_class))
295 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
292 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
296
293
297 def init_signal(self):
294 def init_signal(self):
298 signal.signal(signal.SIGINT, signal.SIG_IGN)
295 signal.signal(signal.SIGINT, signal.SIG_IGN)
299
296
300 def init_kernel(self):
297 def init_kernel(self):
301 """Create the Kernel object itself"""
298 """Create the Kernel object itself"""
302 shell_stream = ZMQStream(self.shell_socket)
299 shell_stream = ZMQStream(self.shell_socket)
303 control_stream = ZMQStream(self.control_socket)
300 control_stream = ZMQStream(self.control_socket)
304
301
305 kernel_factory = self.kernel_class.instance
302 kernel_factory = self.kernel_class.instance
306
303
307 kernel = kernel_factory(parent=self, session=self.session,
304 kernel = kernel_factory(parent=self, session=self.session,
308 shell_streams=[shell_stream, control_stream],
305 shell_streams=[shell_stream, control_stream],
309 iopub_socket=self.iopub_socket,
306 iopub_socket=self.iopub_socket,
310 stdin_socket=self.stdin_socket,
307 stdin_socket=self.stdin_socket,
311 log=self.log,
308 log=self.log,
312 profile_dir=self.profile_dir,
309 profile_dir=self.profile_dir,
313 user_ns=self.user_ns,
310 user_ns=self.user_ns,
314 )
311 )
315 kernel.record_ports(self.ports)
312 kernel.record_ports(self.ports)
316 self.kernel = kernel
313 self.kernel = kernel
317
314
318 def init_gui_pylab(self):
315 def init_gui_pylab(self):
319 """Enable GUI event loop integration, taking pylab into account."""
316 """Enable GUI event loop integration, taking pylab into account."""
320
317
321 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
318 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
322 # to ensure that any exception is printed straight to stderr.
319 # to ensure that any exception is printed straight to stderr.
323 # Normally _showtraceback associates the reply with an execution,
320 # Normally _showtraceback associates the reply with an execution,
324 # which means frontends will never draw it, as this exception
321 # which means frontends will never draw it, as this exception
325 # is not associated with any execute request.
322 # is not associated with any execute request.
326
323
327 shell = self.shell
324 shell = self.shell
328 _showtraceback = shell._showtraceback
325 _showtraceback = shell._showtraceback
329 try:
326 try:
330 # replace error-sending traceback with stderr
327 # replace error-sending traceback with stderr
331 def print_tb(etype, evalue, stb):
328 def print_tb(etype, evalue, stb):
332 print ("GUI event loop or pylab initialization failed",
329 print ("GUI event loop or pylab initialization failed",
333 file=io.stderr)
330 file=io.stderr)
334 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
331 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
335 shell._showtraceback = print_tb
332 shell._showtraceback = print_tb
336 InteractiveShellApp.init_gui_pylab(self)
333 InteractiveShellApp.init_gui_pylab(self)
337 finally:
334 finally:
338 shell._showtraceback = _showtraceback
335 shell._showtraceback = _showtraceback
339
336
340 def init_shell(self):
337 def init_shell(self):
341 self.shell = getattr(self.kernel, 'shell', None)
338 self.shell = getattr(self.kernel, 'shell', None)
342 if self.shell:
339 if self.shell:
343 self.shell.configurables.append(self)
340 self.shell.configurables.append(self)
344
341
345 @catch_config_error
342 @catch_config_error
346 def initialize(self, argv=None):
343 def initialize(self, argv=None):
347 super(IPKernelApp, self).initialize(argv)
344 super(IPKernelApp, self).initialize(argv)
348 default_secure(self.config)
345 default_secure(self.config)
349 self.init_blackhole()
346 self.init_blackhole()
350 self.init_connection_file()
347 self.init_connection_file()
351 self.init_poller()
348 self.init_poller()
352 self.init_sockets()
349 self.init_sockets()
353 self.init_heartbeat()
350 self.init_heartbeat()
354 # writing/displaying connection info must be *after* init_sockets/heartbeat
351 # writing/displaying connection info must be *after* init_sockets/heartbeat
355 self.log_connection_info()
352 self.log_connection_info()
356 self.write_connection_file()
353 self.write_connection_file()
357 self.init_io()
354 self.init_io()
358 self.init_signal()
355 self.init_signal()
359 self.init_kernel()
356 self.init_kernel()
360 # shell init steps
357 # shell init steps
361 self.init_path()
358 self.init_path()
362 self.init_shell()
359 self.init_shell()
363 if self.shell:
360 if self.shell:
364 self.init_gui_pylab()
361 self.init_gui_pylab()
365 self.init_extensions()
362 self.init_extensions()
366 self.init_code()
363 self.init_code()
367 # flush stdout/stderr, so that anything written to these streams during
364 # flush stdout/stderr, so that anything written to these streams during
368 # initialization do not get associated with the first execution request
365 # initialization do not get associated with the first execution request
369 sys.stdout.flush()
366 sys.stdout.flush()
370 sys.stderr.flush()
367 sys.stderr.flush()
371
368
372 def start(self):
369 def start(self):
373 if self.poller is not None:
370 if self.poller is not None:
374 self.poller.start()
371 self.poller.start()
375 self.kernel.start()
372 self.kernel.start()
376 try:
373 try:
377 ioloop.IOLoop.instance().start()
374 ioloop.IOLoop.instance().start()
378 except KeyboardInterrupt:
375 except KeyboardInterrupt:
379 pass
376 pass
380
377
381 launch_new_instance = IPKernelApp.launch_instance
378 launch_new_instance = IPKernelApp.launch_instance
382
379
383 def main():
380 def main():
384 """Run an IPKernel as an application"""
381 """Run an IPKernel as an application"""
385 app = IPKernelApp.instance()
382 app = IPKernelApp.instance()
386 app.initialize()
383 app.initialize()
387 app.start()
384 app.start()
388
385
389
386
390 if __name__ == '__main__':
387 if __name__ == '__main__':
391 main()
388 main()
General Comments 0
You need to be logged in to leave comments. Login now