pgadmin-org / pgadmin-org/pgadmin4
Idle CPU stays at ~35% when restored Query Tool tabs sit at the "Connect to server" password prompt
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.8k
- Forks
- 891
- Avg merge
- 4d 7h
- Merged PRs (30d)
- 8
Description
Describe the bug
When pgAdmin restores Query Tool tabs on startup and those tabs cannot connect because no password is saved, each tab opens a "Connect to server" dialog and its connection bar stays in the (Obtaining connection) state, showing an indeterminate CircularProgress spinner.
Those spinners keep animating for as long as the dialogs wait for input, which can be indefinitely. With four such tabs the application sits at ~35% CPU while completely idle, with no user interaction and no queries running.
The state is reached without doing anything unusual: Save the application state? defaults to True (web/pgadmin/misc/__init__.py:131), so simply quitting with Query Tool tabs open and restarting reproduces it whenever the server's password is not saved.
To Reproduce
Steps to reproduce the behavior:
- Connect to a server whose password is not saved (
Save password?unchecked). - Open four Query Tool tabs against that server.
- Leave
Preferences → Miscellaneous → User Interface → Save the application state?at its default (True). - Quit pgAdmin and start it again.
- The four tabs are restored. Each fails with
fe_sendauth: no password suppliedand opens a "Connect to server" dialog; the connection bar of each shows(Obtaining connection)with a spinner. - Leave the dialogs untouched and watch CPU usage.
Expected behavior
While a dialog is waiting for the user, pgAdmin should be idle. A progress spinner should only animate while a request is actually in flight, not for the entire time a modal waits for input.
Measurements
Taken on the renderer via CDP Performance.getMetrics, as 5-second differences:
| Metric | Per second |
|---|---|
RecalcStyleCount |
480.68 |
RecalcStyleDuration |
0.16 s (≈16% of one core) |
LayoutCount |
0.40 |
ProcessTime |
0.32 s |
ThreadTime |
0.30 s |
480/s is 4 iframes × 120 Hz — the display is a 120 Hz ProMotion panel, so every spinner animates at 120 fps. LayoutCount stays near zero, so this is pure style recalculation from the CSS animations, not layout.
Isolation, same session:
| Condition | Idle CPU |
|---|---|
| As restored (baseline) | 34.6% |
Query Tool iframe requestAnimationFrame loop blocked |
35.6% (no effect) |
The 8 spinner animations paused via Animation.pause() |
0.8% |
Each MUI CircularProgress runs two CSS animations, so four tabs is eight animations. Pausing them alone removes essentially all of the idle cost, which makes the spinners the cause rather than a correlate. That works out to roughly 9% CPU per stuck tab on a 120 Hz display; it will be lower on a 60 Hz one.
Cause in the code
web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx opens the password dialog on HTTP 428 but leaves obtaining_conn set to true for the whole time the dialog is open. ConnectionBar renders the spinner directly off that flag:
// web/pgadmin/tools/sqleditor/static/js/components/sections/ConnectionBar.jsx:52
if(connecting) {
return <CircularProgress style={{height: '18px', width: '18px'}} />;
}
Both connection paths do this — initializeQueryTool() (the restore path above) and updateQueryToolConnection(). Only the cancel callback of the first one clears the flag.
The ERD tool already avoids this: ERDTool.jsx sets conn_status: CONNECT_STATUS.FAILED before calling connectServerModal(), so its spinner stops while the dialog waits. The Query Tool does not do the equivalent.
A related defect on the same path
In updateQueryToolConnection(), the 428 branch's cancel callback is only /*This is intentional (SonarQube)*/. Cancelling that dialog therefore leaves obtaining_conn at true permanently — the connection bar keeps spinning with no dialog on screen and no way to clear it short of closing the tab — and the returned promise is never settled.
Suggested fix
Clear obtaining_conn before showing the dialog and set it again only when a connect request is actually issued. Since connectServer() also re-opens the dialog itself after a wrong password, it needs to report that transition back to the caller for the retry case to be covered too.
Desktop (please complete the following information):
- OS: macOS 26.6.2 (Apple silicon, 120 Hz ProMotion display)
- pgAdmin version: 9.17
- Mode: Desktop (Electron)
- Package type: macOS app bundle
Additional context
ps-reported %CPU is an average since process start, not an instantaneous value, so it understates this considerably; the numbers above come from CDP metric deltas. Note also that document.getAnimations() returns nothing useful from the top-level document here — pgAdmin hosts tools in iframes, so the animations are only visible via iframe.contentDocument.getAnimations().
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx, tracing initializeQueryTool() and updateQueryToolConnection(), then inspect ConnectionBar.jsx and the ERDTool.jsx connection-state handling. Reproduce the restored password-dialog path and verify that the spinner stops while input is pending, cancellation clears the state, and retry connections show progress only during an active request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, javascript, react
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100