-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
fix: ensure the startup is completed before calling startupCompleted
#7525
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
Conversation
Is this ready for review @sadortun? |
@dblythy only missing thing is to move I'll try to finish this and the few other open PR for next Monday. |
yes |
Currently the Security checks can run before everything is initialized. Should I fix this here, or in a separate issue ? For the tests to succeed, we will need a way to know the init has completed. Aka #7527 IMO, it would be best to fix/refactor the constructor init issues in one issue (#7527 and this PR) since it's all related. |
Are you referring to this? parse-server/src/ParseServer.js Lines 112 to 114 in 9ea355b
|
It's a separate issue, if it is currently an issue at all.
We cannot backport breaking changes for code that is outside the scope of a vulnerability. If the constructor change is breaking, but it is not required to fix this bug, it cannot be included in the bug fix. If the constructor change is not breaking, and it reduces complexity to change the constructor together with fixing the bug because they are depending on each other, then they can be done in one PR, because we can backport them. |
Yes. |
I really don't know if it's an issue or not, I'm not going to touch it for now, and they'll keep being executed even if the init is not completed. If it's an issue/bug/vulnerability, please open an issue. Also the |
Depends on the checks, intuitively I would say that they should be executed after initialization, but since the tests all pass, it shouldn't be an issue now. As long as the logger is loaded, I believe the checks are covered. When adding new checks, it may become necessary to address this.
It doesn't need await (and slow down server init), it just prints out logs. |
Ok, perfect! I'll update this PR to reflect what we discussed above and I'll let you know once it's done. |
Amazing! |
0d30231
to
4ca73f6
Compare
Codecov Report
@@ Coverage Diff @@
## alpha #7525 +/- ##
=======================================
Coverage 93.88% 93.88%
=======================================
Files 183 183
Lines 13624 13625 +1
=======================================
+ Hits 12791 12792 +1
Misses 833 833
Continue to review full report at Codecov.
|
Great, could you add a changelog entry and update the TODOs at the top? |
a50c34b
to
7750e52
Compare
Done. |
Are there any open questions that need to be resolved? |
The last discussion above, I'll try to finish It today, but I'm drowning in other stuff 🌊 |
7750e52
to
015be02
Compare
Here you go !
|
Yeah sure ! |
startupCompleted
Thanks for opening this pull request!
|
@sadortun is there any way we can add tests for this? that should also solve the coverage decrease. |
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.
Looks mostly good couple of minor changes.
I have addressed the coverage issues in my cloud code PR (#7564)
What do we need to merge this? |
Here @sadortun i think you only need to move The cloud code loading is sync, then we are safe to execute init if (cloud) {
addParseCloud();
if (typeof cloud === 'function') {
cloud(Parse);
} else if (typeof cloud === 'string') {
if (process.env.npm_package_type === 'module') {
import(path.resolve(process.cwd(), cloud));
} else {
require(path.resolve(process.cwd(), cloud));
}
} else {
throw "argument 'cloud' must either be a string or a function";
}
} before databaseController
.performInitialization()
.then(() => hooksController.load())
.then(async () => {
if (schema) {
await new DefinedSchemas(schema, this.config).execute();
}
if (serverStartComplete) {
serverStartComplete();
}
})
.catch(error => {
if (serverStartComplete) {
serverStartComplete(error);
} else {
console.error(error);
process.exit(1);
}
}); I'll also take a deeper look to the init on the defined schema PR EDIT: Since on defined schema PR we have .then(async () => {
if (schema) {
await new DefinedSchemas(schema, this.config).execute();
}
if (serverStartComplete) {
serverStartComplete();
}
}) Here just moving up the @sadortun i think you can easily write a failing test, trying to use a cloud function into the Also since this issue is related to the defined schema feature, here i suggest to close this PR and add directly the fix + test on the defined schema PR. @mtrezza @dblythy @sadortun what do you think |
Also here the correct structure to ensure proper startup of Parse Server. const server = new Promise((resolve) => {
const parseServer = ParseServer.start({ serverStartComplete: () => {
resolve(parseServer)
// Add a dedicated health check endpoint if you want
parseServer.expressApp.get('/ready', (req: any, res: any) => {
res.send('true')
})
})
})
await server
// Server is loaded
console.log('Started') @sadortun exemple code herer https://github.com/Moumouls/next-atomic-gql-server/blob/master/src/server.ts May be here you are right about returning a promise for const server = await ParseServer.start()
console.log('Server started') Refactoring the parse server start to |
Note this this PR (like any other) must not contain any breaking changes without deprecation, see #7680 (comment). |
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.
Hi @sadortun @dblythy @mtrezza
After a after a careful reading, this PR will introduce a new race issue. We have a similar issue like the import
.
Currently ParseServer was always started in a sync way. Cloud code is also loaded in sync.
Here the PR start to load the code (even with require
) in async since require
is now in a then
function.
So this PR introduce a race and also a breaking change if some developers do not use serverStartComplete
to correctly await the full start.
Here we cannot solve the import
race issue. Also to keep this PR focused on the original issue, i'll recommend to just move the old
if (cloud) {
addParseCloud();
if (typeof cloud === 'function') {
cloud(Parse);
} else if (typeof cloud === 'string') {
if (process.env.npm_package_type === 'module') {
import(path.resolve(process.cwd(), cloud));
} else {
require(path.resolve(process.cwd(), cloud));
}
} else {
throw "argument 'cloud' must either be a string or a function";
}
}
Before
databaseController
.performInitialization()
Then ParseServer will load correctly in this order:
- load cloud code (sync with require, same JS loop as the Parse Server constructor)
- start db init
- after db init load hooks
- after load hooks load DefinedSchemas
- after defined schemas, call server Start complete
Your suggestions make sense, Also just to clarify, the current [Parse release] init may or may not load CloudCode before or after the DB init sequence, since the DB init is made Moving the CloudCode init before would make the whole init process deterministic, which is good. Could it be possible that any user rely on the possibility that when CloudCode is loaded, DB was already initialized? I don't think so, but it might be something to consider if it could be a BC break if we fix this inconsistency. |
@mtrezza do you still plan in merging this (once updated)? I think I saw you wanted to wait in 2022? LMK, I have some time today to finish it so it could be ready later today. This PR should not introduce any BC break |
If there is no breaking change, we can merge this also into beta, as it seems to contain a fix. Is there anything that needs to change in this PR to incorporate the feedback above? Could you bring this up-to-date with alpha? |
Yep, I'll do that in ~1-2 hours. @Moumouls prochaine vides really great feedback
Will do. |
015be02
to
0e6ecba
Compare
I try to do my best here 🙂 I'll take a look here |
Promise.resolve() | ||
.then(async () => await databaseController.performInitialization()) | ||
.then(async () => await hooksController.load()) | ||
.then(async () => { | ||
if (schema) { | ||
await new DefinedSchemas(schema, this.config).execute(); | ||
} | ||
}) | ||
.then(async () => { | ||
if (serverStartComplete) { | ||
serverStartComplete(); | ||
await serverStartComplete(); | ||
} | ||
}) | ||
.catch(error => { | ||
.catch(async (error) => { | ||
if (serverStartComplete) { | ||
serverStartComplete(error); | ||
await serverStartComplete(error); | ||
} else { | ||
console.error(error); | ||
process.exit(1); | ||
} | ||
}); |
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 to help to cross de finish line, here the full correct structure.
// Note: Tests will start to fail if any validation happens after this is called.
databaseController.performInitialization()
.then(() => hooksController.load())
.then(async () => {
if (schema) {
await new DefinedSchemas(schema, this.config).execute();
}
})
.then(async () => {
if (serverStartComplete) {
await Promise.resolve(serverStartComplete());
}
})
.catch(async (error) => {
if (serverStartComplete) {
await Promise.resolve(serverStartComplete(error));
} else {
console.error(error);
process.exit(1);
}
});
Then in a futur PR that refactor the start up (BK change) the structure will be
try {
await databaseController.performInitialization()
await hooksController.load()
if (schema) {
await new DefinedSchemas(schema, this.config).execute();
}
if (serverStartComplete) {
await Promise.resolve(serverStartComplete());
}
} catch(e){
if (serverStartComplete) {
await Promise.resolve(serverStartComplete(error));
} else {
console.error(error);
process.exit(1);
}
}
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.
Mmm, not sure what's the difference.
I don't remember where, but we had a discussion about starting Promises chains with Promise.resolve()
to be consistent with other promises chains in Server and JS SDK
await Promise.resolve(serverStartComplete());
I don't think we need to wrap it around Promise.resolve
, await
will work either way.
Just a reminder that breaking changes are phased-in. So if we know already that a breaking change will be needed in the future, this PR should already deprecate the current approach and provide the new approach optionally. |
Closing as this feature is now part of the alpha branch |
New Pull Request Checklist
Issue Description
During #7418, i noticed that migrations were not always executed before the
cloud
were loaded, orserverStartComplete()
, which in some case cause problem, because ParseCloud is loaded before migrations had time to complete, which can be quite bad !This PR aims to ensure a proper sequential startup. In #7418 i will add
Schema Migrations
betweenhooksController.load()
andserverStartComplete()
I've spitted this here so if we need to discuss this topic on its own.
Related issue: #7527 #7418
Approach
TODOs before merging