Skip to content

Commit 39a6a0b

Browse files
committed
Merge branch 'alpha' into before-password-reset-request
2 parents 6dee8bf + 8ff1d89 commit 39a6a0b

File tree

5 files changed

+56
-18
lines changed

5 files changed

+56
-18
lines changed

changelogs/CHANGELOG_alpha.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [8.5.0-alpha.9](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.8...8.5.0-alpha.9) (2025-11-17)
2+
3+
4+
### Bug Fixes
5+
6+
* Race condition can cause multiple Apollo server initializations under load ([#9929](https://github.com/parse-community/parse-server/issues/9929)) ([7d5e9fc](https://github.com/parse-community/parse-server/commit/7d5e9fcf3ceb0abad8ab49c75bc26f521a0f1bde))
7+
18
# [8.5.0-alpha.8](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.7...8.5.0-alpha.8) (2025-11-17)
29

310

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "8.5.0-alpha.8",
3+
"version": "8.5.0-alpha.9",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/ParseGraphQLServer.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ describe('ParseGraphQLServer', () => {
118118
expect(server3).not.toBe(server2);
119119
expect(server3).toBe(server4);
120120
});
121+
122+
it('should return same server reference when called 100 times in parallel', async () => {
123+
parseGraphQLServer.server = undefined;
124+
125+
// Call _getServer 100 times in parallel
126+
const promises = Array.from({ length: 100 }, () => parseGraphQLServer._getServer());
127+
const servers = await Promise.all(promises);
128+
129+
// All resolved servers should be the same reference
130+
const firstServer = servers[0];
131+
servers.forEach((server, index) => {
132+
expect(server).toBe(firstServer);
133+
});
134+
});
121135
});
122136

123137
describe('_getGraphQLOptions', () => {

src/GraphQL/ParseGraphQLServer.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,38 @@ class ParseGraphQLServer {
9797
if (schemaRef === newSchemaRef && this._server) {
9898
return this._server;
9999
}
100-
const { schema, context } = await this._getGraphQLOptions();
101-
const apollo = new ApolloServer({
102-
csrfPrevention: {
103-
// See https://www.apollographql.com/docs/router/configuration/csrf/
104-
// needed since we use graphql upload
105-
requestHeaders: ['X-Parse-Application-Id'],
106-
},
107-
introspection: this.config.graphQLPublicIntrospection,
108-
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
109-
schema,
110-
});
111-
await apollo.start();
112-
this._server = expressMiddleware(apollo, {
113-
context,
114-
});
100+
// It means a parallel _getServer call is already in progress
101+
if (this._schemaRefMutex === newSchemaRef) {
102+
return this._server;
103+
}
104+
// Update the schema ref mutex to avoid parallel _getServer calls
105+
this._schemaRefMutex = newSchemaRef;
106+
const createServer = async () => {
107+
try {
108+
const { schema, context } = await this._getGraphQLOptions();
109+
const apollo = new ApolloServer({
110+
csrfPrevention: {
111+
// See https://www.apollographql.com/docs/router/configuration/csrf/
112+
// needed since we use graphql upload
113+
requestHeaders: ['X-Parse-Application-Id'],
114+
},
115+
introspection: this.config.graphQLPublicIntrospection,
116+
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
117+
schema,
118+
});
119+
await apollo.start();
120+
return expressMiddleware(apollo, {
121+
context,
122+
});
123+
} catch (e) {
124+
// Reset all mutexes and forward the error
125+
this._server = null;
126+
this._schemaRefMutex = null;
127+
throw e;
128+
}
129+
}
130+
// Do not await so parallel request will wait the same promise ref
131+
this._server = createServer();
115132
return this._server;
116133
}
117134

0 commit comments

Comments
 (0)