diff --git a/IPython/zmq/entry_point.py b/IPython/zmq/entry_point.py index 031036f..ab66e0e 100644 --- a/IPython/zmq/entry_point.py +++ b/IPython/zmq/entry_point.py @@ -2,8 +2,7 @@ launchers. """ -# Standard library imports. -import atexit +# Standard library imports import json import os import socket @@ -17,7 +16,7 @@ import tempfile from IPython.utils.localinterfaces import LOCALHOST from IPython.utils.py3compat import bytes_to_str -# Local imports. +# Local imports from parentpoller import ParentPollerWindows def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0, @@ -98,9 +97,7 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, return fname, cfg -def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None, - executable=None, independent=False, extra_arguments=[], - cwd=None): +def make_kernel_cmd(code, executable=None, extra_arguments=[], **kw): """ Launches a localhost kernel, binding to the specified ports. Parameters @@ -108,17 +105,54 @@ def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None, code : str, A string of Python code that imports and executes a kernel entry point. - stdin, stdout, stderr : optional (default None) - Standards streams, as defined in subprocess.Popen. + executable : str, optional (default sys.executable) + The Python executable to use for the kernel process. + + extra_arguments : list, optional + A list of extra arguments to pass when executing the launch code. + + Returns + ------- + + A Popen command list + """ + + # Build the kernel launch command. + if executable is None: + executable = sys.executable + arguments = [ executable, '-c', code, '-f', '{connection_file}' ] + arguments.extend(extra_arguments) - fname : unicode, optional - The JSON connector file, containing ip/port/hmac key information. + # Spawn a kernel. + if sys.platform == 'win32': - key : str, optional - The Session key used for HMAC authentication. + # If the kernel is running on pythonw and stdout/stderr are not been + # re-directed, it will crash when more than 4KB of data is written to + # stdout or stderr. This is a bug that has been with Python for a very + # long time; see http://bugs.python.org/issue706263. + # A cleaner solution to this problem would be to pass os.devnull to + # Popen directly. Unfortunately, that does not work. + if executable.endswith('pythonw.exe'): + arguments.append('--no-stdout') + arguments.append('--no-stderr') - executable : str, optional (default sys.executable) - The Python executable to use for the kernel process. + return arguments + + +def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, + independent=False, + cwd=None, ipython_kernel=True, + **kw + ): + """ Launches a localhost kernel, binding to the specified ports. + + Parameters + ---------- + cmd : Popen list, + A string of Python code that imports and executes a kernel entry point. + + stdin, stdout, stderr : optional (default None) + Standards streams, as defined in subprocess.Popen. independent : bool, optional (default False) If set, the kernel process is guaranteed to survive if this process @@ -126,24 +160,18 @@ def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None, when this process dies. Note that in this case it is still good practice to kill kernels manually before exiting. - extra_arguments : list, optional - A list of extra arguments to pass when executing the launch code. - cwd : path, optional The working dir of the kernel process (default: cwd of this process). + ipython_kernel : bool, optional + Whether the kernel is an official IPython one, + and should get a bit of special treatment. + Returns ------- - A tuple of form: - (kernel_process, shell_port, iopub_port, stdin_port, hb_port) - where kernel_process is a Popen object and the ports are integers. - """ - # Build the kernel launch command. - if executable is None: - executable = sys.executable - arguments = [ executable, '-c', code, '-f', fname ] - arguments.extend(extra_arguments) + Popen instance for the kernel subprocess + """ # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr # are invalid. Unfortunately, there is in general no way to detect whether @@ -167,50 +195,58 @@ def base_launch_kernel(code, fname, stdin=None, stdout=None, stderr=None, # Spawn a kernel. if sys.platform == 'win32': + # Create a Win32 event for interrupting the kernel. interrupt_event = ParentPollerWindows.create_interrupt_event() - arguments += [ '--interrupt=%i'%interrupt_event ] - - # If the kernel is running on pythonw and stdout/stderr are not been - # re-directed, it will crash when more than 4KB of data is written to - # stdout or stderr. This is a bug that has been with Python for a very - # long time; see http://bugs.python.org/issue706263. - # A cleaner solution to this problem would be to pass os.devnull to - # Popen directly. Unfortunately, that does not work. - if executable.endswith('pythonw.exe'): - if stdout is None: - arguments.append('--no-stdout') - if stderr is None: - arguments.append('--no-stderr') + if ipython_kernel: + cmd += [ '--interrupt=%i' % interrupt_event ] + + # If the kernel is running on pythonw and stdout/stderr are not been + # re-directed, it will crash when more than 4KB of data is written to + # stdout or stderr. This is a bug that has been with Python for a very + # long time; see http://bugs.python.org/issue706263. + # A cleaner solution to this problem would be to pass os.devnull to + # Popen directly. Unfortunately, that does not work. + if cmd[0].endswith('pythonw.exe'): + if stdout is None: + cmd.append('--no-stdout') + if stderr is None: + cmd.append('--no-stderr') # Launch the kernel process. if independent: - proc = Popen(arguments, + proc = Popen(cmd, creationflags=512, # CREATE_NEW_PROCESS_GROUP stdin=_stdin, stdout=_stdout, stderr=_stderr) else: - try: - from _winapi import DuplicateHandle, GetCurrentProcess, \ - DUPLICATE_SAME_ACCESS - except: - from _subprocess import DuplicateHandle, GetCurrentProcess, \ - DUPLICATE_SAME_ACCESS - pid = GetCurrentProcess() - handle = DuplicateHandle(pid, pid, pid, 0, - True, # Inheritable by new processes. - DUPLICATE_SAME_ACCESS) - proc = Popen(arguments + ['--parent=%i'%int(handle)], - stdin=_stdin, stdout=_stdout, stderr=_stderr) + if ipython_kernel: + try: + from _winapi import DuplicateHandle, GetCurrentProcess, \ + DUPLICATE_SAME_ACCESS + except: + from _subprocess import DuplicateHandle, GetCurrentProcess, \ + DUPLICATE_SAME_ACCESS + pid = GetCurrentProcess() + handle = DuplicateHandle(pid, pid, pid, 0, + True, # Inheritable by new processes. + DUPLICATE_SAME_ACCESS) + cmd +=[ '--parent=%i' % handle ] + + + proc = Popen(cmd, + stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd) # Attach the interrupt event to the Popen objet so it can be used later. proc.win32_interrupt_event = interrupt_event else: if independent: - proc = Popen(arguments, preexec_fn=lambda: os.setsid(), + proc = Popen(cmd, preexec_fn=lambda: os.setsid(), stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd) else: - proc = Popen(arguments + ['--parent=1'], + if ipython_kernel: + cmd += ['--parent=1'] + proc = Popen(cmd, stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd) # Clean up pipes created to work around Popen bug.