Skip to content
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

macOS: QuickTerminal under certain circumstances spins at 100% CPU #3998

Closed
mitchellh opened this issue Dec 30, 2024 · 7 comments · Fixed by #4055
Closed

macOS: QuickTerminal under certain circumstances spins at 100% CPU #3998

mitchellh opened this issue Dec 30, 2024 · 7 comments · Fixed by #4055
Labels
gui GUI or app issue regardless of platform (i.e. Swift, GTK) os/macos
Milestone

Comments

@mitchellh
Copy link
Contributor

Discussed in #3723

Originally posted by stuartcarnie December 28, 2024
Noticed that Ghostty was using 100% CPU, so I grabbed a CPU profile under Instruments and found that the hottest call stack was related to the Quick Terminal feature:

img

When I drill into that stack, QuickTerminalController.syncAppearance is a hot function. I currently don't have the window open, and I believe the culprit is this code:

// If our window is not visible, then delay this. This is possible specifically
// during state restoration but probably in other scenarios as well. To delay,
// we just loop directly on the dispatch queue. We have to delay because some
// APIs such as window blur have no effect unless the window is visible.
guard window.isVisible else {
// Weak window so that if the window changes or is destroyed we aren't holding a ref
DispatchQueue.main.async { [weak self] in self?.syncAppearance(config) }
return
}

Given the window isn't open, this will repeatedly call the syncAppearance function.

[!NOTE]

Re-opening the Quick Terminal reduces the CPU to nominal levels again.

To re-trigger the high CPU usage, force a reload of your configuration and watch it spike again.

@mitchellh mitchellh added gui GUI or app issue regardless of platform (i.e. Swift, GTK) os/macos labels Dec 30, 2024
@mitchellh mitchellh added this to the 1.0.1 milestone Dec 30, 2024
@mitchellh
Copy link
Contributor Author

We should flag a pending sync appearance and wait for the window to become visible (via notifications) to apply it.

@divs1502
Copy link

Hi, I was just visiting through the code trying to understand the bug what I believe we should check again if self is still valid before calling syncAppearance

DispatchQueue.main.async { [weak self] in if let self = self { self.syncAppearance(config) } }

@mitchellh
Copy link
Contributor Author

That would probably work but if we can avoid queueing up multiple that'd be ideal, so I think we should fix this in a more robust way.

@divs1502
Copy link

divs1502 commented Dec 30, 2024

Yes, correct then using the flag to prevent queuing multiple sync calls and avoiding unnecessary CPU usage spikes would be ideal.

@dmehala
Copy link
Collaborator

dmehala commented Dec 30, 2024

I observed this behavior and called it here #3742, however I didn't thought it was that bad and was hoping to get some feedback to address it directly in the PR. However, @mitchellh, I can understand if you prefer to address the performance issue separately.

EDIT: Well, I opened #4055.

@pijvari
Copy link

pijvari commented Dec 30, 2024

Hi, I was just visiting through the code trying to understand the bug what I believe we should check again if self is still valid before calling syncAppearance

DispatchQueue.main.async { [weak self] in if let self = self { self.syncAppearance(config) } }

Hmm, does this code change any behaviour? as the original code DispatchQueue.main.async { [weak self] in self?.syncAppearance(config) } have optional chaining in self?.syncAppearance... it wouldn't call syncAppearance anyway if self is nil?

dmehala added a commit to dmehala/ghostty that referenced this issue Dec 30, 2024
@divs1502
Copy link

Hi, I was just visiting through the code trying to understand the bug what I believe we should check again if self is still valid before calling syncAppearance
DispatchQueue.main.async { [weak self] in if let self = self { self.syncAppearance(config) } }

Hmm, does this code change any behaviour? as the original code DispatchQueue.main.async { [weak self] in self?.syncAppearance(config) } have optional chaining in self?.syncAppearance... it wouldn't call syncAppearance anyway if self is nil?

Yes you are correct although this does not explicitly change any code behavior but this will help maintain readability and avoid multiple calls ( if we need to perform multiple actions that depend on self, using if let self avoids repeating self? ).

mitchellh pushed a commit to dmehala/ghostty that referenced this issue Dec 30, 2024
mitchellh added a commit that referenced this issue Dec 30, 2024
# Description

The following code is causing an infinite loop that causes a CPU spikes
until the quick terminal is displayed:

https://github.com/ghostty-org/ghostty/blob/87bd0bb744d6a1c45022aa39f21219d0b6ff3261/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift#L314-L317

## Reproduce steps
1. Open Ghostty.
2. Open the Quick Terminal.
3. Close the Quick Terminal.
4. Reload the configuration (Ghostty > Reload Configuration or
`shift+cmd+,`).
5. Observe CPU spike.

## Fix
Now, `syncAppearance` doesn't postpone the process until it can be
consumed, and the appearance is synchronized once the animation is done
and the quick terminal is visible.

Fixes #3998
otherJL0 pushed a commit to otherJL0/ghostty that referenced this issue Jan 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gui GUI or app issue regardless of platform (i.e. Swift, GTK) os/macos
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants