You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Event handling with input in callback function #30419
When using a button click event handler, if the callback function includes the input function, the figure automatically closes when the callback function finishes.
Code for reproduction
importmatplotlib.pyplotaspltimportnumpyasnpfig, ax=plt.subplots()
ax.plot(np.random.rand(10))
defonclick(event):
print(f'click: x={event.x}, y={event.y}')
info=input('Enter some information about this point: ') # Comment out this lineprint(info) # Comment out this linecid=fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
Actual outcome
After a single event and iteration of the onclick callback function, the figure automatically closes without giving the user the opportunity to click on more points.
Screen.Recording.2025-08-12.at.5.17.43.PM.mov
Expected outcome
The figure should remain open and allow the user to click on many points until the user explicitly closes it. This is the standard behavior when the two lines indicated in the MWE related to the input function are commented out.
This works fine with Qt (other than an exception if you click twice before entering something, since you can't call input twice), so this seems to be a problem with the macosx backend specifically.
Thank you for the tip! This does work as expected if I manually set a different backend. Adding this to the top of the script has served as an effective work-around.
import matplotlib
if matplotlib.get_backend() == 'macosx':
matplotlib.use('tkagg')
Maybe we need to have a different check on what came from stdin "if it was a sigint: stop entirely, otherwise pause/let go of control" or something like that.
I did check back a few versions and this looks like it does work on 3.8.4, but not on 3.9.3, I didn't bisect it yet.
The problem is that we are using -[NSApplication run] from both wait_for_stdin() and show(). When we call -[NSApplication stop:] from stopWithEvent(), it will stop both -run calls.
The documentation for -stop: is deceptive:
If you call this method from an event handler running in your main run loop, the app object exits out of the run method, thereby returning control to the main() function. If you call this method from within a modal event loop, it will exit the modal loop instead of the main event loop.
That seems to indicate that some degree of re-entrancy is supported, but that only applies to modal sessions.
Hence, once -stop: is called, all nested calls to -run will return since NSApplication's _running ivar is 0. Here's a vibe-coded example that demonstrates this:
I've been researching the history of #28981 for the past few days. First, no blame to anyone - NSRunLoop and friends are notoriously difficult. I had to review a bunch of code that I wrote 15 years ago :)
Historically, wait_for_stdin was using -[NSApplication nextEventMatchingMask:…] with a date of -[NSDate distantPast] inside of a while loop. That was always going to result in heavy CPU utilization as the run loop wasn't being allowed to sleep and then wake itself up.
This was changed to use -[NSApplication run]. That correctly loops with -[NSDate distantFuture] but has the side-effect of introducing this bug since re-entrant invocations to -run aren't supported.
I do not understand the following comments at the top of wait_for_stdin():
// Short circuit if no windows are active// Rely on Python's input handling to manage CPU usage
There should be almost no CPU utilization when waiting for an event with an untilDate: of -[NSDate distantFuture]. The only thing that I can think of is that there was thrashing from constantly setting up and tearing down the NSFileHandle notification. There definitely was CPU usage prior to #28981, but that was due to the loop with -distantPast.
Note that I'm uneasy about any type of "short circuit" once NSApplication has been instantiated - there's more parts of AppKit that need a running event loop even when windows aren't present. My best guess is that the constant churning of the run loop (prior to #28981) made everyone associate run loops with high CPU usage; hence, this check got added during development as a precaution.
I'm still working on a solution that fixes both this while not regressing #26869 and #27515.
Activity
QuLogic commentedon Aug 13, 2025
This works fine with Qt (other than an exception if you click twice before entering something, since you can't call
inputtwice), so this seems to be a problem with the macosx backend specifically.QuLogic commentedon Aug 13, 2025
Maybe @greglucas has some thoughts.
ljlamarche commentedon Aug 13, 2025
Thank you for the tip! This does work as expected if I manually set a different backend. Adding this to the top of the script has served as an effective work-around.
greglucas commentedon Aug 15, 2025
I'm guessing this has something to do with our call to
stopWithEvent()when we override the input stream, but I'm not certain.matplotlib/src/_macosx.m
Line 105 in be68dfe
Maybe we need to have a different check on what came from stdin "if it was a sigint: stop entirely, otherwise pause/let go of control" or something like that.
I did check back a few versions and this looks like it does work on 3.8.4, but not on 3.9.3, I didn't bisect it yet.
dstansby commentedon Aug 20, 2025
This bisects back to 18909c7
iccir commentedon Jul 5, 2026
The problem is that we are using
-[NSApplication run]from bothwait_for_stdin()andshow(). When we call-[NSApplication stop:]fromstopWithEvent(), it will stop both-runcalls.The documentation for
-stop:is deceptive:That seems to indicate that some degree of re-entrancy is supported, but that only applies to modal sessions.
The implementation of -run and -stop are:
Pseudocode for -run and -stop:
Hence, once
-stop:is called, all nested calls to-runwill return since NSApplication's_runningivar is 0. Here's a vibe-coded example that demonstrates this:NestedRunTest.m
First pass at MPLEventLoop for matplotlib#30419
iccir commentedon Jul 7, 2026
I've been researching the history of #28981 for the past few days. First, no blame to anyone - NSRunLoop and friends are notoriously difficult. I had to review a bunch of code that I wrote 15 years ago :)
Historically,
wait_for_stdinwas using-[NSApplication nextEventMatchingMask:…]with a date of-[NSDate distantPast]inside of a while loop. That was always going to result in heavy CPU utilization as the run loop wasn't being allowed to sleep and then wake itself up.This was changed to use
-[NSApplication run]. That correctly loops with-[NSDate distantFuture]but has the side-effect of introducing this bug since re-entrant invocations to-runaren't supported.I do not understand the following comments at the top of
wait_for_stdin():There should be almost no CPU utilization when waiting for an event with an
untilDate:of-[NSDate distantFuture]. The only thing that I can think of is that there was thrashing from constantly setting up and tearing down the NSFileHandle notification. There definitely was CPU usage prior to #28981, but that was due to the loop with-distantPast.Note that I'm uneasy about any type of "short circuit" once NSApplication has been instantiated - there's more parts of AppKit that need a running event loop even when windows aren't present. My best guess is that the constant churning of the run loop (prior to #28981) made everyone associate run loops with high CPU usage; hence, this check got added during development as a precaution.
I'm still working on a solution that fixes both this while not regressing #26869 and #27515.