##// END OF EJS Templates
Merge pull request #5598 from takluyver/kernelspec...
Min RK -
r16479:52f7cd40 merge
parent child Browse files
Show More
@@ -0,0 +1,142
1 import io
2 import json
3 import os
4 import sys
5
6 pjoin = os.path.join
7
8 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.py3compat import PY3
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
11
12 if os.name == 'nt':
13 programdata = os.environ.get('PROGRAMDATA', None)
14 if programdata:
15 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
16 else: # PROGRAMDATA is not defined by default on XP.
17 SYSTEM_KERNEL_DIR = None
18 else:
19 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
20
21 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
22
23 class KernelSpec(HasTraits):
24 argv = List()
25 display_name = Unicode()
26 language = Unicode()
27 codemirror_mode = None
28 env = Dict()
29
30 resource_dir = Unicode()
31
32 def __init__(self, resource_dir, argv, display_name, language,
33 codemirror_mode=None):
34 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
35 display_name=display_name, language=language,
36 codemirror_mode=codemirror_mode)
37 if not self.codemirror_mode:
38 self.codemirror_mode = self.language
39
40 @classmethod
41 def from_resource_dir(cls, resource_dir):
42 """Create a KernelSpec object by reading kernel.json
43
44 Pass the path to the *directory* containing kernel.json.
45 """
46 kernel_file = pjoin(resource_dir, 'kernel.json')
47 with io.open(kernel_file, 'r', encoding='utf-8') as f:
48 kernel_dict = json.load(f)
49 return cls(resource_dir=resource_dir, **kernel_dict)
50
51 def _is_kernel_dir(path):
52 """Is ``path`` a kernel directory?"""
53 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
54
55 def _list_kernels_in(dir):
56 """Return a mapping of kernel names to resource directories from dir.
57
58 If dir is None or does not exist, returns an empty dict.
59 """
60 if dir is None or not os.path.isdir(dir):
61 return {}
62 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
63 if _is_kernel_dir(pjoin(dir, f))}
64
65 class NoSuchKernel(KeyError):
66 def __init__(self, name):
67 self.name = name
68
69 class KernelSpecManager(HasTraits):
70 ipython_dir = Unicode()
71 def _ipython_dir_default(self):
72 return get_ipython_dir()
73
74 user_kernel_dir = Unicode()
75 def _user_kernel_dir_default(self):
76 return pjoin(self.ipython_dir, 'kernels')
77
78 kernel_dirs = List(
79 help="List of kernel directories to search. Later ones take priority over earlier."
80 )
81 def _kernel_dirs_default(self):
82 return [
83 SYSTEM_KERNEL_DIR,
84 self.user_kernel_dir,
85 ]
86
87 def _make_native_kernel_dir(self):
88 """Makes a kernel directory for the native kernel.
89
90 The native kernel is the kernel using the same Python runtime as this
91 process. This will put its informatino in the user kernels directory.
92 """
93 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
94 os.makedirs(path, mode=0o755)
95 with open(pjoin(path, 'kernel.json'), 'w') as f:
96 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
97 'from IPython.kernel.zmq.kernelapp import main; main()',
98 '-f', '{connection_file}'],
99 'display_name': 'Python 3' if PY3 else 'Python 2',
100 'language': 'python',
101 'codemirror_mode': {'name': 'python',
102 'version': sys.version_info[0]},
103 },
104 f, indent=1)
105 # TODO: Copy icons into directory
106 return path
107
108 def find_kernel_specs(self):
109 """Returns a dict mapping kernel names to resource directories."""
110 d = {}
111 for kernel_dir in self.kernel_dirs:
112 d.update(_list_kernels_in(kernel_dir))
113
114 if NATIVE_KERNEL_NAME not in d:
115 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
116 return d
117 # TODO: Caching?
118
119 def get_kernel_spec(self, kernel_name):
120 """Returns a :class:`KernelSpec` instance for the given kernel_name.
121
122 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
123 """
124 if kernel_name == 'python':
125 kernel_name = NATIVE_KERNEL_NAME
126 d = self.find_kernel_specs()
127 try:
128 resource_dir = d[kernel_name.lower()]
129 except KeyError:
130 raise NoSuchKernel(kernel_name)
131 return KernelSpec.from_resource_dir(resource_dir)
132
133 def find_kernel_specs():
134 """Returns a dict mapping kernel names to resource directories."""
135 return KernelSpecManager().find_kernel_specs()
136
137 def get_kernel_spec(kernel_name):
138 """Returns a :class:`KernelSpec` instance for the given kernel_name.
139
140 Raises KeyError if the given kernel name is not found.
141 """
142 return KernelSpecManager().get_kernel_spec(kernel_name) No newline at end of file
@@ -0,0 +1,39
1 import json
2 import os
3 from os.path import join as pjoin
4 import unittest
5
6 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.kernel import kernelspec
8
9 sample_kernel_json = {'argv':['cat', '{connection_file}'],
10 'display_name':'Test kernel',
11 'language':'bash',
12 }
13
14 class KernelSpecTests(unittest.TestCase):
15 def setUp(self):
16 self.tempdir = td = TemporaryDirectory()
17 self.sample_kernel_dir = pjoin(td.name, 'kernels', 'Sample')
18 os.makedirs(self.sample_kernel_dir)
19 json_file = pjoin(self.sample_kernel_dir, 'kernel.json')
20 with open(json_file, 'w') as f:
21 json.dump(sample_kernel_json, f)
22
23 self.ksm = kernelspec.KernelSpecManager(ipython_dir=td.name)
24
25 def tearDown(self):
26 self.tempdir.cleanup()
27
28 def test_find_kernel_specs(self):
29 kernels = self.ksm.find_kernel_specs()
30 self.assertEqual(kernels['sample'], self.sample_kernel_dir)
31
32 def test_get_kernel_spec(self):
33 ks = self.ksm.get_kernel_spec('SAMPLE') # Case insensitive
34 self.assertEqual(ks.resource_dir, self.sample_kernel_dir)
35 self.assertEqual(ks.argv, sample_kernel_json['argv'])
36 self.assertEqual(ks.display_name, sample_kernel_json['display_name'])
37 self.assertEqual(ks.language, sample_kernel_json['language'])
38 self.assertEqual(ks.codemirror_mode, sample_kernel_json['language'])
39 self.assertEqual(ks.env, {}) No newline at end of file
@@ -1,387 +1,399
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import signal
27 import signal
28 import sys
28 import sys
29 import uuid
29 import uuid
30
30
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.kernel.blocking import BlockingKernelClient
35 from IPython.kernel.blocking import BlockingKernelClient
36 from IPython.kernel import KernelManager
36 from IPython.kernel import KernelManager
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 from IPython.kernel.kernelspec import NoSuchKernel
38 from IPython.utils.path import filefind
39 from IPython.utils.path import filefind
39 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 )
43 )
43 from IPython.kernel.zmq.kernelapp import (
44 from IPython.kernel.zmq.kernelapp import (
44 kernel_flags,
45 kernel_flags,
45 kernel_aliases,
46 kernel_aliases,
46 IPKernelApp
47 IPKernelApp
47 )
48 )
48 from IPython.kernel.zmq.pylab.config import InlineBackend
49 from IPython.kernel.zmq.pylab.config import InlineBackend
49 from IPython.kernel.zmq.session import Session, default_secure
50 from IPython.kernel.zmq.session import Session, default_secure
50 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.kernel.connect import ConnectionFileMixin
52 from IPython.kernel.connect import ConnectionFileMixin
52
53
53 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
54 # Network Constants
55 # Network Constants
55 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
56
57
57 from IPython.utils.localinterfaces import localhost
58 from IPython.utils.localinterfaces import localhost
58
59
59 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
60 # Globals
61 # Globals
61 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
62
63
63
64
64 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
65 # Aliases and Flags
66 # Aliases and Flags
66 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
67
68
68 flags = dict(kernel_flags)
69 flags = dict(kernel_flags)
69
70
70 # the flags that are specific to the frontend
71 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
72 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
73 # or it will raise an error on unrecognized flags
73 app_flags = {
74 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
77 }
77 app_flags.update(boolean_flag(
78 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
81 to force a direct exit without any confirmation.
81 """,
82 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
83 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
84 if it is owned by the frontend, and leave it alive if it is external.
84 """
85 """
85 ))
86 ))
86 flags.update(app_flags)
87 flags.update(app_flags)
87
88
88 aliases = dict(kernel_aliases)
89 aliases = dict(kernel_aliases)
89
90
90 # also scrub aliases from the frontend
91 # also scrub aliases from the frontend
91 app_aliases = dict(
92 app_aliases = dict(
92 ip = 'IPythonConsoleApp.ip',
93 ip = 'IPythonConsoleApp.ip',
93 transport = 'IPythonConsoleApp.transport',
94 transport = 'IPythonConsoleApp.transport',
94 hb = 'IPythonConsoleApp.hb_port',
95 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
99 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
100 f = 'IPythonConsoleApp.connection_file',
100
101
102 kernel = 'IPythonConsoleApp.kernel_name',
101
103
102 ssh = 'IPythonConsoleApp.sshserver',
104 ssh = 'IPythonConsoleApp.sshserver',
103 )
105 )
104 aliases.update(app_aliases)
106 aliases.update(app_aliases)
105
107
106 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
107 # Classes
109 # Classes
108 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
109
111
110 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
111 # IPythonConsole
113 # IPythonConsole
112 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
113
115
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
116 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
115
117
116 class IPythonConsoleApp(ConnectionFileMixin):
118 class IPythonConsoleApp(ConnectionFileMixin):
117 name = 'ipython-console-mixin'
119 name = 'ipython-console-mixin'
118
120
119 description = """
121 description = """
120 The IPython Mixin Console.
122 The IPython Mixin Console.
121
123
122 This class contains the common portions of console client (QtConsole,
124 This class contains the common portions of console client (QtConsole,
123 ZMQ-based terminal console, etc). It is not a full console, in that
125 ZMQ-based terminal console, etc). It is not a full console, in that
124 launched terminal subprocesses will not be able to accept input.
126 launched terminal subprocesses will not be able to accept input.
125
127
126 The Console using this mixing supports various extra features beyond
128 The Console using this mixing supports various extra features beyond
127 the single-process Terminal IPython shell, such as connecting to
129 the single-process Terminal IPython shell, such as connecting to
128 existing kernel, via:
130 existing kernel, via:
129
131
130 ipython <appname> --existing
132 ipython <appname> --existing
131
133
132 as well as tunnel via SSH
134 as well as tunnel via SSH
133
135
134 """
136 """
135
137
136 classes = classes
138 classes = classes
137 flags = Dict(flags)
139 flags = Dict(flags)
138 aliases = Dict(aliases)
140 aliases = Dict(aliases)
139 kernel_manager_class = KernelManager
141 kernel_manager_class = KernelManager
140 kernel_client_class = BlockingKernelClient
142 kernel_client_class = BlockingKernelClient
141
143
142 kernel_argv = List(Unicode)
144 kernel_argv = List(Unicode)
143 # frontend flags&aliases to be stripped when building kernel_argv
145 # frontend flags&aliases to be stripped when building kernel_argv
144 frontend_flags = Any(app_flags)
146 frontend_flags = Any(app_flags)
145 frontend_aliases = Any(app_aliases)
147 frontend_aliases = Any(app_aliases)
146
148
147 # create requested profiles by default, if they don't exist:
149 # create requested profiles by default, if they don't exist:
148 auto_create = CBool(True)
150 auto_create = CBool(True)
149 # connection info:
151 # connection info:
150
152
151 sshserver = Unicode('', config=True,
153 sshserver = Unicode('', config=True,
152 help="""The SSH server to use to connect to the kernel.""")
154 help="""The SSH server to use to connect to the kernel.""")
153 sshkey = Unicode('', config=True,
155 sshkey = Unicode('', config=True,
154 help="""Path to the ssh key to use for logging in to the ssh server.""")
156 help="""Path to the ssh key to use for logging in to the ssh server.""")
155
157
156 hb_port = Int(0, config=True,
158 hb_port = Int(0, config=True,
157 help="set the heartbeat port [default: random]")
159 help="set the heartbeat port [default: random]")
158 shell_port = Int(0, config=True,
160 shell_port = Int(0, config=True,
159 help="set the shell (ROUTER) port [default: random]")
161 help="set the shell (ROUTER) port [default: random]")
160 iopub_port = Int(0, config=True,
162 iopub_port = Int(0, config=True,
161 help="set the iopub (PUB) port [default: random]")
163 help="set the iopub (PUB) port [default: random]")
162 stdin_port = Int(0, config=True,
164 stdin_port = Int(0, config=True,
163 help="set the stdin (DEALER) port [default: random]")
165 help="set the stdin (DEALER) port [default: random]")
164 connection_file = Unicode('', config=True,
166 connection_file = Unicode('', config=True,
165 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
166
168
167 This file will contain the IP, ports, and authentication key needed to connect
169 This file will contain the IP, ports, and authentication key needed to connect
168 clients to this kernel. By default, this file will be created in the security-dir
170 clients to this kernel. By default, this file will be created in the security-dir
169 of the current profile, but can be specified by absolute path.
171 of the current profile, but can be specified by absolute path.
170 """)
172 """)
171 def _connection_file_default(self):
173 def _connection_file_default(self):
172 return 'kernel-%i.json' % os.getpid()
174 return 'kernel-%i.json' % os.getpid()
173
175
174 existing = CUnicode('', config=True,
176 existing = CUnicode('', config=True,
175 help="""Connect to an already running kernel""")
177 help="""Connect to an already running kernel""")
176
178
179 kernel_name = Unicode('python', config=True,
180 help="""The name of the default kernel to start.""")
181
177 confirm_exit = CBool(True, config=True,
182 confirm_exit = CBool(True, config=True,
178 help="""
183 help="""
179 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
184 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
180 to force a direct exit without any confirmation.""",
185 to force a direct exit without any confirmation.""",
181 )
186 )
182
187
183
188
184 def build_kernel_argv(self, argv=None):
189 def build_kernel_argv(self, argv=None):
185 """build argv to be passed to kernel subprocess"""
190 """build argv to be passed to kernel subprocess"""
186 if argv is None:
191 if argv is None:
187 argv = sys.argv[1:]
192 argv = sys.argv[1:]
188 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
193 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
189 # kernel should inherit default config file from frontend
194 # kernel should inherit default config file from frontend
190 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
195 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
191
196
192 def init_connection_file(self):
197 def init_connection_file(self):
193 """find the connection file, and load the info if found.
198 """find the connection file, and load the info if found.
194
199
195 The current working directory and the current profile's security
200 The current working directory and the current profile's security
196 directory will be searched for the file if it is not given by
201 directory will be searched for the file if it is not given by
197 absolute path.
202 absolute path.
198
203
199 When attempting to connect to an existing kernel and the `--existing`
204 When attempting to connect to an existing kernel and the `--existing`
200 argument does not match an existing file, it will be interpreted as a
205 argument does not match an existing file, it will be interpreted as a
201 fileglob, and the matching file in the current profile's security dir
206 fileglob, and the matching file in the current profile's security dir
202 with the latest access time will be used.
207 with the latest access time will be used.
203
208
204 After this method is called, self.connection_file contains the *full path*
209 After this method is called, self.connection_file contains the *full path*
205 to the connection file, never just its name.
210 to the connection file, never just its name.
206 """
211 """
207 if self.existing:
212 if self.existing:
208 try:
213 try:
209 cf = find_connection_file(self.existing)
214 cf = find_connection_file(self.existing)
210 except Exception:
215 except Exception:
211 self.log.critical("Could not find existing kernel connection file %s", self.existing)
216 self.log.critical("Could not find existing kernel connection file %s", self.existing)
212 self.exit(1)
217 self.exit(1)
213 self.log.debug("Connecting to existing kernel: %s" % cf)
218 self.log.debug("Connecting to existing kernel: %s" % cf)
214 self.connection_file = cf
219 self.connection_file = cf
215 else:
220 else:
216 # not existing, check if we are going to write the file
221 # not existing, check if we are going to write the file
217 # and ensure that self.connection_file is a full path, not just the shortname
222 # and ensure that self.connection_file is a full path, not just the shortname
218 try:
223 try:
219 cf = find_connection_file(self.connection_file)
224 cf = find_connection_file(self.connection_file)
220 except Exception:
225 except Exception:
221 # file might not exist
226 # file might not exist
222 if self.connection_file == os.path.basename(self.connection_file):
227 if self.connection_file == os.path.basename(self.connection_file):
223 # just shortname, put it in security dir
228 # just shortname, put it in security dir
224 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
229 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
225 else:
230 else:
226 cf = self.connection_file
231 cf = self.connection_file
227 self.connection_file = cf
232 self.connection_file = cf
228
233
229 # should load_connection_file only be used for existing?
234 # should load_connection_file only be used for existing?
230 # as it is now, this allows reusing ports if an existing
235 # as it is now, this allows reusing ports if an existing
231 # file is requested
236 # file is requested
232 try:
237 try:
233 self.load_connection_file()
238 self.load_connection_file()
234 except Exception:
239 except Exception:
235 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
240 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
236 self.exit(1)
241 self.exit(1)
237
242
238 def load_connection_file(self):
243 def load_connection_file(self):
239 """load ip/port/hmac config from JSON connection file"""
244 """load ip/port/hmac config from JSON connection file"""
240 # this is identical to IPKernelApp.load_connection_file
245 # this is identical to IPKernelApp.load_connection_file
241 # perhaps it can be centralized somewhere?
246 # perhaps it can be centralized somewhere?
242 try:
247 try:
243 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
248 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
244 except IOError:
249 except IOError:
245 self.log.debug("Connection File not found: %s", self.connection_file)
250 self.log.debug("Connection File not found: %s", self.connection_file)
246 return
251 return
247 self.log.debug(u"Loading connection file %s", fname)
252 self.log.debug(u"Loading connection file %s", fname)
248 with open(fname) as f:
253 with open(fname) as f:
249 cfg = json.load(f)
254 cfg = json.load(f)
250 self.transport = cfg.get('transport', 'tcp')
255 self.transport = cfg.get('transport', 'tcp')
251 self.ip = cfg.get('ip', localhost())
256 self.ip = cfg.get('ip', localhost())
252
257
253 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
258 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
254 name = channel + '_port'
259 name = channel + '_port'
255 if getattr(self, name) == 0 and name in cfg:
260 if getattr(self, name) == 0 and name in cfg:
256 # not overridden by config or cl_args
261 # not overridden by config or cl_args
257 setattr(self, name, cfg[name])
262 setattr(self, name, cfg[name])
258 if 'key' in cfg:
263 if 'key' in cfg:
259 self.config.Session.key = str_to_bytes(cfg['key'])
264 self.config.Session.key = str_to_bytes(cfg['key'])
260 if 'signature_scheme' in cfg:
265 if 'signature_scheme' in cfg:
261 self.config.Session.signature_scheme = cfg['signature_scheme']
266 self.config.Session.signature_scheme = cfg['signature_scheme']
262
267
263 def init_ssh(self):
268 def init_ssh(self):
264 """set up ssh tunnels, if needed."""
269 """set up ssh tunnels, if needed."""
265 if not self.existing or (not self.sshserver and not self.sshkey):
270 if not self.existing or (not self.sshserver and not self.sshkey):
266 return
271 return
267 self.load_connection_file()
272 self.load_connection_file()
268
273
269 transport = self.transport
274 transport = self.transport
270 ip = self.ip
275 ip = self.ip
271
276
272 if transport != 'tcp':
277 if transport != 'tcp':
273 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
278 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
274 sys.exit(-1)
279 sys.exit(-1)
275
280
276 if self.sshkey and not self.sshserver:
281 if self.sshkey and not self.sshserver:
277 # specifying just the key implies that we are connecting directly
282 # specifying just the key implies that we are connecting directly
278 self.sshserver = ip
283 self.sshserver = ip
279 ip = localhost()
284 ip = localhost()
280
285
281 # build connection dict for tunnels:
286 # build connection dict for tunnels:
282 info = dict(ip=ip,
287 info = dict(ip=ip,
283 shell_port=self.shell_port,
288 shell_port=self.shell_port,
284 iopub_port=self.iopub_port,
289 iopub_port=self.iopub_port,
285 stdin_port=self.stdin_port,
290 stdin_port=self.stdin_port,
286 hb_port=self.hb_port
291 hb_port=self.hb_port
287 )
292 )
288
293
289 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
294 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
290
295
291 # tunnels return a new set of ports, which will be on localhost:
296 # tunnels return a new set of ports, which will be on localhost:
292 self.ip = localhost()
297 self.ip = localhost()
293 try:
298 try:
294 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
299 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
295 except:
300 except:
296 # even catch KeyboardInterrupt
301 # even catch KeyboardInterrupt
297 self.log.error("Could not setup tunnels", exc_info=True)
302 self.log.error("Could not setup tunnels", exc_info=True)
298 self.exit(1)
303 self.exit(1)
299
304
300 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
305 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
301
306
302 cf = self.connection_file
307 cf = self.connection_file
303 base,ext = os.path.splitext(cf)
308 base,ext = os.path.splitext(cf)
304 base = os.path.basename(base)
309 base = os.path.basename(base)
305 self.connection_file = os.path.basename(base)+'-ssh'+ext
310 self.connection_file = os.path.basename(base)+'-ssh'+ext
306 self.log.info("To connect another client via this tunnel, use:")
311 self.log.info("To connect another client via this tunnel, use:")
307 self.log.info("--existing %s" % self.connection_file)
312 self.log.info("--existing %s" % self.connection_file)
308
313
309 def _new_connection_file(self):
314 def _new_connection_file(self):
310 cf = ''
315 cf = ''
311 while not cf:
316 while not cf:
312 # we don't need a 128b id to distinguish kernels, use more readable
317 # we don't need a 128b id to distinguish kernels, use more readable
313 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
318 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
314 # kernels can subclass.
319 # kernels can subclass.
315 ident = str(uuid.uuid4()).split('-')[-1]
320 ident = str(uuid.uuid4()).split('-')[-1]
316 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
321 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
317 # only keep if it's actually new. Protect against unlikely collision
322 # only keep if it's actually new. Protect against unlikely collision
318 # in 48b random search space
323 # in 48b random search space
319 cf = cf if not os.path.exists(cf) else ''
324 cf = cf if not os.path.exists(cf) else ''
320 return cf
325 return cf
321
326
322 def init_kernel_manager(self):
327 def init_kernel_manager(self):
323 # Don't let Qt or ZMQ swallow KeyboardInterupts.
328 # Don't let Qt or ZMQ swallow KeyboardInterupts.
324 if self.existing:
329 if self.existing:
325 self.kernel_manager = None
330 self.kernel_manager = None
326 return
331 return
327 signal.signal(signal.SIGINT, signal.SIG_DFL)
332 signal.signal(signal.SIGINT, signal.SIG_DFL)
328
333
329 # Create a KernelManager and start a kernel.
334 # Create a KernelManager and start a kernel.
330 self.kernel_manager = self.kernel_manager_class(
335 try:
331 ip=self.ip,
336 self.kernel_manager = self.kernel_manager_class(
332 transport=self.transport,
337 ip=self.ip,
333 shell_port=self.shell_port,
338 transport=self.transport,
334 iopub_port=self.iopub_port,
339 shell_port=self.shell_port,
335 stdin_port=self.stdin_port,
340 iopub_port=self.iopub_port,
336 hb_port=self.hb_port,
341 stdin_port=self.stdin_port,
337 connection_file=self.connection_file,
342 hb_port=self.hb_port,
338 parent=self,
343 connection_file=self.connection_file,
339 )
344 kernel_name=self.kernel_name,
345 parent=self,
346 ipython_dir=self.ipython_dir,
347 )
348 except NoSuchKernel:
349 self.log.critical("Could not find kernel %s", self.kernel_name)
350 self.exit(1)
351
340 self.kernel_manager.client_factory = self.kernel_client_class
352 self.kernel_manager.client_factory = self.kernel_client_class
341 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
353 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
342 atexit.register(self.kernel_manager.cleanup_ipc_files)
354 atexit.register(self.kernel_manager.cleanup_ipc_files)
343
355
344 if self.sshserver:
356 if self.sshserver:
345 # ssh, write new connection file
357 # ssh, write new connection file
346 self.kernel_manager.write_connection_file()
358 self.kernel_manager.write_connection_file()
347
359
348 # in case KM defaults / ssh writing changes things:
360 # in case KM defaults / ssh writing changes things:
349 km = self.kernel_manager
361 km = self.kernel_manager
350 self.shell_port=km.shell_port
362 self.shell_port=km.shell_port
351 self.iopub_port=km.iopub_port
363 self.iopub_port=km.iopub_port
352 self.stdin_port=km.stdin_port
364 self.stdin_port=km.stdin_port
353 self.hb_port=km.hb_port
365 self.hb_port=km.hb_port
354 self.connection_file = km.connection_file
366 self.connection_file = km.connection_file
355
367
356 atexit.register(self.kernel_manager.cleanup_connection_file)
368 atexit.register(self.kernel_manager.cleanup_connection_file)
357
369
358 def init_kernel_client(self):
370 def init_kernel_client(self):
359 if self.kernel_manager is not None:
371 if self.kernel_manager is not None:
360 self.kernel_client = self.kernel_manager.client()
372 self.kernel_client = self.kernel_manager.client()
361 else:
373 else:
362 self.kernel_client = self.kernel_client_class(
374 self.kernel_client = self.kernel_client_class(
363 ip=self.ip,
375 ip=self.ip,
364 transport=self.transport,
376 transport=self.transport,
365 shell_port=self.shell_port,
377 shell_port=self.shell_port,
366 iopub_port=self.iopub_port,
378 iopub_port=self.iopub_port,
367 stdin_port=self.stdin_port,
379 stdin_port=self.stdin_port,
368 hb_port=self.hb_port,
380 hb_port=self.hb_port,
369 connection_file=self.connection_file,
381 connection_file=self.connection_file,
370 parent=self,
382 parent=self,
371 )
383 )
372
384
373 self.kernel_client.start_channels()
385 self.kernel_client.start_channels()
374
386
375
387
376
388
377 def initialize(self, argv=None):
389 def initialize(self, argv=None):
378 """
390 """
379 Classes which mix this class in should call:
391 Classes which mix this class in should call:
380 IPythonConsoleApp.initialize(self,argv)
392 IPythonConsoleApp.initialize(self,argv)
381 """
393 """
382 self.init_connection_file()
394 self.init_connection_file()
383 default_secure(self.config)
395 default_secure(self.config)
384 self.init_ssh()
396 self.init_ssh()
385 self.init_kernel_manager()
397 self.init_kernel_manager()
386 self.init_kernel_client()
398 self.init_kernel_client()
387
399
@@ -1,272 +1,256
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,389 +1,413
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
14 import warnings
21
15
22 import zmq
16 import zmq
23
17
24 # Local imports
18 # Local imports
25 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.importstring import import_item
20 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import is_local_ip, local_ips
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
28 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 )
25 )
31 from IPython.kernel import (
26 from IPython.kernel import (
32 make_ipkernel_cmd,
27 make_ipkernel_cmd,
33 launch_kernel,
28 launch_kernel,
29 kernelspec,
34 )
30 )
35 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
36 from .zmq.session import Session
32 from .zmq.session import Session
37 from .managerabc import (
33 from .managerabc import (
38 KernelManagerABC
34 KernelManagerABC
39 )
35 )
40
36
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
44
37
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
46 """Manages a single kernel in a subprocess on this host.
39 """Manages a single kernel in a subprocess on this host.
47
40
48 This version starts kernels with Popen.
41 This version starts kernels with Popen.
49 """
42 """
50
43
51 # The PyZMQ Context to use for communication with the kernel.
44 # The PyZMQ Context to use for communication with the kernel.
52 context = Instance(zmq.Context)
45 context = Instance(zmq.Context)
53 def _context_default(self):
46 def _context_default(self):
54 return zmq.Context.instance()
47 return zmq.Context.instance()
55
48
56 # The Session to use for communication with the kernel.
49 # The Session to use for communication with the kernel.
57 session = Instance(Session)
50 session = Instance(Session)
58 def _session_default(self):
51 def _session_default(self):
59 return Session(parent=self)
52 return Session(parent=self)
60
53
61 # the class to create with our `client` method
54 # the class to create with our `client` method
62 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
55 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
63 client_factory = Type()
56 client_factory = Type()
64 def _client_class_changed(self, name, old, new):
57 def _client_class_changed(self, name, old, new):
65 self.client_factory = import_item(str(new))
58 self.client_factory = import_item(str(new))
66
59
67 # The kernel process with which the KernelManager is communicating.
60 # The kernel process with which the KernelManager is communicating.
68 # generally a Popen instance
61 # generally a Popen instance
69 kernel = Any()
62 kernel = Any()
63
64 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
65
66 def _kernel_spec_manager_default(self):
67 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
68
69 kernel_name = Unicode('python')
70
71 kernel_spec = Instance(kernelspec.KernelSpec)
72
73 def _kernel_spec_default(self):
74 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
75
76 def _kernel_name_changed(self, name, old, new):
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
70
79
71 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
72 help="""The Popen Command to launch the kernel.
81 help="""DEPRECATED: Use kernel_name instead.
82
83 The Popen Command to launch the kernel.
73 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
74 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
75 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
76 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
77 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
78 this means that the kernel does not receive the
89 this means that the kernel does not receive the
79 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
80 """
91 """
81 )
92 )
82
93
83 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 "
96 "start different kernels.")
84 self.ipython_kernel = False
97 self.ipython_kernel = False
85
98
86 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
87
104
88 # Protected traits
105 # Protected traits
89 _launch_args = Any()
106 _launch_args = Any()
90 _control_socket = Any()
107 _control_socket = Any()
91
108
92 _restarter = Any()
109 _restarter = Any()
93
110
94 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
95 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
96 )
113 )
97
114
98 def __del__(self):
115 def __del__(self):
99 self._close_control_socket()
116 self._close_control_socket()
100 self.cleanup_connection_file()
117 self.cleanup_connection_file()
101
118
102 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
103 # Kernel restarter
120 # Kernel restarter
104 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
105
122
106 def start_restarter(self):
123 def start_restarter(self):
107 pass
124 pass
108
125
109 def stop_restarter(self):
126 def stop_restarter(self):
110 pass
127 pass
111
128
112 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
113 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
114 if self._restarter is None:
131 if self._restarter is None:
115 return
132 return
116 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
117
134
118 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
119 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
120 if self._restarter is None:
137 if self._restarter is None:
121 return
138 return
122 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
123
140
124 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
125 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
126 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
127
144
128 def client(self, **kwargs):
145 def client(self, **kwargs):
129 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
130 if self.client_factory is None:
147 if self.client_factory is None:
131 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
132
149
133 kw = {}
150 kw = {}
134 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
135 kw.update(dict(
152 kw.update(dict(
136 connection_file=self.connection_file,
153 connection_file=self.connection_file,
137 session=self.session,
154 session=self.session,
138 parent=self,
155 parent=self,
139 ))
156 ))
140
157
141 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
142 kw.update(kwargs)
159 kw.update(kwargs)
143 return self.client_factory(**kw)
160 return self.client_factory(**kw)
144
161
145 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
146 # Kernel management
163 # Kernel management
147 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
148
165
149 def format_kernel_cmd(self, **kw):
166 def format_kernel_cmd(self, **kw):
150 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
151 if self.kernel_cmd:
168 if self.kernel_cmd:
152 cmd = self.kernel_cmd
169 cmd = self.kernel_cmd
153 else:
170 elif self.kernel_name == 'python':
171 # The native kernel gets special handling
154 cmd = make_ipkernel_cmd(
172 cmd = make_ipkernel_cmd(
155 'from IPython.kernel.zmq.kernelapp import main; main()',
173 'from IPython.kernel.zmq.kernelapp import main; main()',
156 **kw
174 **kw
157 )
175 )
176 else:
177 cmd = self.kernel_spec.argv
178
158 ns = dict(connection_file=self.connection_file)
179 ns = dict(connection_file=self.connection_file)
159 ns.update(self._launch_args)
180 ns.update(self._launch_args)
160
181
161 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
182 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
162 def from_ns(match):
183 def from_ns(match):
163 """Get the key out of ns if it's there, otherwise no change."""
184 """Get the key out of ns if it's there, otherwise no change."""
164 return ns.get(match.group(1), match.group())
185 return ns.get(match.group(1), match.group())
165
186
166 return [ pat.sub(from_ns, arg) for arg in cmd ]
187 return [ pat.sub(from_ns, arg) for arg in cmd ]
167
188
168 def _launch_kernel(self, kernel_cmd, **kw):
189 def _launch_kernel(self, kernel_cmd, **kw):
169 """actually launch the kernel
190 """actually launch the kernel
170
191
171 override in a subclass to launch kernel subprocesses differently
192 override in a subclass to launch kernel subprocesses differently
172 """
193 """
173 return launch_kernel(kernel_cmd, **kw)
194 return launch_kernel(kernel_cmd, **kw)
174
195
175 # Control socket used for polite kernel shutdown
196 # Control socket used for polite kernel shutdown
176
197
177 def _connect_control_socket(self):
198 def _connect_control_socket(self):
178 if self._control_socket is None:
199 if self._control_socket is None:
179 self._control_socket = self.connect_control()
200 self._control_socket = self.connect_control()
180 self._control_socket.linger = 100
201 self._control_socket.linger = 100
181
202
182 def _close_control_socket(self):
203 def _close_control_socket(self):
183 if self._control_socket is None:
204 if self._control_socket is None:
184 return
205 return
185 self._control_socket.close()
206 self._control_socket.close()
186 self._control_socket = None
207 self._control_socket = None
187
208
188 def start_kernel(self, **kw):
209 def start_kernel(self, **kw):
189 """Starts a kernel on this host in a separate process.
210 """Starts a kernel on this host in a separate process.
190
211
191 If random ports (port=0) are being used, this method must be called
212 If random ports (port=0) are being used, this method must be called
192 before the channels are created.
213 before the channels are created.
193
214
194 Parameters
215 Parameters
195 ----------
216 ----------
196 **kw : optional
217 **kw : optional
197 keyword arguments that are passed down to build the kernel_cmd
218 keyword arguments that are passed down to build the kernel_cmd
198 and launching the kernel (e.g. Popen kwargs).
219 and launching the kernel (e.g. Popen kwargs).
199 """
220 """
200 if self.transport == 'tcp' and not is_local_ip(self.ip):
221 if self.transport == 'tcp' and not is_local_ip(self.ip):
201 raise RuntimeError("Can only launch a kernel on a local interface. "
222 raise RuntimeError("Can only launch a kernel on a local interface. "
202 "Make sure that the '*_address' attributes are "
223 "Make sure that the '*_address' attributes are "
203 "configured properly. "
224 "configured properly. "
204 "Currently valid addresses are: %s" % local_ips()
225 "Currently valid addresses are: %s" % local_ips()
205 )
226 )
206
227
207 # write connection file / get default ports
228 # write connection file / get default ports
208 self.write_connection_file()
229 self.write_connection_file()
209
230
210 # save kwargs for use in restart
231 # save kwargs for use in restart
211 self._launch_args = kw.copy()
232 self._launch_args = kw.copy()
212 # build the Popen cmd
233 # build the Popen cmd
213 kernel_cmd = self.format_kernel_cmd(**kw)
234 kernel_cmd = self.format_kernel_cmd(**kw)
235 if self.kernel_cmd:
236 # If kernel_cmd has been set manually, don't refer to a kernel spec
237 env = os.environ
238 else:
239 # Environment variables from kernel spec are added to os.environ
240 env = os.environ.copy()
241 env.update(self.kernel_spec.env or {})
214 # launch the kernel subprocess
242 # launch the kernel subprocess
215 self.kernel = self._launch_kernel(kernel_cmd,
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
216 ipython_kernel=self.ipython_kernel,
244 ipython_kernel=self.ipython_kernel,
217 **kw)
245 **kw)
218 self.start_restarter()
246 self.start_restarter()
219 self._connect_control_socket()
247 self._connect_control_socket()
220
248
221 def _send_shutdown_request(self, restart=False):
249 def _send_shutdown_request(self, restart=False):
222 """TODO: send a shutdown request via control channel"""
250 """TODO: send a shutdown request via control channel"""
223 content = dict(restart=restart)
251 content = dict(restart=restart)
224 msg = self.session.msg("shutdown_request", content=content)
252 msg = self.session.msg("shutdown_request", content=content)
225 self.session.send(self._control_socket, msg)
253 self.session.send(self._control_socket, msg)
226
254
227 def shutdown_kernel(self, now=False, restart=False):
255 def shutdown_kernel(self, now=False, restart=False):
228 """Attempts to the stop the kernel process cleanly.
256 """Attempts to the stop the kernel process cleanly.
229
257
230 This attempts to shutdown the kernels cleanly by:
258 This attempts to shutdown the kernels cleanly by:
231
259
232 1. Sending it a shutdown message over the shell channel.
260 1. Sending it a shutdown message over the shell channel.
233 2. If that fails, the kernel is shutdown forcibly by sending it
261 2. If that fails, the kernel is shutdown forcibly by sending it
234 a signal.
262 a signal.
235
263
236 Parameters
264 Parameters
237 ----------
265 ----------
238 now : bool
266 now : bool
239 Should the kernel be forcible killed *now*. This skips the
267 Should the kernel be forcible killed *now*. This skips the
240 first, nice shutdown attempt.
268 first, nice shutdown attempt.
241 restart: bool
269 restart: bool
242 Will this kernel be restarted after it is shutdown. When this
270 Will this kernel be restarted after it is shutdown. When this
243 is True, connection files will not be cleaned up.
271 is True, connection files will not be cleaned up.
244 """
272 """
245 # Stop monitoring for restarting while we shutdown.
273 # Stop monitoring for restarting while we shutdown.
246 self.stop_restarter()
274 self.stop_restarter()
247
275
248 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
276 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
249 if now or sys.platform == 'win32':
277 if now or sys.platform == 'win32':
250 if self.has_kernel:
278 if self.has_kernel:
251 self._kill_kernel()
279 self._kill_kernel()
252 else:
280 else:
253 # Don't send any additional kernel kill messages immediately, to give
281 # Don't send any additional kernel kill messages immediately, to give
254 # the kernel a chance to properly execute shutdown actions. Wait for at
282 # the kernel a chance to properly execute shutdown actions. Wait for at
255 # most 1s, checking every 0.1s.
283 # most 1s, checking every 0.1s.
256 self._send_shutdown_request(restart=restart)
284 self._send_shutdown_request(restart=restart)
257 for i in range(10):
285 for i in range(10):
258 if self.is_alive():
286 if self.is_alive():
259 time.sleep(0.1)
287 time.sleep(0.1)
260 else:
288 else:
261 break
289 break
262 else:
290 else:
263 # OK, we've waited long enough.
291 # OK, we've waited long enough.
264 if self.has_kernel:
292 if self.has_kernel:
265 self._kill_kernel()
293 self._kill_kernel()
266
294
267 if not restart:
295 if not restart:
268 self.cleanup_connection_file()
296 self.cleanup_connection_file()
269 self.cleanup_ipc_files()
297 self.cleanup_ipc_files()
270 else:
298 else:
271 self.cleanup_ipc_files()
299 self.cleanup_ipc_files()
272
300
273 self._close_control_socket()
301 self._close_control_socket()
274
302
275 def restart_kernel(self, now=False, **kw):
303 def restart_kernel(self, now=False, **kw):
276 """Restarts a kernel with the arguments that were used to launch it.
304 """Restarts a kernel with the arguments that were used to launch it.
277
305
278 If the old kernel was launched with random ports, the same ports will be
306 If the old kernel was launched with random ports, the same ports will be
279 used for the new kernel. The same connection file is used again.
307 used for the new kernel. The same connection file is used again.
280
308
281 Parameters
309 Parameters
282 ----------
310 ----------
283 now : bool, optional
311 now : bool, optional
284 If True, the kernel is forcefully restarted *immediately*, without
312 If True, the kernel is forcefully restarted *immediately*, without
285 having a chance to do any cleanup action. Otherwise the kernel is
313 having a chance to do any cleanup action. Otherwise the kernel is
286 given 1s to clean up before a forceful restart is issued.
314 given 1s to clean up before a forceful restart is issued.
287
315
288 In all cases the kernel is restarted, the only difference is whether
316 In all cases the kernel is restarted, the only difference is whether
289 it is given a chance to perform a clean shutdown or not.
317 it is given a chance to perform a clean shutdown or not.
290
318
291 **kw : optional
319 **kw : optional
292 Any options specified here will overwrite those used to launch the
320 Any options specified here will overwrite those used to launch the
293 kernel.
321 kernel.
294 """
322 """
295 if self._launch_args is None:
323 if self._launch_args is None:
296 raise RuntimeError("Cannot restart the kernel. "
324 raise RuntimeError("Cannot restart the kernel. "
297 "No previous call to 'start_kernel'.")
325 "No previous call to 'start_kernel'.")
298 else:
326 else:
299 # Stop currently running kernel.
327 # Stop currently running kernel.
300 self.shutdown_kernel(now=now, restart=True)
328 self.shutdown_kernel(now=now, restart=True)
301
329
302 # Start new kernel.
330 # Start new kernel.
303 self._launch_args.update(kw)
331 self._launch_args.update(kw)
304 self.start_kernel(**self._launch_args)
332 self.start_kernel(**self._launch_args)
305
333
306 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
334 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
307 # unless there is some delay here.
335 # unless there is some delay here.
308 if sys.platform == 'win32':
336 if sys.platform == 'win32':
309 time.sleep(0.2)
337 time.sleep(0.2)
310
338
311 @property
339 @property
312 def has_kernel(self):
340 def has_kernel(self):
313 """Has a kernel been started that we are managing."""
341 """Has a kernel been started that we are managing."""
314 return self.kernel is not None
342 return self.kernel is not None
315
343
316 def _kill_kernel(self):
344 def _kill_kernel(self):
317 """Kill the running kernel.
345 """Kill the running kernel.
318
346
319 This is a private method, callers should use shutdown_kernel(now=True).
347 This is a private method, callers should use shutdown_kernel(now=True).
320 """
348 """
321 if self.has_kernel:
349 if self.has_kernel:
322
350
323 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
351 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
324 # TerminateProcess() on Win32).
352 # TerminateProcess() on Win32).
325 try:
353 try:
326 self.kernel.kill()
354 self.kernel.kill()
327 except OSError as e:
355 except OSError as e:
328 # In Windows, we will get an Access Denied error if the process
356 # In Windows, we will get an Access Denied error if the process
329 # has already terminated. Ignore it.
357 # has already terminated. Ignore it.
330 if sys.platform == 'win32':
358 if sys.platform == 'win32':
331 if e.winerror != 5:
359 if e.winerror != 5:
332 raise
360 raise
333 # On Unix, we may get an ESRCH error if the process has already
361 # On Unix, we may get an ESRCH error if the process has already
334 # terminated. Ignore it.
362 # terminated. Ignore it.
335 else:
363 else:
336 from errno import ESRCH
364 from errno import ESRCH
337 if e.errno != ESRCH:
365 if e.errno != ESRCH:
338 raise
366 raise
339
367
340 # Block until the kernel terminates.
368 # Block until the kernel terminates.
341 self.kernel.wait()
369 self.kernel.wait()
342 self.kernel = None
370 self.kernel = None
343 else:
371 else:
344 raise RuntimeError("Cannot kill kernel. No kernel is running!")
372 raise RuntimeError("Cannot kill kernel. No kernel is running!")
345
373
346 def interrupt_kernel(self):
374 def interrupt_kernel(self):
347 """Interrupts the kernel by sending it a signal.
375 """Interrupts the kernel by sending it a signal.
348
376
349 Unlike ``signal_kernel``, this operation is well supported on all
377 Unlike ``signal_kernel``, this operation is well supported on all
350 platforms.
378 platforms.
351 """
379 """
352 if self.has_kernel:
380 if self.has_kernel:
353 if sys.platform == 'win32':
381 if sys.platform == 'win32':
354 from .zmq.parentpoller import ParentPollerWindows as Poller
382 from .zmq.parentpoller import ParentPollerWindows as Poller
355 Poller.send_interrupt(self.kernel.win32_interrupt_event)
383 Poller.send_interrupt(self.kernel.win32_interrupt_event)
356 else:
384 else:
357 self.kernel.send_signal(signal.SIGINT)
385 self.kernel.send_signal(signal.SIGINT)
358 else:
386 else:
359 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
387 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
360
388
361 def signal_kernel(self, signum):
389 def signal_kernel(self, signum):
362 """Sends a signal to the kernel.
390 """Sends a signal to the kernel.
363
391
364 Note that since only SIGTERM is supported on Windows, this function is
392 Note that since only SIGTERM is supported on Windows, this function is
365 only useful on Unix systems.
393 only useful on Unix systems.
366 """
394 """
367 if self.has_kernel:
395 if self.has_kernel:
368 self.kernel.send_signal(signum)
396 self.kernel.send_signal(signum)
369 else:
397 else:
370 raise RuntimeError("Cannot signal kernel. No kernel is running!")
398 raise RuntimeError("Cannot signal kernel. No kernel is running!")
371
399
372 def is_alive(self):
400 def is_alive(self):
373 """Is the kernel process still running?"""
401 """Is the kernel process still running?"""
374 if self.has_kernel:
402 if self.has_kernel:
375 if self.kernel.poll() is None:
403 if self.kernel.poll() is None:
376 return True
404 return True
377 else:
405 else:
378 return False
406 return False
379 else:
407 else:
380 # we don't have a kernel
408 # we don't have a kernel
381 return False
409 return False
382
410
383
411
384 #-----------------------------------------------------------------------------
385 # ABC Registration
386 #-----------------------------------------------------------------------------
387
388 KernelManagerABC.register(KernelManager)
412 KernelManagerABC.register(KernelManager)
389
413
General Comments 0
You need to be logged in to leave comments. Login now