Repository: ipython/ipython

Issue:
unable to complete remote connections for two-process 
steps to reproduce:

```
ipython console --ip=0
```

and then, try to connect to it from another window:

```
ipython console --exiting
```

and it will stall. If you don't specify --ip, everything works, because the local code goes through a slightly different code path. Just opening this now so @minrk has a thing to reference as he submits the fix



Discussion:

Resolving patch:
diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py
--- a/IPython/consoleapp.py
+++ b/IPython/consoleapp.py
@@ -24,7 +24,6 @@
 import atexit
 import json
 import os
-import shutil
 import signal
 import sys
 import uuid
@@ -32,7 +31,6 @@
 
 # Local imports
 from IPython.config.application import boolean_flag
-from IPython.config.configurable import Configurable
 from IPython.core.profiledir import ProfileDir
 from IPython.kernel.blocking import BlockingKernelClient
 from IPython.kernel import KernelManager
@@ -40,7 +38,7 @@
 from IPython.utils.path import filefind
 from IPython.utils.py3compat import str_to_bytes
 from IPython.utils.traitlets import (
-    Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
+    Dict, List, Unicode, CUnicode, Int, CBool, Any
 )
 from IPython.kernel.zmq.kernelapp import (
     kernel_flags,
@@ -49,12 +47,13 @@
 )
 from IPython.kernel.zmq.session import Session, default_secure
 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
+from IPython.kernel.connect import ConnectionFileMixin
 
 #-----------------------------------------------------------------------------
 # Network Constants
 #-----------------------------------------------------------------------------
 
-from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
+from IPython.utils.localinterfaces import LOCALHOST
 
 #-----------------------------------------------------------------------------
 # Globals
@@ -89,8 +88,8 @@
 
 # also scrub aliases from the frontend
 app_aliases = dict(
-    ip = 'KernelManager.ip',
-    transport = 'KernelManager.transport',
+    ip = 'IPythonConsoleApp.ip',
+    transport = 'IPythonConsoleApp.transport',
     hb = 'IPythonConsoleApp.hb_port',
     shell = 'IPythonConsoleApp.shell_port',
     iopub = 'IPythonConsoleApp.iopub_port',
@@ -120,7 +119,7 @@
 else:
     classes.append(InlineBackend)
 
-class IPythonConsoleApp(Configurable):
+class IPythonConsoleApp(ConnectionFileMixin):
     name = 'ipython-console-mixin'
 
     description = """
@@ -254,11 +253,10 @@ def load_connection_file(self):
         self.log.debug(u"Loading connection file %s", fname)
         with open(fname) as f:
             cfg = json.load(f)
+        self.transport = cfg.get('transport', 'tcp')
+        self.ip = cfg.get('ip', LOCALHOST)
         
-        self.config.KernelManager.transport = cfg.get('transport', 'tcp')
-        self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
-        
-        for channel in ('hb', 'shell', 'iopub', 'stdin'):
+        for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
             name = channel + '_port'
             if getattr(self, name) == 0 and name in cfg:
                 # not overridden by config or cl_args
@@ -272,11 +270,10 @@ def init_ssh(self):
         """set up ssh tunnels, if needed."""
         if not self.existing or (not self.sshserver and not self.sshkey):
             return
-        
         self.load_connection_file()
         
-        transport = self.config.KernelManager.transport
-        ip = self.config.KernelManager.ip
+        transport = self.transport
+        ip = self.ip
         
         if transport != 'tcp':
             self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
@@ -298,7 +295,7 @@ def init_ssh(self):
         self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
         
         # tunnels return a new set of ports, which will be on localhost:
-        self.config.KernelManager.ip = LOCALHOST
+        self.ip = LOCALHOST
         try:
             newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
         except:
@@ -337,6 +334,8 @@ def init_kernel_manager(self):
 
         # Create a KernelManager and start a kernel.
         self.kernel_manager = self.kernel_manager_class(
+                                ip=self.ip,
+                                transport=self.transport,
                                 shell_port=self.shell_port,
                                 iopub_port=self.iopub_port,
                                 stdin_port=self.stdin_port,
@@ -367,6 +366,8 @@ def init_kernel_client(self):
             self.kernel_client = self.kernel_manager.client()
         else:
             self.kernel_client = self.kernel_client_class(
+                                ip=self.ip,
+                                transport=self.transport,
                                 shell_port=self.shell_port,
                                 iopub_port=self.iopub_port,
                                 stdin_port=self.stdin_port,
diff --git a/IPython/kernel/client.py b/IPython/kernel/client.py
--- a/IPython/kernel/client.py
+++ b/IPython/kernel/client.py
@@ -147,8 +147,10 @@ def channels_running(self):
     def shell_channel(self):
         """Get the shell channel object for this kernel."""
         if self._shell_channel is None:
+            url = self._make_url('shell')
+            self.log.debug("connecting shell channel to %s", url)
             self._shell_channel = self.shell_channel_class(
-                self.context, self.session, self._make_url('shell')
+                self.context, self.session, url
             )
         return self._shell_channel
 
@@ -156,8 +158,10 @@ def shell_channel(self):
     def iopub_channel(self):
         """Get the iopub channel object for this kernel."""
         if self._iopub_channel is None:
+            url = self._make_url('iopub')
+            self.log.debug("connecting iopub channel to %s", url)
             self._iopub_channel = self.iopub_channel_class(
-                self.context, self.session, self._make_url('iopub')
+                self.context, self.session, url
             )
         return self._iopub_channel
 
@@ -165,8 +169,10 @@ def iopub_channel(self):
     def stdin_channel(self):
         """Get the stdin channel object for this kernel."""
         if self._stdin_channel is None:
+            url = self._make_url('stdin')
+            self.log.debug("connecting stdin channel to %s", url)
             self._stdin_channel = self.stdin_channel_class(
-                self.context, self.session, self._make_url('stdin')
+                self.context, self.session, url
             )
         return self._stdin_channel
 
@@ -174,8 +180,10 @@ def stdin_channel(self):
     def hb_channel(self):
         """Get the hb channel object for this kernel."""
         if self._hb_channel is None:
+            url = self._make_url('hb')
+            self.log.debug("connecting heartbeat channel to %s", url)
             self._hb_channel = self.hb_channel_class(
-                self.context, self.session, self._make_url('hb')
+                self.context, self.session, url
             )
         return self._hb_channel
 
diff --git a/IPython/qt/console/qtconsoleapp.py b/IPython/qt/console/qtconsoleapp.py
--- a/IPython/qt/console/qtconsoleapp.py
+++ b/IPython/qt/console/qtconsoleapp.py
@@ -249,10 +249,7 @@ def init_qt_elements(self):
         self.app.icon = QtGui.QIcon(icon_path)
         QtGui.QApplication.setWindowIcon(self.app.icon)
 
-        try:
-            ip = self.config.KernelManager.ip
-        except AttributeError:
-            ip = LOCALHOST
+        ip = self.ip
         local_kernel = (not self.existing) or ip in LOCAL_IPS
         self.widget = self.widget_factory(config=self.config,
                                         local_kernel=local_kernel)


Test patch:
diff --git a/IPython/kernel/tests/test_connect.py b/IPython/kernel/tests/test_connect.py
new file mode 100644
--- /dev/null
+++ b/IPython/kernel/tests/test_connect.py
@@ -0,0 +1,124 @@
+"""Tests for kernel connection utilities
+
+Authors
+-------
+* MinRK
+"""
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, the IPython Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import json
+import os
+
+import nose.tools as nt
+
+from IPython.config import Config
+from IPython.consoleapp import IPythonConsoleApp
+from IPython.core.application import BaseIPythonApplication
+from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
+from IPython.utils.py3compat import str_to_bytes
+from IPython.kernel import connect
+
+#-----------------------------------------------------------------------------
+# Classes and functions
+#-----------------------------------------------------------------------------
+
+class DummyConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
+    def initialize(self, argv=[]):
+        BaseIPythonApplication.initialize(self, argv=argv)
+        self.init_connection_file()
+
+sample_info = dict(ip='1.2.3.4', transport='ipc',
+        shell_port=1, hb_port=2, iopub_port=3, stdin_port=4, control_port=5,
+        key=b'abc123', signature_scheme='hmac-md5',
+    )
+
+def test_write_connection_file():
+    with TemporaryDirectory() as d:
+        cf = os.path.join(d, 'kernel.json')
+        connect.write_connection_file(cf, **sample_info)
+        nt.assert_true(os.path.exists(cf))
+        with open(cf, 'r') as f:
+            info = json.load(f)
+    info['key'] = str_to_bytes(info['key'])
+    nt.assert_equal(info, sample_info)
+
+def test_app_load_connection_file():
+    """test `ipython console --existing` loads a connection file"""
+    with TemporaryDirectory() as d:
+        cf = os.path.join(d, 'kernel.json')
+        connect.write_connection_file(cf, **sample_info)
+        app = DummyConsoleApp(connection_file=cf)
+        app.initialize(argv=[])
+    
+    for attr, expected in sample_info.items():
+        if attr in ('key', 'signature_scheme'):
+            continue
+        value = getattr(app, attr)
+        nt.assert_equal(value, expected, "app.%s = %s != %s" % (attr, value, expected))
+
+def test_get_connection_file():
+    cfg = Config()
+    with TemporaryWorkingDirectory() as d:
+        cfg.ProfileDir.location = d
+        cf = 'kernel.json'
+        app = DummyConsoleApp(config=cfg, connection_file=cf)
+        app.initialize(argv=[])
+        
+        profile_cf = os.path.join(app.profile_dir.location, 'security', cf)
+        nt.assert_equal(profile_cf, app.connection_file)
+        with open(profile_cf, 'w') as f:
+            f.write("{}")
+        nt.assert_true(os.path.exists(profile_cf))
+        nt.assert_equal(connect.get_connection_file(app), profile_cf)
+        
+        app.connection_file = cf
+        nt.assert_equal(connect.get_connection_file(app), profile_cf)
+
+def test_find_connection_file():
+    cfg = Config()
+    with TemporaryDirectory() as d:
+        cfg.ProfileDir.location = d
+        cf = 'kernel.json'
+        app = DummyConsoleApp(config=cfg, connection_file=cf)
+        app.initialize(argv=[])
+        BaseIPythonApplication._instance = app
+        
+        profile_cf = os.path.join(app.profile_dir.location, 'security', cf)
+        with open(profile_cf, 'w') as f:
+            f.write("{}")
+        
+        for query in (
+            'kernel.json',
+            'kern*',
+            '*ernel*',
+            'k*',
+            ):
+            nt.assert_equal(connect.find_connection_file(query), profile_cf)
+        
+        BaseIPythonApplication._instance = None
+
+def test_get_connection_info():
+    with TemporaryDirectory() as d:
+        cf = os.path.join(d, 'kernel.json')
+        connect.write_connection_file(cf, **sample_info)
+        json_info = connect.get_connection_info(cf)
+        info = connect.get_connection_info(cf, unpack=True)
+    
+    nt.assert_equal(type(json_info), type(""))
+    nt.assert_equal(info, sample_info)
+    
+    info2 = json.loads(json_info)
+    info2['key'] = str_to_bytes(info2['key'])
+    nt.assert_equal(info2, sample_info)
+    
+