Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,29 @@ describe('ParseGraphQLServer', () => {
expect(server).toBe(firstServer);
});
});

it('does not leak process signal listeners across schema rebuilds (#9813)', async () => {
const originalNodeEnv = process.env.NODE_ENV;
// Apollo registers SIGINT/SIGTERM handlers only when NODE_ENV !== 'test'; force the
// production path so the (pre-fix) leak is exercised deterministically.
process.env.NODE_ENV = 'production';
try {
parseGraphQLServer.server = undefined;
await parseGraphQLServer._getServer();
const before = process.listenerCount('SIGTERM') + process.listenerCount('SIGINT');
// Force several real schema rebuilds (each new class changes the schema).
for (let i = 0; i < 5; i++) {
await new Parse.Object(`LeakClass${i}`).save();
await parseGraphQLServer._getServer();
}
const after = process.listenerCount('SIGTERM') + process.listenerCount('SIGINT');
// Fix: discarded ApolloServers register no process listeners, so the count is stable.
// Baseline: each rebuild leaks a SIGINT + SIGTERM listener (grows by 2 per rebuild).
expect(after).toBe(before);
} finally {
process.env.NODE_ENV = originalNodeEnv;
}
});
});

describe('_getGraphQLOptions', () => {
Expand Down
7 changes: 7 additions & 0 deletions src/GraphQL/ParseGraphQLServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ class ParseGraphQLServer {
// We need always true introspection because apollo server have changing behavior based on the NODE_ENV variable
// we delegate the introspection control to the IntrospectionControlPlugin
introspection: true,
// A new ApolloServer is created every time the GraphQL schema changes, and Parse Server
// already manages its own SIGTERM/SIGINT graceful shutdown (see configureListeners in
// ParseServer). Left to its default, apollo.start() registers a SIGINT and a SIGTERM
// listener on the global `process` per build and only removes them on apollo.stop() —
// which is never called on the discarded server — pinning every old ApolloServer and its
// entire GraphQL schema graph in memory forever. (#9813)
stopOnTerminationSignals: false,
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), SchemaSuggestionsControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)],
schema,
});
Expand Down
Loading