Skip to content

fix(startup): load the application surface only after the startup page settled - #96

Closed
BIackFIame wants to merge 1 commit into
howdeploy:mainfrom
BIackFIame:fix/startup-aborted
Closed

BIackFIame wants to merge 1 commit into
howdeploy:mainfrom
BIackFIame:fix/startup-aborted

Conversation

@BIackFIame

Copy link
Copy Markdown
Contributor

PR description for branch fix/startup-aborted (one commit on main 20f6855). #94 and #95 are already merged, so this is a follow-up PR against main, not a comment on #94 or #95.

Problem

On a busy machine, startup sometimes fails and shows the startup failure page:

ERR_ABORTED (-3) loading 'data:text/html;charset=utf-8,...'

It started with "perf(startup): start services while the startup page loads" (#94). Since that change, services start while the startup page (a data: URL) is still loading. When services finish first, loadFile for the application surface starts while the startup page is still committing or loading in its own renderer process. If the application surface commits first, the page's did-fail-load (-3, the data: URL) arrives afterwards, while the loadFile promise is still waiting. Electron's loadURL/loadFile promise takes the first main-frame load failure it sees as its own. So the application load rejects with the startup page's abort, and startApplication shows the failure page.

The superseded flag from #94 did not cover this. It kept the page's own promise from reporting the abort, but the error came through the application load's promise.

Change

  • createWindow still starts the page load without awaiting it. The page load now resolves to the error to report, or to null. It never rejects.
  • startApplication still starts services right away. Before it loads the application surface, it waits for the page load to settle. On a normal launch the page has usually finished by the time services are up.
  • A close during the page load is still a quiet quit. A real page error on a live window still fails startup.

Tests

  • tests/startup-lifecycle.test.mjs: new sequencing test with injected fakes. It fails on main (the application surface loads while the page is still pending). It passes with this change and also covers a real page error and a close while waiting.
  • npm run typecheck, full suite (--test-concurrency=2, 1155/1155), electron-vite build: pass.

Launch loop

Sequential hidden launches with a throw-away HOME and the keychain stubbed. Each launch runs until the window is ready or startup fails. Plain launches fail too rarely to measure: 0 of 40 on every build, including main. To make the race reproducible, the loop pauses a random helper process (renderer, GPU or utility) three times during the first navigations, for 20 to 300 ms each (SIGSTOP/SIGCONT). This is the kind of descheduling a loaded machine produces.

Build Failed startups (with pauses)
main before the #89–#95 stack (31f287d) 0 / 40
#89 head (7d6009c) 0 / 40
parent of the #94 startup commit (b57b9dd) 0 / 40
#94 startup commit (6a40740) 1 / 40
#94 head (8accbd1) 2 / 40 and 4 / 30
main (20f6855) 4 / 40 and 6 / 40
this branch 0 / 80

Every failure has the same trace: the page's did-fail-load -3 arrives after the application surface committed, and then comes "startup failed" with the data: URL.

Cost: without pauses, the median time from launch to a ready window goes from 342 ms to 379 ms (40 launches each). The application surface now starts after the page has loaded (about 85 ms after the page load starts) instead of when services are up (about 45 ms). For reference, the parent of the #94 startup commit, where the page and services ran one after the other, measured 457 ms in the same loop. That build has different renderer code, so the comparison is only approximate.

…e settled

Startup could fail with "ERR_ABORTED (-3) loading 'data:text/html...'"
when the machine was busy. Since services start while the startup page
loads, the application surface could start its navigation while the
page was still committing or loading in its own renderer process. The
application surface then committed first, and the page's ERR_ABORTED
arrived afterwards, while the loadFile promise was still waiting.
Electron's loadURL/loadFile promise takes the first main-frame
did-fail-load it sees as its own, so the application load rejected with
the startup page's abort and startup showed the failure page.

Services still start while the page loads. The application surface now
waits until the page load has settled, which it usually has by the time
services are up. A close during the page load is still a quiet quit, and
a real page error on a live window still fails startup.

Measured with 40 sequential hidden launches per build, each helper
process (renderer, GPU, utility) paused at random for 20 to 300 ms
during the first navigations: origin/main failed 4 and 6 of 40, the
same loop with this change 0 of 80. The build before
"perf(startup): start services while the startup page loads" had 0 of
40. Without pauses all builds start 40 of 40; the median time to a
ready window goes from 342 to 379 ms.
@BIackFIame

Copy link
Copy Markdown
Contributor Author

Combined into #100 together with the other post-merge fixes, so they can be reviewed in one place. The commits are unchanged.

@BIackFIame BIackFIame closed this Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant