##// END OF EJS Templates
Separate parent functionality from ParentPollerWindows...
Thomas Kluyver -
Show More
@@ -0,0 +1,39 b''
1 """Use a Windows event to interrupt a child process like SIGINT.
2
3 The child needs to explicitly listen for this - see
4 ipython_kernel.parentpoller.ParentPollerWindows for a Python implementation.
5 """
6
7 import ctypes
8
9 def create_interrupt_event():
10 """Create an interrupt event handle.
11
12 The parent process should call this to create the
13 interrupt event that is passed to the child process. It should store
14 this handle and use it with ``send_interrupt`` to interrupt the child
15 process.
16 """
17 # Create a security attributes struct that permits inheritance of the
18 # handle by new processes.
19 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
20 class SECURITY_ATTRIBUTES(ctypes.Structure):
21 _fields_ = [ ("nLength", ctypes.c_int),
22 ("lpSecurityDescriptor", ctypes.c_void_p),
23 ("bInheritHandle", ctypes.c_int) ]
24 sa = SECURITY_ATTRIBUTES()
25 sa_p = ctypes.pointer(sa)
26 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
27 sa.lpSecurityDescriptor = 0
28 sa.bInheritHandle = 1
29
30 return ctypes.windll.kernel32.CreateEventA(
31 sa_p, # lpEventAttributes
32 False, # bManualReset
33 False, # bInitialState
34 '') # lpName
35
36 def send_interrupt(interrupt_handle):
37 """ Sends an interrupt event using the specified handle.
38 """
39 ctypes.windll.kernel32.SetEvent(interrupt_handle)
@@ -1,148 +1,114 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 try:
4 try:
5 import ctypes
5 import ctypes
6 except:
6 except:
7 ctypes = None
7 ctypes = None
8 import os
8 import os
9 import platform
9 import platform
10 import signal
10 import signal
11 import time
11 import time
12 try:
12 try:
13 from _thread import interrupt_main # Py 3
13 from _thread import interrupt_main # Py 3
14 except ImportError:
14 except ImportError:
15 from thread import interrupt_main # Py 2
15 from thread import interrupt_main # Py 2
16 from threading import Thread
16 from threading import Thread
17
17
18 from IPython.utils.warn import warn
18 from IPython.utils.warn import warn
19
19
20
20
21 class ParentPollerUnix(Thread):
21 class ParentPollerUnix(Thread):
22 """ A Unix-specific daemon thread that terminates the program immediately
22 """ A Unix-specific daemon thread that terminates the program immediately
23 when the parent process no longer exists.
23 when the parent process no longer exists.
24 """
24 """
25
25
26 def __init__(self):
26 def __init__(self):
27 super(ParentPollerUnix, self).__init__()
27 super(ParentPollerUnix, self).__init__()
28 self.daemon = True
28 self.daemon = True
29
29
30 def run(self):
30 def run(self):
31 # We cannot use os.waitpid because it works only for child processes.
31 # We cannot use os.waitpid because it works only for child processes.
32 from errno import EINTR
32 from errno import EINTR
33 while True:
33 while True:
34 try:
34 try:
35 if os.getppid() == 1:
35 if os.getppid() == 1:
36 os._exit(1)
36 os._exit(1)
37 time.sleep(1.0)
37 time.sleep(1.0)
38 except OSError as e:
38 except OSError as e:
39 if e.errno == EINTR:
39 if e.errno == EINTR:
40 continue
40 continue
41 raise
41 raise
42
42
43
43
44 class ParentPollerWindows(Thread):
44 class ParentPollerWindows(Thread):
45 """ A Windows-specific daemon thread that listens for a special event that
45 """ A Windows-specific daemon thread that listens for a special event that
46 signals an interrupt and, optionally, terminates the program immediately
46 signals an interrupt and, optionally, terminates the program immediately
47 when the parent process no longer exists.
47 when the parent process no longer exists.
48 """
48 """
49
49
50 def __init__(self, interrupt_handle=None, parent_handle=None):
50 def __init__(self, interrupt_handle=None, parent_handle=None):
51 """ Create the poller. At least one of the optional parameters must be
51 """ Create the poller. At least one of the optional parameters must be
52 provided.
52 provided.
53
53
54 Parameters
54 Parameters
55 ----------
55 ----------
56 interrupt_handle : HANDLE (int), optional
56 interrupt_handle : HANDLE (int), optional
57 If provided, the program will generate a Ctrl+C event when this
57 If provided, the program will generate a Ctrl+C event when this
58 handle is signaled.
58 handle is signaled.
59
59
60 parent_handle : HANDLE (int), optional
60 parent_handle : HANDLE (int), optional
61 If provided, the program will terminate immediately when this
61 If provided, the program will terminate immediately when this
62 handle is signaled.
62 handle is signaled.
63 """
63 """
64 assert(interrupt_handle or parent_handle)
64 assert(interrupt_handle or parent_handle)
65 super(ParentPollerWindows, self).__init__()
65 super(ParentPollerWindows, self).__init__()
66 if ctypes is None:
66 if ctypes is None:
67 raise ImportError("ParentPollerWindows requires ctypes")
67 raise ImportError("ParentPollerWindows requires ctypes")
68 self.daemon = True
68 self.daemon = True
69 self.interrupt_handle = interrupt_handle
69 self.interrupt_handle = interrupt_handle
70 self.parent_handle = parent_handle
70 self.parent_handle = parent_handle
71
71
72 @staticmethod
73 def create_interrupt_event():
74 """ Create an interrupt event handle.
75
76 The parent process should use this static method for creating the
77 interrupt event that is passed to the child process. It should store
78 this handle and use it with ``send_interrupt`` to interrupt the child
79 process.
80 """
81 # Create a security attributes struct that permits inheritance of the
82 # handle by new processes.
83 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
84 class SECURITY_ATTRIBUTES(ctypes.Structure):
85 _fields_ = [ ("nLength", ctypes.c_int),
86 ("lpSecurityDescriptor", ctypes.c_void_p),
87 ("bInheritHandle", ctypes.c_int) ]
88 sa = SECURITY_ATTRIBUTES()
89 sa_p = ctypes.pointer(sa)
90 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
91 sa.lpSecurityDescriptor = 0
92 sa.bInheritHandle = 1
93
94 return ctypes.windll.kernel32.CreateEventA(
95 sa_p, # lpEventAttributes
96 False, # bManualReset
97 False, # bInitialState
98 '') # lpName
99
100 @staticmethod
101 def send_interrupt(interrupt_handle):
102 """ Sends an interrupt event using the specified handle.
103 """
104 ctypes.windll.kernel32.SetEvent(interrupt_handle)
105
106 def run(self):
72 def run(self):
107 """ Run the poll loop. This method never returns.
73 """ Run the poll loop. This method never returns.
108 """
74 """
109 try:
75 try:
110 from _winapi import WAIT_OBJECT_0, INFINITE
76 from _winapi import WAIT_OBJECT_0, INFINITE
111 except ImportError:
77 except ImportError:
112 from _subprocess import WAIT_OBJECT_0, INFINITE
78 from _subprocess import WAIT_OBJECT_0, INFINITE
113
79
114 # Build the list of handle to listen on.
80 # Build the list of handle to listen on.
115 handles = []
81 handles = []
116 if self.interrupt_handle:
82 if self.interrupt_handle:
117 handles.append(self.interrupt_handle)
83 handles.append(self.interrupt_handle)
118 if self.parent_handle:
84 if self.parent_handle:
119 handles.append(self.parent_handle)
85 handles.append(self.parent_handle)
120 arch = platform.architecture()[0]
86 arch = platform.architecture()[0]
121 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
87 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
122
88
123 # Listen forever.
89 # Listen forever.
124 while True:
90 while True:
125 result = ctypes.windll.kernel32.WaitForMultipleObjects(
91 result = ctypes.windll.kernel32.WaitForMultipleObjects(
126 len(handles), # nCount
92 len(handles), # nCount
127 (c_int * len(handles))(*handles), # lpHandles
93 (c_int * len(handles))(*handles), # lpHandles
128 False, # bWaitAll
94 False, # bWaitAll
129 INFINITE) # dwMilliseconds
95 INFINITE) # dwMilliseconds
130
96
131 if WAIT_OBJECT_0 <= result < len(handles):
97 if WAIT_OBJECT_0 <= result < len(handles):
132 handle = handles[result - WAIT_OBJECT_0]
98 handle = handles[result - WAIT_OBJECT_0]
133
99
134 if handle == self.interrupt_handle:
100 if handle == self.interrupt_handle:
135 # check if signal handler is callable
101 # check if signal handler is callable
136 # to avoid 'int not callable' error (Python issue #23395)
102 # to avoid 'int not callable' error (Python issue #23395)
137 if callable(signal.getsignal(signal.SIGINT)):
103 if callable(signal.getsignal(signal.SIGINT)):
138 interrupt_main()
104 interrupt_main()
139
105
140 elif handle == self.parent_handle:
106 elif handle == self.parent_handle:
141 os._exit(1)
107 os._exit(1)
142 elif result < 0:
108 elif result < 0:
143 # wait failed, just give up and stop polling.
109 # wait failed, just give up and stop polling.
144 warn("""Parent poll failed. If the frontend dies,
110 warn("""Parent poll failed. If the frontend dies,
145 the kernel may be left running. Please let us know
111 the kernel may be left running. Please let us know
146 about your system (bitness, Python, etc.) at
112 about your system (bitness, Python, etc.) at
147 ipython-dev@scipy.org""")
113 ipython-dev@scipy.org""")
148 return
114 return
@@ -1,226 +1,226 b''
1 """Utilities for launching kernels
1 """Utilities for launching kernels
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import sys
8 import sys
9 from subprocess import Popen, PIPE
9 from subprocess import Popen, PIPE
10
10
11 from IPython.utils.encoding import getdefaultencoding
11 from IPython.utils.encoding import getdefaultencoding
12 from IPython.utils.py3compat import cast_bytes_py2
12 from IPython.utils.py3compat import cast_bytes_py2
13
13
14
14
15 def swallow_argv(argv, aliases=None, flags=None):
15 def swallow_argv(argv, aliases=None, flags=None):
16 """strip frontend-specific aliases and flags from an argument list
16 """strip frontend-specific aliases and flags from an argument list
17
17
18 For use primarily in frontend apps that want to pass a subset of command-line
18 For use primarily in frontend apps that want to pass a subset of command-line
19 arguments through to a subprocess, where frontend-specific flags and aliases
19 arguments through to a subprocess, where frontend-specific flags and aliases
20 should be removed from the list.
20 should be removed from the list.
21
21
22 Parameters
22 Parameters
23 ----------
23 ----------
24
24
25 argv : list(str)
25 argv : list(str)
26 The starting argv, to be filtered
26 The starting argv, to be filtered
27 aliases : container of aliases (dict, list, set, etc.)
27 aliases : container of aliases (dict, list, set, etc.)
28 The frontend-specific aliases to be removed
28 The frontend-specific aliases to be removed
29 flags : container of flags (dict, list, set, etc.)
29 flags : container of flags (dict, list, set, etc.)
30 The frontend-specific flags to be removed
30 The frontend-specific flags to be removed
31
31
32 Returns
32 Returns
33 -------
33 -------
34
34
35 argv : list(str)
35 argv : list(str)
36 The argv list, excluding flags and aliases that have been stripped
36 The argv list, excluding flags and aliases that have been stripped
37 """
37 """
38
38
39 if aliases is None:
39 if aliases is None:
40 aliases = set()
40 aliases = set()
41 if flags is None:
41 if flags is None:
42 flags = set()
42 flags = set()
43
43
44 stripped = list(argv) # copy
44 stripped = list(argv) # copy
45
45
46 swallow_next = False
46 swallow_next = False
47 was_flag = False
47 was_flag = False
48 for a in argv:
48 for a in argv:
49 if a == '--':
49 if a == '--':
50 break
50 break
51 if swallow_next:
51 if swallow_next:
52 swallow_next = False
52 swallow_next = False
53 # last arg was an alias, remove the next one
53 # last arg was an alias, remove the next one
54 # *unless* the last alias has a no-arg flag version, in which
54 # *unless* the last alias has a no-arg flag version, in which
55 # case, don't swallow the next arg if it's also a flag:
55 # case, don't swallow the next arg if it's also a flag:
56 if not (was_flag and a.startswith('-')):
56 if not (was_flag and a.startswith('-')):
57 stripped.remove(a)
57 stripped.remove(a)
58 continue
58 continue
59 if a.startswith('-'):
59 if a.startswith('-'):
60 split = a.lstrip('-').split('=')
60 split = a.lstrip('-').split('=')
61 name = split[0]
61 name = split[0]
62 # we use startswith because argparse accepts any arg to be specified
62 # we use startswith because argparse accepts any arg to be specified
63 # by any leading section, as long as it is unique,
63 # by any leading section, as long as it is unique,
64 # so `--no-br` means `--no-browser` in the notebook, etc.
64 # so `--no-br` means `--no-browser` in the notebook, etc.
65 if any(alias.startswith(name) for alias in aliases):
65 if any(alias.startswith(name) for alias in aliases):
66 stripped.remove(a)
66 stripped.remove(a)
67 if len(split) == 1:
67 if len(split) == 1:
68 # alias passed with arg via space
68 # alias passed with arg via space
69 swallow_next = True
69 swallow_next = True
70 # could have been a flag that matches an alias, e.g. `existing`
70 # could have been a flag that matches an alias, e.g. `existing`
71 # in which case, we might not swallow the next arg
71 # in which case, we might not swallow the next arg
72 was_flag = name in flags
72 was_flag = name in flags
73 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
73 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
74 # strip flag, but don't swallow next, as flags don't take args
74 # strip flag, but don't swallow next, as flags don't take args
75 stripped.remove(a)
75 stripped.remove(a)
76
76
77 # return shortened list
77 # return shortened list
78 return stripped
78 return stripped
79
79
80
80
81 def make_ipkernel_cmd(mod='ipython_kernel', executable=None, extra_arguments=[], **kw):
81 def make_ipkernel_cmd(mod='ipython_kernel', executable=None, extra_arguments=[], **kw):
82 """Build Popen command list for launching an IPython kernel.
82 """Build Popen command list for launching an IPython kernel.
83
83
84 Parameters
84 Parameters
85 ----------
85 ----------
86 mod : str, optional (default 'ipython_kernel')
86 mod : str, optional (default 'ipython_kernel')
87 A string of an IPython module whose __main__ starts an IPython kernel
87 A string of an IPython module whose __main__ starts an IPython kernel
88
88
89 executable : str, optional (default sys.executable)
89 executable : str, optional (default sys.executable)
90 The Python executable to use for the kernel process.
90 The Python executable to use for the kernel process.
91
91
92 extra_arguments : list, optional
92 extra_arguments : list, optional
93 A list of extra arguments to pass when executing the launch code.
93 A list of extra arguments to pass when executing the launch code.
94
94
95 Returns
95 Returns
96 -------
96 -------
97
97
98 A Popen command list
98 A Popen command list
99 """
99 """
100 if executable is None:
100 if executable is None:
101 executable = sys.executable
101 executable = sys.executable
102 arguments = [ executable, '-m', mod, '-f', '{connection_file}' ]
102 arguments = [ executable, '-m', mod, '-f', '{connection_file}' ]
103 arguments.extend(extra_arguments)
103 arguments.extend(extra_arguments)
104
104
105 return arguments
105 return arguments
106
106
107
107
108 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
108 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
109 independent=False,
109 independent=False,
110 cwd=None,
110 cwd=None,
111 **kw
111 **kw
112 ):
112 ):
113 """ Launches a localhost kernel, binding to the specified ports.
113 """ Launches a localhost kernel, binding to the specified ports.
114
114
115 Parameters
115 Parameters
116 ----------
116 ----------
117 cmd : Popen list,
117 cmd : Popen list,
118 A string of Python code that imports and executes a kernel entry point.
118 A string of Python code that imports and executes a kernel entry point.
119
119
120 stdin, stdout, stderr : optional (default None)
120 stdin, stdout, stderr : optional (default None)
121 Standards streams, as defined in subprocess.Popen.
121 Standards streams, as defined in subprocess.Popen.
122
122
123 independent : bool, optional (default False)
123 independent : bool, optional (default False)
124 If set, the kernel process is guaranteed to survive if this process
124 If set, the kernel process is guaranteed to survive if this process
125 dies. If not set, an effort is made to ensure that the kernel is killed
125 dies. If not set, an effort is made to ensure that the kernel is killed
126 when this process dies. Note that in this case it is still good practice
126 when this process dies. Note that in this case it is still good practice
127 to kill kernels manually before exiting.
127 to kill kernels manually before exiting.
128
128
129 cwd : path, optional
129 cwd : path, optional
130 The working dir of the kernel process (default: cwd of this process).
130 The working dir of the kernel process (default: cwd of this process).
131
131
132 Returns
132 Returns
133 -------
133 -------
134
134
135 Popen instance for the kernel subprocess
135 Popen instance for the kernel subprocess
136 """
136 """
137
137
138 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
138 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
139 # are invalid. Unfortunately, there is in general no way to detect whether
139 # are invalid. Unfortunately, there is in general no way to detect whether
140 # they are valid. The following two blocks redirect them to (temporary)
140 # they are valid. The following two blocks redirect them to (temporary)
141 # pipes in certain important cases.
141 # pipes in certain important cases.
142
142
143 # If this process has been backgrounded, our stdin is invalid. Since there
143 # If this process has been backgrounded, our stdin is invalid. Since there
144 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
144 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
145 # place this one safe and always redirect.
145 # place this one safe and always redirect.
146 redirect_in = True
146 redirect_in = True
147 _stdin = PIPE if stdin is None else stdin
147 _stdin = PIPE if stdin is None else stdin
148
148
149 # If this process in running on pythonw, we know that stdin, stdout, and
149 # If this process in running on pythonw, we know that stdin, stdout, and
150 # stderr are all invalid.
150 # stderr are all invalid.
151 redirect_out = sys.executable.endswith('pythonw.exe')
151 redirect_out = sys.executable.endswith('pythonw.exe')
152 if redirect_out:
152 if redirect_out:
153 blackhole = open(os.devnull, 'w')
153 blackhole = open(os.devnull, 'w')
154 _stdout = blackhole if stdout is None else stdout
154 _stdout = blackhole if stdout is None else stdout
155 _stderr = blackhole if stderr is None else stderr
155 _stderr = blackhole if stderr is None else stderr
156 else:
156 else:
157 _stdout, _stderr = stdout, stderr
157 _stdout, _stderr = stdout, stderr
158
158
159 env = env if (env is not None) else os.environ.copy()
159 env = env if (env is not None) else os.environ.copy()
160
160
161 encoding = getdefaultencoding(prefer_stream=False)
161 encoding = getdefaultencoding(prefer_stream=False)
162 kwargs = dict(
162 kwargs = dict(
163 stdin=_stdin,
163 stdin=_stdin,
164 stdout=_stdout,
164 stdout=_stdout,
165 stderr=_stderr,
165 stderr=_stderr,
166 cwd=cwd,
166 cwd=cwd,
167 env=env,
167 env=env,
168 )
168 )
169
169
170 # Spawn a kernel.
170 # Spawn a kernel.
171 if sys.platform == 'win32':
171 if sys.platform == 'win32':
172 # Popen on Python 2 on Windows cannot handle unicode args or cwd
172 # Popen on Python 2 on Windows cannot handle unicode args or cwd
173 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
173 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
174 if cwd:
174 if cwd:
175 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
175 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
176 kwargs['cwd'] = cwd
176 kwargs['cwd'] = cwd
177
177
178 from jupyter_client.parentpoller import ParentPollerWindows
178 from .win_interrupt import create_interrupt_event
179 # Create a Win32 event for interrupting the kernel
179 # Create a Win32 event for interrupting the kernel
180 # and store it in an environment variable.
180 # and store it in an environment variable.
181 interrupt_event = ParentPollerWindows.create_interrupt_event()
181 interrupt_event = create_interrupt_event()
182 env["JPY_INTERRUPT_EVENT"] = str(interrupt_event)
182 env["JPY_INTERRUPT_EVENT"] = str(interrupt_event)
183 # deprecated old env name:
183 # deprecated old env name:
184 env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"]
184 env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"]
185
185
186 try:
186 try:
187 from _winapi import DuplicateHandle, GetCurrentProcess, \
187 from _winapi import DuplicateHandle, GetCurrentProcess, \
188 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
188 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
189 except:
189 except:
190 from _subprocess import DuplicateHandle, GetCurrentProcess, \
190 from _subprocess import DuplicateHandle, GetCurrentProcess, \
191 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
191 DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
192 # Launch the kernel process
192 # Launch the kernel process
193 if independent:
193 if independent:
194 kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
194 kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
195 else:
195 else:
196 pid = GetCurrentProcess()
196 pid = GetCurrentProcess()
197 handle = DuplicateHandle(pid, pid, pid, 0,
197 handle = DuplicateHandle(pid, pid, pid, 0,
198 True, # Inheritable by new processes.
198 True, # Inheritable by new processes.
199 DUPLICATE_SAME_ACCESS)
199 DUPLICATE_SAME_ACCESS)
200 env['JPY_PARENT_PID'] = str(int(handle))
200 env['JPY_PARENT_PID'] = str(int(handle))
201
201
202 proc = Popen(cmd, **kwargs)
202 proc = Popen(cmd, **kwargs)
203
203
204 # Attach the interrupt event to the Popen objet so it can be used later.
204 # Attach the interrupt event to the Popen objet so it can be used later.
205 proc.win32_interrupt_event = interrupt_event
205 proc.win32_interrupt_event = interrupt_event
206
206
207 else:
207 else:
208 if independent:
208 if independent:
209 kwargs['preexec_fn'] = lambda: os.setsid()
209 kwargs['preexec_fn'] = lambda: os.setsid()
210 else:
210 else:
211 env['JPY_PARENT_PID'] = str(os.getpid())
211 env['JPY_PARENT_PID'] = str(os.getpid())
212
212
213 proc = Popen(cmd, **kwargs)
213 proc = Popen(cmd, **kwargs)
214
214
215 # Clean up pipes created to work around Popen bug.
215 # Clean up pipes created to work around Popen bug.
216 if redirect_in:
216 if redirect_in:
217 if stdin is None:
217 if stdin is None:
218 proc.stdin.close()
218 proc.stdin.close()
219
219
220 return proc
220 return proc
221
221
222 __all__ = [
222 __all__ = [
223 'swallow_argv',
223 'swallow_argv',
224 'make_ipkernel_cmd',
224 'make_ipkernel_cmd',
225 'launch_kernel',
225 'launch_kernel',
226 ]
226 ]
@@ -1,442 +1,442 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15 try:
15 try:
16 from queue import Empty # Py 3
16 from queue import Empty # Py 3
17 except ImportError:
17 except ImportError:
18 from Queue import Empty # Py 2
18 from Queue import Empty # Py 2
19
19
20 import zmq
20 import zmq
21
21
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
24 from IPython.utils.path import get_ipython_dir
24 from IPython.utils.path import get_ipython_dir
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
27 )
27 )
28 from jupyter_client import (
28 from jupyter_client import (
29 launch_kernel,
29 launch_kernel,
30 kernelspec,
30 kernelspec,
31 )
31 )
32 from .connect import ConnectionFileMixin
32 from .connect import ConnectionFileMixin
33 from .session import Session
33 from .session import Session
34 from .managerabc import (
34 from .managerabc import (
35 KernelManagerABC
35 KernelManagerABC
36 )
36 )
37
37
38
38
39 class KernelManager(ConnectionFileMixin):
39 class KernelManager(ConnectionFileMixin):
40 """Manages a single kernel in a subprocess on this host.
40 """Manages a single kernel in a subprocess on this host.
41
41
42 This version starts kernels with Popen.
42 This version starts kernels with Popen.
43 """
43 """
44
44
45 # The PyZMQ Context to use for communication with the kernel.
45 # The PyZMQ Context to use for communication with the kernel.
46 context = Instance(zmq.Context)
46 context = Instance(zmq.Context)
47 def _context_default(self):
47 def _context_default(self):
48 return zmq.Context.instance()
48 return zmq.Context.instance()
49
49
50 # the class to create with our `client` method
50 # the class to create with our `client` method
51 client_class = DottedObjectName('jupyter_client.blocking.BlockingKernelClient')
51 client_class = DottedObjectName('jupyter_client.blocking.BlockingKernelClient')
52 client_factory = Type(allow_none=True)
52 client_factory = Type(allow_none=True)
53 def _client_class_changed(self, name, old, new):
53 def _client_class_changed(self, name, old, new):
54 self.client_factory = import_item(str(new))
54 self.client_factory = import_item(str(new))
55
55
56 # The kernel process with which the KernelManager is communicating.
56 # The kernel process with which the KernelManager is communicating.
57 # generally a Popen instance
57 # generally a Popen instance
58 kernel = Any()
58 kernel = Any()
59
59
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
61
61
62 def _kernel_spec_manager_default(self):
62 def _kernel_spec_manager_default(self):
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
64
64
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
66
66
67 kernel_spec = Instance(kernelspec.KernelSpec)
67 kernel_spec = Instance(kernelspec.KernelSpec)
68
68
69 def _kernel_spec_default(self):
69 def _kernel_spec_default(self):
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
71
71
72 def _kernel_name_changed(self, name, old, new):
72 def _kernel_name_changed(self, name, old, new):
73 if new == 'python':
73 if new == 'python':
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
75 # This triggered another run of this function, so we can exit now
75 # This triggered another run of this function, so we can exit now
76 return
76 return
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
79
79
80 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
81 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
82
82
83 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
84 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
85 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
86 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
87 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
88 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
89 this means that the kernel does not receive the
89 this means that the kernel does not receive the
90 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
91 """
91 """
92 )
92 )
93
93
94 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
96 "start different kernels.")
97 self.ipython_kernel = False
97 self.ipython_kernel = False
98
98
99 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
100
101 ipython_dir = Unicode()
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
103 return get_ipython_dir()
104
104
105 # Protected traits
105 # Protected traits
106 _launch_args = Any()
106 _launch_args = Any()
107 _control_socket = Any()
107 _control_socket = Any()
108
108
109 _restarter = Any()
109 _restarter = Any()
110
110
111 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
113 )
113 )
114
114
115 def __del__(self):
115 def __del__(self):
116 self._close_control_socket()
116 self._close_control_socket()
117 self.cleanup_connection_file()
117 self.cleanup_connection_file()
118
118
119 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
120 # Kernel restarter
120 # Kernel restarter
121 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
122
122
123 def start_restarter(self):
123 def start_restarter(self):
124 pass
124 pass
125
125
126 def stop_restarter(self):
126 def stop_restarter(self):
127 pass
127 pass
128
128
129 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
131 if self._restarter is None:
132 return
132 return
133 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
134
134
135 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
137 if self._restarter is None:
138 return
138 return
139 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
140
140
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
144
144
145 def client(self, **kwargs):
145 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
147 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
149
149
150 kw = {}
150 kw = {}
151 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
152 kw.update(dict(
152 kw.update(dict(
153 connection_file=self.connection_file,
153 connection_file=self.connection_file,
154 session=self.session,
154 session=self.session,
155 parent=self,
155 parent=self,
156 ))
156 ))
157
157
158 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
159 kw.update(kwargs)
159 kw.update(kwargs)
160 return self.client_factory(**kw)
160 return self.client_factory(**kw)
161
161
162 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
163 # Kernel management
163 # Kernel management
164 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
165
165
166 def format_kernel_cmd(self, extra_arguments=None):
166 def format_kernel_cmd(self, extra_arguments=None):
167 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
168 extra_arguments = extra_arguments or []
168 extra_arguments = extra_arguments or []
169 if self.kernel_cmd:
169 if self.kernel_cmd:
170 cmd = self.kernel_cmd + extra_arguments
170 cmd = self.kernel_cmd + extra_arguments
171 else:
171 else:
172 cmd = self.kernel_spec.argv + extra_arguments
172 cmd = self.kernel_spec.argv + extra_arguments
173
173
174 ns = dict(connection_file=self.connection_file)
174 ns = dict(connection_file=self.connection_file)
175 ns.update(self._launch_args)
175 ns.update(self._launch_args)
176
176
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
178 def from_ns(match):
178 def from_ns(match):
179 """Get the key out of ns if it's there, otherwise no change."""
179 """Get the key out of ns if it's there, otherwise no change."""
180 return ns.get(match.group(1), match.group())
180 return ns.get(match.group(1), match.group())
181
181
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
183
183
184 def _launch_kernel(self, kernel_cmd, **kw):
184 def _launch_kernel(self, kernel_cmd, **kw):
185 """actually launch the kernel
185 """actually launch the kernel
186
186
187 override in a subclass to launch kernel subprocesses differently
187 override in a subclass to launch kernel subprocesses differently
188 """
188 """
189 return launch_kernel(kernel_cmd, **kw)
189 return launch_kernel(kernel_cmd, **kw)
190
190
191 # Control socket used for polite kernel shutdown
191 # Control socket used for polite kernel shutdown
192
192
193 def _connect_control_socket(self):
193 def _connect_control_socket(self):
194 if self._control_socket is None:
194 if self._control_socket is None:
195 self._control_socket = self.connect_control()
195 self._control_socket = self.connect_control()
196 self._control_socket.linger = 100
196 self._control_socket.linger = 100
197
197
198 def _close_control_socket(self):
198 def _close_control_socket(self):
199 if self._control_socket is None:
199 if self._control_socket is None:
200 return
200 return
201 self._control_socket.close()
201 self._control_socket.close()
202 self._control_socket = None
202 self._control_socket = None
203
203
204 def start_kernel(self, **kw):
204 def start_kernel(self, **kw):
205 """Starts a kernel on this host in a separate process.
205 """Starts a kernel on this host in a separate process.
206
206
207 If random ports (port=0) are being used, this method must be called
207 If random ports (port=0) are being used, this method must be called
208 before the channels are created.
208 before the channels are created.
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 **kw : optional
212 **kw : optional
213 keyword arguments that are passed down to build the kernel_cmd
213 keyword arguments that are passed down to build the kernel_cmd
214 and launching the kernel (e.g. Popen kwargs).
214 and launching the kernel (e.g. Popen kwargs).
215 """
215 """
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
217 raise RuntimeError("Can only launch a kernel on a local interface. "
217 raise RuntimeError("Can only launch a kernel on a local interface. "
218 "Make sure that the '*_address' attributes are "
218 "Make sure that the '*_address' attributes are "
219 "configured properly. "
219 "configured properly. "
220 "Currently valid addresses are: %s" % local_ips()
220 "Currently valid addresses are: %s" % local_ips()
221 )
221 )
222
222
223 # write connection file / get default ports
223 # write connection file / get default ports
224 self.write_connection_file()
224 self.write_connection_file()
225
225
226 # save kwargs for use in restart
226 # save kwargs for use in restart
227 self._launch_args = kw.copy()
227 self._launch_args = kw.copy()
228 # build the Popen cmd
228 # build the Popen cmd
229 extra_arguments = kw.pop('extra_arguments', [])
229 extra_arguments = kw.pop('extra_arguments', [])
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
231 if self.kernel_cmd:
231 if self.kernel_cmd:
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
233 env = os.environ
233 env = os.environ
234 else:
234 else:
235 # Environment variables from kernel spec are added to os.environ
235 # Environment variables from kernel spec are added to os.environ
236 env = os.environ.copy()
236 env = os.environ.copy()
237 env.update(self.kernel_spec.env or {})
237 env.update(self.kernel_spec.env or {})
238 # launch the kernel subprocess
238 # launch the kernel subprocess
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
240 **kw)
240 **kw)
241 self.start_restarter()
241 self.start_restarter()
242 self._connect_control_socket()
242 self._connect_control_socket()
243
243
244 def request_shutdown(self, restart=False):
244 def request_shutdown(self, restart=False):
245 """Send a shutdown request via control channel
245 """Send a shutdown request via control channel
246
246
247 On Windows, this just kills kernels instead, because the shutdown
247 On Windows, this just kills kernels instead, because the shutdown
248 messages don't work.
248 messages don't work.
249 """
249 """
250 content = dict(restart=restart)
250 content = dict(restart=restart)
251 msg = self.session.msg("shutdown_request", content=content)
251 msg = self.session.msg("shutdown_request", content=content)
252 self.session.send(self._control_socket, msg)
252 self.session.send(self._control_socket, msg)
253
253
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
256
256
257 This does not send shutdown requests - use :meth:`request_shutdown`
257 This does not send shutdown requests - use :meth:`request_shutdown`
258 first.
258 first.
259 """
259 """
260 for i in range(int(waittime/pollinterval)):
260 for i in range(int(waittime/pollinterval)):
261 if self.is_alive():
261 if self.is_alive():
262 time.sleep(pollinterval)
262 time.sleep(pollinterval)
263 else:
263 else:
264 break
264 break
265 else:
265 else:
266 # OK, we've waited long enough.
266 # OK, we've waited long enough.
267 if self.has_kernel:
267 if self.has_kernel:
268 self._kill_kernel()
268 self._kill_kernel()
269
269
270 def cleanup(self, connection_file=True):
270 def cleanup(self, connection_file=True):
271 """Clean up resources when the kernel is shut down"""
271 """Clean up resources when the kernel is shut down"""
272 if connection_file:
272 if connection_file:
273 self.cleanup_connection_file()
273 self.cleanup_connection_file()
274
274
275 self.cleanup_ipc_files()
275 self.cleanup_ipc_files()
276 self._close_control_socket()
276 self._close_control_socket()
277
277
278 def shutdown_kernel(self, now=False, restart=False):
278 def shutdown_kernel(self, now=False, restart=False):
279 """Attempts to the stop the kernel process cleanly.
279 """Attempts to the stop the kernel process cleanly.
280
280
281 This attempts to shutdown the kernels cleanly by:
281 This attempts to shutdown the kernels cleanly by:
282
282
283 1. Sending it a shutdown message over the shell channel.
283 1. Sending it a shutdown message over the shell channel.
284 2. If that fails, the kernel is shutdown forcibly by sending it
284 2. If that fails, the kernel is shutdown forcibly by sending it
285 a signal.
285 a signal.
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 now : bool
289 now : bool
290 Should the kernel be forcible killed *now*. This skips the
290 Should the kernel be forcible killed *now*. This skips the
291 first, nice shutdown attempt.
291 first, nice shutdown attempt.
292 restart: bool
292 restart: bool
293 Will this kernel be restarted after it is shutdown. When this
293 Will this kernel be restarted after it is shutdown. When this
294 is True, connection files will not be cleaned up.
294 is True, connection files will not be cleaned up.
295 """
295 """
296 # Stop monitoring for restarting while we shutdown.
296 # Stop monitoring for restarting while we shutdown.
297 self.stop_restarter()
297 self.stop_restarter()
298
298
299 if now:
299 if now:
300 self._kill_kernel()
300 self._kill_kernel()
301 else:
301 else:
302 self.request_shutdown(restart=restart)
302 self.request_shutdown(restart=restart)
303 # Don't send any additional kernel kill messages immediately, to give
303 # Don't send any additional kernel kill messages immediately, to give
304 # the kernel a chance to properly execute shutdown actions. Wait for at
304 # the kernel a chance to properly execute shutdown actions. Wait for at
305 # most 1s, checking every 0.1s.
305 # most 1s, checking every 0.1s.
306 self.finish_shutdown()
306 self.finish_shutdown()
307
307
308 self.cleanup(connection_file=not restart)
308 self.cleanup(connection_file=not restart)
309
309
310 def restart_kernel(self, now=False, **kw):
310 def restart_kernel(self, now=False, **kw):
311 """Restarts a kernel with the arguments that were used to launch it.
311 """Restarts a kernel with the arguments that were used to launch it.
312
312
313 If the old kernel was launched with random ports, the same ports will be
313 If the old kernel was launched with random ports, the same ports will be
314 used for the new kernel. The same connection file is used again.
314 used for the new kernel. The same connection file is used again.
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 now : bool, optional
318 now : bool, optional
319 If True, the kernel is forcefully restarted *immediately*, without
319 If True, the kernel is forcefully restarted *immediately*, without
320 having a chance to do any cleanup action. Otherwise the kernel is
320 having a chance to do any cleanup action. Otherwise the kernel is
321 given 1s to clean up before a forceful restart is issued.
321 given 1s to clean up before a forceful restart is issued.
322
322
323 In all cases the kernel is restarted, the only difference is whether
323 In all cases the kernel is restarted, the only difference is whether
324 it is given a chance to perform a clean shutdown or not.
324 it is given a chance to perform a clean shutdown or not.
325
325
326 **kw : optional
326 **kw : optional
327 Any options specified here will overwrite those used to launch the
327 Any options specified here will overwrite those used to launch the
328 kernel.
328 kernel.
329 """
329 """
330 if self._launch_args is None:
330 if self._launch_args is None:
331 raise RuntimeError("Cannot restart the kernel. "
331 raise RuntimeError("Cannot restart the kernel. "
332 "No previous call to 'start_kernel'.")
332 "No previous call to 'start_kernel'.")
333 else:
333 else:
334 # Stop currently running kernel.
334 # Stop currently running kernel.
335 self.shutdown_kernel(now=now, restart=True)
335 self.shutdown_kernel(now=now, restart=True)
336
336
337 # Start new kernel.
337 # Start new kernel.
338 self._launch_args.update(kw)
338 self._launch_args.update(kw)
339 self.start_kernel(**self._launch_args)
339 self.start_kernel(**self._launch_args)
340
340
341 @property
341 @property
342 def has_kernel(self):
342 def has_kernel(self):
343 """Has a kernel been started that we are managing."""
343 """Has a kernel been started that we are managing."""
344 return self.kernel is not None
344 return self.kernel is not None
345
345
346 def _kill_kernel(self):
346 def _kill_kernel(self):
347 """Kill the running kernel.
347 """Kill the running kernel.
348
348
349 This is a private method, callers should use shutdown_kernel(now=True).
349 This is a private method, callers should use shutdown_kernel(now=True).
350 """
350 """
351 if self.has_kernel:
351 if self.has_kernel:
352
352
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
354 # TerminateProcess() on Win32).
354 # TerminateProcess() on Win32).
355 try:
355 try:
356 self.kernel.kill()
356 self.kernel.kill()
357 except OSError as e:
357 except OSError as e:
358 # In Windows, we will get an Access Denied error if the process
358 # In Windows, we will get an Access Denied error if the process
359 # has already terminated. Ignore it.
359 # has already terminated. Ignore it.
360 if sys.platform == 'win32':
360 if sys.platform == 'win32':
361 if e.winerror != 5:
361 if e.winerror != 5:
362 raise
362 raise
363 # On Unix, we may get an ESRCH error if the process has already
363 # On Unix, we may get an ESRCH error if the process has already
364 # terminated. Ignore it.
364 # terminated. Ignore it.
365 else:
365 else:
366 from errno import ESRCH
366 from errno import ESRCH
367 if e.errno != ESRCH:
367 if e.errno != ESRCH:
368 raise
368 raise
369
369
370 # Block until the kernel terminates.
370 # Block until the kernel terminates.
371 self.kernel.wait()
371 self.kernel.wait()
372 self.kernel = None
372 self.kernel = None
373 else:
373 else:
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
375
375
376 def interrupt_kernel(self):
376 def interrupt_kernel(self):
377 """Interrupts the kernel by sending it a signal.
377 """Interrupts the kernel by sending it a signal.
378
378
379 Unlike ``signal_kernel``, this operation is well supported on all
379 Unlike ``signal_kernel``, this operation is well supported on all
380 platforms.
380 platforms.
381 """
381 """
382 if self.has_kernel:
382 if self.has_kernel:
383 if sys.platform == 'win32':
383 if sys.platform == 'win32':
384 from .parentpoller import ParentPollerWindows as Poller
384 from .win_interrupt import send_interrupt
385 Poller.send_interrupt(self.kernel.win32_interrupt_event)
385 send_interrupt(self.kernel.win32_interrupt_event)
386 else:
386 else:
387 self.kernel.send_signal(signal.SIGINT)
387 self.kernel.send_signal(signal.SIGINT)
388 else:
388 else:
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
390
390
391 def signal_kernel(self, signum):
391 def signal_kernel(self, signum):
392 """Sends a signal to the kernel.
392 """Sends a signal to the kernel.
393
393
394 Note that since only SIGTERM is supported on Windows, this function is
394 Note that since only SIGTERM is supported on Windows, this function is
395 only useful on Unix systems.
395 only useful on Unix systems.
396 """
396 """
397 if self.has_kernel:
397 if self.has_kernel:
398 self.kernel.send_signal(signum)
398 self.kernel.send_signal(signum)
399 else:
399 else:
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
401
401
402 def is_alive(self):
402 def is_alive(self):
403 """Is the kernel process still running?"""
403 """Is the kernel process still running?"""
404 if self.has_kernel:
404 if self.has_kernel:
405 if self.kernel.poll() is None:
405 if self.kernel.poll() is None:
406 return True
406 return True
407 else:
407 else:
408 return False
408 return False
409 else:
409 else:
410 # we don't have a kernel
410 # we don't have a kernel
411 return False
411 return False
412
412
413
413
414 KernelManagerABC.register(KernelManager)
414 KernelManagerABC.register(KernelManager)
415
415
416
416
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
418 """Start a new kernel, and return its Manager and Client"""
418 """Start a new kernel, and return its Manager and Client"""
419 km = KernelManager(kernel_name=kernel_name)
419 km = KernelManager(kernel_name=kernel_name)
420 km.start_kernel(**kwargs)
420 km.start_kernel(**kwargs)
421 kc = km.client()
421 kc = km.client()
422 kc.start_channels()
422 kc.start_channels()
423 kc.wait_for_ready()
423 kc.wait_for_ready()
424
424
425 return km, kc
425 return km, kc
426
426
427 @contextmanager
427 @contextmanager
428 def run_kernel(**kwargs):
428 def run_kernel(**kwargs):
429 """Context manager to create a kernel in a subprocess.
429 """Context manager to create a kernel in a subprocess.
430
430
431 The kernel is shut down when the context exits.
431 The kernel is shut down when the context exits.
432
432
433 Returns
433 Returns
434 -------
434 -------
435 kernel_client: connected KernelClient instance
435 kernel_client: connected KernelClient instance
436 """
436 """
437 km, kc = start_new_kernel(**kwargs)
437 km, kc = start_new_kernel(**kwargs)
438 try:
438 try:
439 yield kc
439 yield kc
440 finally:
440 finally:
441 kc.stop_channels()
441 kc.stop_channels()
442 km.shutdown_kernel(now=True)
442 km.shutdown_kernel(now=True)
General Comments 0
You need to be logged in to leave comments. Login now