##// END OF EJS Templates
BUG: Ensure that kernel can be spawned from a backgrounded process.
epatters -
Show More
@@ -1,158 +1,167
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 os
7 import os
8 import socket
8 import socket
9 from subprocess import Popen, PIPE
9 from subprocess import Popen, PIPE
10 import sys
10 import sys
11
11
12 # Local imports.
12 # Local imports.
13 from parentpoller import ParentPollerWindows
13 from parentpoller import ParentPollerWindows
14
14
15
15
16 def base_launch_kernel(code, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
16 def base_launch_kernel(code, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
17 ip=None, stdin=None, stdout=None, stderr=None,
17 ip=None, stdin=None, stdout=None, stderr=None,
18 executable=None, independent=False, extra_arguments=[]):
18 executable=None, independent=False, extra_arguments=[]):
19 """ Launches a localhost kernel, binding to the specified ports.
19 """ Launches a localhost kernel, binding to the specified ports.
20
20
21 Parameters
21 Parameters
22 ----------
22 ----------
23 code : str,
23 code : str,
24 A string of Python code that imports and executes a kernel entry point.
24 A string of Python code that imports and executes a kernel entry point.
25
25
26 shell_port : int, optional
26 shell_port : int, optional
27 The port to use for XREP channel.
27 The port to use for XREP channel.
28
28
29 iopub_port : int, optional
29 iopub_port : int, optional
30 The port to use for the SUB channel.
30 The port to use for the SUB channel.
31
31
32 stdin_port : int, optional
32 stdin_port : int, optional
33 The port to use for the REQ (raw input) channel.
33 The port to use for the REQ (raw input) channel.
34
34
35 hb_port : int, optional
35 hb_port : int, optional
36 The port to use for the hearbeat REP channel.
36 The port to use for the hearbeat REP channel.
37
37
38 ip : str, optional
38 ip : str, optional
39 The ip address the kernel will bind to.
39 The ip address the kernel will bind to.
40
40
41 stdin, stdout, stderr : optional (default None)
41 stdin, stdout, stderr : optional (default None)
42 Standards streams, as defined in subprocess.Popen.
42 Standards streams, as defined in subprocess.Popen.
43
43
44 executable : str, optional (default sys.executable)
44 executable : str, optional (default sys.executable)
45 The Python executable to use for the kernel process.
45 The Python executable to use for the kernel process.
46
46
47 independent : bool, optional (default False)
47 independent : bool, optional (default False)
48 If set, the kernel process is guaranteed to survive if this process
48 If set, the kernel process is guaranteed to survive if this process
49 dies. If not set, an effort is made to ensure that the kernel is killed
49 dies. If not set, an effort is made to ensure that the kernel is killed
50 when this process dies. Note that in this case it is still good practice
50 when this process dies. Note that in this case it is still good practice
51 to kill kernels manually before exiting.
51 to kill kernels manually before exiting.
52
52
53 extra_arguments = list, optional
53 extra_arguments = list, optional
54 A list of extra arguments to pass when executing the launch code.
54 A list of extra arguments to pass when executing the launch code.
55
55
56 Returns
56 Returns
57 -------
57 -------
58 A tuple of form:
58 A tuple of form:
59 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
59 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
60 where kernel_process is a Popen object and the ports are integers.
60 where kernel_process is a Popen object and the ports are integers.
61 """
61 """
62 # Find open ports as necessary.
62 # Find open ports as necessary.
63 ports = []
63 ports = []
64 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
64 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
65 int(stdin_port <= 0) + int(hb_port <= 0)
65 int(stdin_port <= 0) + int(hb_port <= 0)
66 for i in xrange(ports_needed):
66 for i in xrange(ports_needed):
67 sock = socket.socket()
67 sock = socket.socket()
68 sock.bind(('', 0))
68 sock.bind(('', 0))
69 ports.append(sock)
69 ports.append(sock)
70 for i, sock in enumerate(ports):
70 for i, sock in enumerate(ports):
71 port = sock.getsockname()[1]
71 port = sock.getsockname()[1]
72 sock.close()
72 sock.close()
73 ports[i] = port
73 ports[i] = port
74 if shell_port <= 0:
74 if shell_port <= 0:
75 shell_port = ports.pop(0)
75 shell_port = ports.pop(0)
76 if iopub_port <= 0:
76 if iopub_port <= 0:
77 iopub_port = ports.pop(0)
77 iopub_port = ports.pop(0)
78 if stdin_port <= 0:
78 if stdin_port <= 0:
79 stdin_port = ports.pop(0)
79 stdin_port = ports.pop(0)
80 if hb_port <= 0:
80 if hb_port <= 0:
81 hb_port = ports.pop(0)
81 hb_port = ports.pop(0)
82
82
83 # Build the kernel launch command.
83 # Build the kernel launch command.
84 if executable is None:
84 if executable is None:
85 executable = sys.executable
85 executable = sys.executable
86 arguments = [ executable, '-c', code, '--shell=%i'%shell_port,
86 arguments = [ executable, '-c', code, '--shell=%i'%shell_port,
87 '--iopub=%i'%iopub_port, '--stdin=%i'%stdin_port,
87 '--iopub=%i'%iopub_port, '--stdin=%i'%stdin_port,
88 '--hb=%i'%hb_port
88 '--hb=%i'%hb_port ]
89 ]
90 if ip is not None:
89 if ip is not None:
91 arguments.append('--ip=%s'%ip)
90 arguments.append('--ip=%s'%ip)
92 arguments.extend(extra_arguments)
91 arguments.extend(extra_arguments)
93
92
94 # Spawn a kernel.
93 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
95 if sys.platform == 'win32':
94 # are invalid. Unfortunately, there is in general no way to detect whether
96 # Create a Win32 event for interrupting the kernel.
95 # they are valid. The following two blocks redirect them to (temporary)
97 interrupt_event = ParentPollerWindows.create_interrupt_event()
96 # pipes in certain important cases.
98 arguments += [ '--interrupt=%i'%interrupt_event ]
99
97
100 # If this process in running on pythonw, stdin, stdout, and stderr are
98 # If this process has been backgrounded, our stdin is invalid. Since there
101 # invalid. Popen will fail unless they are suitably redirected. We don't
99 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
102 # read from the pipes, but they must exist.
100 # place this one safe and always redirect.
103 if sys.executable.endswith('pythonw.exe'):
101 redirect_in = True
104 redirect = True
105 _stdin = PIPE if stdin is None else stdin
102 _stdin = PIPE if stdin is None else stdin
103
104 # If this process in running on pythonw, we know that stdin, stdout, and
105 # stderr are all invalid.
106 redirect_out = sys.executable.endswith('pythonw.exe')
107 if redirect_out:
106 _stdout = PIPE if stdout is None else stdout
108 _stdout = PIPE if stdout is None else stdout
107 _stderr = PIPE if stderr is None else stderr
109 _stderr = PIPE if stderr is None else stderr
108 else:
110 else:
109 redirect = False
111 _stdout, _stderr = stdout, stderr
110 _stdin, _stdout, _stderr = stdin, stdout, stderr
112
113 # Spawn a kernel.
114 if sys.platform == 'win32':
115 # Create a Win32 event for interrupting the kernel.
116 interrupt_event = ParentPollerWindows.create_interrupt_event()
117 arguments += [ '--interrupt=%i'%interrupt_event ]
111
118
112 # If the kernel is running on pythonw and stdout/stderr are not been
119 # If the kernel is running on pythonw and stdout/stderr are not been
113 # re-directed, it will crash when more than 4KB of data is written to
120 # re-directed, it will crash when more than 4KB of data is written to
114 # stdout or stderr. This is a bug that has been with Python for a very
121 # stdout or stderr. This is a bug that has been with Python for a very
115 # long time; see http://bugs.python.org/issue706263.
122 # long time; see http://bugs.python.org/issue706263.
116 # A cleaner solution to this problem would be to pass os.devnull to
123 # A cleaner solution to this problem would be to pass os.devnull to
117 # Popen directly. Unfortunately, that does not work.
124 # Popen directly. Unfortunately, that does not work.
118 if executable.endswith('pythonw.exe'):
125 if executable.endswith('pythonw.exe'):
119 if stdout is None:
126 if stdout is None:
120 arguments.append('--no-stdout')
127 arguments.append('--no-stdout')
121 if stderr is None:
128 if stderr is None:
122 arguments.append('--no-stderr')
129 arguments.append('--no-stderr')
123
130
124 # Launch the kernel process.
131 # Launch the kernel process.
125 if independent:
132 if independent:
126 proc = Popen(arguments,
133 proc = Popen(arguments,
127 creationflags=512, # CREATE_NEW_PROCESS_GROUP
134 creationflags=512, # CREATE_NEW_PROCESS_GROUP
128 stdin=_stdin, stdout=_stdout, stderr=_stderr)
135 stdin=_stdin, stdout=_stdout, stderr=_stderr)
129 else:
136 else:
130 from _subprocess import DuplicateHandle, GetCurrentProcess, \
137 from _subprocess import DuplicateHandle, GetCurrentProcess, \
131 DUPLICATE_SAME_ACCESS
138 DUPLICATE_SAME_ACCESS
132 pid = GetCurrentProcess()
139 pid = GetCurrentProcess()
133 handle = DuplicateHandle(pid, pid, pid, 0,
140 handle = DuplicateHandle(pid, pid, pid, 0,
134 True, # Inheritable by new processes.
141 True, # Inheritable by new processes.
135 DUPLICATE_SAME_ACCESS)
142 DUPLICATE_SAME_ACCESS)
136 proc = Popen(arguments + ['--parent=%i'%int(handle)],
143 proc = Popen(arguments + ['--parent=%i'%int(handle)],
137 stdin=_stdin, stdout=_stdout, stderr=_stderr)
144 stdin=_stdin, stdout=_stdout, stderr=_stderr)
138
145
139 # Attach the interrupt event to the Popen objet so it can be used later.
146 # Attach the interrupt event to the Popen objet so it can be used later.
140 proc.win32_interrupt_event = interrupt_event
147 proc.win32_interrupt_event = interrupt_event
141
148
149 else:
150 if independent:
151 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
152 stdin=_stdin, stdout=_stdout, stderr=_stderr)
153 else:
154 proc = Popen(arguments + ['--parent=1'],
155 stdin=_stdin, stdout=_stdout, stderr=_stderr)
156
142 # Clean up pipes created to work around Popen bug.
157 # Clean up pipes created to work around Popen bug.
143 if redirect:
158 if redirect_in:
144 if stdin is None:
159 if stdin is None:
145 proc.stdin.close()
160 proc.stdin.close()
161 if redirect_out:
146 if stdout is None:
162 if stdout is None:
147 proc.stdout.close()
163 proc.stdout.close()
148 if stderr is None:
164 if stderr is None:
149 proc.stderr.close()
165 proc.stderr.close()
150
166
151 else:
152 if independent:
153 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
154 stdin=stdin, stdout=stdout, stderr=stderr)
155 else:
156 proc = Popen(arguments + ['--parent=1'],
157 stdin=stdin, stdout=stdout, stderr=stderr)
158 return proc, shell_port, iopub_port, stdin_port, hb_port
167 return proc, shell_port, iopub_port, stdin_port, hb_port
General Comments 0
You need to be logged in to leave comments. Login now