Skip to content

Commit 7d5e9fc

Browse files
authored
fix: Race condition can cause multiple Apollo server initializations under load (#9929)
1 parent 306c5fd commit 7d5e9fc

File tree

2 files changed

+46
-15
lines changed

2 files changed

+46
-15
lines changed

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)