##// END OF EJS Templates
ConnectionFileMixin is a LoggingConfigurable
MinRK -
Show More
@@ -1,201 +1,186 b''
1 """Base class to manage the interaction with a running kernel
1 """Base class to manage the interaction with a running kernel"""
2 """
3
2
4 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
5 # Copyright (C) 2013 The IPython Development Team
4 # Distributed under the terms of the Modified BSD License.
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
5
15 from __future__ import absolute_import
6 from __future__ import absolute_import
16
7
17 import zmq
8 import zmq
18
9
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
10 from IPython.utils.traitlets import (
22 Any, Instance, Type,
11 Any, Instance, Type,
23 )
12 )
24
13
25 from .zmq.session import Session
14 from .zmq.session import Session
26 from .channels import (
15 from .channels import (
27 ShellChannel, IOPubChannel,
16 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
17 HBChannel, StdInChannel,
29 )
18 )
30 from .clientabc import KernelClientABC
19 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
20 from .connect import ConnectionFileMixin
32
21
33
22
34 #-----------------------------------------------------------------------------
23 class KernelClient(ConnectionFileMixin):
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
24 """Communicates with a single kernel on any host via zmq channels.
40
25
41 There are four channels associated with each kernel:
26 There are four channels associated with each kernel:
42
27
43 * shell: for request/reply calls to the kernel.
28 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
29 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
30 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
31 * stdin: for frontends to reply to raw_input calls in the kernel.
47
32
48 The methods of the channels are exposed as methods of the client itself
33 The methods of the channels are exposed as methods of the client itself
49 (KernelClient.execute, complete, history, etc.).
34 (KernelClient.execute, complete, history, etc.).
50 See the channels themselves for documentation of these methods.
35 See the channels themselves for documentation of these methods.
51
36
52 """
37 """
53
38
54 # The PyZMQ Context to use for communication with the kernel.
39 # The PyZMQ Context to use for communication with the kernel.
55 context = Instance(zmq.Context)
40 context = Instance(zmq.Context)
56 def _context_default(self):
41 def _context_default(self):
57 return zmq.Context.instance()
42 return zmq.Context.instance()
58
43
59 # The classes to use for the various channels
44 # The classes to use for the various channels
60 shell_channel_class = Type(ShellChannel)
45 shell_channel_class = Type(ShellChannel)
61 iopub_channel_class = Type(IOPubChannel)
46 iopub_channel_class = Type(IOPubChannel)
62 stdin_channel_class = Type(StdInChannel)
47 stdin_channel_class = Type(StdInChannel)
63 hb_channel_class = Type(HBChannel)
48 hb_channel_class = Type(HBChannel)
64
49
65 # Protected traits
50 # Protected traits
66 _shell_channel = Any
51 _shell_channel = Any
67 _iopub_channel = Any
52 _iopub_channel = Any
68 _stdin_channel = Any
53 _stdin_channel = Any
69 _hb_channel = Any
54 _hb_channel = Any
70
55
71 #--------------------------------------------------------------------------
56 #--------------------------------------------------------------------------
72 # Channel proxy methods
57 # Channel proxy methods
73 #--------------------------------------------------------------------------
58 #--------------------------------------------------------------------------
74
59
75 def _get_msg(channel, *args, **kwargs):
60 def _get_msg(channel, *args, **kwargs):
76 return channel.get_msg(*args, **kwargs)
61 return channel.get_msg(*args, **kwargs)
77
62
78 def get_shell_msg(self, *args, **kwargs):
63 def get_shell_msg(self, *args, **kwargs):
79 """Get a message from the shell channel"""
64 """Get a message from the shell channel"""
80 return self.shell_channel.get_msg(*args, **kwargs)
65 return self.shell_channel.get_msg(*args, **kwargs)
81
66
82 def get_iopub_msg(self, *args, **kwargs):
67 def get_iopub_msg(self, *args, **kwargs):
83 """Get a message from the iopub channel"""
68 """Get a message from the iopub channel"""
84 return self.iopub_channel.get_msg(*args, **kwargs)
69 return self.iopub_channel.get_msg(*args, **kwargs)
85
70
86 def get_stdin_msg(self, *args, **kwargs):
71 def get_stdin_msg(self, *args, **kwargs):
87 """Get a message from the stdin channel"""
72 """Get a message from the stdin channel"""
88 return self.stdin_channel.get_msg(*args, **kwargs)
73 return self.stdin_channel.get_msg(*args, **kwargs)
89
74
90 #--------------------------------------------------------------------------
75 #--------------------------------------------------------------------------
91 # Channel management methods
76 # Channel management methods
92 #--------------------------------------------------------------------------
77 #--------------------------------------------------------------------------
93
78
94 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
79 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
95 """Starts the channels for this kernel.
80 """Starts the channels for this kernel.
96
81
97 This will create the channels if they do not exist and then start
82 This will create the channels if they do not exist and then start
98 them (their activity runs in a thread). If port numbers of 0 are
83 them (their activity runs in a thread). If port numbers of 0 are
99 being used (random ports) then you must first call
84 being used (random ports) then you must first call
100 :meth:`start_kernel`. If the channels have been stopped and you
85 :meth:`start_kernel`. If the channels have been stopped and you
101 call this, :class:`RuntimeError` will be raised.
86 call this, :class:`RuntimeError` will be raised.
102 """
87 """
103 if shell:
88 if shell:
104 self.shell_channel.start()
89 self.shell_channel.start()
105 for method in self.shell_channel.proxy_methods:
90 for method in self.shell_channel.proxy_methods:
106 setattr(self, method, getattr(self.shell_channel, method))
91 setattr(self, method, getattr(self.shell_channel, method))
107 if iopub:
92 if iopub:
108 self.iopub_channel.start()
93 self.iopub_channel.start()
109 for method in self.iopub_channel.proxy_methods:
94 for method in self.iopub_channel.proxy_methods:
110 setattr(self, method, getattr(self.iopub_channel, method))
95 setattr(self, method, getattr(self.iopub_channel, method))
111 if stdin:
96 if stdin:
112 self.stdin_channel.start()
97 self.stdin_channel.start()
113 for method in self.stdin_channel.proxy_methods:
98 for method in self.stdin_channel.proxy_methods:
114 setattr(self, method, getattr(self.stdin_channel, method))
99 setattr(self, method, getattr(self.stdin_channel, method))
115 self.shell_channel.allow_stdin = True
100 self.shell_channel.allow_stdin = True
116 else:
101 else:
117 self.shell_channel.allow_stdin = False
102 self.shell_channel.allow_stdin = False
118 if hb:
103 if hb:
119 self.hb_channel.start()
104 self.hb_channel.start()
120
105
121 def stop_channels(self):
106 def stop_channels(self):
122 """Stops all the running channels for this kernel.
107 """Stops all the running channels for this kernel.
123
108
124 This stops their event loops and joins their threads.
109 This stops their event loops and joins their threads.
125 """
110 """
126 if self.shell_channel.is_alive():
111 if self.shell_channel.is_alive():
127 self.shell_channel.stop()
112 self.shell_channel.stop()
128 if self.iopub_channel.is_alive():
113 if self.iopub_channel.is_alive():
129 self.iopub_channel.stop()
114 self.iopub_channel.stop()
130 if self.stdin_channel.is_alive():
115 if self.stdin_channel.is_alive():
131 self.stdin_channel.stop()
116 self.stdin_channel.stop()
132 if self.hb_channel.is_alive():
117 if self.hb_channel.is_alive():
133 self.hb_channel.stop()
118 self.hb_channel.stop()
134
119
135 @property
120 @property
136 def channels_running(self):
121 def channels_running(self):
137 """Are any of the channels created and running?"""
122 """Are any of the channels created and running?"""
138 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
123 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
139 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
124 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
140
125
141 @property
126 @property
142 def shell_channel(self):
127 def shell_channel(self):
143 """Get the shell channel object for this kernel."""
128 """Get the shell channel object for this kernel."""
144 if self._shell_channel is None:
129 if self._shell_channel is None:
145 url = self._make_url('shell')
130 url = self._make_url('shell')
146 self.log.debug("connecting shell channel to %s", url)
131 self.log.debug("connecting shell channel to %s", url)
147 self._shell_channel = self.shell_channel_class(
132 self._shell_channel = self.shell_channel_class(
148 self.context, self.session, url
133 self.context, self.session, url
149 )
134 )
150 return self._shell_channel
135 return self._shell_channel
151
136
152 @property
137 @property
153 def iopub_channel(self):
138 def iopub_channel(self):
154 """Get the iopub channel object for this kernel."""
139 """Get the iopub channel object for this kernel."""
155 if self._iopub_channel is None:
140 if self._iopub_channel is None:
156 url = self._make_url('iopub')
141 url = self._make_url('iopub')
157 self.log.debug("connecting iopub channel to %s", url)
142 self.log.debug("connecting iopub channel to %s", url)
158 self._iopub_channel = self.iopub_channel_class(
143 self._iopub_channel = self.iopub_channel_class(
159 self.context, self.session, url
144 self.context, self.session, url
160 )
145 )
161 return self._iopub_channel
146 return self._iopub_channel
162
147
163 @property
148 @property
164 def stdin_channel(self):
149 def stdin_channel(self):
165 """Get the stdin channel object for this kernel."""
150 """Get the stdin channel object for this kernel."""
166 if self._stdin_channel is None:
151 if self._stdin_channel is None:
167 url = self._make_url('stdin')
152 url = self._make_url('stdin')
168 self.log.debug("connecting stdin channel to %s", url)
153 self.log.debug("connecting stdin channel to %s", url)
169 self._stdin_channel = self.stdin_channel_class(
154 self._stdin_channel = self.stdin_channel_class(
170 self.context, self.session, url
155 self.context, self.session, url
171 )
156 )
172 return self._stdin_channel
157 return self._stdin_channel
173
158
174 @property
159 @property
175 def hb_channel(self):
160 def hb_channel(self):
176 """Get the hb channel object for this kernel."""
161 """Get the hb channel object for this kernel."""
177 if self._hb_channel is None:
162 if self._hb_channel is None:
178 url = self._make_url('hb')
163 url = self._make_url('hb')
179 self.log.debug("connecting heartbeat channel to %s", url)
164 self.log.debug("connecting heartbeat channel to %s", url)
180 self._hb_channel = self.hb_channel_class(
165 self._hb_channel = self.hb_channel_class(
181 self.context, self.session, url
166 self.context, self.session, url
182 )
167 )
183 return self._hb_channel
168 return self._hb_channel
184
169
185 def is_alive(self):
170 def is_alive(self):
186 """Is the kernel process still running?"""
171 """Is the kernel process still running?"""
187 if self._hb_channel is not None:
172 if self._hb_channel is not None:
188 # We didn't start the kernel with this KernelManager so we
173 # We didn't start the kernel with this KernelManager so we
189 # use the heartbeat.
174 # use the heartbeat.
190 return self._hb_channel.is_beating()
175 return self._hb_channel.is_beating()
191 else:
176 else:
192 # no heartbeat and not local, we can't tell if it's running,
177 # no heartbeat and not local, we can't tell if it's running,
193 # so naively return True
178 # so naively return True
194 return True
179 return True
195
180
196
181
197 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
198 # ABC Registration
183 # ABC Registration
199 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
200
185
201 KernelClientABC.register(KernelClient)
186 KernelClientABC.register(KernelClient)
@@ -1,578 +1,578 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
4 related to writing and reading connections files.
4 related to writing and reading connections files.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 import glob
15 import glob
16 import json
16 import json
17 import os
17 import os
18 import socket
18 import socket
19 import sys
19 import sys
20 from getpass import getpass
20 from getpass import getpass
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22 import tempfile
22 import tempfile
23
23
24 import zmq
24 import zmq
25
25
26 # external imports
26 # external imports
27 from IPython.external.ssh import tunnel
27 from IPython.external.ssh import tunnel
28
28
29 # IPython imports
29 # IPython imports
30 from IPython.config import Configurable
30 from IPython.config import LoggingConfigurable
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.localinterfaces import localhost
32 from IPython.utils.localinterfaces import localhost
33 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.path import filefind, get_ipython_dir
34 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
34 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
35 string_types)
35 string_types)
36 from IPython.utils.traitlets import (
36 from IPython.utils.traitlets import (
37 Bool, Integer, Unicode, CaselessStrEnum, Instance,
37 Bool, Integer, Unicode, CaselessStrEnum, Instance,
38 )
38 )
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Working with Connection Files
42 # Working with Connection Files
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
45 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
46 control_port=0, ip='', key=b'', transport='tcp',
46 control_port=0, ip='', key=b'', transport='tcp',
47 signature_scheme='hmac-sha256',
47 signature_scheme='hmac-sha256',
48 ):
48 ):
49 """Generates a JSON config file, including the selection of random ports.
49 """Generates a JSON config file, including the selection of random ports.
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53
53
54 fname : unicode
54 fname : unicode
55 The path to the file to write
55 The path to the file to write
56
56
57 shell_port : int, optional
57 shell_port : int, optional
58 The port to use for ROUTER (shell) channel.
58 The port to use for ROUTER (shell) channel.
59
59
60 iopub_port : int, optional
60 iopub_port : int, optional
61 The port to use for the SUB channel.
61 The port to use for the SUB channel.
62
62
63 stdin_port : int, optional
63 stdin_port : int, optional
64 The port to use for the ROUTER (raw input) channel.
64 The port to use for the ROUTER (raw input) channel.
65
65
66 control_port : int, optional
66 control_port : int, optional
67 The port to use for the ROUTER (control) channel.
67 The port to use for the ROUTER (control) channel.
68
68
69 hb_port : int, optional
69 hb_port : int, optional
70 The port to use for the heartbeat REP channel.
70 The port to use for the heartbeat REP channel.
71
71
72 ip : str, optional
72 ip : str, optional
73 The ip address the kernel will bind to.
73 The ip address the kernel will bind to.
74
74
75 key : str, optional
75 key : str, optional
76 The Session key used for message authentication.
76 The Session key used for message authentication.
77
77
78 signature_scheme : str, optional
78 signature_scheme : str, optional
79 The scheme used for message authentication.
79 The scheme used for message authentication.
80 This has the form 'digest-hash', where 'digest'
80 This has the form 'digest-hash', where 'digest'
81 is the scheme used for digests, and 'hash' is the name of the hash function
81 is the scheme used for digests, and 'hash' is the name of the hash function
82 used by the digest scheme.
82 used by the digest scheme.
83 Currently, 'hmac' is the only supported digest scheme,
83 Currently, 'hmac' is the only supported digest scheme,
84 and 'sha256' is the default hash function.
84 and 'sha256' is the default hash function.
85
85
86 """
86 """
87 if not ip:
87 if not ip:
88 ip = localhost()
88 ip = localhost()
89 # default to temporary connector file
89 # default to temporary connector file
90 if not fname:
90 if not fname:
91 fd, fname = tempfile.mkstemp('.json')
91 fd, fname = tempfile.mkstemp('.json')
92 os.close(fd)
92 os.close(fd)
93
93
94 # Find open ports as necessary.
94 # Find open ports as necessary.
95
95
96 ports = []
96 ports = []
97 ports_needed = int(shell_port <= 0) + \
97 ports_needed = int(shell_port <= 0) + \
98 int(iopub_port <= 0) + \
98 int(iopub_port <= 0) + \
99 int(stdin_port <= 0) + \
99 int(stdin_port <= 0) + \
100 int(control_port <= 0) + \
100 int(control_port <= 0) + \
101 int(hb_port <= 0)
101 int(hb_port <= 0)
102 if transport == 'tcp':
102 if transport == 'tcp':
103 for i in range(ports_needed):
103 for i in range(ports_needed):
104 sock = socket.socket()
104 sock = socket.socket()
105 # struct.pack('ii', (0,0)) is 8 null bytes
105 # struct.pack('ii', (0,0)) is 8 null bytes
106 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
106 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
107 sock.bind(('', 0))
107 sock.bind(('', 0))
108 ports.append(sock)
108 ports.append(sock)
109 for i, sock in enumerate(ports):
109 for i, sock in enumerate(ports):
110 port = sock.getsockname()[1]
110 port = sock.getsockname()[1]
111 sock.close()
111 sock.close()
112 ports[i] = port
112 ports[i] = port
113 else:
113 else:
114 N = 1
114 N = 1
115 for i in range(ports_needed):
115 for i in range(ports_needed):
116 while os.path.exists("%s-%s" % (ip, str(N))):
116 while os.path.exists("%s-%s" % (ip, str(N))):
117 N += 1
117 N += 1
118 ports.append(N)
118 ports.append(N)
119 N += 1
119 N += 1
120 if shell_port <= 0:
120 if shell_port <= 0:
121 shell_port = ports.pop(0)
121 shell_port = ports.pop(0)
122 if iopub_port <= 0:
122 if iopub_port <= 0:
123 iopub_port = ports.pop(0)
123 iopub_port = ports.pop(0)
124 if stdin_port <= 0:
124 if stdin_port <= 0:
125 stdin_port = ports.pop(0)
125 stdin_port = ports.pop(0)
126 if control_port <= 0:
126 if control_port <= 0:
127 control_port = ports.pop(0)
127 control_port = ports.pop(0)
128 if hb_port <= 0:
128 if hb_port <= 0:
129 hb_port = ports.pop(0)
129 hb_port = ports.pop(0)
130
130
131 cfg = dict( shell_port=shell_port,
131 cfg = dict( shell_port=shell_port,
132 iopub_port=iopub_port,
132 iopub_port=iopub_port,
133 stdin_port=stdin_port,
133 stdin_port=stdin_port,
134 control_port=control_port,
134 control_port=control_port,
135 hb_port=hb_port,
135 hb_port=hb_port,
136 )
136 )
137 cfg['ip'] = ip
137 cfg['ip'] = ip
138 cfg['key'] = bytes_to_str(key)
138 cfg['key'] = bytes_to_str(key)
139 cfg['transport'] = transport
139 cfg['transport'] = transport
140 cfg['signature_scheme'] = signature_scheme
140 cfg['signature_scheme'] = signature_scheme
141
141
142 with open(fname, 'w') as f:
142 with open(fname, 'w') as f:
143 f.write(json.dumps(cfg, indent=2))
143 f.write(json.dumps(cfg, indent=2))
144
144
145 return fname, cfg
145 return fname, cfg
146
146
147
147
148 def get_connection_file(app=None):
148 def get_connection_file(app=None):
149 """Return the path to the connection file of an app
149 """Return the path to the connection file of an app
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 app : IPKernelApp instance [optional]
153 app : IPKernelApp instance [optional]
154 If unspecified, the currently running app will be used
154 If unspecified, the currently running app will be used
155 """
155 """
156 if app is None:
156 if app is None:
157 from IPython.kernel.zmq.kernelapp import IPKernelApp
157 from IPython.kernel.zmq.kernelapp import IPKernelApp
158 if not IPKernelApp.initialized():
158 if not IPKernelApp.initialized():
159 raise RuntimeError("app not specified, and not in a running Kernel")
159 raise RuntimeError("app not specified, and not in a running Kernel")
160
160
161 app = IPKernelApp.instance()
161 app = IPKernelApp.instance()
162 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
162 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
163
163
164
164
165 def find_connection_file(filename, profile=None):
165 def find_connection_file(filename, profile=None):
166 """find a connection file, and return its absolute path.
166 """find a connection file, and return its absolute path.
167
167
168 The current working directory and the profile's security
168 The current working directory and the profile's security
169 directory will be searched for the file if it is not given by
169 directory will be searched for the file if it is not given by
170 absolute path.
170 absolute path.
171
171
172 If profile is unspecified, then the current running application's
172 If profile is unspecified, then the current running application's
173 profile will be used, or 'default', if not run from IPython.
173 profile will be used, or 'default', if not run from IPython.
174
174
175 If the argument does not match an existing file, it will be interpreted as a
175 If the argument does not match an existing file, it will be interpreted as a
176 fileglob, and the matching file in the profile's security dir with
176 fileglob, and the matching file in the profile's security dir with
177 the latest access time will be used.
177 the latest access time will be used.
178
178
179 Parameters
179 Parameters
180 ----------
180 ----------
181 filename : str
181 filename : str
182 The connection file or fileglob to search for.
182 The connection file or fileglob to search for.
183 profile : str [optional]
183 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
184 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
185 if different from the current IPython session or 'default'.
186
186
187 Returns
187 Returns
188 -------
188 -------
189 str : The absolute path of the connection file.
189 str : The absolute path of the connection file.
190 """
190 """
191 from IPython.core.application import BaseIPythonApplication as IPApp
191 from IPython.core.application import BaseIPythonApplication as IPApp
192 try:
192 try:
193 # quick check for absolute path, before going through logic
193 # quick check for absolute path, before going through logic
194 return filefind(filename)
194 return filefind(filename)
195 except IOError:
195 except IOError:
196 pass
196 pass
197
197
198 if profile is None:
198 if profile is None:
199 # profile unspecified, check if running from an IPython app
199 # profile unspecified, check if running from an IPython app
200 if IPApp.initialized():
200 if IPApp.initialized():
201 app = IPApp.instance()
201 app = IPApp.instance()
202 profile_dir = app.profile_dir
202 profile_dir = app.profile_dir
203 else:
203 else:
204 # not running in IPython, use default profile
204 # not running in IPython, use default profile
205 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
205 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
206 else:
206 else:
207 # find profiledir by profile name:
207 # find profiledir by profile name:
208 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
208 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
209 security_dir = profile_dir.security_dir
209 security_dir = profile_dir.security_dir
210
210
211 try:
211 try:
212 # first, try explicit name
212 # first, try explicit name
213 return filefind(filename, ['.', security_dir])
213 return filefind(filename, ['.', security_dir])
214 except IOError:
214 except IOError:
215 pass
215 pass
216
216
217 # not found by full name
217 # not found by full name
218
218
219 if '*' in filename:
219 if '*' in filename:
220 # given as a glob already
220 # given as a glob already
221 pat = filename
221 pat = filename
222 else:
222 else:
223 # accept any substring match
223 # accept any substring match
224 pat = '*%s*' % filename
224 pat = '*%s*' % filename
225 matches = glob.glob( os.path.join(security_dir, pat) )
225 matches = glob.glob( os.path.join(security_dir, pat) )
226 if not matches:
226 if not matches:
227 raise IOError("Could not find %r in %r" % (filename, security_dir))
227 raise IOError("Could not find %r in %r" % (filename, security_dir))
228 elif len(matches) == 1:
228 elif len(matches) == 1:
229 return matches[0]
229 return matches[0]
230 else:
230 else:
231 # get most recent match, by access time:
231 # get most recent match, by access time:
232 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
232 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
233
233
234
234
235 def get_connection_info(connection_file=None, unpack=False, profile=None):
235 def get_connection_info(connection_file=None, unpack=False, profile=None):
236 """Return the connection information for the current Kernel.
236 """Return the connection information for the current Kernel.
237
237
238 Parameters
238 Parameters
239 ----------
239 ----------
240 connection_file : str [optional]
240 connection_file : str [optional]
241 The connection file to be used. Can be given by absolute path, or
241 The connection file to be used. Can be given by absolute path, or
242 IPython will search in the security directory of a given profile.
242 IPython will search in the security directory of a given profile.
243 If run from IPython,
243 If run from IPython,
244
244
245 If unspecified, the connection file for the currently running
245 If unspecified, the connection file for the currently running
246 IPython Kernel will be used, which is only allowed from inside a kernel.
246 IPython Kernel will be used, which is only allowed from inside a kernel.
247 unpack : bool [default: False]
247 unpack : bool [default: False]
248 if True, return the unpacked dict, otherwise just the string contents
248 if True, return the unpacked dict, otherwise just the string contents
249 of the file.
249 of the file.
250 profile : str [optional]
250 profile : str [optional]
251 The name of the profile to use when searching for the connection file,
251 The name of the profile to use when searching for the connection file,
252 if different from the current IPython session or 'default'.
252 if different from the current IPython session or 'default'.
253
253
254
254
255 Returns
255 Returns
256 -------
256 -------
257 The connection dictionary of the current kernel, as string or dict,
257 The connection dictionary of the current kernel, as string or dict,
258 depending on `unpack`.
258 depending on `unpack`.
259 """
259 """
260 if connection_file is None:
260 if connection_file is None:
261 # get connection file from current kernel
261 # get connection file from current kernel
262 cf = get_connection_file()
262 cf = get_connection_file()
263 else:
263 else:
264 # connection file specified, allow shortnames:
264 # connection file specified, allow shortnames:
265 cf = find_connection_file(connection_file, profile=profile)
265 cf = find_connection_file(connection_file, profile=profile)
266
266
267 with open(cf) as f:
267 with open(cf) as f:
268 info = f.read()
268 info = f.read()
269
269
270 if unpack:
270 if unpack:
271 info = json.loads(info)
271 info = json.loads(info)
272 # ensure key is bytes:
272 # ensure key is bytes:
273 info['key'] = str_to_bytes(info.get('key', ''))
273 info['key'] = str_to_bytes(info.get('key', ''))
274 return info
274 return info
275
275
276
276
277 def connect_qtconsole(connection_file=None, argv=None, profile=None):
277 def connect_qtconsole(connection_file=None, argv=None, profile=None):
278 """Connect a qtconsole to the current kernel.
278 """Connect a qtconsole to the current kernel.
279
279
280 This is useful for connecting a second qtconsole to a kernel, or to a
280 This is useful for connecting a second qtconsole to a kernel, or to a
281 local notebook.
281 local notebook.
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 connection_file : str [optional]
285 connection_file : str [optional]
286 The connection file to be used. Can be given by absolute path, or
286 The connection file to be used. Can be given by absolute path, or
287 IPython will search in the security directory of a given profile.
287 IPython will search in the security directory of a given profile.
288 If run from IPython,
288 If run from IPython,
289
289
290 If unspecified, the connection file for the currently running
290 If unspecified, the connection file for the currently running
291 IPython Kernel will be used, which is only allowed from inside a kernel.
291 IPython Kernel will be used, which is only allowed from inside a kernel.
292 argv : list [optional]
292 argv : list [optional]
293 Any extra args to be passed to the console.
293 Any extra args to be passed to the console.
294 profile : str [optional]
294 profile : str [optional]
295 The name of the profile to use when searching for the connection file,
295 The name of the profile to use when searching for the connection file,
296 if different from the current IPython session or 'default'.
296 if different from the current IPython session or 'default'.
297
297
298
298
299 Returns
299 Returns
300 -------
300 -------
301 :class:`subprocess.Popen` instance running the qtconsole frontend
301 :class:`subprocess.Popen` instance running the qtconsole frontend
302 """
302 """
303 argv = [] if argv is None else argv
303 argv = [] if argv is None else argv
304
304
305 if connection_file is None:
305 if connection_file is None:
306 # get connection file from current kernel
306 # get connection file from current kernel
307 cf = get_connection_file()
307 cf = get_connection_file()
308 else:
308 else:
309 cf = find_connection_file(connection_file, profile=profile)
309 cf = find_connection_file(connection_file, profile=profile)
310
310
311 cmd = ';'.join([
311 cmd = ';'.join([
312 "from IPython.qt.console import qtconsoleapp",
312 "from IPython.qt.console import qtconsoleapp",
313 "qtconsoleapp.main()"
313 "qtconsoleapp.main()"
314 ])
314 ])
315
315
316 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
316 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
317 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
317 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
318 )
318 )
319
319
320
320
321 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
321 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
322 """tunnel connections to a kernel via ssh
322 """tunnel connections to a kernel via ssh
323
323
324 This will open four SSH tunnels from localhost on this machine to the
324 This will open four SSH tunnels from localhost on this machine to the
325 ports associated with the kernel. They can be either direct
325 ports associated with the kernel. They can be either direct
326 localhost-localhost tunnels, or if an intermediate server is necessary,
326 localhost-localhost tunnels, or if an intermediate server is necessary,
327 the kernel must be listening on a public IP.
327 the kernel must be listening on a public IP.
328
328
329 Parameters
329 Parameters
330 ----------
330 ----------
331 connection_info : dict or str (path)
331 connection_info : dict or str (path)
332 Either a connection dict, or the path to a JSON connection file
332 Either a connection dict, or the path to a JSON connection file
333 sshserver : str
333 sshserver : str
334 The ssh sever to use to tunnel to the kernel. Can be a full
334 The ssh sever to use to tunnel to the kernel. Can be a full
335 `user@server:port` string. ssh config aliases are respected.
335 `user@server:port` string. ssh config aliases are respected.
336 sshkey : str [optional]
336 sshkey : str [optional]
337 Path to file containing ssh key to use for authentication.
337 Path to file containing ssh key to use for authentication.
338 Only necessary if your ssh config does not already associate
338 Only necessary if your ssh config does not already associate
339 a keyfile with the host.
339 a keyfile with the host.
340
340
341 Returns
341 Returns
342 -------
342 -------
343
343
344 (shell, iopub, stdin, hb) : ints
344 (shell, iopub, stdin, hb) : ints
345 The four ports on localhost that have been forwarded to the kernel.
345 The four ports on localhost that have been forwarded to the kernel.
346 """
346 """
347 if isinstance(connection_info, string_types):
347 if isinstance(connection_info, string_types):
348 # it's a path, unpack it
348 # it's a path, unpack it
349 with open(connection_info) as f:
349 with open(connection_info) as f:
350 connection_info = json.loads(f.read())
350 connection_info = json.loads(f.read())
351
351
352 cf = connection_info
352 cf = connection_info
353
353
354 lports = tunnel.select_random_ports(4)
354 lports = tunnel.select_random_ports(4)
355 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
355 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
356
356
357 remote_ip = cf['ip']
357 remote_ip = cf['ip']
358
358
359 if tunnel.try_passwordless_ssh(sshserver, sshkey):
359 if tunnel.try_passwordless_ssh(sshserver, sshkey):
360 password=False
360 password=False
361 else:
361 else:
362 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
362 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
363
363
364 for lp,rp in zip(lports, rports):
364 for lp,rp in zip(lports, rports):
365 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
365 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
366
366
367 return tuple(lports)
367 return tuple(lports)
368
368
369
369
370 #-----------------------------------------------------------------------------
370 #-----------------------------------------------------------------------------
371 # Mixin for classes that work with connection files
371 # Mixin for classes that work with connection files
372 #-----------------------------------------------------------------------------
372 #-----------------------------------------------------------------------------
373
373
374 channel_socket_types = {
374 channel_socket_types = {
375 'hb' : zmq.REQ,
375 'hb' : zmq.REQ,
376 'shell' : zmq.DEALER,
376 'shell' : zmq.DEALER,
377 'iopub' : zmq.SUB,
377 'iopub' : zmq.SUB,
378 'stdin' : zmq.DEALER,
378 'stdin' : zmq.DEALER,
379 'control': zmq.DEALER,
379 'control': zmq.DEALER,
380 }
380 }
381
381
382 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
382 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
383
383
384 class ConnectionFileMixin(Configurable):
384 class ConnectionFileMixin(LoggingConfigurable):
385 """Mixin for configurable classes that work with connection files"""
385 """Mixin for configurable classes that work with connection files"""
386
386
387 # The addresses for the communication channels
387 # The addresses for the communication channels
388 connection_file = Unicode('', config=True,
388 connection_file = Unicode('', config=True,
389 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
389 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
390
390
391 This file will contain the IP, ports, and authentication key needed to connect
391 This file will contain the IP, ports, and authentication key needed to connect
392 clients to this kernel. By default, this file will be created in the security dir
392 clients to this kernel. By default, this file will be created in the security dir
393 of the current profile, but can be specified by absolute path.
393 of the current profile, but can be specified by absolute path.
394 """)
394 """)
395 _connection_file_written = Bool(False)
395 _connection_file_written = Bool(False)
396
396
397 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
397 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
398
398
399 ip = Unicode(config=True,
399 ip = Unicode(config=True,
400 help="""Set the kernel\'s IP address [default localhost].
400 help="""Set the kernel\'s IP address [default localhost].
401 If the IP address is something other than localhost, then
401 If the IP address is something other than localhost, then
402 Consoles on other machines will be able to connect
402 Consoles on other machines will be able to connect
403 to the Kernel, so be careful!"""
403 to the Kernel, so be careful!"""
404 )
404 )
405
405
406 def _ip_default(self):
406 def _ip_default(self):
407 if self.transport == 'ipc':
407 if self.transport == 'ipc':
408 if self.connection_file:
408 if self.connection_file:
409 return os.path.splitext(self.connection_file)[0] + '-ipc'
409 return os.path.splitext(self.connection_file)[0] + '-ipc'
410 else:
410 else:
411 return 'kernel-ipc'
411 return 'kernel-ipc'
412 else:
412 else:
413 return localhost()
413 return localhost()
414
414
415 def _ip_changed(self, name, old, new):
415 def _ip_changed(self, name, old, new):
416 if new == '*':
416 if new == '*':
417 self.ip = '0.0.0.0'
417 self.ip = '0.0.0.0'
418
418
419 # protected traits
419 # protected traits
420
420
421 hb_port = Integer(0, config=True,
421 hb_port = Integer(0, config=True,
422 help="set the heartbeat port [default: random]")
422 help="set the heartbeat port [default: random]")
423 shell_port = Integer(0, config=True,
423 shell_port = Integer(0, config=True,
424 help="set the shell (ROUTER) port [default: random]")
424 help="set the shell (ROUTER) port [default: random]")
425 iopub_port = Integer(0, config=True,
425 iopub_port = Integer(0, config=True,
426 help="set the iopub (PUB) port [default: random]")
426 help="set the iopub (PUB) port [default: random]")
427 stdin_port = Integer(0, config=True,
427 stdin_port = Integer(0, config=True,
428 help="set the stdin (ROUTER) port [default: random]")
428 help="set the stdin (ROUTER) port [default: random]")
429 control_port = Integer(0, config=True,
429 control_port = Integer(0, config=True,
430 help="set the control (ROUTER) port [default: random]")
430 help="set the control (ROUTER) port [default: random]")
431
431
432 @property
432 @property
433 def ports(self):
433 def ports(self):
434 return [ getattr(self, name) for name in port_names ]
434 return [ getattr(self, name) for name in port_names ]
435
435
436 # The Session to use for communication with the kernel.
436 # The Session to use for communication with the kernel.
437 session = Instance('IPython.kernel.zmq.session.Session')
437 session = Instance('IPython.kernel.zmq.session.Session')
438 def _session_default(self):
438 def _session_default(self):
439 from IPython.kernel.zmq.session import Session
439 from IPython.kernel.zmq.session import Session
440 return Session(parent=self)
440 return Session(parent=self)
441
441
442 #--------------------------------------------------------------------------
442 #--------------------------------------------------------------------------
443 # Connection and ipc file management
443 # Connection and ipc file management
444 #--------------------------------------------------------------------------
444 #--------------------------------------------------------------------------
445
445
446 def get_connection_info(self):
446 def get_connection_info(self):
447 """return the connection info as a dict"""
447 """return the connection info as a dict"""
448 return dict(
448 return dict(
449 transport=self.transport,
449 transport=self.transport,
450 ip=self.ip,
450 ip=self.ip,
451 shell_port=self.shell_port,
451 shell_port=self.shell_port,
452 iopub_port=self.iopub_port,
452 iopub_port=self.iopub_port,
453 stdin_port=self.stdin_port,
453 stdin_port=self.stdin_port,
454 hb_port=self.hb_port,
454 hb_port=self.hb_port,
455 control_port=self.control_port,
455 control_port=self.control_port,
456 signature_scheme=self.session.signature_scheme,
456 signature_scheme=self.session.signature_scheme,
457 key=self.session.key,
457 key=self.session.key,
458 )
458 )
459
459
460 def cleanup_connection_file(self):
460 def cleanup_connection_file(self):
461 """Cleanup connection file *if we wrote it*
461 """Cleanup connection file *if we wrote it*
462
462
463 Will not raise if the connection file was already removed somehow.
463 Will not raise if the connection file was already removed somehow.
464 """
464 """
465 if self._connection_file_written:
465 if self._connection_file_written:
466 # cleanup connection files on full shutdown of kernel we started
466 # cleanup connection files on full shutdown of kernel we started
467 self._connection_file_written = False
467 self._connection_file_written = False
468 try:
468 try:
469 os.remove(self.connection_file)
469 os.remove(self.connection_file)
470 except (IOError, OSError, AttributeError):
470 except (IOError, OSError, AttributeError):
471 pass
471 pass
472
472
473 def cleanup_ipc_files(self):
473 def cleanup_ipc_files(self):
474 """Cleanup ipc files if we wrote them."""
474 """Cleanup ipc files if we wrote them."""
475 if self.transport != 'ipc':
475 if self.transport != 'ipc':
476 return
476 return
477 for port in self.ports:
477 for port in self.ports:
478 ipcfile = "%s-%i" % (self.ip, port)
478 ipcfile = "%s-%i" % (self.ip, port)
479 try:
479 try:
480 os.remove(ipcfile)
480 os.remove(ipcfile)
481 except (IOError, OSError):
481 except (IOError, OSError):
482 pass
482 pass
483
483
484 def write_connection_file(self):
484 def write_connection_file(self):
485 """Write connection info to JSON dict in self.connection_file."""
485 """Write connection info to JSON dict in self.connection_file."""
486 if self._connection_file_written and os.path.exists(self.connection_file):
486 if self._connection_file_written and os.path.exists(self.connection_file):
487 return
487 return
488
488
489 self.connection_file, cfg = write_connection_file(self.connection_file,
489 self.connection_file, cfg = write_connection_file(self.connection_file,
490 transport=self.transport, ip=self.ip, key=self.session.key,
490 transport=self.transport, ip=self.ip, key=self.session.key,
491 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
491 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
492 shell_port=self.shell_port, hb_port=self.hb_port,
492 shell_port=self.shell_port, hb_port=self.hb_port,
493 control_port=self.control_port,
493 control_port=self.control_port,
494 signature_scheme=self.session.signature_scheme,
494 signature_scheme=self.session.signature_scheme,
495 )
495 )
496 # write_connection_file also sets default ports:
496 # write_connection_file also sets default ports:
497 for name in port_names:
497 for name in port_names:
498 setattr(self, name, cfg[name])
498 setattr(self, name, cfg[name])
499
499
500 self._connection_file_written = True
500 self._connection_file_written = True
501
501
502 def load_connection_file(self):
502 def load_connection_file(self):
503 """Load connection info from JSON dict in self.connection_file."""
503 """Load connection info from JSON dict in self.connection_file."""
504 self.log.debug(u"Loading connection file %s", self.connection_file)
504 self.log.debug(u"Loading connection file %s", self.connection_file)
505 with open(self.connection_file) as f:
505 with open(self.connection_file) as f:
506 cfg = json.load(f)
506 cfg = json.load(f)
507 self.transport = cfg.get('transport', self.transport)
507 self.transport = cfg.get('transport', self.transport)
508 self.ip = cfg.get('ip', self._ip_default())
508 self.ip = cfg.get('ip', self._ip_default())
509
509
510 for name in port_names:
510 for name in port_names:
511 if getattr(self, name) == 0 and name in cfg:
511 if getattr(self, name) == 0 and name in cfg:
512 # not overridden by config or cl_args
512 # not overridden by config or cl_args
513 setattr(self, name, cfg[name])
513 setattr(self, name, cfg[name])
514
514
515 if 'key' in cfg:
515 if 'key' in cfg:
516 self.session.key = str_to_bytes(cfg['key'])
516 self.session.key = str_to_bytes(cfg['key'])
517 if 'signature_scheme' in cfg:
517 if 'signature_scheme' in cfg:
518 self.session.signature_scheme = cfg['signature_scheme']
518 self.session.signature_scheme = cfg['signature_scheme']
519
519
520 #--------------------------------------------------------------------------
520 #--------------------------------------------------------------------------
521 # Creating connected sockets
521 # Creating connected sockets
522 #--------------------------------------------------------------------------
522 #--------------------------------------------------------------------------
523
523
524 def _make_url(self, channel):
524 def _make_url(self, channel):
525 """Make a ZeroMQ URL for a given channel."""
525 """Make a ZeroMQ URL for a given channel."""
526 transport = self.transport
526 transport = self.transport
527 ip = self.ip
527 ip = self.ip
528 port = getattr(self, '%s_port' % channel)
528 port = getattr(self, '%s_port' % channel)
529
529
530 if transport == 'tcp':
530 if transport == 'tcp':
531 return "tcp://%s:%i" % (ip, port)
531 return "tcp://%s:%i" % (ip, port)
532 else:
532 else:
533 return "%s://%s-%s" % (transport, ip, port)
533 return "%s://%s-%s" % (transport, ip, port)
534
534
535 def _create_connected_socket(self, channel, identity=None):
535 def _create_connected_socket(self, channel, identity=None):
536 """Create a zmq Socket and connect it to the kernel."""
536 """Create a zmq Socket and connect it to the kernel."""
537 url = self._make_url(channel)
537 url = self._make_url(channel)
538 socket_type = channel_socket_types[channel]
538 socket_type = channel_socket_types[channel]
539 self.log.debug("Connecting to: %s" % url)
539 self.log.debug("Connecting to: %s" % url)
540 sock = self.context.socket(socket_type)
540 sock = self.context.socket(socket_type)
541 # set linger to 1s to prevent hangs at exit
541 # set linger to 1s to prevent hangs at exit
542 sock.linger = 1000
542 sock.linger = 1000
543 if identity:
543 if identity:
544 sock.identity = identity
544 sock.identity = identity
545 sock.connect(url)
545 sock.connect(url)
546 return sock
546 return sock
547
547
548 def connect_iopub(self, identity=None):
548 def connect_iopub(self, identity=None):
549 """return zmq Socket connected to the IOPub channel"""
549 """return zmq Socket connected to the IOPub channel"""
550 sock = self._create_connected_socket('iopub', identity=identity)
550 sock = self._create_connected_socket('iopub', identity=identity)
551 sock.setsockopt(zmq.SUBSCRIBE, b'')
551 sock.setsockopt(zmq.SUBSCRIBE, b'')
552 return sock
552 return sock
553
553
554 def connect_shell(self, identity=None):
554 def connect_shell(self, identity=None):
555 """return zmq Socket connected to the Shell channel"""
555 """return zmq Socket connected to the Shell channel"""
556 return self._create_connected_socket('shell', identity=identity)
556 return self._create_connected_socket('shell', identity=identity)
557
557
558 def connect_stdin(self, identity=None):
558 def connect_stdin(self, identity=None):
559 """return zmq Socket connected to the StdIn channel"""
559 """return zmq Socket connected to the StdIn channel"""
560 return self._create_connected_socket('stdin', identity=identity)
560 return self._create_connected_socket('stdin', identity=identity)
561
561
562 def connect_hb(self, identity=None):
562 def connect_hb(self, identity=None):
563 """return zmq Socket connected to the Heartbeat channel"""
563 """return zmq Socket connected to the Heartbeat channel"""
564 return self._create_connected_socket('hb', identity=identity)
564 return self._create_connected_socket('hb', identity=identity)
565
565
566 def connect_control(self, identity=None):
566 def connect_control(self, identity=None):
567 """return zmq Socket connected to the Heartbeat channel"""
567 """return zmq Socket connected to the Heartbeat channel"""
568 return self._create_connected_socket('control', identity=identity)
568 return self._create_connected_socket('control', identity=identity)
569
569
570
570
571 __all__ = [
571 __all__ = [
572 'write_connection_file',
572 'write_connection_file',
573 'get_connection_file',
573 'get_connection_file',
574 'find_connection_file',
574 'find_connection_file',
575 'get_connection_info',
575 'get_connection_info',
576 'connect_qtconsole',
576 'connect_qtconsole',
577 'tunnel_to_kernel',
577 'tunnel_to_kernel',
578 ]
578 ]
@@ -1,415 +1,412 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 # Standard library imports
9 import os
8 import os
10 import re
9 import re
11 import signal
10 import signal
12 import sys
11 import sys
13 import time
12 import time
14 import warnings
13 import warnings
15
14
16 import zmq
15 import zmq
17
16
18 # Local imports
19 from IPython.config.configurable import LoggingConfigurable
20 from IPython.utils.importstring import import_item
17 from IPython.utils.importstring import import_item
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
18 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
19 from IPython.utils.path import get_ipython_dir
23 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
21 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
25 )
22 )
26 from IPython.kernel import (
23 from IPython.kernel import (
27 make_ipkernel_cmd,
24 make_ipkernel_cmd,
28 launch_kernel,
25 launch_kernel,
29 kernelspec,
26 kernelspec,
30 )
27 )
31 from .connect import ConnectionFileMixin
28 from .connect import ConnectionFileMixin
32 from .zmq.session import Session
29 from .zmq.session import Session
33 from .managerabc import (
30 from .managerabc import (
34 KernelManagerABC
31 KernelManagerABC
35 )
32 )
36
33
37
34
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
35 class KernelManager(ConnectionFileMixin):
39 """Manages a single kernel in a subprocess on this host.
36 """Manages a single kernel in a subprocess on this host.
40
37
41 This version starts kernels with Popen.
38 This version starts kernels with Popen.
42 """
39 """
43
40
44 # The PyZMQ Context to use for communication with the kernel.
41 # The PyZMQ Context to use for communication with the kernel.
45 context = Instance(zmq.Context)
42 context = Instance(zmq.Context)
46 def _context_default(self):
43 def _context_default(self):
47 return zmq.Context.instance()
44 return zmq.Context.instance()
48
45
49 # the class to create with our `client` method
46 # the class to create with our `client` method
50 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
47 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
51 client_factory = Type()
48 client_factory = Type()
52 def _client_class_changed(self, name, old, new):
49 def _client_class_changed(self, name, old, new):
53 self.client_factory = import_item(str(new))
50 self.client_factory = import_item(str(new))
54
51
55 # The kernel process with which the KernelManager is communicating.
52 # The kernel process with which the KernelManager is communicating.
56 # generally a Popen instance
53 # generally a Popen instance
57 kernel = Any()
54 kernel = Any()
58
55
59 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
56 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
60
57
61 def _kernel_spec_manager_default(self):
58 def _kernel_spec_manager_default(self):
62 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
59 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
63
60
64 kernel_name = Unicode('python')
61 kernel_name = Unicode('python')
65
62
66 kernel_spec = Instance(kernelspec.KernelSpec)
63 kernel_spec = Instance(kernelspec.KernelSpec)
67
64
68 def _kernel_spec_default(self):
65 def _kernel_spec_default(self):
69 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
66 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
70
67
71 def _kernel_name_changed(self, name, old, new):
68 def _kernel_name_changed(self, name, old, new):
72 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
69 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
73 self.ipython_kernel = new in {'python', 'python2', 'python3'}
70 self.ipython_kernel = new in {'python', 'python2', 'python3'}
74
71
75 kernel_cmd = List(Unicode, config=True,
72 kernel_cmd = List(Unicode, config=True,
76 help="""DEPRECATED: Use kernel_name instead.
73 help="""DEPRECATED: Use kernel_name instead.
77
74
78 The Popen Command to launch the kernel.
75 The Popen Command to launch the kernel.
79 Override this if you have a custom kernel.
76 Override this if you have a custom kernel.
80 If kernel_cmd is specified in a configuration file,
77 If kernel_cmd is specified in a configuration file,
81 IPython does not pass any arguments to the kernel,
78 IPython does not pass any arguments to the kernel,
82 because it cannot make any assumptions about the
79 because it cannot make any assumptions about the
83 arguments that the kernel understands. In particular,
80 arguments that the kernel understands. In particular,
84 this means that the kernel does not receive the
81 this means that the kernel does not receive the
85 option --debug if it given on the IPython command line.
82 option --debug if it given on the IPython command line.
86 """
83 """
87 )
84 )
88
85
89 def _kernel_cmd_changed(self, name, old, new):
86 def _kernel_cmd_changed(self, name, old, new):
90 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
87 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
91 "start different kernels.")
88 "start different kernels.")
92 self.ipython_kernel = False
89 self.ipython_kernel = False
93
90
94 ipython_kernel = Bool(True)
91 ipython_kernel = Bool(True)
95
92
96 ipython_dir = Unicode()
93 ipython_dir = Unicode()
97 def _ipython_dir_default(self):
94 def _ipython_dir_default(self):
98 return get_ipython_dir()
95 return get_ipython_dir()
99
96
100 # Protected traits
97 # Protected traits
101 _launch_args = Any()
98 _launch_args = Any()
102 _control_socket = Any()
99 _control_socket = Any()
103
100
104 _restarter = Any()
101 _restarter = Any()
105
102
106 autorestart = Bool(False, config=True,
103 autorestart = Bool(False, config=True,
107 help="""Should we autorestart the kernel if it dies."""
104 help="""Should we autorestart the kernel if it dies."""
108 )
105 )
109
106
110 def __del__(self):
107 def __del__(self):
111 self._close_control_socket()
108 self._close_control_socket()
112 self.cleanup_connection_file()
109 self.cleanup_connection_file()
113
110
114 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
115 # Kernel restarter
112 # Kernel restarter
116 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
117
114
118 def start_restarter(self):
115 def start_restarter(self):
119 pass
116 pass
120
117
121 def stop_restarter(self):
118 def stop_restarter(self):
122 pass
119 pass
123
120
124 def add_restart_callback(self, callback, event='restart'):
121 def add_restart_callback(self, callback, event='restart'):
125 """register a callback to be called when a kernel is restarted"""
122 """register a callback to be called when a kernel is restarted"""
126 if self._restarter is None:
123 if self._restarter is None:
127 return
124 return
128 self._restarter.add_callback(callback, event)
125 self._restarter.add_callback(callback, event)
129
126
130 def remove_restart_callback(self, callback, event='restart'):
127 def remove_restart_callback(self, callback, event='restart'):
131 """unregister a callback to be called when a kernel is restarted"""
128 """unregister a callback to be called when a kernel is restarted"""
132 if self._restarter is None:
129 if self._restarter is None:
133 return
130 return
134 self._restarter.remove_callback(callback, event)
131 self._restarter.remove_callback(callback, event)
135
132
136 #--------------------------------------------------------------------------
133 #--------------------------------------------------------------------------
137 # create a Client connected to our Kernel
134 # create a Client connected to our Kernel
138 #--------------------------------------------------------------------------
135 #--------------------------------------------------------------------------
139
136
140 def client(self, **kwargs):
137 def client(self, **kwargs):
141 """Create a client configured to connect to our kernel"""
138 """Create a client configured to connect to our kernel"""
142 if self.client_factory is None:
139 if self.client_factory is None:
143 self.client_factory = import_item(self.client_class)
140 self.client_factory = import_item(self.client_class)
144
141
145 kw = {}
142 kw = {}
146 kw.update(self.get_connection_info())
143 kw.update(self.get_connection_info())
147 kw.update(dict(
144 kw.update(dict(
148 connection_file=self.connection_file,
145 connection_file=self.connection_file,
149 session=self.session,
146 session=self.session,
150 parent=self,
147 parent=self,
151 ))
148 ))
152
149
153 # add kwargs last, for manual overrides
150 # add kwargs last, for manual overrides
154 kw.update(kwargs)
151 kw.update(kwargs)
155 return self.client_factory(**kw)
152 return self.client_factory(**kw)
156
153
157 #--------------------------------------------------------------------------
154 #--------------------------------------------------------------------------
158 # Kernel management
155 # Kernel management
159 #--------------------------------------------------------------------------
156 #--------------------------------------------------------------------------
160
157
161 def format_kernel_cmd(self, **kw):
158 def format_kernel_cmd(self, **kw):
162 """replace templated args (e.g. {connection_file})"""
159 """replace templated args (e.g. {connection_file})"""
163 if self.kernel_cmd:
160 if self.kernel_cmd:
164 cmd = self.kernel_cmd
161 cmd = self.kernel_cmd
165 elif self.kernel_name == 'python':
162 elif self.kernel_name == 'python':
166 # The native kernel gets special handling
163 # The native kernel gets special handling
167 cmd = make_ipkernel_cmd(
164 cmd = make_ipkernel_cmd(
168 'from IPython.kernel.zmq.kernelapp import main; main()',
165 'from IPython.kernel.zmq.kernelapp import main; main()',
169 **kw
166 **kw
170 )
167 )
171 else:
168 else:
172 cmd = self.kernel_spec.argv
169 cmd = self.kernel_spec.argv
173
170
174 ns = dict(connection_file=self.connection_file)
171 ns = dict(connection_file=self.connection_file)
175 ns.update(self._launch_args)
172 ns.update(self._launch_args)
176
173
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
174 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
178 def from_ns(match):
175 def from_ns(match):
179 """Get the key out of ns if it's there, otherwise no change."""
176 """Get the key out of ns if it's there, otherwise no change."""
180 return ns.get(match.group(1), match.group())
177 return ns.get(match.group(1), match.group())
181
178
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
179 return [ pat.sub(from_ns, arg) for arg in cmd ]
183
180
184 def _launch_kernel(self, kernel_cmd, **kw):
181 def _launch_kernel(self, kernel_cmd, **kw):
185 """actually launch the kernel
182 """actually launch the kernel
186
183
187 override in a subclass to launch kernel subprocesses differently
184 override in a subclass to launch kernel subprocesses differently
188 """
185 """
189 return launch_kernel(kernel_cmd, **kw)
186 return launch_kernel(kernel_cmd, **kw)
190
187
191 # Control socket used for polite kernel shutdown
188 # Control socket used for polite kernel shutdown
192
189
193 def _connect_control_socket(self):
190 def _connect_control_socket(self):
194 if self._control_socket is None:
191 if self._control_socket is None:
195 self._control_socket = self.connect_control()
192 self._control_socket = self.connect_control()
196 self._control_socket.linger = 100
193 self._control_socket.linger = 100
197
194
198 def _close_control_socket(self):
195 def _close_control_socket(self):
199 if self._control_socket is None:
196 if self._control_socket is None:
200 return
197 return
201 self._control_socket.close()
198 self._control_socket.close()
202 self._control_socket = None
199 self._control_socket = None
203
200
204 def start_kernel(self, **kw):
201 def start_kernel(self, **kw):
205 """Starts a kernel on this host in a separate process.
202 """Starts a kernel on this host in a separate process.
206
203
207 If random ports (port=0) are being used, this method must be called
204 If random ports (port=0) are being used, this method must be called
208 before the channels are created.
205 before the channels are created.
209
206
210 Parameters
207 Parameters
211 ----------
208 ----------
212 **kw : optional
209 **kw : optional
213 keyword arguments that are passed down to build the kernel_cmd
210 keyword arguments that are passed down to build the kernel_cmd
214 and launching the kernel (e.g. Popen kwargs).
211 and launching the kernel (e.g. Popen kwargs).
215 """
212 """
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
213 if self.transport == 'tcp' and not is_local_ip(self.ip):
217 raise RuntimeError("Can only launch a kernel on a local interface. "
214 raise RuntimeError("Can only launch a kernel on a local interface. "
218 "Make sure that the '*_address' attributes are "
215 "Make sure that the '*_address' attributes are "
219 "configured properly. "
216 "configured properly. "
220 "Currently valid addresses are: %s" % local_ips()
217 "Currently valid addresses are: %s" % local_ips()
221 )
218 )
222
219
223 # write connection file / get default ports
220 # write connection file / get default ports
224 self.write_connection_file()
221 self.write_connection_file()
225
222
226 # save kwargs for use in restart
223 # save kwargs for use in restart
227 self._launch_args = kw.copy()
224 self._launch_args = kw.copy()
228 # build the Popen cmd
225 # build the Popen cmd
229 kernel_cmd = self.format_kernel_cmd(**kw)
226 kernel_cmd = self.format_kernel_cmd(**kw)
230 if self.kernel_cmd:
227 if self.kernel_cmd:
231 # If kernel_cmd has been set manually, don't refer to a kernel spec
228 # If kernel_cmd has been set manually, don't refer to a kernel spec
232 env = os.environ
229 env = os.environ
233 else:
230 else:
234 # Environment variables from kernel spec are added to os.environ
231 # Environment variables from kernel spec are added to os.environ
235 env = os.environ.copy()
232 env = os.environ.copy()
236 env.update(self.kernel_spec.env or {})
233 env.update(self.kernel_spec.env or {})
237 # launch the kernel subprocess
234 # launch the kernel subprocess
238 self.kernel = self._launch_kernel(kernel_cmd, env=env,
235 self.kernel = self._launch_kernel(kernel_cmd, env=env,
239 ipython_kernel=self.ipython_kernel,
236 ipython_kernel=self.ipython_kernel,
240 **kw)
237 **kw)
241 self.start_restarter()
238 self.start_restarter()
242 self._connect_control_socket()
239 self._connect_control_socket()
243
240
244 def request_shutdown(self, restart=False):
241 def request_shutdown(self, restart=False):
245 """Send a shutdown request via control channel
242 """Send a shutdown request via control channel
246
243
247 On Windows, this just kills kernels instead, because the shutdown
244 On Windows, this just kills kernels instead, because the shutdown
248 messages don't work.
245 messages don't work.
249 """
246 """
250 content = dict(restart=restart)
247 content = dict(restart=restart)
251 msg = self.session.msg("shutdown_request", content=content)
248 msg = self.session.msg("shutdown_request", content=content)
252 self.session.send(self._control_socket, msg)
249 self.session.send(self._control_socket, msg)
253
250
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
251 def finish_shutdown(self, waittime=1, pollinterval=0.1):
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
252 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
256
253
257 This does not send shutdown requests - use :meth:`request_shutdown`
254 This does not send shutdown requests - use :meth:`request_shutdown`
258 first.
255 first.
259 """
256 """
260 for i in range(int(waittime/pollinterval)):
257 for i in range(int(waittime/pollinterval)):
261 if self.is_alive():
258 if self.is_alive():
262 time.sleep(pollinterval)
259 time.sleep(pollinterval)
263 else:
260 else:
264 break
261 break
265 else:
262 else:
266 # OK, we've waited long enough.
263 # OK, we've waited long enough.
267 if self.has_kernel:
264 if self.has_kernel:
268 self._kill_kernel()
265 self._kill_kernel()
269
266
270 def cleanup(self, connection_file=True):
267 def cleanup(self, connection_file=True):
271 """Clean up resources when the kernel is shut down"""
268 """Clean up resources when the kernel is shut down"""
272 if connection_file:
269 if connection_file:
273 self.cleanup_connection_file()
270 self.cleanup_connection_file()
274
271
275 self.cleanup_ipc_files()
272 self.cleanup_ipc_files()
276 self._close_control_socket()
273 self._close_control_socket()
277
274
278 def shutdown_kernel(self, now=False, restart=False):
275 def shutdown_kernel(self, now=False, restart=False):
279 """Attempts to the stop the kernel process cleanly.
276 """Attempts to the stop the kernel process cleanly.
280
277
281 This attempts to shutdown the kernels cleanly by:
278 This attempts to shutdown the kernels cleanly by:
282
279
283 1. Sending it a shutdown message over the shell channel.
280 1. Sending it a shutdown message over the shell channel.
284 2. If that fails, the kernel is shutdown forcibly by sending it
281 2. If that fails, the kernel is shutdown forcibly by sending it
285 a signal.
282 a signal.
286
283
287 Parameters
284 Parameters
288 ----------
285 ----------
289 now : bool
286 now : bool
290 Should the kernel be forcible killed *now*. This skips the
287 Should the kernel be forcible killed *now*. This skips the
291 first, nice shutdown attempt.
288 first, nice shutdown attempt.
292 restart: bool
289 restart: bool
293 Will this kernel be restarted after it is shutdown. When this
290 Will this kernel be restarted after it is shutdown. When this
294 is True, connection files will not be cleaned up.
291 is True, connection files will not be cleaned up.
295 """
292 """
296 # Stop monitoring for restarting while we shutdown.
293 # Stop monitoring for restarting while we shutdown.
297 self.stop_restarter()
294 self.stop_restarter()
298
295
299 if now:
296 if now:
300 self._kill_kernel()
297 self._kill_kernel()
301 else:
298 else:
302 self.request_shutdown(restart=restart)
299 self.request_shutdown(restart=restart)
303 # Don't send any additional kernel kill messages immediately, to give
300 # Don't send any additional kernel kill messages immediately, to give
304 # the kernel a chance to properly execute shutdown actions. Wait for at
301 # the kernel a chance to properly execute shutdown actions. Wait for at
305 # most 1s, checking every 0.1s.
302 # most 1s, checking every 0.1s.
306 self.finish_shutdown()
303 self.finish_shutdown()
307
304
308 self.cleanup(connection_file=not restart)
305 self.cleanup(connection_file=not restart)
309
306
310 def restart_kernel(self, now=False, **kw):
307 def restart_kernel(self, now=False, **kw):
311 """Restarts a kernel with the arguments that were used to launch it.
308 """Restarts a kernel with the arguments that were used to launch it.
312
309
313 If the old kernel was launched with random ports, the same ports will be
310 If the old kernel was launched with random ports, the same ports will be
314 used for the new kernel. The same connection file is used again.
311 used for the new kernel. The same connection file is used again.
315
312
316 Parameters
313 Parameters
317 ----------
314 ----------
318 now : bool, optional
315 now : bool, optional
319 If True, the kernel is forcefully restarted *immediately*, without
316 If True, the kernel is forcefully restarted *immediately*, without
320 having a chance to do any cleanup action. Otherwise the kernel is
317 having a chance to do any cleanup action. Otherwise the kernel is
321 given 1s to clean up before a forceful restart is issued.
318 given 1s to clean up before a forceful restart is issued.
322
319
323 In all cases the kernel is restarted, the only difference is whether
320 In all cases the kernel is restarted, the only difference is whether
324 it is given a chance to perform a clean shutdown or not.
321 it is given a chance to perform a clean shutdown or not.
325
322
326 **kw : optional
323 **kw : optional
327 Any options specified here will overwrite those used to launch the
324 Any options specified here will overwrite those used to launch the
328 kernel.
325 kernel.
329 """
326 """
330 if self._launch_args is None:
327 if self._launch_args is None:
331 raise RuntimeError("Cannot restart the kernel. "
328 raise RuntimeError("Cannot restart the kernel. "
332 "No previous call to 'start_kernel'.")
329 "No previous call to 'start_kernel'.")
333 else:
330 else:
334 # Stop currently running kernel.
331 # Stop currently running kernel.
335 self.shutdown_kernel(now=now, restart=True)
332 self.shutdown_kernel(now=now, restart=True)
336
333
337 # Start new kernel.
334 # Start new kernel.
338 self._launch_args.update(kw)
335 self._launch_args.update(kw)
339 self.start_kernel(**self._launch_args)
336 self.start_kernel(**self._launch_args)
340
337
341 @property
338 @property
342 def has_kernel(self):
339 def has_kernel(self):
343 """Has a kernel been started that we are managing."""
340 """Has a kernel been started that we are managing."""
344 return self.kernel is not None
341 return self.kernel is not None
345
342
346 def _kill_kernel(self):
343 def _kill_kernel(self):
347 """Kill the running kernel.
344 """Kill the running kernel.
348
345
349 This is a private method, callers should use shutdown_kernel(now=True).
346 This is a private method, callers should use shutdown_kernel(now=True).
350 """
347 """
351 if self.has_kernel:
348 if self.has_kernel:
352
349
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
350 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
354 # TerminateProcess() on Win32).
351 # TerminateProcess() on Win32).
355 try:
352 try:
356 self.kernel.kill()
353 self.kernel.kill()
357 except OSError as e:
354 except OSError as e:
358 # In Windows, we will get an Access Denied error if the process
355 # In Windows, we will get an Access Denied error if the process
359 # has already terminated. Ignore it.
356 # has already terminated. Ignore it.
360 if sys.platform == 'win32':
357 if sys.platform == 'win32':
361 if e.winerror != 5:
358 if e.winerror != 5:
362 raise
359 raise
363 # On Unix, we may get an ESRCH error if the process has already
360 # On Unix, we may get an ESRCH error if the process has already
364 # terminated. Ignore it.
361 # terminated. Ignore it.
365 else:
362 else:
366 from errno import ESRCH
363 from errno import ESRCH
367 if e.errno != ESRCH:
364 if e.errno != ESRCH:
368 raise
365 raise
369
366
370 # Block until the kernel terminates.
367 # Block until the kernel terminates.
371 self.kernel.wait()
368 self.kernel.wait()
372 self.kernel = None
369 self.kernel = None
373 else:
370 else:
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
371 raise RuntimeError("Cannot kill kernel. No kernel is running!")
375
372
376 def interrupt_kernel(self):
373 def interrupt_kernel(self):
377 """Interrupts the kernel by sending it a signal.
374 """Interrupts the kernel by sending it a signal.
378
375
379 Unlike ``signal_kernel``, this operation is well supported on all
376 Unlike ``signal_kernel``, this operation is well supported on all
380 platforms.
377 platforms.
381 """
378 """
382 if self.has_kernel:
379 if self.has_kernel:
383 if sys.platform == 'win32':
380 if sys.platform == 'win32':
384 from .zmq.parentpoller import ParentPollerWindows as Poller
381 from .zmq.parentpoller import ParentPollerWindows as Poller
385 Poller.send_interrupt(self.kernel.win32_interrupt_event)
382 Poller.send_interrupt(self.kernel.win32_interrupt_event)
386 else:
383 else:
387 self.kernel.send_signal(signal.SIGINT)
384 self.kernel.send_signal(signal.SIGINT)
388 else:
385 else:
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
386 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
390
387
391 def signal_kernel(self, signum):
388 def signal_kernel(self, signum):
392 """Sends a signal to the kernel.
389 """Sends a signal to the kernel.
393
390
394 Note that since only SIGTERM is supported on Windows, this function is
391 Note that since only SIGTERM is supported on Windows, this function is
395 only useful on Unix systems.
392 only useful on Unix systems.
396 """
393 """
397 if self.has_kernel:
394 if self.has_kernel:
398 self.kernel.send_signal(signum)
395 self.kernel.send_signal(signum)
399 else:
396 else:
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
397 raise RuntimeError("Cannot signal kernel. No kernel is running!")
401
398
402 def is_alive(self):
399 def is_alive(self):
403 """Is the kernel process still running?"""
400 """Is the kernel process still running?"""
404 if self.has_kernel:
401 if self.has_kernel:
405 if self.kernel.poll() is None:
402 if self.kernel.poll() is None:
406 return True
403 return True
407 else:
404 else:
408 return False
405 return False
409 else:
406 else:
410 # we don't have a kernel
407 # we don't have a kernel
411 return False
408 return False
412
409
413
410
414 KernelManagerABC.register(KernelManager)
411 KernelManagerABC.register(KernelManager)
415
412
General Comments 0
You need to be logged in to leave comments. Login now