##// END OF EJS Templates
Add mechanism to specify environment variables in kernel spec
Thomas Kluyver -
Show More
@@ -1,112 +1,113 b''
1 import io
1 import io
2 import json
2 import json
3 import os
3 import os
4 import sys
4 import sys
5
5
6 pjoin = os.path.join
6 pjoin = os.path.join
7
7
8 from IPython.utils.path import get_ipython_dir
8 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.py3compat import PY3
9 from IPython.utils.py3compat import PY3
10 from IPython.utils.traitlets import HasTraits, List, Unicode
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
11
11
12 USER_KERNEL_DIR = pjoin(get_ipython_dir(), 'kernels')
12 USER_KERNEL_DIR = pjoin(get_ipython_dir(), 'kernels')
13
13
14 if os.name == 'nt':
14 if os.name == 'nt':
15 programdata = os.environ.get('PROGRAMDATA', None)
15 programdata = os.environ.get('PROGRAMDATA', None)
16 if programdata:
16 if programdata:
17 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
17 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
18 else: # PROGRAMDATA is not defined by default on XP.
18 else: # PROGRAMDATA is not defined by default on XP.
19 SYSTEM_KERNEL_DIR = None
19 SYSTEM_KERNEL_DIR = None
20 else:
20 else:
21 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
21 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
22
22
23 # List of kernel directories to search. Later ones take priority over earlier.
23 # List of kernel directories to search. Later ones take priority over earlier.
24 kernel_dirs = [
24 kernel_dirs = [
25 SYSTEM_KERNEL_DIR,
25 SYSTEM_KERNEL_DIR,
26 USER_KERNEL_DIR,
26 USER_KERNEL_DIR,
27 ]
27 ]
28
28
29 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
29 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
30
30
31 class KernelSpec(HasTraits):
31 class KernelSpec(HasTraits):
32 argv = List()
32 argv = List()
33 display_name = Unicode()
33 display_name = Unicode()
34 language = Unicode()
34 language = Unicode()
35 codemirror_mode = None
35 codemirror_mode = None
36 env = Dict()
36
37
37 resource_dir = Unicode()
38 resource_dir = Unicode()
38
39
39 def __init__(self, resource_dir, argv, display_name, language,
40 def __init__(self, resource_dir, argv, display_name, language,
40 codemirror_mode=None):
41 codemirror_mode=None):
41 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
42 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
42 display_name=display_name, language=language,
43 display_name=display_name, language=language,
43 codemirror_mode=codemirror_mode)
44 codemirror_mode=codemirror_mode)
44 if not self.codemirror_mode:
45 if not self.codemirror_mode:
45 self.codemirror_mode = self.language
46 self.codemirror_mode = self.language
46
47
47 @classmethod
48 @classmethod
48 def from_resource_dir(cls, resource_dir):
49 def from_resource_dir(cls, resource_dir):
49 """Create a KernelSpec object by reading kernel.json
50 """Create a KernelSpec object by reading kernel.json
50
51
51 Pass the path to the *directory* containing kernel.json.
52 Pass the path to the *directory* containing kernel.json.
52 """
53 """
53 kernel_file = pjoin(resource_dir, 'kernel.json')
54 kernel_file = pjoin(resource_dir, 'kernel.json')
54 with io.open(kernel_file, 'r', encoding='utf-8') as f:
55 with io.open(kernel_file, 'r', encoding='utf-8') as f:
55 kernel_dict = json.load(f)
56 kernel_dict = json.load(f)
56 return cls(resource_dir=resource_dir, **kernel_dict)
57 return cls(resource_dir=resource_dir, **kernel_dict)
57
58
58 def _is_kernel_dir(path):
59 def _is_kernel_dir(path):
59 """Is ``path`` a kernel directory?"""
60 """Is ``path`` a kernel directory?"""
60 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
61 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
61
62
62 def _list_kernels_in(dir):
63 def _list_kernels_in(dir):
63 """Ensure dir exists, and return a mapping of kernel names to resource
64 """Ensure dir exists, and return a mapping of kernel names to resource
64 directories from it.
65 directories from it.
65 """
66 """
66 if dir is None or not os.path.isdir(dir):
67 if dir is None or not os.path.isdir(dir):
67 return {}
68 return {}
68 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
69 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
69 if _is_kernel_dir(pjoin(dir, f))}
70 if _is_kernel_dir(pjoin(dir, f))}
70
71
71 def _make_native_kernel_dir():
72 def _make_native_kernel_dir():
72 """Makes a kernel directory for the native kernel.
73 """Makes a kernel directory for the native kernel.
73
74
74 The native kernel is the kernel using the same Python runtime as this
75 The native kernel is the kernel using the same Python runtime as this
75 process. This will put its informatino in the user kernels directory.
76 process. This will put its informatino in the user kernels directory.
76 """
77 """
77 path = pjoin(USER_KERNEL_DIR, NATIVE_KERNEL_NAME)
78 path = pjoin(USER_KERNEL_DIR, NATIVE_KERNEL_NAME)
78 os.makedirs(path, mode=0o755)
79 os.makedirs(path, mode=0o755)
79 with open(pjoin(path, 'kernel.json'), 'w') as f:
80 with open(pjoin(path, 'kernel.json'), 'w') as f:
80 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
81 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
81 'from IPython.kernel.zmq.kernelapp import main; main()',
82 'from IPython.kernel.zmq.kernelapp import main; main()',
82 '-f', '{connection_file}'],
83 '-f', '{connection_file}'],
83 'display_name': 'Python 3' if PY3 else 'Python 2',
84 'display_name': 'Python 3' if PY3 else 'Python 2',
84 'language': 'python',
85 'language': 'python',
85 'codemirror_mode': {'name': 'python',
86 'codemirror_mode': {'name': 'python',
86 'version': sys.version_info[0]},
87 'version': sys.version_info[0]},
87 },
88 },
88 f, indent=1)
89 f, indent=1)
89 # TODO: Copy icons into directory
90 # TODO: Copy icons into directory
90 return path
91 return path
91
92
92 def find_kernel_specs():
93 def find_kernel_specs():
93 """Returns a dict mapping kernel names to resource directories."""
94 """Returns a dict mapping kernel names to resource directories."""
94 d = {}
95 d = {}
95 for kernel_dir in kernel_dirs:
96 for kernel_dir in kernel_dirs:
96 d.update(_list_kernels_in(kernel_dir))
97 d.update(_list_kernels_in(kernel_dir))
97
98
98 if NATIVE_KERNEL_NAME not in d:
99 if NATIVE_KERNEL_NAME not in d:
99 d[NATIVE_KERNEL_NAME] = _make_native_kernel_dir()
100 d[NATIVE_KERNEL_NAME] = _make_native_kernel_dir()
100 return d
101 return d
101 # TODO: Caching?
102 # TODO: Caching?
102
103
103 def get_kernel_spec(kernel_name):
104 def get_kernel_spec(kernel_name):
104 """Returns a :class:`KernelSpec` instance for the given kernel_name.
105 """Returns a :class:`KernelSpec` instance for the given kernel_name.
105
106
106 Raises KeyError if the given kernel name is not found.
107 Raises KeyError if the given kernel name is not found.
107 """
108 """
108 if kernel_name == 'python':
109 if kernel_name == 'python':
109 kernel_name = NATIVE_KERNEL_NAME
110 kernel_name = NATIVE_KERNEL_NAME
110 d = find_kernel_specs()
111 d = find_kernel_specs()
111 resource_dir = d[kernel_name.lower()]
112 resource_dir = d[kernel_name.lower()]
112 return KernelSpec.from_resource_dir(resource_dir) No newline at end of file
113 return KernelSpec.from_resource_dir(resource_dir)
@@ -1,272 +1,256 b''
1 """Utilities for launching kernels
1 """Utilities for launching kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
6
7 """
2 """
8
3
9 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
10 # Copyright (C) 2013 The IPython Development Team
5 # Distributed under the terms of the Modified BSD License.
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
6
20 import os
7 import os
21 import sys
8 import sys
22 from subprocess import Popen, PIPE
9 from subprocess import Popen, PIPE
23
10
24 from IPython.utils.encoding import getdefaultencoding
11 from IPython.utils.encoding import getdefaultencoding
25 from IPython.utils.py3compat import cast_bytes_py2
12 from IPython.utils.py3compat import cast_bytes_py2
26
13
27 #-----------------------------------------------------------------------------
28 # Launching Kernels
29 #-----------------------------------------------------------------------------
30
14
31 def swallow_argv(argv, aliases=None, flags=None):
15 def swallow_argv(argv, aliases=None, flags=None):
32 """strip frontend-specific aliases and flags from an argument list
16 """strip frontend-specific aliases and flags from an argument list
33
17
34 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
35 arguments through to a subprocess, where frontend-specific flags and aliases
19 arguments through to a subprocess, where frontend-specific flags and aliases
36 should be removed from the list.
20 should be removed from the list.
37
21
38 Parameters
22 Parameters
39 ----------
23 ----------
40
24
41 argv : list(str)
25 argv : list(str)
42 The starting argv, to be filtered
26 The starting argv, to be filtered
43 aliases : container of aliases (dict, list, set, etc.)
27 aliases : container of aliases (dict, list, set, etc.)
44 The frontend-specific aliases to be removed
28 The frontend-specific aliases to be removed
45 flags : container of flags (dict, list, set, etc.)
29 flags : container of flags (dict, list, set, etc.)
46 The frontend-specific flags to be removed
30 The frontend-specific flags to be removed
47
31
48 Returns
32 Returns
49 -------
33 -------
50
34
51 argv : list(str)
35 argv : list(str)
52 The argv list, excluding flags and aliases that have been stripped
36 The argv list, excluding flags and aliases that have been stripped
53 """
37 """
54
38
55 if aliases is None:
39 if aliases is None:
56 aliases = set()
40 aliases = set()
57 if flags is None:
41 if flags is None:
58 flags = set()
42 flags = set()
59
43
60 stripped = list(argv) # copy
44 stripped = list(argv) # copy
61
45
62 swallow_next = False
46 swallow_next = False
63 was_flag = False
47 was_flag = False
64 for a in argv:
48 for a in argv:
65 if a == '--':
49 if a == '--':
66 break
50 break
67 if swallow_next:
51 if swallow_next:
68 swallow_next = False
52 swallow_next = False
69 # last arg was an alias, remove the next one
53 # last arg was an alias, remove the next one
70 # *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
71 # 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:
72 if not (was_flag and a.startswith('-')):
56 if not (was_flag and a.startswith('-')):
73 stripped.remove(a)
57 stripped.remove(a)
74 continue
58 continue
75 if a.startswith('-'):
59 if a.startswith('-'):
76 split = a.lstrip('-').split('=')
60 split = a.lstrip('-').split('=')
77 name = split[0]
61 name = split[0]
78 # we use startswith because argparse accepts any arg to be specified
62 # we use startswith because argparse accepts any arg to be specified
79 # by any leading section, as long as it is unique,
63 # by any leading section, as long as it is unique,
80 # so `--no-br` means `--no-browser` in the notebook, etc.
64 # so `--no-br` means `--no-browser` in the notebook, etc.
81 if any(alias.startswith(name) for alias in aliases):
65 if any(alias.startswith(name) for alias in aliases):
82 stripped.remove(a)
66 stripped.remove(a)
83 if len(split) == 1:
67 if len(split) == 1:
84 # alias passed with arg via space
68 # alias passed with arg via space
85 swallow_next = True
69 swallow_next = True
86 # 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`
87 # in which case, we might not swallow the next arg
71 # in which case, we might not swallow the next arg
88 was_flag = name in flags
72 was_flag = name in flags
89 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):
90 # 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
91 stripped.remove(a)
75 stripped.remove(a)
92
76
93 # return shortened list
77 # return shortened list
94 return stripped
78 return stripped
95
79
96
80
97 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
81 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
98 """Build Popen command list for launching an IPython kernel.
82 """Build Popen command list for launching an IPython kernel.
99
83
100 Parameters
84 Parameters
101 ----------
85 ----------
102 code : str,
86 code : str,
103 A string of Python code that imports and executes a kernel entry point.
87 A string of Python code that imports and executes a kernel entry point.
104
88
105 executable : str, optional (default sys.executable)
89 executable : str, optional (default sys.executable)
106 The Python executable to use for the kernel process.
90 The Python executable to use for the kernel process.
107
91
108 extra_arguments : list, optional
92 extra_arguments : list, optional
109 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.
110
94
111 Returns
95 Returns
112 -------
96 -------
113
97
114 A Popen command list
98 A Popen command list
115 """
99 """
116
100
117 # Build the kernel launch command.
101 # Build the kernel launch command.
118 if executable is None:
102 if executable is None:
119 executable = sys.executable
103 executable = sys.executable
120 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
104 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
121 arguments.extend(extra_arguments)
105 arguments.extend(extra_arguments)
122
106
123 # Spawn a kernel.
107 # Spawn a kernel.
124 if sys.platform == 'win32':
108 if sys.platform == 'win32':
125
109
126 # If the kernel is running on pythonw and stdout/stderr are not been
110 # If the kernel is running on pythonw and stdout/stderr are not been
127 # re-directed, it will crash when more than 4KB of data is written to
111 # re-directed, it will crash when more than 4KB of data is written to
128 # stdout or stderr. This is a bug that has been with Python for a very
112 # stdout or stderr. This is a bug that has been with Python for a very
129 # long time; see http://bugs.python.org/issue706263.
113 # long time; see http://bugs.python.org/issue706263.
130 # A cleaner solution to this problem would be to pass os.devnull to
114 # A cleaner solution to this problem would be to pass os.devnull to
131 # Popen directly. Unfortunately, that does not work.
115 # Popen directly. Unfortunately, that does not work.
132 if executable.endswith('pythonw.exe'):
116 if executable.endswith('pythonw.exe'):
133 arguments.append('--no-stdout')
117 arguments.append('--no-stdout')
134 arguments.append('--no-stderr')
118 arguments.append('--no-stderr')
135
119
136 return arguments
120 return arguments
137
121
138
122
139 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
123 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
140 independent=False,
124 independent=False,
141 cwd=None, ipython_kernel=True,
125 cwd=None, ipython_kernel=True,
142 **kw
126 **kw
143 ):
127 ):
144 """ Launches a localhost kernel, binding to the specified ports.
128 """ Launches a localhost kernel, binding to the specified ports.
145
129
146 Parameters
130 Parameters
147 ----------
131 ----------
148 cmd : Popen list,
132 cmd : Popen list,
149 A string of Python code that imports and executes a kernel entry point.
133 A string of Python code that imports and executes a kernel entry point.
150
134
151 stdin, stdout, stderr : optional (default None)
135 stdin, stdout, stderr : optional (default None)
152 Standards streams, as defined in subprocess.Popen.
136 Standards streams, as defined in subprocess.Popen.
153
137
154 independent : bool, optional (default False)
138 independent : bool, optional (default False)
155 If set, the kernel process is guaranteed to survive if this process
139 If set, the kernel process is guaranteed to survive if this process
156 dies. If not set, an effort is made to ensure that the kernel is killed
140 dies. If not set, an effort is made to ensure that the kernel is killed
157 when this process dies. Note that in this case it is still good practice
141 when this process dies. Note that in this case it is still good practice
158 to kill kernels manually before exiting.
142 to kill kernels manually before exiting.
159
143
160 cwd : path, optional
144 cwd : path, optional
161 The working dir of the kernel process (default: cwd of this process).
145 The working dir of the kernel process (default: cwd of this process).
162
146
163 ipython_kernel : bool, optional
147 ipython_kernel : bool, optional
164 Whether the kernel is an official IPython one,
148 Whether the kernel is an official IPython one,
165 and should get a bit of special treatment.
149 and should get a bit of special treatment.
166
150
167 Returns
151 Returns
168 -------
152 -------
169
153
170 Popen instance for the kernel subprocess
154 Popen instance for the kernel subprocess
171 """
155 """
172
156
173 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
157 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
174 # are invalid. Unfortunately, there is in general no way to detect whether
158 # are invalid. Unfortunately, there is in general no way to detect whether
175 # they are valid. The following two blocks redirect them to (temporary)
159 # they are valid. The following two blocks redirect them to (temporary)
176 # pipes in certain important cases.
160 # pipes in certain important cases.
177
161
178 # If this process has been backgrounded, our stdin is invalid. Since there
162 # If this process has been backgrounded, our stdin is invalid. Since there
179 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
163 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
180 # place this one safe and always redirect.
164 # place this one safe and always redirect.
181 redirect_in = True
165 redirect_in = True
182 _stdin = PIPE if stdin is None else stdin
166 _stdin = PIPE if stdin is None else stdin
183
167
184 # If this process in running on pythonw, we know that stdin, stdout, and
168 # If this process in running on pythonw, we know that stdin, stdout, and
185 # stderr are all invalid.
169 # stderr are all invalid.
186 redirect_out = sys.executable.endswith('pythonw.exe')
170 redirect_out = sys.executable.endswith('pythonw.exe')
187 if redirect_out:
171 if redirect_out:
188 _stdout = PIPE if stdout is None else stdout
172 _stdout = PIPE if stdout is None else stdout
189 _stderr = PIPE if stderr is None else stderr
173 _stderr = PIPE if stderr is None else stderr
190 else:
174 else:
191 _stdout, _stderr = stdout, stderr
175 _stdout, _stderr = stdout, stderr
192
176
193 encoding = getdefaultencoding(prefer_stream=False)
177 encoding = getdefaultencoding(prefer_stream=False)
194
178
195 # Spawn a kernel.
179 # Spawn a kernel.
196 if sys.platform == 'win32':
180 if sys.platform == 'win32':
197 # Popen on Python 2 on Windows cannot handle unicode args or cwd
181 # Popen on Python 2 on Windows cannot handle unicode args or cwd
198 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
182 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
199 if cwd:
183 if cwd:
200 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
184 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
201
185
202 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
186 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
203 # Create a Win32 event for interrupting the kernel.
187 # Create a Win32 event for interrupting the kernel.
204 interrupt_event = ParentPollerWindows.create_interrupt_event()
188 interrupt_event = ParentPollerWindows.create_interrupt_event()
205 if ipython_kernel:
189 if ipython_kernel:
206 cmd += [ '--interrupt=%i' % interrupt_event ]
190 cmd += [ '--interrupt=%i' % interrupt_event ]
207
191
208 # If the kernel is running on pythonw and stdout/stderr are not been
192 # If the kernel is running on pythonw and stdout/stderr are not been
209 # re-directed, it will crash when more than 4KB of data is written to
193 # re-directed, it will crash when more than 4KB of data is written to
210 # stdout or stderr. This is a bug that has been with Python for a very
194 # stdout or stderr. This is a bug that has been with Python for a very
211 # long time; see http://bugs.python.org/issue706263.
195 # long time; see http://bugs.python.org/issue706263.
212 # A cleaner solution to this problem would be to pass os.devnull to
196 # A cleaner solution to this problem would be to pass os.devnull to
213 # Popen directly. Unfortunately, that does not work.
197 # Popen directly. Unfortunately, that does not work.
214 if cmd[0].endswith('pythonw.exe'):
198 if cmd[0].endswith('pythonw.exe'):
215 if stdout is None:
199 if stdout is None:
216 cmd.append('--no-stdout')
200 cmd.append('--no-stdout')
217 if stderr is None:
201 if stderr is None:
218 cmd.append('--no-stderr')
202 cmd.append('--no-stderr')
219
203
220 # Launch the kernel process.
204 # Launch the kernel process.
221 if independent:
205 if independent:
222 proc = Popen(cmd,
206 proc = Popen(cmd,
223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
207 creationflags=512, # CREATE_NEW_PROCESS_GROUP
224 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=os.environ)
208 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
225 else:
209 else:
226 if ipython_kernel:
210 if ipython_kernel:
227 try:
211 try:
228 from _winapi import DuplicateHandle, GetCurrentProcess, \
212 from _winapi import DuplicateHandle, GetCurrentProcess, \
229 DUPLICATE_SAME_ACCESS
213 DUPLICATE_SAME_ACCESS
230 except:
214 except:
231 from _subprocess import DuplicateHandle, GetCurrentProcess, \
215 from _subprocess import DuplicateHandle, GetCurrentProcess, \
232 DUPLICATE_SAME_ACCESS
216 DUPLICATE_SAME_ACCESS
233 pid = GetCurrentProcess()
217 pid = GetCurrentProcess()
234 handle = DuplicateHandle(pid, pid, pid, 0,
218 handle = DuplicateHandle(pid, pid, pid, 0,
235 True, # Inheritable by new processes.
219 True, # Inheritable by new processes.
236 DUPLICATE_SAME_ACCESS)
220 DUPLICATE_SAME_ACCESS)
237 cmd +=[ '--parent=%i' % handle ]
221 cmd +=[ '--parent=%i' % handle ]
238
222
239
223
240 proc = Popen(cmd,
224 proc = Popen(cmd,
241 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
225 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
242
226
243 # Attach the interrupt event to the Popen objet so it can be used later.
227 # Attach the interrupt event to the Popen objet so it can be used later.
244 proc.win32_interrupt_event = interrupt_event
228 proc.win32_interrupt_event = interrupt_event
245
229
246 else:
230 else:
247 if independent:
231 if independent:
248 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
232 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
249 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
233 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
250 else:
234 else:
251 if ipython_kernel:
235 if ipython_kernel:
252 cmd += ['--parent=1']
236 cmd += ['--parent=1']
253 proc = Popen(cmd,
237 proc = Popen(cmd,
254 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
255
239
256 # Clean up pipes created to work around Popen bug.
240 # Clean up pipes created to work around Popen bug.
257 if redirect_in:
241 if redirect_in:
258 if stdin is None:
242 if stdin is None:
259 proc.stdin.close()
243 proc.stdin.close()
260 if redirect_out:
244 if redirect_out:
261 if stdout is None:
245 if stdout is None:
262 proc.stdout.close()
246 proc.stdout.close()
263 if stderr is None:
247 if stderr is None:
264 proc.stderr.close()
248 proc.stderr.close()
265
249
266 return proc
250 return proc
267
251
268 __all__ = [
252 __all__ = [
269 'swallow_argv',
253 'swallow_argv',
270 'make_ipkernel_cmd',
254 'make_ipkernel_cmd',
271 'launch_kernel',
255 'launch_kernel',
272 ]
256 ]
@@ -1,410 +1,403 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Copyright (C) 2013 The IPython Development Team
4 # Distributed under the terms of the Modified BSD License.
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
5
14 from __future__ import absolute_import
6 from __future__ import absolute_import
15
7
16 # Standard library imports
8 # Standard library imports
9 import os
17 import re
10 import re
18 import signal
11 import signal
19 import sys
12 import sys
20 import time
13 import time
21 import warnings
14 import warnings
22
15
23 import zmq
16 import zmq
24
17
25 # Local imports
18 # Local imports
26 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
27 from IPython.utils.importstring import import_item
20 from IPython.utils.importstring import import_item
28 from IPython.utils.localinterfaces import is_local_ip, local_ips
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
29 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
30 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
23 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
31 )
24 )
32 from IPython.kernel import (
25 from IPython.kernel import (
33 make_ipkernel_cmd,
26 make_ipkernel_cmd,
34 launch_kernel,
27 launch_kernel,
35 kernelspec,
28 kernelspec,
36 )
29 )
37 from .connect import ConnectionFileMixin
30 from .connect import ConnectionFileMixin
38 from .zmq.session import Session
31 from .zmq.session import Session
39 from .managerabc import (
32 from .managerabc import (
40 KernelManagerABC
33 KernelManagerABC
41 )
34 )
42
35
43 #-----------------------------------------------------------------------------
44 # Main kernel manager class
45 #-----------------------------------------------------------------------------
46
36
47 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
37 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
48 """Manages a single kernel in a subprocess on this host.
38 """Manages a single kernel in a subprocess on this host.
49
39
50 This version starts kernels with Popen.
40 This version starts kernels with Popen.
51 """
41 """
52
42
53 # The PyZMQ Context to use for communication with the kernel.
43 # The PyZMQ Context to use for communication with the kernel.
54 context = Instance(zmq.Context)
44 context = Instance(zmq.Context)
55 def _context_default(self):
45 def _context_default(self):
56 return zmq.Context.instance()
46 return zmq.Context.instance()
57
47
58 # The Session to use for communication with the kernel.
48 # The Session to use for communication with the kernel.
59 session = Instance(Session)
49 session = Instance(Session)
60 def _session_default(self):
50 def _session_default(self):
61 return Session(parent=self)
51 return Session(parent=self)
62
52
63 # the class to create with our `client` method
53 # the class to create with our `client` method
64 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
54 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
65 client_factory = Type()
55 client_factory = Type()
66 def _client_class_changed(self, name, old, new):
56 def _client_class_changed(self, name, old, new):
67 self.client_factory = import_item(str(new))
57 self.client_factory = import_item(str(new))
68
58
69 # The kernel process with which the KernelManager is communicating.
59 # The kernel process with which the KernelManager is communicating.
70 # generally a Popen instance
60 # generally a Popen instance
71 kernel = Any()
61 kernel = Any()
72
62
73 kernel_name = Unicode('python')
63 kernel_name = Unicode('python')
74
64
75 kernel_spec = Instance(kernelspec.KernelSpec)
65 kernel_spec = Instance(kernelspec.KernelSpec)
76
66
77 def _kernel_spec_default(self):
67 def _kernel_spec_default(self):
78 return kernelspec.get_kernel_spec(self.kernel_name)
68 return kernelspec.get_kernel_spec(self.kernel_name)
79
69
80 def _kernel_name_changed(self, name, old, new):
70 def _kernel_name_changed(self, name, old, new):
81 self.kernel_spec = kernelspec.get_kernel_spec(new)
71 self.kernel_spec = kernelspec.get_kernel_spec(new)
82 self.ipython_kernel = new in {'python', 'python2', 'python3'}
72 self.ipython_kernel = new in {'python', 'python2', 'python3'}
83
73
84 kernel_cmd = List(Unicode, config=True,
74 kernel_cmd = List(Unicode, config=True,
85 help="""DEPRECATED: Use kernel_name instead.
75 help="""DEPRECATED: Use kernel_name instead.
86
76
87 The Popen Command to launch the kernel.
77 The Popen Command to launch the kernel.
88 Override this if you have a custom kernel.
78 Override this if you have a custom kernel.
89 If kernel_cmd is specified in a configuration file,
79 If kernel_cmd is specified in a configuration file,
90 IPython does not pass any arguments to the kernel,
80 IPython does not pass any arguments to the kernel,
91 because it cannot make any assumptions about the
81 because it cannot make any assumptions about the
92 arguments that the kernel understands. In particular,
82 arguments that the kernel understands. In particular,
93 this means that the kernel does not receive the
83 this means that the kernel does not receive the
94 option --debug if it given on the IPython command line.
84 option --debug if it given on the IPython command line.
95 """
85 """
96 )
86 )
97
87
98 def _kernel_cmd_changed(self, name, old, new):
88 def _kernel_cmd_changed(self, name, old, new):
99 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
89 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
100 "start different kernels.")
90 "start different kernels.")
101 self.ipython_kernel = False
91 self.ipython_kernel = False
102
92
103 ipython_kernel = Bool(True)
93 ipython_kernel = Bool(True)
104
94
105 # Protected traits
95 # Protected traits
106 _launch_args = Any()
96 _launch_args = Any()
107 _control_socket = Any()
97 _control_socket = Any()
108
98
109 _restarter = Any()
99 _restarter = Any()
110
100
111 autorestart = Bool(False, config=True,
101 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
102 help="""Should we autorestart the kernel if it dies."""
113 )
103 )
114
104
115 def __del__(self):
105 def __del__(self):
116 self._close_control_socket()
106 self._close_control_socket()
117 self.cleanup_connection_file()
107 self.cleanup_connection_file()
118
108
119 #--------------------------------------------------------------------------
109 #--------------------------------------------------------------------------
120 # Kernel restarter
110 # Kernel restarter
121 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
122
112
123 def start_restarter(self):
113 def start_restarter(self):
124 pass
114 pass
125
115
126 def stop_restarter(self):
116 def stop_restarter(self):
127 pass
117 pass
128
118
129 def add_restart_callback(self, callback, event='restart'):
119 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
120 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
121 if self._restarter is None:
132 return
122 return
133 self._restarter.add_callback(callback, event)
123 self._restarter.add_callback(callback, event)
134
124
135 def remove_restart_callback(self, callback, event='restart'):
125 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
126 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
127 if self._restarter is None:
138 return
128 return
139 self._restarter.remove_callback(callback, event)
129 self._restarter.remove_callback(callback, event)
140
130
141 #--------------------------------------------------------------------------
131 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
132 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
133 #--------------------------------------------------------------------------
144
134
145 def client(self, **kwargs):
135 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
136 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
137 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
138 self.client_factory = import_item(self.client_class)
149
139
150 kw = {}
140 kw = {}
151 kw.update(self.get_connection_info())
141 kw.update(self.get_connection_info())
152 kw.update(dict(
142 kw.update(dict(
153 connection_file=self.connection_file,
143 connection_file=self.connection_file,
154 session=self.session,
144 session=self.session,
155 parent=self,
145 parent=self,
156 ))
146 ))
157
147
158 # add kwargs last, for manual overrides
148 # add kwargs last, for manual overrides
159 kw.update(kwargs)
149 kw.update(kwargs)
160 return self.client_factory(**kw)
150 return self.client_factory(**kw)
161
151
162 #--------------------------------------------------------------------------
152 #--------------------------------------------------------------------------
163 # Kernel management
153 # Kernel management
164 #--------------------------------------------------------------------------
154 #--------------------------------------------------------------------------
165
155
166 def format_kernel_cmd(self, **kw):
156 def format_kernel_cmd(self, **kw):
167 """replace templated args (e.g. {connection_file})"""
157 """replace templated args (e.g. {connection_file})"""
168 if self.kernel_cmd:
158 if self.kernel_cmd:
169 cmd = self.kernel_cmd
159 cmd = self.kernel_cmd
170 elif self.kernel_name == 'python':
160 elif self.kernel_name == 'python':
171 # The native kernel gets special handling
161 # The native kernel gets special handling
172 cmd = make_ipkernel_cmd(
162 cmd = make_ipkernel_cmd(
173 'from IPython.kernel.zmq.kernelapp import main; main()',
163 'from IPython.kernel.zmq.kernelapp import main; main()',
174 **kw
164 **kw
175 )
165 )
176 else:
166 else:
177 cmd = self.kernel_spec.argv
167 cmd = self.kernel_spec.argv
178
168
179 ns = dict(connection_file=self.connection_file)
169 ns = dict(connection_file=self.connection_file)
180 ns.update(self._launch_args)
170 ns.update(self._launch_args)
181
171
182 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
172 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
183 def from_ns(match):
173 def from_ns(match):
184 """Get the key out of ns if it's there, otherwise no change."""
174 """Get the key out of ns if it's there, otherwise no change."""
185 return ns.get(match.group(1), match.group())
175 return ns.get(match.group(1), match.group())
186
176
187 return [ pat.sub(from_ns, arg) for arg in cmd ]
177 return [ pat.sub(from_ns, arg) for arg in cmd ]
188
178
189 def _launch_kernel(self, kernel_cmd, **kw):
179 def _launch_kernel(self, kernel_cmd, **kw):
190 """actually launch the kernel
180 """actually launch the kernel
191
181
192 override in a subclass to launch kernel subprocesses differently
182 override in a subclass to launch kernel subprocesses differently
193 """
183 """
194 return launch_kernel(kernel_cmd, **kw)
184 return launch_kernel(kernel_cmd, **kw)
195
185
196 # Control socket used for polite kernel shutdown
186 # Control socket used for polite kernel shutdown
197
187
198 def _connect_control_socket(self):
188 def _connect_control_socket(self):
199 if self._control_socket is None:
189 if self._control_socket is None:
200 self._control_socket = self.connect_control()
190 self._control_socket = self.connect_control()
201 self._control_socket.linger = 100
191 self._control_socket.linger = 100
202
192
203 def _close_control_socket(self):
193 def _close_control_socket(self):
204 if self._control_socket is None:
194 if self._control_socket is None:
205 return
195 return
206 self._control_socket.close()
196 self._control_socket.close()
207 self._control_socket = None
197 self._control_socket = None
208
198
209 def start_kernel(self, **kw):
199 def start_kernel(self, **kw):
210 """Starts a kernel on this host in a separate process.
200 """Starts a kernel on this host in a separate process.
211
201
212 If random ports (port=0) are being used, this method must be called
202 If random ports (port=0) are being used, this method must be called
213 before the channels are created.
203 before the channels are created.
214
204
215 Parameters
205 Parameters
216 ----------
206 ----------
217 **kw : optional
207 **kw : optional
218 keyword arguments that are passed down to build the kernel_cmd
208 keyword arguments that are passed down to build the kernel_cmd
219 and launching the kernel (e.g. Popen kwargs).
209 and launching the kernel (e.g. Popen kwargs).
220 """
210 """
221 if self.transport == 'tcp' and not is_local_ip(self.ip):
211 if self.transport == 'tcp' and not is_local_ip(self.ip):
222 raise RuntimeError("Can only launch a kernel on a local interface. "
212 raise RuntimeError("Can only launch a kernel on a local interface. "
223 "Make sure that the '*_address' attributes are "
213 "Make sure that the '*_address' attributes are "
224 "configured properly. "
214 "configured properly. "
225 "Currently valid addresses are: %s" % local_ips()
215 "Currently valid addresses are: %s" % local_ips()
226 )
216 )
227
217
228 # write connection file / get default ports
218 # write connection file / get default ports
229 self.write_connection_file()
219 self.write_connection_file()
230
220
231 # save kwargs for use in restart
221 # save kwargs for use in restart
232 self._launch_args = kw.copy()
222 self._launch_args = kw.copy()
233 # build the Popen cmd
223 # build the Popen cmd
234 kernel_cmd = self.format_kernel_cmd(**kw)
224 kernel_cmd = self.format_kernel_cmd(**kw)
225 if self.kernel_cmd:
226 # If kernel_cmd has been set manually, don't refer to a kernel spec
227 env = os.environ
228 else:
229 # Environment variables from kernel spec are added to os.environ
230 env = os.environ.copy()
231 env.update(self.kernel_spec.env or {})
235 # launch the kernel subprocess
232 # launch the kernel subprocess
236 self.kernel = self._launch_kernel(kernel_cmd,
233 self.kernel = self._launch_kernel(kernel_cmd, env=env,
237 ipython_kernel=self.ipython_kernel,
234 ipython_kernel=self.ipython_kernel,
238 **kw)
235 **kw)
239 self.start_restarter()
236 self.start_restarter()
240 self._connect_control_socket()
237 self._connect_control_socket()
241
238
242 def _send_shutdown_request(self, restart=False):
239 def _send_shutdown_request(self, restart=False):
243 """TODO: send a shutdown request via control channel"""
240 """TODO: send a shutdown request via control channel"""
244 content = dict(restart=restart)
241 content = dict(restart=restart)
245 msg = self.session.msg("shutdown_request", content=content)
242 msg = self.session.msg("shutdown_request", content=content)
246 self.session.send(self._control_socket, msg)
243 self.session.send(self._control_socket, msg)
247
244
248 def shutdown_kernel(self, now=False, restart=False):
245 def shutdown_kernel(self, now=False, restart=False):
249 """Attempts to the stop the kernel process cleanly.
246 """Attempts to the stop the kernel process cleanly.
250
247
251 This attempts to shutdown the kernels cleanly by:
248 This attempts to shutdown the kernels cleanly by:
252
249
253 1. Sending it a shutdown message over the shell channel.
250 1. Sending it a shutdown message over the shell channel.
254 2. If that fails, the kernel is shutdown forcibly by sending it
251 2. If that fails, the kernel is shutdown forcibly by sending it
255 a signal.
252 a signal.
256
253
257 Parameters
254 Parameters
258 ----------
255 ----------
259 now : bool
256 now : bool
260 Should the kernel be forcible killed *now*. This skips the
257 Should the kernel be forcible killed *now*. This skips the
261 first, nice shutdown attempt.
258 first, nice shutdown attempt.
262 restart: bool
259 restart: bool
263 Will this kernel be restarted after it is shutdown. When this
260 Will this kernel be restarted after it is shutdown. When this
264 is True, connection files will not be cleaned up.
261 is True, connection files will not be cleaned up.
265 """
262 """
266 # Stop monitoring for restarting while we shutdown.
263 # Stop monitoring for restarting while we shutdown.
267 self.stop_restarter()
264 self.stop_restarter()
268
265
269 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
266 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
270 if now or sys.platform == 'win32':
267 if now or sys.platform == 'win32':
271 if self.has_kernel:
268 if self.has_kernel:
272 self._kill_kernel()
269 self._kill_kernel()
273 else:
270 else:
274 # Don't send any additional kernel kill messages immediately, to give
271 # Don't send any additional kernel kill messages immediately, to give
275 # the kernel a chance to properly execute shutdown actions. Wait for at
272 # the kernel a chance to properly execute shutdown actions. Wait for at
276 # most 1s, checking every 0.1s.
273 # most 1s, checking every 0.1s.
277 self._send_shutdown_request(restart=restart)
274 self._send_shutdown_request(restart=restart)
278 for i in range(10):
275 for i in range(10):
279 if self.is_alive():
276 if self.is_alive():
280 time.sleep(0.1)
277 time.sleep(0.1)
281 else:
278 else:
282 break
279 break
283 else:
280 else:
284 # OK, we've waited long enough.
281 # OK, we've waited long enough.
285 if self.has_kernel:
282 if self.has_kernel:
286 self._kill_kernel()
283 self._kill_kernel()
287
284
288 if not restart:
285 if not restart:
289 self.cleanup_connection_file()
286 self.cleanup_connection_file()
290 self.cleanup_ipc_files()
287 self.cleanup_ipc_files()
291 else:
288 else:
292 self.cleanup_ipc_files()
289 self.cleanup_ipc_files()
293
290
294 self._close_control_socket()
291 self._close_control_socket()
295
292
296 def restart_kernel(self, now=False, **kw):
293 def restart_kernel(self, now=False, **kw):
297 """Restarts a kernel with the arguments that were used to launch it.
294 """Restarts a kernel with the arguments that were used to launch it.
298
295
299 If the old kernel was launched with random ports, the same ports will be
296 If the old kernel was launched with random ports, the same ports will be
300 used for the new kernel. The same connection file is used again.
297 used for the new kernel. The same connection file is used again.
301
298
302 Parameters
299 Parameters
303 ----------
300 ----------
304 now : bool, optional
301 now : bool, optional
305 If True, the kernel is forcefully restarted *immediately*, without
302 If True, the kernel is forcefully restarted *immediately*, without
306 having a chance to do any cleanup action. Otherwise the kernel is
303 having a chance to do any cleanup action. Otherwise the kernel is
307 given 1s to clean up before a forceful restart is issued.
304 given 1s to clean up before a forceful restart is issued.
308
305
309 In all cases the kernel is restarted, the only difference is whether
306 In all cases the kernel is restarted, the only difference is whether
310 it is given a chance to perform a clean shutdown or not.
307 it is given a chance to perform a clean shutdown or not.
311
308
312 **kw : optional
309 **kw : optional
313 Any options specified here will overwrite those used to launch the
310 Any options specified here will overwrite those used to launch the
314 kernel.
311 kernel.
315 """
312 """
316 if self._launch_args is None:
313 if self._launch_args is None:
317 raise RuntimeError("Cannot restart the kernel. "
314 raise RuntimeError("Cannot restart the kernel. "
318 "No previous call to 'start_kernel'.")
315 "No previous call to 'start_kernel'.")
319 else:
316 else:
320 # Stop currently running kernel.
317 # Stop currently running kernel.
321 self.shutdown_kernel(now=now, restart=True)
318 self.shutdown_kernel(now=now, restart=True)
322
319
323 # Start new kernel.
320 # Start new kernel.
324 self._launch_args.update(kw)
321 self._launch_args.update(kw)
325 self.start_kernel(**self._launch_args)
322 self.start_kernel(**self._launch_args)
326
323
327 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
324 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
328 # unless there is some delay here.
325 # unless there is some delay here.
329 if sys.platform == 'win32':
326 if sys.platform == 'win32':
330 time.sleep(0.2)
327 time.sleep(0.2)
331
328
332 @property
329 @property
333 def has_kernel(self):
330 def has_kernel(self):
334 """Has a kernel been started that we are managing."""
331 """Has a kernel been started that we are managing."""
335 return self.kernel is not None
332 return self.kernel is not None
336
333
337 def _kill_kernel(self):
334 def _kill_kernel(self):
338 """Kill the running kernel.
335 """Kill the running kernel.
339
336
340 This is a private method, callers should use shutdown_kernel(now=True).
337 This is a private method, callers should use shutdown_kernel(now=True).
341 """
338 """
342 if self.has_kernel:
339 if self.has_kernel:
343
340
344 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
341 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
345 # TerminateProcess() on Win32).
342 # TerminateProcess() on Win32).
346 try:
343 try:
347 self.kernel.kill()
344 self.kernel.kill()
348 except OSError as e:
345 except OSError as e:
349 # In Windows, we will get an Access Denied error if the process
346 # In Windows, we will get an Access Denied error if the process
350 # has already terminated. Ignore it.
347 # has already terminated. Ignore it.
351 if sys.platform == 'win32':
348 if sys.platform == 'win32':
352 if e.winerror != 5:
349 if e.winerror != 5:
353 raise
350 raise
354 # On Unix, we may get an ESRCH error if the process has already
351 # On Unix, we may get an ESRCH error if the process has already
355 # terminated. Ignore it.
352 # terminated. Ignore it.
356 else:
353 else:
357 from errno import ESRCH
354 from errno import ESRCH
358 if e.errno != ESRCH:
355 if e.errno != ESRCH:
359 raise
356 raise
360
357
361 # Block until the kernel terminates.
358 # Block until the kernel terminates.
362 self.kernel.wait()
359 self.kernel.wait()
363 self.kernel = None
360 self.kernel = None
364 else:
361 else:
365 raise RuntimeError("Cannot kill kernel. No kernel is running!")
362 raise RuntimeError("Cannot kill kernel. No kernel is running!")
366
363
367 def interrupt_kernel(self):
364 def interrupt_kernel(self):
368 """Interrupts the kernel by sending it a signal.
365 """Interrupts the kernel by sending it a signal.
369
366
370 Unlike ``signal_kernel``, this operation is well supported on all
367 Unlike ``signal_kernel``, this operation is well supported on all
371 platforms.
368 platforms.
372 """
369 """
373 if self.has_kernel:
370 if self.has_kernel:
374 if sys.platform == 'win32':
371 if sys.platform == 'win32':
375 from .zmq.parentpoller import ParentPollerWindows as Poller
372 from .zmq.parentpoller import ParentPollerWindows as Poller
376 Poller.send_interrupt(self.kernel.win32_interrupt_event)
373 Poller.send_interrupt(self.kernel.win32_interrupt_event)
377 else:
374 else:
378 self.kernel.send_signal(signal.SIGINT)
375 self.kernel.send_signal(signal.SIGINT)
379 else:
376 else:
380 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
377 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
381
378
382 def signal_kernel(self, signum):
379 def signal_kernel(self, signum):
383 """Sends a signal to the kernel.
380 """Sends a signal to the kernel.
384
381
385 Note that since only SIGTERM is supported on Windows, this function is
382 Note that since only SIGTERM is supported on Windows, this function is
386 only useful on Unix systems.
383 only useful on Unix systems.
387 """
384 """
388 if self.has_kernel:
385 if self.has_kernel:
389 self.kernel.send_signal(signum)
386 self.kernel.send_signal(signum)
390 else:
387 else:
391 raise RuntimeError("Cannot signal kernel. No kernel is running!")
388 raise RuntimeError("Cannot signal kernel. No kernel is running!")
392
389
393 def is_alive(self):
390 def is_alive(self):
394 """Is the kernel process still running?"""
391 """Is the kernel process still running?"""
395 if self.has_kernel:
392 if self.has_kernel:
396 if self.kernel.poll() is None:
393 if self.kernel.poll() is None:
397 return True
394 return True
398 else:
395 else:
399 return False
396 return False
400 else:
397 else:
401 # we don't have a kernel
398 # we don't have a kernel
402 return False
399 return False
403
400
404
401
405 #-----------------------------------------------------------------------------
406 # ABC Registration
407 #-----------------------------------------------------------------------------
408
409 KernelManagerABC.register(KernelManager)
402 KernelManagerABC.register(KernelManager)
410
403
General Comments 0
You need to be logged in to leave comments. Login now