-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
FIX: macos: Use standard NSApp run loop in our input hook #28981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Running the example from #26869 on current main and waiting for The removal of the runloop timer was to fix this issue: #27515 I think that was the wrong "fix" of the issue, and the real way to fix that issue is to remove our own input hook and yield back to Python's terminal handling. In all of these examples, we should be at minimal CPU usage when sitting still. I'm still not sure how we could test/verify this? But it would be nice to add something to make sure we don't regress on this in the future. |
src/_macosx.m
Outdated
@@ -1122,7 +1128,7 @@ - (void)close | |||
{ | |||
[super close]; | |||
--FigureWindowCount; | |||
if (!FigureWindowCount) [NSApp stop: self]; | |||
if (!FigureWindowCount) stop(NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leaks the None
reference returned from stop
(at least on versions before it became immortal).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Added a decref to the return object now. I could change this back to the original [NSApp stop: self]
and add the PyOS_InputHook = NULL
call here too instead if that is better? I just figured it might be nice to try and go through one stop function everywhere we need it, although it is only in two places currently.
We want to defer back to Python's native input hook handling when we don't have any figures to interact with, otherwise our runloop will continue running unecessarily. Additionally, we need to add a short runloop burst in our hotloop to avoid saturating the CPU while waiting for input. (i.e. sitting on an idle figure would keep 100% cpu while waiting for an input() event without this runloop trigger).
298243c
to
b1822cf
Compare
@ksunden and I spent a while looking at this on Friday, but not sure where that landed / if he pushed anything. |
Feel free to push directly here or open a separate PR and I'm happy to review it too. |
You can see what I have been trying in https://github.com/ksunden/matplotlib/tree/macos-pyos-input-hook Essentially:
|
FYI, I think v3.6.3 is where some of this logic was referenced from with the double loop and breaking out of it: Lines 149 to 169 in abcbf3d
specifically, there was the call to the cfrunloop: Line 158 in abcbf3d
which is what the "sleep" you mention is from. |
// Short circuit if no windows are active | ||
// Rely on Python's input handling to manage CPU usage | ||
// This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still | ||
// need to be processed to properly close the windows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will also make us play nicer with any other OSX apps you launch from Python (which I am not aware of any others....)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use this method in the close()
method too instead of keeping track of it ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some folks embedding this in C++/objc it would appear: #27147
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is its own bit of fun, but I think this will not intersect with that issue because the only place the PyOS_InputHook
is run is from the Python repl (either readline or the new one) so if they are embedding Python in another application (which means they make an interpreter object and then stuff strings into it to run Python code) so I expect the repl will never run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oooh, just after I clicked comment I connected some more dots.
Yes, we probably should be using something like this in close
and than may fix #27147 .
src/_macosx.m
Outdated
@@ -192,16 +216,16 @@ void process_event(char const* cls_name, char const* fmt, ...) | |||
static bool backend_inited = false; | |||
|
|||
static void lazy_init(void) { | |||
// Run our own event loop while waiting for stdin on the Python side | |||
// this is needed to keep the application responsive while waiting for input | |||
PyOS_InputHook = wait_for_stdin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can probably move this back down so we are not re-setting it all the time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice updates @ksunden, thanks for pushing those!
I verified that this new change fixes the slow input typing lag regression I saw before as well.
src/_macosx.m
Outdated
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny | ||
untilDate: [NSDate distantPast] | ||
inMode: NSDefaultRunLoopMode | ||
dequeue: YES]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've been aligning on :
it seems like, so suggest doing the same here and below after the notificationID
indent now.
The name of this function is flushEvents()
but above you added stop_with_event()
is there a common form we want to use between them?
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny | |
untilDate: [NSDate distantPast] | |
inMode: NSDefaultRunLoopMode | |
dequeue: YES]; | |
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny | |
untilDate: [NSDate distantPast] | |
inMode: NSDefaultRunLoopMode | |
dequeue: YES]; |
// Short circuit if no windows are active | ||
// Rely on Python's input handling to manage CPU usage | ||
// This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still | ||
// need to be processed to properly close the windows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use this method in the close()
method too instead of keeping track of it ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I was on a call with Kyle as we worked through this so this should probably count as less than 1 full review.
I have run through this with a few different test cases as well and it looks good to me and is a nice simplification. It has been significantly changed by @ksunden, so I would consider it his PR now :) I "approve", although I can not technically click the button on my own PR. |
I'm going to go ahead and merge this. |
… in our input hook
…981-on-v3.9.x Backport PR #28981 on branch v3.9.x (FIX: macos: Use standard NSApp run loop in our input hook)
PR summary
We want to defer back to Python's native input hook handling when we don't have any figures to interact with, otherwise our runloop will continue running unecessarily.
Additionally, we need to add a short runloop burst in our hotloop to avoid saturating the CPU while waiting for input. (i.e. sitting on an idle figure would keep 100% cpu while waiting for an input() event without this runloop trigger).
closes #28960
PR checklist