Show More
@@ -0,0 +1,253 | |||
|
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 |
@@ -24,7 +24,7 import nose.tools as nt | |||
|
24 | 24 | |
|
25 | 25 | # Our own imports |
|
26 | 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 | 30 | # Classes and functions |
@@ -51,7 +51,7 def test_swallow_argv(): | |||
|
51 | 51 | (['bar'], ['--foo=5', 'bar'], ['foo'], None), |
|
52 | 52 | ] |
|
53 | 53 | for expected, argv, aliases, flags in tests: |
|
54 |
stripped = |
|
|
54 | stripped = swallow_argv(argv, aliases=aliases, flags=flags) | |
|
55 | 55 | message = '\n'.join(['', |
|
56 | 56 | "argv: %r" % argv, |
|
57 | 57 | "aliases: %r" % aliases, |
@@ -430,7 +430,7 def make_runners(inc_slow=False): | |||
|
430 | 430 | """ |
|
431 | 431 | |
|
432 | 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 | 434 | 'testing', 'utils', 'nbformat', 'inprocess' ] |
|
435 | 435 | |
|
436 | 436 | if have['zmq']: |
General Comments 0
You need to be logged in to leave comments.
Login now