Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

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

Merged
merged 2 commits into from

2 participants

@minrk
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).

@minrk minrk 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))
+ real_excepthook = sys.excepthook
+ def handle_int(etype, value, tb):
+ """don't let KeyboardInterrupts look like crashes"""
+ if etype is KeyboardInterrupt:
+ io.raw_print("KeyboardInterrupt caught in CFRunLoop")
+ else:
+ real_excepthook(etype, value, tb)
+
+ # add doi() as a Timer to the CFRunLoop
+ def doi():
+ # restore excepthook during IPython code
+ sys.excepthook = real_excepthook
+ self.do_one_iteration()
+ # and back:
+ sys.excepthook = handle_int
+ t = TimerMac(poll_interval)
@fperez Owner
fperez added a note

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

@minrk Owner
minrk added a note

done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fperez fperez commented on the diff
IPython/zmq/ipkernel.py
((44 lines not shown))
+ t.start()
+
+ # but still need a Poller for when there are no active windows,
+ # during which time mainloop() returns immediately
+ poller = zmq.Poller()
+ poller.register(self.shell_socket, zmq.POLLIN)
+
+ while True:
+ try:
+ # double nested try/except, to properly catch KeyboardInterrupt
+ # due to pyzmq Issue #130
+ try:
+ # don't let interrupts during mainloop invoke crash_handler:
+ sys.excepthook = handle_int
+ show.mainloop()
+ sys.excepthook = real_excepthook
@fperez Owner
fperez added a note

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

@minrk Owner
minrk added a note

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.

@fperez Owner
fperez added a note

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.

@minrk Owner
minrk added a note

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
@minrk minrk ensure excepthook is restored in OSXKernel mainloop
also add some friendly whitespace per @fperez review
5175b0e
@minrk minrk merged commit 321d643 into from
@ellisonbg ellisonbg referenced this pull request from a commit
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
Commits on Sep 20, 2011
  1. @minrk

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

    minrk authored
    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.
Commits on Oct 1, 2011
  1. @minrk

    ensure excepthook is restored in OSXKernel mainloop

    minrk authored
    also add some friendly whitespace per @fperez review
This page is out of date. Refresh to see the latest.
Showing with 73 additions and 2 deletions.
  1. +72 −1 IPython/zmq/ipkernel.py
  2. +1 −1  docs/source/interactive/qtconsole.txt
View
73 IPython/zmq/ipkernel.py
@@ -586,6 +586,77 @@ def start(self):
gtk_kernel.start()
+class OSXKernel(TkKernel):
+ """A Kernel subclass with Cocoa support via the matplotlib OSX backend."""
+
+ def start(self):
+ """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
+ via the matplotlib MacOSX backend.
+ """
+ import matplotlib
+ if matplotlib.__version__ < '1.1.0':
+ self.log.warn(
+ "MacOSX backend in matplotlib %s doesn't have a Timer, "
+ "falling back on Tk for CFRunLoop integration. Note that "
+ "even this won't work if Tk is linked against X11 instead of "
+ "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
+ "you must use matplotlib >= 1.1.0, or a native libtk."
+ )
+ return TkKernel.start(self)
+
+ from matplotlib.backends.backend_macosx import TimerMac, show
+
+ # scale interval for sec->ms
+ poll_interval = int(1000*self._poll_interval)
+
+ real_excepthook = sys.excepthook
+ def handle_int(etype, value, tb):
+ """don't let KeyboardInterrupts look like crashes"""
+ if etype is KeyboardInterrupt:
+ io.raw_print("KeyboardInterrupt caught in CFRunLoop")
+ else:
+ real_excepthook(etype, value, tb)
+
+ # add doi() as a Timer to the CFRunLoop
+ def doi():
+ # restore excepthook during IPython code
+ sys.excepthook = real_excepthook
+ self.do_one_iteration()
+ # and back:
+ sys.excepthook = handle_int
+
+ t = TimerMac(poll_interval)
+ t.add_callback(doi)
+ t.start()
+
+ # but still need a Poller for when there are no active windows,
+ # during which time mainloop() returns immediately
+ poller = zmq.Poller()
+ poller.register(self.shell_socket, zmq.POLLIN)
+
+ while True:
+ try:
+ # double nested try/except, to properly catch KeyboardInterrupt
+ # due to pyzmq Issue #130
+ try:
+ # don't let interrupts during mainloop invoke crash_handler:
+ sys.excepthook = handle_int
+ show.mainloop()
+ sys.excepthook = real_excepthook
@fperez Owner
fperez added a note

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

@minrk Owner
minrk added a note

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.

@fperez Owner
fperez added a note

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.

@minrk Owner
minrk added a note

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
+ # use poller if mainloop returned (no windows)
+ # scale by extra factor of 10, since it's a real poll
+ poller.poll(10*poll_interval)
+ self.do_one_iteration()
+ except:
+ raise
+ except KeyboardInterrupt:
+ # Ctrl-C shouldn't crash the kernel
+ io.raw_print("KeyboardInterrupt caught in kernel")
+ finally:
+ # ensure excepthook is restored
+ sys.excepthook = real_excepthook
+
+
#-----------------------------------------------------------------------------
# Aliases and Flags for the IPKernelApp
#-----------------------------------------------------------------------------
@@ -639,7 +710,7 @@ def init_kernel(self):
'qt' : QtKernel,
'qt4': QtKernel,
'inline': Kernel,
- 'osx': TkKernel,
+ 'osx': OSXKernel,
'wx' : WxKernel,
'tk' : TkKernel,
'gtk': GTKKernel,
View
2  docs/source/interactive/qtconsole.txt
@@ -61,7 +61,7 @@ Pylab
=====
One of the most exciting features of the new console is embedded matplotlib
-figures. You can use any standard matplotlib GUI backend (Except native MacOSX)
+figures. You can use any standard matplotlib GUI backend
to draw the figures, and since there is now a two-process model, there is no
longer a conflict between user input and the drawing eventloop.
Something went wrong with that request. Please try again.