-
-
Notifications
You must be signed in to change notification settings - Fork 41
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
fix: delay plugin loading until next tick #177
Conversation
plugin.js
Outdated
@@ -40,6 +40,13 @@ function promise () { | |||
return obj | |||
} | |||
|
|||
function loadPluginNextTick (toLoad, cb) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment explaining why this is necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
plugin.js
Outdated
const parent = this | ||
process.nextTick(() => { | ||
loadPlugin.call(parent, toLoad, cb) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please avoid creating this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey @mcollina - apologies if I'm being dense here, but is the issue here that this work is being delayed until the next tick? or that it's being done within an anonymous function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just with the anonymous function. Instead of allocating a new closure you can use a top-level function and pass arguments to it:
process.nextTick(console.log, 42)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh wow - I had no idea that process.nextTick()
could accept a list of args 🤦
I just push a commit with this refactor. The function still needs to use .bind(parent)
to set the right context for the loadPlugin
call (which still creates a new function internally), but the refactor is a bit more readable IMO.
test/await-after.test.js
Outdated
t.notOk(thirdLoaded, 'third is not loaded') | ||
t.notOk(fourthLoaded, 'fourth is not loaded') | ||
|
||
const contents = await fs.readFile(path.join(__dirname, 'fixtures', 'dummy.txt'), 'utf-8') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is your intention reading it twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanting to mix in a bit of async FS I/O in various contexts (e.g., both at the top-level and within a plugin) per @Eomm's request on the original PR:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - so it looks like this top-level async FS read (a late addition to the original PR) was what caused the test to pass without my changes. It prevented the bug from occurring. I've removed the top-level FS read from the test and verified that test fails as expected without my changes.
@Eomm - can you verify that the remaining FS read that occurs within the plugin registration function on L363 still satisfies your original request to mix in some async FS I/O in the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests added are passing even without your change. Could you take a look?
ooof - that's obviously unexpected. Will take a look ASAP. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this case can be added in test cases:
const t = require('tap')
const avvio = require('avvio')
let record = ''
const app = {}
avvio(app, { autostart: false })
function sleep(timeMs){
return new Promise((resolve) => setTimeout(resolve, timeMs));
}
(async () => {
await app.use(async function plugin0() {
t.pass("plugin0 init");
record += 'plugin0|'
})
await app.use(async function plugin1() {
t.pass("plugin1 init");
await sleep(500);
record += 'plugin1|'
})
await sleep(1);
await app.use(async function plugin2() {
t.pass("plugin2 init");
await sleep(500);
record += 'plugin2|'
})
record += 'ready'
t.equal(record, 'plugin0|plugin1|plugin2|ready')
})()
Nice! Would you like to see that test in place of the existing test added in this PR? Or alongside it? It's more or less testing the same behavior AFAICT, but with a different style of assertions. |
Actually, I see that your test case is testing the |
Also, the false negative mentioned above was addressed by removing the top-level async FS read that was preventing the underlying bug from occurring when my code wasn't present: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
* fix: delay plugin loading until next tick * move `loadPluginNextTick` function alongside `loadPlugin` and add comment * refactor loadPluginNextTick to use a bound function rather than an anonymous closure * remove top-level FS read from unit test that was preventing the bug from occurring * add additional test case
😬 I mean, what are the odds I screw this up twice? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!
Description
This PR is a followup to #173. That original PR attempted to fix the async plugin loading issue described in fastify/3726 by delaying each plugin's
after
promise resolution until the next tick. However, this resulted in a subtle but significant change where subsequent plugin registration functions were now being enqueued inthis.q
and executed on the same tick of the event loop as registration, a consequence of the registration function being the only item inthis.q
. We observed a symptom of this change in the unrelated unit test linked below:https://github.com/fastify/avvio/pull/173/files#r814200533
We didn't think the above was a major/breaking change in behavior, but that proved to be incorrect per this bug report:
fastify/fastify-cors#189
This PR improves on the initial PR by delaying the entire loading of each plugin to the next tick rather than just the plugin's
after
promise resolution. This change has been tested in @pvanagtmaal's test repo and produces the expected output for both the working and previously-broken test cases:It also doesn't require any unexpected changes in unrelated test cases like the previous PR.
Checklist
npm run test
andnpm run benchmark
and the Code of conduct