Dispose hive client on signal termination for yoga and envelop (#4109)

Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
This commit is contained in:
Valentin Cocaud 2024-03-18 15:33:40 +01:00 committed by GitHub
parent 89f3d0c56d
commit 441b71fdab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 57 additions and 6 deletions

View file

@ -0,0 +1,5 @@
---
"@graphql-hive/client": minor
---
Automatic disposal of managed clients for Yoga and Envelop plugins.

View file

@ -2,7 +2,7 @@ import { createHash } from 'crypto';
import axios from 'axios';
import type { DocumentNode } from 'graphql';
import type { ApolloServerPlugin } from '@apollo/server';
import { createHive } from './client.js';
import { autoDisposeSymbol, createHive } from './client.js';
import type {
HiveClient,
HivePluginOptions,
@ -263,7 +263,9 @@ export function hiveApollo(clientOrOptions: HiveClient | HivePluginOptions): Apo
if (isLegacyV0) {
return {
async serverWillStop() {
await hive.dispose();
if (hive[autoDisposeSymbol]) {
await hive.dispose();
}
},
} as any;
}
@ -272,7 +274,9 @@ export function hiveApollo(clientOrOptions: HiveClient | HivePluginOptions): Apo
return Promise.resolve({
async serverWillStop() {
await hive.dispose();
if (hive[autoDisposeSymbol]) {
await hive.dispose();
}
},
schemaDidLoadOrUpdate(schemaContext) {
if (ctx.schema !== schemaContext.apiSchema) {

View file

@ -169,6 +169,7 @@ export function createHive(options: HivePluginOptions): HiveClient {
return {
[hiveClientSymbol]: true,
[autoDisposeSymbol]: options.autoDispose ?? true,
info,
reportSchema,
collectUsage,
@ -177,6 +178,7 @@ export function createHive(options: HivePluginOptions): HiveClient {
}
export const hiveClientSymbol: unique symbol = Symbol('hive-client');
export const autoDisposeSymbol: unique symbol = Symbol('hive-auto-dispose');
function createPrinter(values: string[]) {
const maxLen = Math.max(...values.map(v => v.length)) + 4;

View file

@ -1,5 +1,5 @@
import type { Plugin } from '@envelop/types';
import { createHive } from './client.js';
import { autoDisposeSymbol, createHive } from './client.js';
import type { HiveClient, HivePluginOptions } from './internal/types.js';
import { isHiveClient } from './internal/utils.js';
@ -18,6 +18,22 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
void hive.info();
if (hive[autoDisposeSymbol]) {
if (global.process) {
const signals = Array.isArray(hive[autoDisposeSymbol])
? hive[autoDisposeSymbol]
: ['SIGINT', 'SIGTERM'];
for (const signal of signals) {
process.once(signal, () => hive.dispose());
}
} else {
console.error(
'It seems that GraphQL Hive is not being executed in Node.js. ' +
'Please attempt manual client disposal and use autoDispose: false option.',
);
}
}
return {
onSchemaChange({ schema }) {
hive.reportSchema({ schema });

View file

@ -1,10 +1,11 @@
import type { ExecutionArgs } from 'graphql';
import type { hiveClientSymbol } from '../client.js';
import type { autoDisposeSymbol, hiveClientSymbol } from '../client.js';
import type { AgentOptions } from './agent.js';
import type { SchemaReporter } from './reporting.js';
export interface HiveClient {
[hiveClientSymbol]: true;
[autoDisposeSymbol]: boolean | NodeJS.Signals[];
info(): Promise<void>;
reportSchema: SchemaReporter['report'];
collectUsage(): CollectUsageCallback;
@ -189,6 +190,13 @@ export type HivePluginOptions = OptionalWhenFalse<
* Disabled by default
*/
reporting?: HiveReportingPluginOptions | false;
/**
* Automatically dispose the client when the process is terminated
*
* Apollo: Enabled by default
* Yoga / Envelop: Enabled by default for SIGINT and SIGTERM signals
*/
autoDispose?: boolean | NodeJS.Signals[];
},
'enabled',
'token'

View file

@ -1,7 +1,7 @@
import { DocumentNode, ExecutionArgs, GraphQLSchema, Kind, parse } from 'graphql';
import type { GraphQLParams, Plugin } from 'graphql-yoga';
import LRU from 'tiny-lru';
import { createHive } from './client.js';
import { autoDisposeSymbol, createHive } from './client.js';
import type { CollectUsageCallback, HiveClient, HivePluginOptions } from './internal/types.js';
import { isHiveClient } from './internal/utils.js';
@ -27,6 +27,22 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
void hive.info();
if (hive[autoDisposeSymbol]) {
if (global.process) {
const signals = Array.isArray(hive[autoDisposeSymbol])
? hive[autoDisposeSymbol]
: ['SIGINT', 'SIGTERM'];
for (const signal of signals) {
process.once(signal, () => hive.dispose());
}
} else {
console.error(
'It seems that GraphQL Hive is not being executed in Node.js. ' +
'Please attempt manual client disposal and use autoDispose: false option.',
);
}
}
const parsedDocumentCache = LRU<DocumentNode>(10_000);
let latestSchema: GraphQLSchema | null = null;
const cache = new WeakMap<Request, CacheRecord>();