##// END OF EJS Templates
Merge pull request #7121 from takluyver/windows-int-parent-handle...
Thomas Kluyver -
r19295:abab15a2 merge
parent child Browse files
Show More
@@ -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 IPython.kernel.zmq.parentpoller import ParentPollerWindows
178 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
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 = ParentPollerWindows.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(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 ]
General Comments 0
You need to be logged in to leave comments. Login now