Skip to content
This repository

use CFRunLoop directly in `ipython kernel --pylab osx` #809

Merged
merged 2 commits into from over 2 years ago

2 participants

Min RK Fernando Perez
Min RK
Owner

CFRunLoop integration is now made via matplotlib.backend_macosx.TimerMac, rather than assuming Tk is native.

It will still fallback on Tk if matplotlib is < 1.1.0, which introduces the necessary Timer. This means that it still won't work on current EPD, which has X11-linked libtk and matplotlib 1.0.1, but at least it will display a warning explaining why.

Also removes caveat in docs that qtconsole doesn't work with native MacOSX, since it does on any normal (non-EPD) install.

So this will work in more places, but still not in the most common failure case (stock EPD) described in #640.

Yet another approach is to use PyObjC to hook into the eventloop directly, but that still doesn't solve the EPD problem, which doesn't ship with PyObjC (it probably should).

Min RK use CFRunLoop directly in `ipython kernel --pylab osx`
via matplotlib.backend_macosx.TimerMac, rather than Tk

Fallback on Tk if matplotlib is < 1.1.0, which introduces the necessary Timer. This means that it still won't work on current EPD, which has X11-linked libtk and matplotlib 1.0.1,
but at least it will display a warning explaining why.

also remove caveat in docs that qtconsole doesn't work with native MacOSX, since it does on normal (non-EPD) installs.

So this will work in more places, but still not in most common failure case (stock EPD) described in #640.
5942e2a
IPython/zmq/ipkernel.py
((27 lines not shown))
  612
+        real_excepthook = sys.excepthook
  613
+        def handle_int(etype, value, tb):
  614
+            """don't let KeyboardInterrupts look like crashes"""
  615
+            if etype is KeyboardInterrupt:
  616
+                io.raw_print("KeyboardInterrupt caught in CFRunLoop")
  617
+            else:
  618
+                real_excepthook(etype, value, tb)
  619
+        
  620
+        # add doi() as a Timer to the CFRunLoop
  621
+        def doi():
  622
+            # restore excepthook during IPython code
  623
+            sys.excepthook = real_excepthook
  624
+            self.do_one_iteration()
  625
+            # and back:
  626
+            sys.excepthook = handle_int
  627
+        t = TimerMac(poll_interval)
2
Fernando Perez Owner
fperez added a note September 30, 2011

I'd add a blank line after the end of the func definition for readability

Min RK Owner
minrk added a note September 30, 2011

done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez fperez commented on the diff September 30, 2011
IPython/zmq/ipkernel.py
((44 lines not shown))
  629
+        t.start()
  630
+        
  631
+        # but still need a Poller for when there are no active windows,
  632
+        # during which time mainloop() returns immediately
  633
+        poller = zmq.Poller()
  634
+        poller.register(self.shell_socket, zmq.POLLIN)
  635
+        
  636
+        while True:
  637
+            try:
  638
+                # double nested try/except, to properly catch KeyboardInterrupt
  639
+                # due to pyzmq Issue #130
  640
+                try:
  641
+                    # don't let interrupts during mainloop invoke crash_handler:
  642
+                    sys.excepthook = handle_int
  643
+                    show.mainloop()
  644
+                    sys.excepthook = real_excepthook
4
Fernando Perez Owner
fperez added a note September 30, 2011

What happens if there's an exception in the mainloop call? sys.excepthook won't get properly restored...

Min RK Owner
minrk added a note September 30, 2011

I'll stick the restore-excepthook in a finally clause, to make sure it is restored. Note that the special excepthook is identical to the original, save for handing keyboard interrupts, so there really isn't any difference. Either the error is a KeyboardInterrupt, and the loop is entered again, with no change, or it is anything else, which generates a crash report as usual, and exits.

Here's a question, though - should an exception in mainloop cause an IPython crash report? There is zero IPython code run in there, it's all matplotlib. Should a matplotlib bug be presented to the user as an IPython crash?

I worry that our crash reports are sometimes overzealous, often presenting far too much information, and ultimately being less instructive than a regular traceback.

Fernando Perez Owner
fperez added a note September 30, 2011

Valid concern, and it seems we're producing more of them lately. They were originally so aggressive because they really were a way for us to debug an actual crash in the user's code. So they should only happen when IPython totally died.

But whenever we catch a normal exception (possibly from mpl or another tool), showtraceback() should be used instead. I'm not sure why we've ended up over time with our crash handler showing up so often.

Min RK Owner
minrk added a note September 30, 2011

Actually, I was wrong - mainloop includes calls to IPython's doi() via a timer, so all IPython code does end up within that call once a single plot window exists. And the KernelApp does not register a full crashhandler, it just registers FormattedTB, so you do just get a regular traceback instead of a crash. But if the crash_handler catches an error, it goes to the terminal, instead of being posted via PUB to frontends. This is true for all Kernels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Min RK ensure excepthook is restored in OSXKernel mainloop
also add some friendly whitespace per @fperez review
5175b0e
Min RK minrk merged commit 321d643 into from October 07, 2011
Min RK minrk closed this October 07, 2011
Brian E. Granger ellisonbg referenced this pull request from a commit January 10, 2012
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Sep 20, 2011
Min RK use CFRunLoop directly in `ipython kernel --pylab osx`
via matplotlib.backend_macosx.TimerMac, rather than Tk

Fallback on Tk if matplotlib is < 1.1.0, which introduces the necessary Timer. This means that it still won't work on current EPD, which has X11-linked libtk and matplotlib 1.0.1,
but at least it will display a warning explaining why.

also remove caveat in docs that qtconsole doesn't work with native MacOSX, since it does on normal (non-EPD) installs.

So this will work in more places, but still not in most common failure case (stock EPD) described in #640.
5942e2a
Sep 30, 2011
Min RK ensure excepthook is restored in OSXKernel mainloop
also add some friendly whitespace per @fperez review
5175b0e
This page is out of date. Refresh to see the latest.
73  IPython/zmq/ipkernel.py
@@ -586,6 +586,77 @@ def start(self):
586 586
         gtk_kernel.start()
587 587
 
588 588
 
  589
+class OSXKernel(TkKernel):
  590
+    """A Kernel subclass with Cocoa support via the matplotlib OSX backend."""
  591
+    
  592
+    def start(self):
  593
+        """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
  594
+        via the matplotlib MacOSX backend.
  595
+        """
  596
+        import matplotlib
  597
+        if matplotlib.__version__ < '1.1.0':
  598
+            self.log.warn(
  599
+            "MacOSX backend in matplotlib %s doesn't have a Timer, "
  600
+            "falling back on Tk for CFRunLoop integration.  Note that "
  601
+            "even this won't work if Tk is linked against X11 instead of "
  602
+            "Cocoa (e.g. EPD).  To use the MacOSX backend in the kernel, "
  603
+            "you must use matplotlib >= 1.1.0, or a native libtk."
  604
+            )
  605
+            return TkKernel.start(self)
  606
+        
  607
+        from matplotlib.backends.backend_macosx import TimerMac, show
  608
+        
  609
+        # scale interval for sec->ms
  610
+        poll_interval = int(1000*self._poll_interval)
  611
+        
  612
+        real_excepthook = sys.excepthook
  613
+        def handle_int(etype, value, tb):
  614
+            """don't let KeyboardInterrupts look like crashes"""
  615
+            if etype is KeyboardInterrupt:
  616
+                io.raw_print("KeyboardInterrupt caught in CFRunLoop")
  617
+            else:
  618
+                real_excepthook(etype, value, tb)
  619
+        
  620
+        # add doi() as a Timer to the CFRunLoop
  621
+        def doi():
  622
+            # restore excepthook during IPython code
  623
+            sys.excepthook = real_excepthook
  624
+            self.do_one_iteration()
  625
+            # and back:
  626
+            sys.excepthook = handle_int
  627
+        
  628
+        t = TimerMac(poll_interval)
  629
+        t.add_callback(doi)
  630
+        t.start()
  631
+        
  632
+        # but still need a Poller for when there are no active windows,
  633
+        # during which time mainloop() returns immediately
  634
+        poller = zmq.Poller()
  635
+        poller.register(self.shell_socket, zmq.POLLIN)
  636
+        
  637
+        while True:
  638
+            try:
  639
+                # double nested try/except, to properly catch KeyboardInterrupt
  640
+                # due to pyzmq Issue #130
  641
+                try:
  642
+                    # don't let interrupts during mainloop invoke crash_handler:
  643
+                    sys.excepthook = handle_int
  644
+                    show.mainloop()
  645
+                    sys.excepthook = real_excepthook
  646
+                    # use poller if mainloop returned (no windows)
  647
+                    # scale by extra factor of 10, since it's a real poll
  648
+                    poller.poll(10*poll_interval)
  649
+                    self.do_one_iteration()
  650
+                except:
  651
+                    raise
  652
+            except KeyboardInterrupt:
  653
+                # Ctrl-C shouldn't crash the kernel
  654
+                io.raw_print("KeyboardInterrupt caught in kernel")
  655
+            finally:
  656
+                # ensure excepthook is restored
  657
+                sys.excepthook = real_excepthook
  658
+
  659
+
589 660
 #-----------------------------------------------------------------------------
590 661
 # Aliases and Flags for the IPKernelApp
591 662
 #-----------------------------------------------------------------------------
@@ -639,7 +710,7 @@ def init_kernel(self):
639 710
             'qt' : QtKernel,
640 711
             'qt4': QtKernel,
641 712
             'inline': Kernel,
642  
-            'osx': TkKernel,
  713
+            'osx': OSXKernel,
643 714
             'wx' : WxKernel,
644 715
             'tk' : TkKernel,
645 716
             'gtk': GTKKernel,
2  docs/source/interactive/qtconsole.txt
@@ -61,7 +61,7 @@ Pylab
61 61
 =====
62 62
 
63 63
 One of the most exciting features of the new console is embedded matplotlib
64  
-figures. You can use any standard matplotlib GUI backend (Except native MacOSX)
  64
+figures. You can use any standard matplotlib GUI backend
65 65
 to draw the figures, and since there is now a two-process model, there is no
66 66
 longer a conflict between user input and the drawing eventloop.
67 67
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.