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