##// END OF EJS Templates
use new _winapi instead of removed _subprocess...
MinRK -
Show More
@@ -1,222 +1,226
1 """ Defines helper functions for creating kernel entry points and process
1 """ Defines helper functions for creating kernel entry points and process
2 launchers.
2 launchers.
3 """
3 """
4
4
5 # Standard library imports.
5 # Standard library imports.
6 import atexit
6 import atexit
7 import json
7 import json
8 import os
8 import os
9 import socket
9 import socket
10 from subprocess import Popen, PIPE
10 from subprocess import Popen, PIPE
11 import sys
11 import sys
12 import tempfile
12 import tempfile
13
13
14 # System library imports
14 # System library imports
15
15
16 # IPython imports
16 # IPython imports
17 from IPython.utils.localinterfaces import LOCALHOST
17 from IPython.utils.localinterfaces import LOCALHOST
18 from IPython.utils.py3compat import bytes_to_str
18 from IPython.utils.py3compat import bytes_to_str
19
19
20 # Local imports.
20 # Local imports.
21 from parentpoller import ParentPollerWindows
21 from parentpoller import ParentPollerWindows
22
22
23 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
23 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
24 ip=LOCALHOST, key=b'', transport='tcp'):
24 ip=LOCALHOST, key=b'', transport='tcp'):
25 """Generates a JSON config file, including the selection of random ports.
25 """Generates a JSON config file, including the selection of random ports.
26
26
27 Parameters
27 Parameters
28 ----------
28 ----------
29
29
30 fname : unicode
30 fname : unicode
31 The path to the file to write
31 The path to the file to write
32
32
33 shell_port : int, optional
33 shell_port : int, optional
34 The port to use for ROUTER channel.
34 The port to use for ROUTER channel.
35
35
36 iopub_port : int, optional
36 iopub_port : int, optional
37 The port to use for the SUB channel.
37 The port to use for the SUB channel.
38
38
39 stdin_port : int, optional
39 stdin_port : int, optional
40 The port to use for the REQ (raw input) channel.
40 The port to use for the REQ (raw input) channel.
41
41
42 hb_port : int, optional
42 hb_port : int, optional
43 The port to use for the hearbeat REP channel.
43 The port to use for the hearbeat REP channel.
44
44
45 ip : str, optional
45 ip : str, optional
46 The ip address the kernel will bind to.
46 The ip address the kernel will bind to.
47
47
48 key : str, optional
48 key : str, optional
49 The Session key used for HMAC authentication.
49 The Session key used for HMAC authentication.
50
50
51 """
51 """
52 # default to temporary connector file
52 # default to temporary connector file
53 if not fname:
53 if not fname:
54 fname = tempfile.mktemp('.json')
54 fname = tempfile.mktemp('.json')
55
55
56 # Find open ports as necessary.
56 # Find open ports as necessary.
57
57
58 ports = []
58 ports = []
59 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
59 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
60 int(stdin_port <= 0) + int(hb_port <= 0)
60 int(stdin_port <= 0) + int(hb_port <= 0)
61 if transport == 'tcp':
61 if transport == 'tcp':
62 for i in range(ports_needed):
62 for i in range(ports_needed):
63 sock = socket.socket()
63 sock = socket.socket()
64 sock.bind(('', 0))
64 sock.bind(('', 0))
65 ports.append(sock)
65 ports.append(sock)
66 for i, sock in enumerate(ports):
66 for i, sock in enumerate(ports):
67 port = sock.getsockname()[1]
67 port = sock.getsockname()[1]
68 sock.close()
68 sock.close()
69 ports[i] = port
69 ports[i] = port
70 else:
70 else:
71 N = 1
71 N = 1
72 for i in range(ports_needed):
72 for i in range(ports_needed):
73 while os.path.exists("%s-%s" % (ip, str(N))):
73 while os.path.exists("%s-%s" % (ip, str(N))):
74 N += 1
74 N += 1
75 ports.append(N)
75 ports.append(N)
76 N += 1
76 N += 1
77 if shell_port <= 0:
77 if shell_port <= 0:
78 shell_port = ports.pop(0)
78 shell_port = ports.pop(0)
79 if iopub_port <= 0:
79 if iopub_port <= 0:
80 iopub_port = ports.pop(0)
80 iopub_port = ports.pop(0)
81 if stdin_port <= 0:
81 if stdin_port <= 0:
82 stdin_port = ports.pop(0)
82 stdin_port = ports.pop(0)
83 if hb_port <= 0:
83 if hb_port <= 0:
84 hb_port = ports.pop(0)
84 hb_port = ports.pop(0)
85
85
86 cfg = dict( shell_port=shell_port,
86 cfg = dict( shell_port=shell_port,
87 iopub_port=iopub_port,
87 iopub_port=iopub_port,
88 stdin_port=stdin_port,
88 stdin_port=stdin_port,
89 hb_port=hb_port,
89 hb_port=hb_port,
90 )
90 )
91 cfg['ip'] = ip
91 cfg['ip'] = ip
92 cfg['key'] = bytes_to_str(key)
92 cfg['key'] = bytes_to_str(key)
93 cfg['transport'] = transport
93 cfg['transport'] = transport
94
94
95 with open(fname, 'w') as f:
95 with open(fname, 'w') as f:
96 f.write(json.dumps(cfg, indent=2))
96 f.write(json.dumps(cfg, indent=2))
97
97
98 return fname, cfg
98 return fname, cfg
99
99
100
100
101 def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None,
101 def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None,
102 executable=None, independent=False, extra_arguments=[],
102 executable=None, independent=False, extra_arguments=[],
103 cwd=None):
103 cwd=None):
104 """ Launches a localhost kernel, binding to the specified ports.
104 """ Launches a localhost kernel, binding to the specified ports.
105
105
106 Parameters
106 Parameters
107 ----------
107 ----------
108 code : str,
108 code : str,
109 A string of Python code that imports and executes a kernel entry point.
109 A string of Python code that imports and executes a kernel entry point.
110
110
111 stdin, stdout, stderr : optional (default None)
111 stdin, stdout, stderr : optional (default None)
112 Standards streams, as defined in subprocess.Popen.
112 Standards streams, as defined in subprocess.Popen.
113
113
114 fname : unicode, optional
114 fname : unicode, optional
115 The JSON connector file, containing ip/port/hmac key information.
115 The JSON connector file, containing ip/port/hmac key information.
116
116
117 key : str, optional
117 key : str, optional
118 The Session key used for HMAC authentication.
118 The Session key used for HMAC authentication.
119
119
120 executable : str, optional (default sys.executable)
120 executable : str, optional (default sys.executable)
121 The Python executable to use for the kernel process.
121 The Python executable to use for the kernel process.
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 extra_arguments : list, optional
129 extra_arguments : list, optional
130 A list of extra arguments to pass when executing the launch code.
130 A list of extra arguments to pass when executing the launch code.
131
131
132 cwd : path, optional
132 cwd : path, optional
133 The working dir of the kernel process (default: cwd of this process).
133 The working dir of the kernel process (default: cwd of this process).
134
134
135 Returns
135 Returns
136 -------
136 -------
137 A tuple of form:
137 A tuple of form:
138 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
138 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
139 where kernel_process is a Popen object and the ports are integers.
139 where kernel_process is a Popen object and the ports are integers.
140 """
140 """
141
141
142 # Build the kernel launch command.
142 # Build the kernel launch command.
143 if executable is None:
143 if executable is None:
144 executable = sys.executable
144 executable = sys.executable
145 arguments = [ executable, '-c', code, '-f', fname ]
145 arguments = [ executable, '-c', code, '-f', fname ]
146 arguments.extend(extra_arguments)
146 arguments.extend(extra_arguments)
147
147
148 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
148 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
149 # are invalid. Unfortunately, there is in general no way to detect whether
149 # are invalid. Unfortunately, there is in general no way to detect whether
150 # they are valid. The following two blocks redirect them to (temporary)
150 # they are valid. The following two blocks redirect them to (temporary)
151 # pipes in certain important cases.
151 # pipes in certain important cases.
152
152
153 # If this process has been backgrounded, our stdin is invalid. Since there
153 # If this process has been backgrounded, our stdin is invalid. Since there
154 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
154 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
155 # place this one safe and always redirect.
155 # place this one safe and always redirect.
156 redirect_in = True
156 redirect_in = True
157 _stdin = PIPE if stdin is None else stdin
157 _stdin = PIPE if stdin is None else stdin
158
158
159 # If this process in running on pythonw, we know that stdin, stdout, and
159 # If this process in running on pythonw, we know that stdin, stdout, and
160 # stderr are all invalid.
160 # stderr are all invalid.
161 redirect_out = sys.executable.endswith('pythonw.exe')
161 redirect_out = sys.executable.endswith('pythonw.exe')
162 if redirect_out:
162 if redirect_out:
163 _stdout = PIPE if stdout is None else stdout
163 _stdout = PIPE if stdout is None else stdout
164 _stderr = PIPE if stderr is None else stderr
164 _stderr = PIPE if stderr is None else stderr
165 else:
165 else:
166 _stdout, _stderr = stdout, stderr
166 _stdout, _stderr = stdout, stderr
167
167
168 # Spawn a kernel.
168 # Spawn a kernel.
169 if sys.platform == 'win32':
169 if sys.platform == 'win32':
170 # Create a Win32 event for interrupting the kernel.
170 # Create a Win32 event for interrupting the kernel.
171 interrupt_event = ParentPollerWindows.create_interrupt_event()
171 interrupt_event = ParentPollerWindows.create_interrupt_event()
172 arguments += [ '--interrupt=%i'%interrupt_event ]
172 arguments += [ '--interrupt=%i'%interrupt_event ]
173
173
174 # If the kernel is running on pythonw and stdout/stderr are not been
174 # If the kernel is running on pythonw and stdout/stderr are not been
175 # re-directed, it will crash when more than 4KB of data is written to
175 # re-directed, it will crash when more than 4KB of data is written to
176 # stdout or stderr. This is a bug that has been with Python for a very
176 # stdout or stderr. This is a bug that has been with Python for a very
177 # long time; see http://bugs.python.org/issue706263.
177 # long time; see http://bugs.python.org/issue706263.
178 # A cleaner solution to this problem would be to pass os.devnull to
178 # A cleaner solution to this problem would be to pass os.devnull to
179 # Popen directly. Unfortunately, that does not work.
179 # Popen directly. Unfortunately, that does not work.
180 if executable.endswith('pythonw.exe'):
180 if executable.endswith('pythonw.exe'):
181 if stdout is None:
181 if stdout is None:
182 arguments.append('--no-stdout')
182 arguments.append('--no-stdout')
183 if stderr is None:
183 if stderr is None:
184 arguments.append('--no-stderr')
184 arguments.append('--no-stderr')
185
185
186 # Launch the kernel process.
186 # Launch the kernel process.
187 if independent:
187 if independent:
188 proc = Popen(arguments,
188 proc = Popen(arguments,
189 creationflags=512, # CREATE_NEW_PROCESS_GROUP
189 creationflags=512, # CREATE_NEW_PROCESS_GROUP
190 stdin=_stdin, stdout=_stdout, stderr=_stderr)
190 stdin=_stdin, stdout=_stdout, stderr=_stderr)
191 else:
191 else:
192 from _subprocess import DuplicateHandle, GetCurrentProcess, \
192 try:
193 DUPLICATE_SAME_ACCESS
193 from _winapi import DuplicateHandle, GetCurrentProcess, \
194 DUPLICATE_SAME_ACCESS
195 except:
196 from _subprocess import DuplicateHandle, GetCurrentProcess, \
197 DUPLICATE_SAME_ACCESS
194 pid = GetCurrentProcess()
198 pid = GetCurrentProcess()
195 handle = DuplicateHandle(pid, pid, pid, 0,
199 handle = DuplicateHandle(pid, pid, pid, 0,
196 True, # Inheritable by new processes.
200 True, # Inheritable by new processes.
197 DUPLICATE_SAME_ACCESS)
201 DUPLICATE_SAME_ACCESS)
198 proc = Popen(arguments + ['--parent=%i'%int(handle)],
202 proc = Popen(arguments + ['--parent=%i'%int(handle)],
199 stdin=_stdin, stdout=_stdout, stderr=_stderr)
203 stdin=_stdin, stdout=_stdout, stderr=_stderr)
200
204
201 # Attach the interrupt event to the Popen objet so it can be used later.
205 # Attach the interrupt event to the Popen objet so it can be used later.
202 proc.win32_interrupt_event = interrupt_event
206 proc.win32_interrupt_event = interrupt_event
203
207
204 else:
208 else:
205 if independent:
209 if independent:
206 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
210 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
207 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
211 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
208 else:
212 else:
209 proc = Popen(arguments + ['--parent=1'],
213 proc = Popen(arguments + ['--parent=1'],
210 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
214 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
211
215
212 # Clean up pipes created to work around Popen bug.
216 # Clean up pipes created to work around Popen bug.
213 if redirect_in:
217 if redirect_in:
214 if stdin is None:
218 if stdin is None:
215 proc.stdin.close()
219 proc.stdin.close()
216 if redirect_out:
220 if redirect_out:
217 if stdout is None:
221 if stdout is None:
218 proc.stdout.close()
222 proc.stdout.close()
219 if stderr is None:
223 if stderr is None:
220 proc.stderr.close()
224 proc.stderr.close()
221
225
222 return proc
226 return proc
@@ -1,136 +1,139
1 # Standard library imports.
1 # Standard library imports.
2 try:
2 try:
3 import ctypes
3 import ctypes
4 except:
4 except:
5 ctypes = None
5 ctypes = None
6 import os
6 import os
7 import platform
7 import platform
8 import time
8 import time
9 from thread import interrupt_main
9 from thread import interrupt_main
10 from threading import Thread
10 from threading import Thread
11
11
12 from IPython.utils.warn import warn
12 from IPython.utils.warn import warn
13
13
14
14
15 class ParentPollerUnix(Thread):
15 class ParentPollerUnix(Thread):
16 """ A Unix-specific daemon thread that terminates the program immediately
16 """ A Unix-specific daemon thread that terminates the program immediately
17 when the parent process no longer exists.
17 when the parent process no longer exists.
18 """
18 """
19
19
20 def __init__(self):
20 def __init__(self):
21 super(ParentPollerUnix, self).__init__()
21 super(ParentPollerUnix, self).__init__()
22 self.daemon = True
22 self.daemon = True
23
23
24 def run(self):
24 def run(self):
25 # We cannot use os.waitpid because it works only for child processes.
25 # We cannot use os.waitpid because it works only for child processes.
26 from errno import EINTR
26 from errno import EINTR
27 while True:
27 while True:
28 try:
28 try:
29 if os.getppid() == 1:
29 if os.getppid() == 1:
30 os._exit(1)
30 os._exit(1)
31 time.sleep(1.0)
31 time.sleep(1.0)
32 except OSError as e:
32 except OSError as e:
33 if e.errno == EINTR:
33 if e.errno == EINTR:
34 continue
34 continue
35 raise
35 raise
36
36
37
37
38 class ParentPollerWindows(Thread):
38 class ParentPollerWindows(Thread):
39 """ A Windows-specific daemon thread that listens for a special event that
39 """ A Windows-specific daemon thread that listens for a special event that
40 signals an interrupt and, optionally, terminates the program immediately
40 signals an interrupt and, optionally, terminates the program immediately
41 when the parent process no longer exists.
41 when the parent process no longer exists.
42 """
42 """
43
43
44 def __init__(self, interrupt_handle=None, parent_handle=None):
44 def __init__(self, interrupt_handle=None, parent_handle=None):
45 """ Create the poller. At least one of the optional parameters must be
45 """ Create the poller. At least one of the optional parameters must be
46 provided.
46 provided.
47
47
48 Parameters:
48 Parameters:
49 -----------
49 -----------
50 interrupt_handle : HANDLE (int), optional
50 interrupt_handle : HANDLE (int), optional
51 If provided, the program will generate a Ctrl+C event when this
51 If provided, the program will generate a Ctrl+C event when this
52 handle is signaled.
52 handle is signaled.
53
53
54 parent_handle : HANDLE (int), optional
54 parent_handle : HANDLE (int), optional
55 If provided, the program will terminate immediately when this
55 If provided, the program will terminate immediately when this
56 handle is signaled.
56 handle is signaled.
57 """
57 """
58 assert(interrupt_handle or parent_handle)
58 assert(interrupt_handle or parent_handle)
59 super(ParentPollerWindows, self).__init__()
59 super(ParentPollerWindows, self).__init__()
60 if ctypes is None:
60 if ctypes is None:
61 raise ImportError("ParentPollerWindows requires ctypes")
61 raise ImportError("ParentPollerWindows requires ctypes")
62 self.daemon = True
62 self.daemon = True
63 self.interrupt_handle = interrupt_handle
63 self.interrupt_handle = interrupt_handle
64 self.parent_handle = parent_handle
64 self.parent_handle = parent_handle
65
65
66 @staticmethod
66 @staticmethod
67 def create_interrupt_event():
67 def create_interrupt_event():
68 """ Create an interrupt event handle.
68 """ Create an interrupt event handle.
69
69
70 The parent process should use this static method for creating the
70 The parent process should use this static method for creating the
71 interrupt event that is passed to the child process. It should store
71 interrupt event that is passed to the child process. It should store
72 this handle and use it with ``send_interrupt`` to interrupt the child
72 this handle and use it with ``send_interrupt`` to interrupt the child
73 process.
73 process.
74 """
74 """
75 # Create a security attributes struct that permits inheritance of the
75 # Create a security attributes struct that permits inheritance of the
76 # handle by new processes.
76 # handle by new processes.
77 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
77 # FIXME: We can clean up this mess by requiring pywin32 for IPython.
78 class SECURITY_ATTRIBUTES(ctypes.Structure):
78 class SECURITY_ATTRIBUTES(ctypes.Structure):
79 _fields_ = [ ("nLength", ctypes.c_int),
79 _fields_ = [ ("nLength", ctypes.c_int),
80 ("lpSecurityDescriptor", ctypes.c_void_p),
80 ("lpSecurityDescriptor", ctypes.c_void_p),
81 ("bInheritHandle", ctypes.c_int) ]
81 ("bInheritHandle", ctypes.c_int) ]
82 sa = SECURITY_ATTRIBUTES()
82 sa = SECURITY_ATTRIBUTES()
83 sa_p = ctypes.pointer(sa)
83 sa_p = ctypes.pointer(sa)
84 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
84 sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
85 sa.lpSecurityDescriptor = 0
85 sa.lpSecurityDescriptor = 0
86 sa.bInheritHandle = 1
86 sa.bInheritHandle = 1
87
87
88 return ctypes.windll.kernel32.CreateEventA(
88 return ctypes.windll.kernel32.CreateEventA(
89 sa_p, # lpEventAttributes
89 sa_p, # lpEventAttributes
90 False, # bManualReset
90 False, # bManualReset
91 False, # bInitialState
91 False, # bInitialState
92 '') # lpName
92 '') # lpName
93
93
94 @staticmethod
94 @staticmethod
95 def send_interrupt(interrupt_handle):
95 def send_interrupt(interrupt_handle):
96 """ Sends an interrupt event using the specified handle.
96 """ Sends an interrupt event using the specified handle.
97 """
97 """
98 ctypes.windll.kernel32.SetEvent(interrupt_handle)
98 ctypes.windll.kernel32.SetEvent(interrupt_handle)
99
99
100 def run(self):
100 def run(self):
101 """ Run the poll loop. This method never returns.
101 """ Run the poll loop. This method never returns.
102 """
102 """
103 from _subprocess import WAIT_OBJECT_0, INFINITE
103 try:
104 from _winapi import WAIT_OBJECT_0, INFINITE
105 except ImportError:
106 from _subprocess import WAIT_OBJECT_0, INFINITE
104
107
105 # Build the list of handle to listen on.
108 # Build the list of handle to listen on.
106 handles = []
109 handles = []
107 if self.interrupt_handle:
110 if self.interrupt_handle:
108 handles.append(self.interrupt_handle)
111 handles.append(self.interrupt_handle)
109 if self.parent_handle:
112 if self.parent_handle:
110 handles.append(self.parent_handle)
113 handles.append(self.parent_handle)
111 arch = platform.architecture()[0]
114 arch = platform.architecture()[0]
112 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
115 c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int
113
116
114 # Listen forever.
117 # Listen forever.
115 while True:
118 while True:
116 result = ctypes.windll.kernel32.WaitForMultipleObjects(
119 result = ctypes.windll.kernel32.WaitForMultipleObjects(
117 len(handles), # nCount
120 len(handles), # nCount
118 (c_int * len(handles))(*handles), # lpHandles
121 (c_int * len(handles))(*handles), # lpHandles
119 False, # bWaitAll
122 False, # bWaitAll
120 INFINITE) # dwMilliseconds
123 INFINITE) # dwMilliseconds
121
124
122 if WAIT_OBJECT_0 <= result < len(handles):
125 if WAIT_OBJECT_0 <= result < len(handles):
123 handle = handles[result - WAIT_OBJECT_0]
126 handle = handles[result - WAIT_OBJECT_0]
124
127
125 if handle == self.interrupt_handle:
128 if handle == self.interrupt_handle:
126 interrupt_main()
129 interrupt_main()
127
130
128 elif handle == self.parent_handle:
131 elif handle == self.parent_handle:
129 os._exit(1)
132 os._exit(1)
130 elif result < 0:
133 elif result < 0:
131 # wait failed, just give up and stop polling.
134 # wait failed, just give up and stop polling.
132 warn("""Parent poll failed. If the frontend dies,
135 warn("""Parent poll failed. If the frontend dies,
133 the kernel may be left running. Please let us know
136 the kernel may be left running. Please let us know
134 about your system (bitness, Python, etc.) at
137 about your system (bitness, Python, etc.) at
135 ipython-dev@scipy.org""")
138 ipython-dev@scipy.org""")
136 return
139 return
General Comments 0
You need to be logged in to leave comments. Login now