##// END OF EJS Templates
Pass Windows interrupt event to kernels as an environment variable...
Thomas Kluyver -
Show More
@@ -1,256 +1,261 b''
1 1 """Utilities for launching kernels
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import sys
9 9 from subprocess import Popen, PIPE
10 10
11 11 from IPython.utils.encoding import getdefaultencoding
12 12 from IPython.utils.py3compat import cast_bytes_py2
13 13
14 14
15 15 def swallow_argv(argv, aliases=None, flags=None):
16 16 """strip frontend-specific aliases and flags from an argument list
17 17
18 18 For use primarily in frontend apps that want to pass a subset of command-line
19 19 arguments through to a subprocess, where frontend-specific flags and aliases
20 20 should be removed from the list.
21 21
22 22 Parameters
23 23 ----------
24 24
25 25 argv : list(str)
26 26 The starting argv, to be filtered
27 27 aliases : container of aliases (dict, list, set, etc.)
28 28 The frontend-specific aliases to be removed
29 29 flags : container of flags (dict, list, set, etc.)
30 30 The frontend-specific flags to be removed
31 31
32 32 Returns
33 33 -------
34 34
35 35 argv : list(str)
36 36 The argv list, excluding flags and aliases that have been stripped
37 37 """
38 38
39 39 if aliases is None:
40 40 aliases = set()
41 41 if flags is None:
42 42 flags = set()
43 43
44 44 stripped = list(argv) # copy
45 45
46 46 swallow_next = False
47 47 was_flag = False
48 48 for a in argv:
49 49 if a == '--':
50 50 break
51 51 if swallow_next:
52 52 swallow_next = False
53 53 # last arg was an alias, remove the next one
54 54 # *unless* the last alias has a no-arg flag version, in which
55 55 # case, don't swallow the next arg if it's also a flag:
56 56 if not (was_flag and a.startswith('-')):
57 57 stripped.remove(a)
58 58 continue
59 59 if a.startswith('-'):
60 60 split = a.lstrip('-').split('=')
61 61 name = split[0]
62 62 # we use startswith because argparse accepts any arg to be specified
63 63 # by any leading section, as long as it is unique,
64 64 # so `--no-br` means `--no-browser` in the notebook, etc.
65 65 if any(alias.startswith(name) for alias in aliases):
66 66 stripped.remove(a)
67 67 if len(split) == 1:
68 68 # alias passed with arg via space
69 69 swallow_next = True
70 70 # could have been a flag that matches an alias, e.g. `existing`
71 71 # in which case, we might not swallow the next arg
72 72 was_flag = name in flags
73 73 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
74 74 # strip flag, but don't swallow next, as flags don't take args
75 75 stripped.remove(a)
76 76
77 77 # return shortened list
78 78 return stripped
79 79
80 80
81 81 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
82 82 """Build Popen command list for launching an IPython kernel.
83 83
84 84 Parameters
85 85 ----------
86 86 code : str,
87 87 A string of Python code that imports and executes a kernel entry point.
88 88
89 89 executable : str, optional (default sys.executable)
90 90 The Python executable to use for the kernel process.
91 91
92 92 extra_arguments : list, optional
93 93 A list of extra arguments to pass when executing the launch code.
94 94
95 95 Returns
96 96 -------
97 97
98 98 A Popen command list
99 99 """
100 100
101 101 # Build the kernel launch command.
102 102 if executable is None:
103 103 executable = sys.executable
104 104 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
105 105 arguments.extend(extra_arguments)
106 106
107 107 # Spawn a kernel.
108 108 if sys.platform == 'win32':
109 109
110 110 # If the kernel is running on pythonw and stdout/stderr are not been
111 111 # re-directed, it will crash when more than 4KB of data is written to
112 112 # stdout or stderr. This is a bug that has been with Python for a very
113 113 # long time; see http://bugs.python.org/issue706263.
114 114 # A cleaner solution to this problem would be to pass os.devnull to
115 115 # Popen directly. Unfortunately, that does not work.
116 116 if executable.endswith('pythonw.exe'):
117 117 arguments.append('--no-stdout')
118 118 arguments.append('--no-stderr')
119 119
120 120 return arguments
121 121
122 122
123 123 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
124 124 independent=False,
125 125 cwd=None, ipython_kernel=True,
126 126 **kw
127 127 ):
128 128 """ Launches a localhost kernel, binding to the specified ports.
129 129
130 130 Parameters
131 131 ----------
132 132 cmd : Popen list,
133 133 A string of Python code that imports and executes a kernel entry point.
134 134
135 135 stdin, stdout, stderr : optional (default None)
136 136 Standards streams, as defined in subprocess.Popen.
137 137
138 138 independent : bool, optional (default False)
139 139 If set, the kernel process is guaranteed to survive if this process
140 140 dies. If not set, an effort is made to ensure that the kernel is killed
141 141 when this process dies. Note that in this case it is still good practice
142 142 to kill kernels manually before exiting.
143 143
144 144 cwd : path, optional
145 145 The working dir of the kernel process (default: cwd of this process).
146 146
147 147 ipython_kernel : bool, optional
148 148 Whether the kernel is an official IPython one,
149 149 and should get a bit of special treatment.
150 150
151 151 Returns
152 152 -------
153 153
154 154 Popen instance for the kernel subprocess
155 155 """
156 156
157 157 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
158 158 # are invalid. Unfortunately, there is in general no way to detect whether
159 159 # they are valid. The following two blocks redirect them to (temporary)
160 160 # pipes in certain important cases.
161 161
162 162 # If this process has been backgrounded, our stdin is invalid. Since there
163 163 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
164 164 # place this one safe and always redirect.
165 165 redirect_in = True
166 166 _stdin = PIPE if stdin is None else stdin
167 167
168 168 # If this process in running on pythonw, we know that stdin, stdout, and
169 169 # stderr are all invalid.
170 170 redirect_out = sys.executable.endswith('pythonw.exe')
171 171 if redirect_out:
172 172 _stdout = PIPE if stdout is None else stdout
173 173 _stderr = PIPE if stderr is None else stderr
174 174 else:
175 175 _stdout, _stderr = stdout, stderr
176 176
177 env = env if (env is not None) else os.environ.copy()
178
177 179 encoding = getdefaultencoding(prefer_stream=False)
178 180
179 181 # Spawn a kernel.
180 182 if sys.platform == 'win32':
181 183 # Popen on Python 2 on Windows cannot handle unicode args or cwd
182 184 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
183 185 if cwd:
184 186 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
185 187
186 188 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
187 189 # Create a Win32 event for interrupting the kernel.
188 190 interrupt_event = ParentPollerWindows.create_interrupt_event()
191 # Store this in an environment variable for third party kernels, but at
192 # present, our own kernel expects this as a command line argument.
193 env["IPY_INTERRUPT_EVENT"] = str(interrupt_event)
189 194 if ipython_kernel:
190 195 cmd += [ '--interrupt=%i' % interrupt_event ]
191 196
192 197 # If the kernel is running on pythonw and stdout/stderr are not been
193 198 # re-directed, it will crash when more than 4KB of data is written to
194 199 # stdout or stderr. This is a bug that has been with Python for a very
195 200 # long time; see http://bugs.python.org/issue706263.
196 201 # A cleaner solution to this problem would be to pass os.devnull to
197 202 # Popen directly. Unfortunately, that does not work.
198 203 if cmd[0].endswith('pythonw.exe'):
199 204 if stdout is None:
200 205 cmd.append('--no-stdout')
201 206 if stderr is None:
202 207 cmd.append('--no-stderr')
203 208
204 209 # Launch the kernel process.
205 210 if independent:
206 211 proc = Popen(cmd,
207 212 creationflags=512, # CREATE_NEW_PROCESS_GROUP
208 213 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
209 214 else:
210 215 if ipython_kernel:
211 216 try:
212 217 from _winapi import DuplicateHandle, GetCurrentProcess, \
213 218 DUPLICATE_SAME_ACCESS
214 219 except:
215 220 from _subprocess import DuplicateHandle, GetCurrentProcess, \
216 221 DUPLICATE_SAME_ACCESS
217 222 pid = GetCurrentProcess()
218 223 handle = DuplicateHandle(pid, pid, pid, 0,
219 224 True, # Inheritable by new processes.
220 225 DUPLICATE_SAME_ACCESS)
221 226 cmd +=[ '--parent=%i' % handle ]
222 227
223 228
224 229 proc = Popen(cmd,
225 230 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
226 231
227 232 # Attach the interrupt event to the Popen objet so it can be used later.
228 233 proc.win32_interrupt_event = interrupt_event
229 234
230 235 else:
231 236 if independent:
232 237 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
233 238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
234 239 else:
235 240 if ipython_kernel:
236 241 cmd += ['--parent=1']
237 242 proc = Popen(cmd,
238 243 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
239 244
240 245 # Clean up pipes created to work around Popen bug.
241 246 if redirect_in:
242 247 if stdin is None:
243 248 proc.stdin.close()
244 249 if redirect_out:
245 250 if stdout is None:
246 251 proc.stdout.close()
247 252 if stderr is None:
248 253 proc.stderr.close()
249 254
250 255 return proc
251 256
252 257 __all__ = [
253 258 'swallow_argv',
254 259 'make_ipkernel_cmd',
255 260 'launch_kernel',
256 261 ]
General Comments 0
You need to be logged in to leave comments. Login now