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