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 supplied and 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().
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 indeterminateCircularProgressspinner.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 toTrue(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:
Save password?unchecked).Preferences → Miscellaneous → User Interface → Save the application state?at its default (True).fe_sendauth: no password suppliedand opens a "Connect to server" dialog; the connection bar of each shows(Obtaining connection)with a spinner.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:RecalcStyleCountRecalcStyleDurationLayoutCountProcessTimeThreadTime480/sis 4 iframes × 120 Hz — the display is a 120 Hz ProMotion panel, so every spinner animates at 120 fps.LayoutCountstays near zero, so this is pure style recalculation from the CSS animations, not layout.Isolation, same session:
requestAnimationFrameloop blockedAnimation.pause()Each MUI
CircularProgressruns 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.jsxopens the password dialog on HTTP 428 but leavesobtaining_connset totruefor the whole time the dialog is open.ConnectionBarrenders the spinner directly off that flag:Both connection paths do this —
initializeQueryTool()(the restore path above) andupdateQueryToolConnection(). Only the cancel callback of the first one clears the flag.The ERD tool already avoids this:
ERDTool.jsxsetsconn_status: CONNECT_STATUS.FAILEDbefore callingconnectServerModal(), 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 leavesobtaining_connattruepermanently — 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_connbefore showing the dialog and set it again only when a connect request is actually issued. SinceconnectServer()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):
Additional context
ps-reported%CPUis an average since process start, not an instantaneous value, so it understates this considerably; the numbers above come from CDP metric deltas. Note also thatdocument.getAnimations()returns nothing useful from the top-level document here — pgAdmin hosts tools in iframes, so the animations are only visible viaiframe.contentDocument.getAnimations().