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

feat: app lazy-loads app-context #2378

Merged
merged 16 commits into from
Jul 18, 2023
Merged

feat: app lazy-loads app-context #2378

merged 16 commits into from
Jul 18, 2023

Conversation

SgtPooki
Copy link
Member

@SgtPooki SgtPooki commented Jan 17, 2023

This PR is focused on updating ctx so that it can tolerate extension, updating, and no longer requires specific imports or tribal knowledge to use.

A side-effect of this PR is that the latency of app startup is reduced significantly.

  • chore: update ctx refactor
  • fix: tests
  • feat: add context.spec.js tests
  • fix: startup issues
  • feat: app context supports lazy functions

fixes #1177

Benchmarks

Note: nothing else was actively running on my laptop when the "statistical outliers" were detected. It's just that doing things in parallel reduces the chances for outliers to occur because we're doing everything as fast as we can.

> hyperfine --parameter-list branch main,feat/ctx-refactor --setup "git switch {branch}" "npm run test:e2e"
Benchmark 1: npm run test:e2e (branch = main)
  Time (mean ± σ):     26.950 s ±  0.610 s    [User: 13.728 s, System: 3.717 s]
  Range (min … max):   26.491 s … 28.485 s    10 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 2: npm run test:e2e (branch = feat/ctx-refactor)
  Time (mean ± σ):     22.183 s ±  0.229 s    [User: 11.185 s, System: 3.115 s]
  Range (min … max):   21.973 s … 22.783 s    10 runs

Summary
  npm run test:e2e (branch = feat/ctx-refactor) ran
    1.21 ± 0.03 times faster than npm run test:e2e (branch = main)
hyperfine --parameter-list branch main,feat/ctx-refactor --setup "git switch {branch}" --runs 25 -w 1 "npm run test:e2e"
Benchmark 1: npm run test:e2e (branch = main)
  Time (mean ± σ):     27.262 s ±  0.798 s    [User: 13.872 s, System: 3.751 s]
  Range (min … max):   26.923 s … 31.025 s    25 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 2: npm run test:e2e (branch = feat/ctx-refactor)
  Time (mean ± σ):     22.785 s ±  0.331 s    [User: 11.432 s, System: 3.327 s]
  Range (min … max):   22.396 s … 23.913 s    25 runs

Summary
  npm run test:e2e (branch = feat/ctx-refactor) ran
    1.20 ± 0.04 times faster than npm run test:e2e (branch = main)

@SgtPooki SgtPooki requested a review from a team as a code owner January 17, 2023 20:59
This was referenced Jan 17, 2023
Copy link
Member Author

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

these comments are on code I pushed prior to this PR self review..

src/daemon/index.js Outdated Show resolved Hide resolved
@@ -0,0 +1,25 @@
// @ts-check
Copy link
Member Author

Choose a reason for hiding this comment

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

just abstracting this out from src/index.js

await i18n
// @ts-expect-error
Copy link
Member Author

Choose a reason for hiding this comment

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

addressing some type errors

Comment on lines +10 to +13
if (process.env.HOME) {
app.setPath('home', process.env.HOME)
app.setPath('userData', path.join(process.env.HOME, 'data'))
}
Copy link
Member Author

Choose a reason for hiding this comment

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

addressing type error (cannot set path to string|undefined)

src/index.js Outdated Show resolved Hide resolved
@@ -47,7 +47,7 @@
"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
Copy link
Member Author

Choose a reason for hiding this comment

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

ran npx -y tsc and it output a ton of files into src/ that had me scratching my head for a while. I had updated code, but types were still suck to what they were when I ran tsc. 🙄

src/common/config-keys.js Show resolved Hide resolved
@@ -57,7 +58,7 @@ logger.info(`[meta] logs can be found on ${logsPath}`)

/**
*
* @param {AnalyticsTimeOptions & import('countly-sdk-nodejs').CountlyAddEventOptions}
* @param {AnalyticsTimeOptions & import('countly-sdk-nodejs').CountlyAddEventOptions} args
Copy link
Member Author

Choose a reason for hiding this comment

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

fixing error for unnamed parameter

src/context.js Outdated Show resolved Hide resolved
Comment on lines +57 to +58
// @ts-ignore
return value
Copy link
Member Author

Choose a reason for hiding this comment

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

typing is fairly loose in this right now, but resolving that would require updating the entire codebase

@tinytb tinytb mentioned this pull request Jan 18, 2023
@SgtPooki SgtPooki marked this pull request as draft May 4, 2023 17:46
@SgtPooki
Copy link
Member Author

SgtPooki commented Jul 8, 2023

e2e tests are passing for me locally now. idk whats going on here.

@SgtPooki
Copy link
Member Author

SgtPooki commented Jul 8, 2023

tried reverting the daemonReady function.. its passing for me locally again.. lets see if that works on CI

@SgtPooki SgtPooki marked this pull request as ready for review July 8, 2023 02:16
@SgtPooki SgtPooki requested a review from whizzzkid as a code owner July 8, 2023 02:16
Copy link
Contributor

@whizzzkid whizzzkid left a comment

Choose a reason for hiding this comment

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

Took me a while to grasp this, do we need a testing strategy around this? Added a few questions/concerns.

One quick nit (this is not really better in the current release), once ipfs-desktop boots, I have to double click to switch between tabs.

2387-dbl-clk.mov

src/context.js Outdated Show resolved Hide resolved
const originalFnPromise = this.getProp(propertyName)

return async (...args) => {
const originalFn = await originalFnPromise
Copy link
Contributor

Choose a reason for hiding this comment

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

uhm, why have a closure here?

Copy link
Member Author

@SgtPooki SgtPooki Jul 18, 2023

Choose a reason for hiding this comment

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

To delay the await until the function is actually called. If we await before it's called then we end up blocking before we need to.

src/context.js Outdated Show resolved Hide resolved
src/context.js Outdated
Comment on lines 110 to 111
this._createDeferredForProp(propertyName)
this._resolvePropForValue(propertyName, value)
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this needed?

src/context.js Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

overall I think this is abstracting a lot of complex logic and creating a promise(-hell-ish) landscape, I wonder if there's a way to simplify this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I hope you feel differently now. This is simplifying things drastically because all you do is set/get properties that may be set/gotten elsewhere. This unblocks a number of things we couldn't do previously, and doesn't require a complete re-write. see #1177 for more information

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, please double-check test/unit/context.spec.js to confirm that we have enough tests to ensure the core is functioning how we expect.

src/context.js Outdated Show resolved Hide resolved
src/handleError.js Show resolved Hide resolved
src/tray.js Outdated Show resolved Hide resolved
@SgtPooki
Copy link
Member Author

SgtPooki commented Jul 10, 2023

A logic table for the context class:

Context property exists? Is the backing promise fulfilled? Method called Is a deferred promise created? Returned Value
No N/A GetProp Yes A newly created deferred promise(unfulfilled)
No N/A SetProp Yes A newly created deferred promise(fulfilled)
Yes No GetProp No The found deferred promise (unfulfilled)
Yes No SetProp No The found deferred promise (fulfilled)
Yes Yes GetProp No The found deferred promise (fulfilled)
Yes Yes SetProp No We should throw/log an error here. Any getProps called for the property prior to this will have a hanging promise.

@SgtPooki
Copy link
Member Author

One quick nit (this is not really better in the current release), once ipfs-desktop boots, I have to double click to switch between tabs.

This is an existing bug: #762

Copy link
Member Author

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

another self review

Copy link
Member Author

Choose a reason for hiding this comment

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

I hope you feel differently now. This is simplifying things drastically because all you do is set/get properties that may be set/gotten elsewhere. This unblocks a number of things we couldn't do previously, and doesn't require a complete re-write. see #1177 for more information

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, please double-check test/unit/context.spec.js to confirm that we have enough tests to ensure the core is functioning how we expect.

src/tray.js Outdated Show resolved Hide resolved
Comment on lines +74 to +91
test('can get and call a function before its set', async () => {
let isPending = true
const spy = sinon.spy()
const actualLazyFn = (...args) => {
isPending = false
spy(...args)
}
const lazyFn = ctx.getFn('lazyFn')
const lazyFnCallPromise = lazyFn(123)
expect(isPending).toBe(true)
expect(spy.callCount).toBe(0)
expect(ctx.getProp('lazyFn')).resolves.toBe(actualLazyFn)
ctx.setProp('lazyFn', actualLazyFn)
await lazyFnCallPromise
expect(isPending).toBe(false)
expect(spy.callCount).toBe(1)
expect(spy.getCall(0).args).toEqual([123])
})
Copy link
Member Author

Choose a reason for hiding this comment

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

@whizzzkid this test confirms the lazy function execution works like we expect.

const fn = ctx.getFn('someFnThatMayBeSet_orNot')
await fn() // can be called at any time.. regardless of when we actually set the value.

Comment on lines +56 to +64
const cid = await addToIpfs(getFixtures('dir'))
expect(electron.clipboard.writeText.callCount).toEqual(1)
expect(notify.notifyError.callCount).toEqual(0)
expect(notify.notify.callCount).toEqual(1)
expect(cid.toString()).toEqual('bafybeieyzpi3qdqtj7b7hfzdpt2mnmaiicvtrj7ngmf2fc76byecj62gea')
})

test('add to ipfs multiple files', async () => {
const cid = await addToIpfs(ctx, getFixtures('dir', 'hello-world.txt'))
const cid = await addToIpfs(getFixtures('dir', 'hello-world.txt'))
Copy link
Member Author

Choose a reason for hiding this comment

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

we no longer need to pass ctx around.

Comment on lines +78 to +81
const apiAddress = urlParams.get('api')
if (apiAddress != null) {
window.localStorage.setItem('ipfsApi', apiAddress)
}
Copy link
Member Author

Choose a reason for hiding this comment

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

fixes console error spotted when testing

@SgtPooki SgtPooki self-assigned this Jul 18, 2023
Copy link
Contributor

@whizzzkid whizzzkid left a comment

Choose a reason for hiding this comment

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

lgtm

@SgtPooki SgtPooki merged commit 4641dcb into main Jul 18, 2023
@SgtPooki SgtPooki deleted the feat/ctx-refactor branch July 18, 2023 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Refactor ctx
2 participants