Skip to content

Make initialization of Connect synchronous#33444

Merged
ravicious merged 1 commit intomasterfrom
ravicious/sync-init
Oct 16, 2023
Merged

Make initialization of Connect synchronous#33444
ravicious merged 1 commit intomasterfrom
ravicious/sync-init

Conversation

@ravicious
Copy link
Copy Markdown
Member

In order to support deep links (#32188), we need to add listeners for certain events to make the app respond to opening it from a custom protocol link in the Web UI.

While playing with it, I noticed that nothing would happen if I clicked the link while the app wasn't running, the app would launch but it didn't show the dialog box I wired up in the event listener.

It turns out it's because initializeApp where I added the listener, and where we keep most of our other app and process listeners, is async. This means it doesn't get processed immediately on app launch, at best it happens during the next tick. This is too late for registering the open-url listener and the event is simply dropped.

This PR replaces the async actions during initialization with their sync equivalents, making initializeApp sync. The only async actions were file system operations related to createFileStorage – this is a function we use to store and read certain JSON blobs from disk, such as app config and app state that is persisted between app launches.

I packaged the app on all three platforms and verified that it launches without any problems.

Blocking the main process 😈

Generally, Electron discourages blocking the main process. If the main process is blocked, the UI will freeze until the main process is unblocked.

However, during initialization there's no UI. The main app window is not even shown until we call windowsManager.createWindow() in main.ts. Currently, windowsManager.createWindow() gets called only after the calls to createFileStorage finish. This means two things:

  1. Since there's no UI during initialization, blocking the main process doesn't freeze the UI.
  2. If the calls to createFileStorage are blocked on filesystem access, even today this means that the user will not see the window until those operations finish.

This means that this change doesn't introduce any adverse effects for the user, unless there's something I'm not aware of related to what Electron does during app start.

An alternative

An alternative to blocking the main process would be to segregate what happens during startup. We'd set up the listeners in sync code and the rest of the initialization process, such as calling createFileStorage, would happen in async code.

I did craft a quick proof of concept. The problem with this approach is that the open-url handler would likely need to know when the frontend app is ready in some way. Currently, all IPC communication is set up through our MainProcess class which depends on a couple of file storages initialized through createFileStorage.

So we'd either have to solve this problem or add some IPC handlers in another object which doesn't depend on file storage. This is not impossible but certainly adds some complexity.

Blocking the main process 😌

I did some quick tests where I tested the current app initialization, the segregated approach and the sync approach. With performance.now(), I measured the type from initial load of main.ts (taking a measurement right after imports) and then made another measurement once app.whenReady has resolved.

On both my MBP and on Windows VM, the sync version performed better than others.

My MBP:

Method First run Second run Third run
Current 136ms 125ms 122ms
Sync 79ms 79ms 78ms
Segregated 136ms 118ms 129ms

Windows VM:

Method First run Second run Third run Fourth run Fifth run
Sync 374ms 280ms 130ms 114ms 124ms
Segregated 465ms 141ms 143ms 141ms 148ms

This is needed in order to register event listeners for deep linking ASAP
on app start. Otherwise, the app won't be able to handle deep links on
cold start.
Copy link
Copy Markdown
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent PR description.

Comment on lines +49 to +62
/**
* createFileStorage reads and parses existing JSON structure from filePath or creates a new file
* under filePath with an empty object if the file is missing.
*
* createFileStorage itself uses blocking filesystem APIs but the functions of the returned
* FileStorage interface, such as write and replace, are async.
*/
// createFileStorage needs to be kept sync so that initialization of the app in main.ts can be sync.
// createFileStorage is called only during initialization, so blocking the main process during that
// time is acceptable.
//
// However, functions such as write or replace returned by createFileStorage need to be async as
// those are called after initialization.
export function createFileStorage(opts: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two comment styles here struck me as odd but now I understand that the comments under the jsdoc style comments are actually omitted in the documentation. neat trick

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I didn't want the comment about the function internals pollute the JSDoc comment. I could've moved the // comment to be inside the function, but since it's about createFileStorage in general, I decided to keep it there.

@public-teleport-github-review-bot public-teleport-github-review-bot Bot removed the request for review from gzdunek October 13, 2023 17:42
@ravicious ravicious enabled auto-merge October 16, 2023 08:51
@ravicious ravicious added this pull request to the merge queue Oct 16, 2023
Merged via the queue into master with commit 0005391 Oct 16, 2023
@ravicious ravicious deleted the ravicious/sync-init branch October 16, 2023 09:03
@public-teleport-github-review-bot
Copy link
Copy Markdown

@ravicious See the table below for backport results.

Branch Result
branch/v14 Create PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants