Repository: ipython/ipython

Issue:
Should wrapper kernels have ipython_kernel == True in launcher.py?
When running a wrapper kernel, one always sees the scary message:

``` shell
$ ipython console --kernel metakernel_python
NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049


To connect another client to this kernel, use:
    --existing kernel-32663.json
IPython Console 3.0.0-dev

In [1]: 
```

Both of these messages (--existing-kernel and Ctrl+\) are unnecessary. In tracking down the issue, I see that `ipython_kernel` is False in `IPython.kernel.launcher` and it is true that it is not the official IPython kernel. But this makes it so that parent_handle is 0, and also the interrupt flag is not set. Should it be for wrapper kernels? If not, how can we set these values appropriately?



Discussion:
I didn't explicitly say it but the scary messages show because parent_handle == 0. Although it is 0, the kernel properly shuts down---everything works correctly, regardless of the incorrect messages.

I did force ipython_kernel to be True, and things appear to work correctly, so maybe it should be True for wrapper kernels too?

ipython_kernel should be False for all kernels other than the one and only IPython kernel. The distinction and special treatment will go away when IPython becomes just another kernel for Jupyter.

The message should probably be moved to the IPython-specific bits, instead of in the common code, and it may be that it's out of date entirely.

It seems that both the `--parent=1` (or an actual handle on Windows) and `--interrupt` (on Windows) flags should be set when using the kernel wrapper, right? So I am not sure that they should be moved or only be for the IPython kernel. I think these (at least here) should be for `ipython_infrastructure` not just for `ipython_kernel`. Or the flags should be also set for kernel wrappers.


Resolving patch:
diff --git a/IPython/kernel/launcher.py b/IPython/kernel/launcher.py
--- a/IPython/kernel/launcher.py
+++ b/IPython/kernel/launcher.py
@@ -101,25 +101,13 @@ def make_ipkernel_cmd(mod='IPython.kernel', executable=None, extra_arguments=[],
         executable = sys.executable
     arguments = [ executable, '-m', mod, '-f', '{connection_file}' ]
     arguments.extend(extra_arguments)
-
-    if sys.platform == 'win32':
-
-        # 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')
-
+    
     return arguments
 
 
 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
                         independent=False,
-                        cwd=None, ipython_kernel=True,
+                        cwd=None,
                         **kw
                         ):
     """ Launches a localhost kernel, binding to the specified ports.
@@ -141,10 +129,6 @@ def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
     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
     -------
     
@@ -166,14 +150,22 @@ def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
     # stderr are all invalid.
     redirect_out = sys.executable.endswith('pythonw.exe')
     if redirect_out:
-        _stdout = PIPE if stdout is None else stdout
-        _stderr = PIPE if stderr is None else stderr
+        blackhole = open(os.devnull, 'w')
+        _stdout = blackhole if stdout is None else stdout
+        _stderr = blackhole if stderr is None else stderr
     else:
         _stdout, _stderr = stdout, stderr
     
     env = env if (env is not None) else os.environ.copy()
 
     encoding = getdefaultencoding(prefer_stream=False)
+    kwargs = dict(
+        stdin=_stdin,
+        stdout=_stdout,
+        stderr=_stderr,
+        cwd=cwd,
+        env=env,
+    )
     
     # Spawn a kernel.
     if sys.platform == 'win32':
@@ -181,73 +173,49 @@ def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
         cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
         if cwd:
             cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
+            kwargs['cwd'] = cwd
         
         from IPython.kernel.zmq.parentpoller import ParentPollerWindows
-        # Create a Win32 event for interrupting the kernel.
+        # Create a Win32 event for interrupting the kernel
+        # and store it in an environment variable.
         interrupt_event = ParentPollerWindows.create_interrupt_event()
-        # Store this in an environment variable for third party kernels, but at
-        # present, our own kernel expects this as a command line argument.
-        env["IPY_INTERRUPT_EVENT"] = str(interrupt_event)
-        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.
+        env["JPY_INTERRUPT_EVENT"] = str(interrupt_event)
+        # deprecated old env name:
+        env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"]
+
+        try:
+            from _winapi import DuplicateHandle, GetCurrentProcess, \
+                DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
+        except:
+            from _subprocess import DuplicateHandle, GetCurrentProcess, \
+                DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP
+        # Launch the kernel process
         if independent:
-            proc = Popen(cmd,
-                         creationflags=512, # CREATE_NEW_PROCESS_GROUP
-                         stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
+            kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
         else:
-            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, env=env)
+            pid = GetCurrentProcess()
+            handle = DuplicateHandle(pid, pid, pid, 0,
+                                     True, # Inheritable by new processes.
+                                     DUPLICATE_SAME_ACCESS)
+            env['JPY_PARENT_PID'] = str(handle)
+        
+        proc = Popen(cmd, **kwargs)
 
         # 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(cmd, preexec_fn=lambda: os.setsid(),
-                         stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
+            kwargs['preexec_fn'] = lambda: os.setsid()
         else:
-            if ipython_kernel:
-                cmd += ['--parent=1']
-            proc = Popen(cmd,
-                         stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
+            env['JPY_PARENT_PID'] = str(os.getpid())
+        
+        proc = Popen(cmd, **kwargs)
 
     # Clean up pipes created to work around Popen bug.
     if redirect_in:
         if stdin is None:
             proc.stdin.close()
-    if redirect_out:
-        if stdout is None:
-            proc.stdout.close()
-        if stderr is None:
-            proc.stderr.close()
 
     return proc
 
diff --git a/IPython/kernel/manager.py b/IPython/kernel/manager.py
--- a/IPython/kernel/manager.py
+++ b/IPython/kernel/manager.py
@@ -237,7 +237,6 @@ def start_kernel(self, **kw):
             env.update(self.kernel_spec.env or {})
         # launch the kernel subprocess
         self.kernel = self._launch_kernel(kernel_cmd, env=env,
-                                    ipython_kernel=self.ipython_kernel,
                                     **kw)
         self.start_restarter()
         self._connect_control_socket()
diff --git a/IPython/kernel/zmq/kernelapp.py b/IPython/kernel/zmq/kernelapp.py
--- a/IPython/kernel/zmq/kernelapp.py
+++ b/IPython/kernel/zmq/kernelapp.py
@@ -53,11 +53,8 @@
     'stdin' : 'IPKernelApp.stdin_port',
     'control' : 'IPKernelApp.control_port',
     'f' : 'IPKernelApp.connection_file',
-    'parent': 'IPKernelApp.parent_handle',
     'transport': 'IPKernelApp.transport',
 })
-if sys.platform.startswith('win'):
-    kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
 
 kernel_flags = dict(base_flags)
 kernel_flags.update({
@@ -133,11 +130,11 @@ def abs_connection_file(self):
         config=True, help="The importstring for the DisplayHook factory")
 
     # polling
-    parent_handle = Integer(0, config=True,
+    parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), config=True,
         help="""kill this process if its parent dies.  On Windows, the argument
         specifies the HANDLE of the parent process, otherwise it is simply boolean.
         """)
-    interrupt = Integer(0, config=True,
+    interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), config=True,
         help="""ONLY USED ON WINDOWS
         Interrupt this process when the parent is signaled.
         """)


Test patch: