fix: Race condition can cause multiple Apollo server initializations under load (#9929)
This commit is contained in:
@@ -118,6 +118,20 @@ describe('ParseGraphQLServer', () => {
|
|||||||
expect(server3).not.toBe(server2);
|
expect(server3).not.toBe(server2);
|
||||||
expect(server3).toBe(server4);
|
expect(server3).toBe(server4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return same server reference when called 100 times in parallel', async () => {
|
||||||
|
parseGraphQLServer.server = undefined;
|
||||||
|
|
||||||
|
// Call _getServer 100 times in parallel
|
||||||
|
const promises = Array.from({ length: 100 }, () => parseGraphQLServer._getServer());
|
||||||
|
const servers = await Promise.all(promises);
|
||||||
|
|
||||||
|
// All resolved servers should be the same reference
|
||||||
|
const firstServer = servers[0];
|
||||||
|
servers.forEach((server, index) => {
|
||||||
|
expect(server).toBe(firstServer);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_getGraphQLOptions', () => {
|
describe('_getGraphQLOptions', () => {
|
||||||
|
|||||||
@@ -97,21 +97,38 @@ class ParseGraphQLServer {
|
|||||||
if (schemaRef === newSchemaRef && this._server) {
|
if (schemaRef === newSchemaRef && this._server) {
|
||||||
return this._server;
|
return this._server;
|
||||||
}
|
}
|
||||||
const { schema, context } = await this._getGraphQLOptions();
|
// It means a parallel _getServer call is already in progress
|
||||||
const apollo = new ApolloServer({
|
if (this._schemaRefMutex === newSchemaRef) {
|
||||||
csrfPrevention: {
|
return this._server;
|
||||||
// See https://www.apollographql.com/docs/router/configuration/csrf/
|
}
|
||||||
// needed since we use graphql upload
|
// Update the schema ref mutex to avoid parallel _getServer calls
|
||||||
requestHeaders: ['X-Parse-Application-Id'],
|
this._schemaRefMutex = newSchemaRef;
|
||||||
},
|
const createServer = async () => {
|
||||||
introspection: this.config.graphQLPublicIntrospection,
|
try {
|
||||||
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
|
const { schema, context } = await this._getGraphQLOptions();
|
||||||
schema,
|
const apollo = new ApolloServer({
|
||||||
});
|
csrfPrevention: {
|
||||||
await apollo.start();
|
// See https://www.apollographql.com/docs/router/configuration/csrf/
|
||||||
this._server = expressMiddleware(apollo, {
|
// needed since we use graphql upload
|
||||||
context,
|
requestHeaders: ['X-Parse-Application-Id'],
|
||||||
});
|
},
|
||||||
|
introspection: this.config.graphQLPublicIntrospection,
|
||||||
|
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
await apollo.start();
|
||||||
|
return expressMiddleware(apollo, {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Reset all mutexes and forward the error
|
||||||
|
this._server = null;
|
||||||
|
this._schemaRefMutex = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do not await so parallel request will wait the same promise ref
|
||||||
|
this._server = createServer();
|
||||||
return this._server;
|
return this._server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user