@@ -48,22 +48,6 @@ const reportHeaderDefaults = {
4848 uname : `${ os . platform ( ) } , ${ os . type ( ) } , ${ os . release ( ) } , ${ os . arch ( ) } )` ,
4949} ;
5050
51- class ReportData {
52- report ! : OurReport ;
53- readonly header : ReportHeader ;
54- constructor ( executableSchemaId : string , graphRef : string ) {
55- this . header = new ReportHeader ( {
56- ...reportHeaderDefaults ,
57- executableSchemaId,
58- graphRef,
59- } ) ;
60- this . reset ( ) ;
61- }
62- reset ( ) {
63- this . report = new OurReport ( this . header ) ;
64- }
65- }
66-
6751export function ApolloServerPluginUsageReporting < TContext extends BaseContext > (
6852 options : ApolloServerPluginUsageReportingOptions < TContext > = Object . create (
6953 null ,
@@ -145,9 +129,45 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
145129 cache : LRUCache < string , OperationDerivedData > ;
146130 } | null = null ;
147131
148- const reportDataByExecutableSchemaId : {
149- [ executableSchemaId : string ] : ReportData | undefined ;
150- } = Object . create ( null ) ;
132+ // This map maps from executable schema ID (schema hash, basically) to the
133+ // report we'll send about it. That's because when we're using a gateway,
134+ // the schema can change over time, but each report needs to be about a
135+ // single schema. We avoid having this function be a memory leak by
136+ // removing values from it when we're in the process of sending reports.
137+ // That means we have to be very careful never to pull a Report out of it
138+ // and hang on to it for a while before writing to it, because the report
139+ // might have gotten sent and discarded in the meantime. So you should
140+ // only access the values of this Map via
141+ // getReportWhichMustBeUsedImmediately and getAndDeleteReport, and never
142+ // hang on to the value returned by getReportWhichMustBeUsedImmediately.
143+ const reportByExecutableSchemaId = new Map < string , OurReport > ( ) ;
144+ const getReportWhichMustBeUsedImmediately = (
145+ executableSchemaId : string ,
146+ ) : OurReport => {
147+ const existing = reportByExecutableSchemaId . get ( executableSchemaId ) ;
148+ if ( existing ) {
149+ return existing ;
150+ }
151+ const report = new OurReport (
152+ new ReportHeader ( {
153+ ...reportHeaderDefaults ,
154+ executableSchemaId,
155+ graphRef,
156+ } ) ,
157+ ) ;
158+ reportByExecutableSchemaId . set ( executableSchemaId , report ) ;
159+ return report ;
160+ } ;
161+ const getAndDeleteReport = (
162+ executableSchemaId : string ,
163+ ) : OurReport | null => {
164+ const report = reportByExecutableSchemaId . get ( executableSchemaId ) ;
165+ if ( report ) {
166+ reportByExecutableSchemaId . delete ( executableSchemaId ) ;
167+ return report ;
168+ }
169+ return null ;
170+ } ;
151171
152172 const overriddenExecutableSchemaId = options . overrideReportedSchema
153173 ? computeCoreSchemaHash ( options . overrideReportedSchema )
@@ -193,21 +213,10 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
193213 return id ;
194214 }
195215
196- const getReportData = ( executableSchemaId : string ) : ReportData => {
197- const existing = reportDataByExecutableSchemaId [ executableSchemaId ] ;
198- if ( existing ) {
199- return existing ;
200- }
201- const reportData = new ReportData ( executableSchemaId , graphRef ) ;
202- reportDataByExecutableSchemaId [ executableSchemaId ] = reportData ;
203- return reportData ;
204- } ;
205-
206216 async function sendAllReportsAndReportErrors ( ) : Promise < void > {
207217 await Promise . all (
208- Object . keys ( reportDataByExecutableSchemaId ) . map (
209- ( executableSchemaId ) =>
210- sendReportAndReportErrors ( executableSchemaId ) ,
218+ [ ...reportByExecutableSchemaId . keys ( ) ] . map ( ( executableSchemaId ) =>
219+ sendReportAndReportErrors ( executableSchemaId ) ,
211220 ) ,
212221 ) ;
213222 }
@@ -229,13 +238,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
229238
230239 // Needs to be an arrow function to be confident that key is defined.
231240 const sendReport = async ( executableSchemaId : string ) : Promise < void > => {
232- const reportData = getReportData ( executableSchemaId ) ;
233- const { report } = reportData ;
234- reportData . reset ( ) ;
235-
241+ const report = getAndDeleteReport ( executableSchemaId ) ;
236242 if (
237- Object . keys ( report . tracesPerQuery ) . length === 0 &&
238- report . operationCount === 0
243+ ! report ||
244+ ( Object . keys ( report . tracesPerQuery ) . length === 0 &&
245+ report . operationCount === 0 )
239246 ) {
240247 return ;
241248 }
@@ -580,10 +587,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
580587 const executableSchemaId =
581588 overriddenExecutableSchemaId ??
582589 executableSchemaIdForSchema ( schema ) ;
583- const reportData = getReportData ( executableSchemaId ) ;
584590
585591 if ( includeOperationInUsageReporting === false ) {
586- if ( resolvedOperation ) reportData . report . operationCount ++ ;
592+ if ( resolvedOperation )
593+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
594+ . operationCount ++ ;
587595 return ;
588596 }
589597
@@ -638,8 +646,6 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
638646 overriddenExecutableSchemaId ??
639647 executableSchemaIdForSchema ( schema ) ;
640648
641- const reportData = getReportData ( executableSchemaId ) ;
642- const { report } = reportData ;
643649 const { trace } = treeBuilder ;
644650
645651 let statsReportKey : string | undefined = undefined ;
@@ -677,9 +683,12 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
677683 throw new Error ( `Error encoding trace: ${ protobufError } ` ) ;
678684 }
679685
680- if ( resolvedOperation ) report . operationCount ++ ;
686+ if ( resolvedOperation ) {
687+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
688+ . operationCount ++ ;
689+ }
681690
682- report . addTrace ( {
691+ getReportWhichMustBeUsedImmediately ( executableSchemaId ) . addTrace ( {
683692 statsReportKey,
684693 trace,
685694 // We include the operation as a trace (rather than aggregated
@@ -705,7 +714,8 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
705714 // If the buffer gets big (according to our estimate), send.
706715 if (
707716 sendReportsImmediately ||
708- report . sizeEstimator . bytes >=
717+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
718+ . sizeEstimator . bytes >=
709719 ( options . maxUncompressedReportSize || 4 * 1024 * 1024 )
710720 ) {
711721 await sendReportAndReportErrors ( executableSchemaId ) ;
0 commit comments