Repository: ipython/ipython

Issue:
ipython 5.1 sporadically freezes when matplotlib plots are active
I've using ipython 5.1 with matplotlib 1.5.1 with the Qt4Agg backend on Mac OS X 10.9.5.  There have now been 3 occasions when the ipython terminal froze entirely - it would not accept control-c, control-z, or any command I tried.  Quitting the Qt plot windows re-activated the python terminal.

This so far has *not* affected me the first or second time I've made a plot window, but eventually it seems to affect every ipython session I've started with a plot window.

I upgraded to matplotlib 2.0, and the same thing happened, but this time (now with Qt5Agg as a backend), I get a message:

```
QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier
```

After that notification, the entire terminal containing ipython became unresponsive.  I think the same message was issued under ipython + matplotlib 1.5.1, but the message only appeared after closing ipython entirely, i.e.:

```
In [11]:
Do you really want to exit ([y]/n)?
QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier
```


Discussion:
Ping @tacaswell 

Have you run a `%matplotlib` command to integrate the event loops? What backend are you using?
No, I did not run `%matplotlib` in any of these sessions, but I started up ipython with `--matplotlib`.

The two different cases described above are with Qt4Agg and Qt5Agg backends.
My naive guess is that something is wrong with the way the eventloops are being hooked together (but only on mac).
Is there anything I can do to test that hypothesis or work around it?
The code handling Qt event loop integration in terminal IPython lives here:
https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/qt.py

I don't understand what might be going wrong, though.
Where does the `context` object come from?

I wonder if there is a race condition between triggering the `inputhook`, the socket being ready to be read, the notifier being created, and the loop starting?

The story here would be things fail when:

 - `inputhook` gets called
 - the context file descriptor is ready to be read and then torn down
 - the notifier in created, but is broken due to the previous step (this is the dicey part)
 - the Qt event loop now starts, but the notifier that needs to stop it to let the terminal work again is broken so the loop Qt event loop runs forever.
  - closing all of the plot windows (which should still be responsive to pan/zoom and keyboard inputs?)  implicitly stops the event loop, returning control to the terminal 

From work with asyncio I have the general impression that `select` on OSX is wonky (which I assume _something_ related to that is being under the hood here).

I suspect that the fix will be to notice when constructing the notifier fails and not starting the event loop.  It might also make sense to also hook up a timer that kills the event loop after ~0.1s?  
The context object comes from prompt_toolkit.
who is the right person from `prompt_toolkit` to loop into this?
@jonathanslenders is the author of prompt_toolkit. I'd suspect the error is more likely to be in our qt inputhook code, but maybe he can help us to work it out.

Resolving patch:
diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py
--- a/IPython/terminal/pt_inputhooks/qt.py
+++ b/IPython/terminal/pt_inputhooks/qt.py
@@ -5,6 +5,7 @@
 # garbage collected.
 _appref = None
 
+
 def inputhook(context):
     global _appref
     app = QtCore.QCoreApplication.instance()
@@ -24,7 +25,11 @@ def inputhook(context):
     else:
         # On POSIX platforms, we can use a file descriptor to quit the event
         # loop when there is input ready to read.
-        notifier = QtCore.QSocketNotifier(context.fileno(), QtCore.QSocketNotifier.Read)
-        notifier.setEnabled(True)
+        notifier = QtCore.QSocketNotifier(context.fileno(),
+                                          QtCore.QSocketNotifier.Read)
+        # connect the callback we care about before we turn it on
         notifier.activated.connect(event_loop.exit)
-        event_loop.exec_()
+        notifier.setEnabled(True)
+        # only start the event loop we are not already flipped
+        if not context.input_is_ready():
+            event_loop.exec_()


Test patch: