##// END OF EJS Templates
Pass Windows interrupt event to kernels as an environment variable...
Thomas Kluyver -
Show More
@@ -1,256 +1,261 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(code, executable=None, extra_arguments=[], **kw):
81 def make_ipkernel_cmd(code, 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 code : str,
86 code : str,
87 A string of Python code that imports and executes a kernel entry point.
87 A string of Python code that imports and executes a kernel entry point.
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
100
101 # Build the kernel launch command.
101 # Build the kernel launch command.
102 if executable is None:
102 if executable is None:
103 executable = sys.executable
103 executable = sys.executable
104 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
104 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
105 arguments.extend(extra_arguments)
105 arguments.extend(extra_arguments)
106
106
107 # Spawn a kernel.
107 # Spawn a kernel.
108 if sys.platform == 'win32':
108 if sys.platform == 'win32':
109
109
110 # If the kernel is running on pythonw and stdout/stderr are not been
110 # If the kernel is running on pythonw and stdout/stderr are not been
111 # re-directed, it will crash when more than 4KB of data is written to
111 # re-directed, it will crash when more than 4KB of data is written to
112 # stdout or stderr. This is a bug that has been with Python for a very
112 # stdout or stderr. This is a bug that has been with Python for a very
113 # long time; see http://bugs.python.org/issue706263.
113 # long time; see http://bugs.python.org/issue706263.
114 # A cleaner solution to this problem would be to pass os.devnull to
114 # A cleaner solution to this problem would be to pass os.devnull to
115 # Popen directly. Unfortunately, that does not work.
115 # Popen directly. Unfortunately, that does not work.
116 if executable.endswith('pythonw.exe'):
116 if executable.endswith('pythonw.exe'):
117 arguments.append('--no-stdout')
117 arguments.append('--no-stdout')
118 arguments.append('--no-stderr')
118 arguments.append('--no-stderr')
119
119
120 return arguments
120 return arguments
121
121
122
122
123 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
123 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
124 independent=False,
124 independent=False,
125 cwd=None, ipython_kernel=True,
125 cwd=None, ipython_kernel=True,
126 **kw
126 **kw
127 ):
127 ):
128 """ Launches a localhost kernel, binding to the specified ports.
128 """ Launches a localhost kernel, binding to the specified ports.
129
129
130 Parameters
130 Parameters
131 ----------
131 ----------
132 cmd : Popen list,
132 cmd : Popen list,
133 A string of Python code that imports and executes a kernel entry point.
133 A string of Python code that imports and executes a kernel entry point.
134
134
135 stdin, stdout, stderr : optional (default None)
135 stdin, stdout, stderr : optional (default None)
136 Standards streams, as defined in subprocess.Popen.
136 Standards streams, as defined in subprocess.Popen.
137
137
138 independent : bool, optional (default False)
138 independent : bool, optional (default False)
139 If set, the kernel process is guaranteed to survive if this process
139 If set, the kernel process is guaranteed to survive if this process
140 dies. If not set, an effort is made to ensure that the kernel is killed
140 dies. If not set, an effort is made to ensure that the kernel is killed
141 when this process dies. Note that in this case it is still good practice
141 when this process dies. Note that in this case it is still good practice
142 to kill kernels manually before exiting.
142 to kill kernels manually before exiting.
143
143
144 cwd : path, optional
144 cwd : path, optional
145 The working dir of the kernel process (default: cwd of this process).
145 The working dir of the kernel process (default: cwd of this process).
146
146
147 ipython_kernel : bool, optional
147 ipython_kernel : bool, optional
148 Whether the kernel is an official IPython one,
148 Whether the kernel is an official IPython one,
149 and should get a bit of special treatment.
149 and should get a bit of special treatment.
150
150
151 Returns
151 Returns
152 -------
152 -------
153
153
154 Popen instance for the kernel subprocess
154 Popen instance for the kernel subprocess
155 """
155 """
156
156
157 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
157 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
158 # are invalid. Unfortunately, there is in general no way to detect whether
158 # are invalid. Unfortunately, there is in general no way to detect whether
159 # they are valid. The following two blocks redirect them to (temporary)
159 # they are valid. The following two blocks redirect them to (temporary)
160 # pipes in certain important cases.
160 # pipes in certain important cases.
161
161
162 # If this process has been backgrounded, our stdin is invalid. Since there
162 # If this process has been backgrounded, our stdin is invalid. Since there
163 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
163 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
164 # place this one safe and always redirect.
164 # place this one safe and always redirect.
165 redirect_in = True
165 redirect_in = True
166 _stdin = PIPE if stdin is None else stdin
166 _stdin = PIPE if stdin is None else stdin
167
167
168 # If this process in running on pythonw, we know that stdin, stdout, and
168 # If this process in running on pythonw, we know that stdin, stdout, and
169 # stderr are all invalid.
169 # stderr are all invalid.
170 redirect_out = sys.executable.endswith('pythonw.exe')
170 redirect_out = sys.executable.endswith('pythonw.exe')
171 if redirect_out:
171 if redirect_out:
172 _stdout = PIPE if stdout is None else stdout
172 _stdout = PIPE if stdout is None else stdout
173 _stderr = PIPE if stderr is None else stderr
173 _stderr = PIPE if stderr is None else stderr
174 else:
174 else:
175 _stdout, _stderr = stdout, stderr
175 _stdout, _stderr = stdout, stderr
176
176
177 env = env if (env is not None) else os.environ.copy()
178
177 encoding = getdefaultencoding(prefer_stream=False)
179 encoding = getdefaultencoding(prefer_stream=False)
178
180
179 # Spawn a kernel.
181 # Spawn a kernel.
180 if sys.platform == 'win32':
182 if sys.platform == 'win32':
181 # Popen on Python 2 on Windows cannot handle unicode args or cwd
183 # Popen on Python 2 on Windows cannot handle unicode args or cwd
182 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
184 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
183 if cwd:
185 if cwd:
184 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
186 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
185
187
186 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
188 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
187 # Create a Win32 event for interrupting the kernel.
189 # Create a Win32 event for interrupting the kernel.
188 interrupt_event = ParentPollerWindows.create_interrupt_event()
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 if ipython_kernel:
194 if ipython_kernel:
190 cmd += [ '--interrupt=%i' % interrupt_event ]
195 cmd += [ '--interrupt=%i' % interrupt_event ]
191
196
192 # If the kernel is running on pythonw and stdout/stderr are not been
197 # If the kernel is running on pythonw and stdout/stderr are not been
193 # re-directed, it will crash when more than 4KB of data is written to
198 # re-directed, it will crash when more than 4KB of data is written to
194 # stdout or stderr. This is a bug that has been with Python for a very
199 # stdout or stderr. This is a bug that has been with Python for a very
195 # long time; see http://bugs.python.org/issue706263.
200 # long time; see http://bugs.python.org/issue706263.
196 # A cleaner solution to this problem would be to pass os.devnull to
201 # A cleaner solution to this problem would be to pass os.devnull to
197 # Popen directly. Unfortunately, that does not work.
202 # Popen directly. Unfortunately, that does not work.
198 if cmd[0].endswith('pythonw.exe'):
203 if cmd[0].endswith('pythonw.exe'):
199 if stdout is None:
204 if stdout is None:
200 cmd.append('--no-stdout')
205 cmd.append('--no-stdout')
201 if stderr is None:
206 if stderr is None:
202 cmd.append('--no-stderr')
207 cmd.append('--no-stderr')
203
208
204 # Launch the kernel process.
209 # Launch the kernel process.
205 if independent:
210 if independent:
206 proc = Popen(cmd,
211 proc = Popen(cmd,
207 creationflags=512, # CREATE_NEW_PROCESS_GROUP
212 creationflags=512, # CREATE_NEW_PROCESS_GROUP
208 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
213 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
209 else:
214 else:
210 if ipython_kernel:
215 if ipython_kernel:
211 try:
216 try:
212 from _winapi import DuplicateHandle, GetCurrentProcess, \
217 from _winapi import DuplicateHandle, GetCurrentProcess, \
213 DUPLICATE_SAME_ACCESS
218 DUPLICATE_SAME_ACCESS
214 except:
219 except:
215 from _subprocess import DuplicateHandle, GetCurrentProcess, \
220 from _subprocess import DuplicateHandle, GetCurrentProcess, \
216 DUPLICATE_SAME_ACCESS
221 DUPLICATE_SAME_ACCESS
217 pid = GetCurrentProcess()
222 pid = GetCurrentProcess()
218 handle = DuplicateHandle(pid, pid, pid, 0,
223 handle = DuplicateHandle(pid, pid, pid, 0,
219 True, # Inheritable by new processes.
224 True, # Inheritable by new processes.
220 DUPLICATE_SAME_ACCESS)
225 DUPLICATE_SAME_ACCESS)
221 cmd +=[ '--parent=%i' % handle ]
226 cmd +=[ '--parent=%i' % handle ]
222
227
223
228
224 proc = Popen(cmd,
229 proc = Popen(cmd,
225 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
230 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
226
231
227 # Attach the interrupt event to the Popen objet so it can be used later.
232 # Attach the interrupt event to the Popen objet so it can be used later.
228 proc.win32_interrupt_event = interrupt_event
233 proc.win32_interrupt_event = interrupt_event
229
234
230 else:
235 else:
231 if independent:
236 if independent:
232 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
237 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
233 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
234 else:
239 else:
235 if ipython_kernel:
240 if ipython_kernel:
236 cmd += ['--parent=1']
241 cmd += ['--parent=1']
237 proc = Popen(cmd,
242 proc = Popen(cmd,
238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
243 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
239
244
240 # Clean up pipes created to work around Popen bug.
245 # Clean up pipes created to work around Popen bug.
241 if redirect_in:
246 if redirect_in:
242 if stdin is None:
247 if stdin is None:
243 proc.stdin.close()
248 proc.stdin.close()
244 if redirect_out:
249 if redirect_out:
245 if stdout is None:
250 if stdout is None:
246 proc.stdout.close()
251 proc.stdout.close()
247 if stderr is None:
252 if stderr is None:
248 proc.stderr.close()
253 proc.stderr.close()
249
254
250 return proc
255 return proc
251
256
252 __all__ = [
257 __all__ = [
253 'swallow_argv',
258 'swallow_argv',
254 'make_ipkernel_cmd',
259 'make_ipkernel_cmd',
255 'launch_kernel',
260 'launch_kernel',
256 ]
261 ]
General Comments 0
You need to be logged in to leave comments. Login now