@@ -46,22 +46,6 @@ const reportHeaderDefaults = {
4646 uname : `${ os . platform ( ) } , ${ os . type ( ) } , ${ os . release ( ) } , ${ os . arch ( ) } )` ,
4747} ;
4848
49- class ReportData {
50- report ! : OurReport ;
51- readonly header : ReportHeader ;
52- constructor ( executableSchemaId : string , graphRef : string ) {
53- this . header = new ReportHeader ( {
54- ...reportHeaderDefaults ,
55- executableSchemaId,
56- graphRef,
57- } ) ;
58- this . reset ( ) ;
59- }
60- reset ( ) {
61- this . report = new OurReport ( this . header ) ;
62- }
63- }
64-
6549export function ApolloServerPluginUsageReporting < TContext extends BaseContext > (
6650 options : ApolloServerPluginUsageReportingOptions < TContext > = Object . create (
6751 null ,
@@ -143,9 +127,45 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
143127 cache : LRUCache < string , OperationDerivedData > ;
144128 } | null = null ;
145129
146- const reportDataByExecutableSchemaId : {
147- [ executableSchemaId : string ] : ReportData | undefined ;
148- } = Object . create ( null ) ;
130+ // This map maps from executable schema ID (schema hash, basically) to the
131+ // report we'll send about it. That's because when we're using a gateway,
132+ // the schema can change over time, but each report needs to be about a
133+ // single schema. We avoid having this function be a memory leak by
134+ // removing values from it when we're in the process of sending reports.
135+ // That means we have to be very careful never to pull a Report out of it
136+ // and hang on to it for a while before writing to it, because the report
137+ // might have gotten sent and discarded in the meantime. So you should
138+ // only access the values of this Map via
139+ // getReportWhichMustBeUsedImmediately and getAndDeleteReport, and never
140+ // hang on to the value returned by getReportWhichMustBeUsedImmediately.
141+ const reportByExecutableSchemaId = new Map < string , OurReport > ( ) ;
142+ const getReportWhichMustBeUsedImmediately = (
143+ executableSchemaId : string ,
144+ ) : OurReport => {
145+ const existing = reportByExecutableSchemaId . get ( executableSchemaId ) ;
146+ if ( existing ) {
147+ return existing ;
148+ }
149+ const report = new OurReport (
150+ new ReportHeader ( {
151+ ...reportHeaderDefaults ,
152+ executableSchemaId,
153+ graphRef,
154+ } ) ,
155+ ) ;
156+ reportByExecutableSchemaId . set ( executableSchemaId , report ) ;
157+ return report ;
158+ } ;
159+ const getAndDeleteReport = (
160+ executableSchemaId : string ,
161+ ) : OurReport | null => {
162+ const report = reportByExecutableSchemaId . get ( executableSchemaId ) ;
163+ if ( report ) {
164+ reportByExecutableSchemaId . delete ( executableSchemaId ) ;
165+ return report ;
166+ }
167+ return null ;
168+ } ;
149169
150170 const overriddenExecutableSchemaId = options . overrideReportedSchema
151171 ? computeCoreSchemaHash ( options . overrideReportedSchema )
@@ -192,21 +212,10 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
192212 return id ;
193213 }
194214
195- const getReportData = ( executableSchemaId : string ) : ReportData => {
196- const existing = reportDataByExecutableSchemaId [ executableSchemaId ] ;
197- if ( existing ) {
198- return existing ;
199- }
200- const reportData = new ReportData ( executableSchemaId , graphRef ) ;
201- reportDataByExecutableSchemaId [ executableSchemaId ] = reportData ;
202- return reportData ;
203- } ;
204-
205215 async function sendAllReportsAndReportErrors ( ) : Promise < void > {
206216 await Promise . all (
207- Object . keys ( reportDataByExecutableSchemaId ) . map (
208- ( executableSchemaId ) =>
209- sendReportAndReportErrors ( executableSchemaId ) ,
217+ [ ...reportByExecutableSchemaId . keys ( ) ] . map ( ( executableSchemaId ) =>
218+ sendReportAndReportErrors ( executableSchemaId ) ,
210219 ) ,
211220 ) ;
212221 }
@@ -228,13 +237,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
228237
229238 // Needs to be an arrow function to be confident that key is defined.
230239 const sendReport = async ( executableSchemaId : string ) : Promise < void > => {
231- const reportData = getReportData ( executableSchemaId ) ;
232- const { report } = reportData ;
233- reportData . reset ( ) ;
234-
240+ const report = getAndDeleteReport ( executableSchemaId ) ;
235241 if (
236- Object . keys ( report . tracesPerQuery ) . length === 0 &&
237- report . operationCount === 0
242+ ! report ||
243+ ( Object . keys ( report . tracesPerQuery ) . length === 0 &&
244+ report . operationCount === 0 )
238245 ) {
239246 return ;
240247 }
@@ -577,10 +584,12 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
577584 treeBuilder . stopTiming ( ) ;
578585 const executableSchemaId =
579586 overriddenExecutableSchemaId ?? executableSchemaIdForSchema ( schema ) ;
580- const reportData = getReportData ( executableSchemaId ) ;
581587
582588 if ( includeOperationInUsageReporting === false ) {
583- if ( resolvedOperation ) reportData . report . operationCount ++ ;
589+ if ( resolvedOperation ) {
590+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
591+ . operationCount ++ ;
592+ }
584593 return ;
585594 }
586595
@@ -634,8 +643,6 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
634643 overriddenExecutableSchemaId ??
635644 executableSchemaIdForSchema ( schema ) ;
636645
637- const reportData = getReportData ( executableSchemaId ) ;
638- const { report } = reportData ;
639646 const { trace } = treeBuilder ;
640647
641648 let statsReportKey : string | undefined = undefined ;
@@ -673,9 +680,12 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
673680 throw new Error ( `Error encoding trace: ${ protobufError } ` ) ;
674681 }
675682
676- if ( resolvedOperation ) report . operationCount ++ ;
683+ if ( resolvedOperation ) {
684+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
685+ . operationCount ++ ;
686+ }
677687
678- report . addTrace ( {
688+ getReportWhichMustBeUsedImmediately ( executableSchemaId ) . addTrace ( {
679689 statsReportKey,
680690 trace,
681691 // We include the operation as a trace (rather than aggregated
@@ -700,7 +710,8 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
700710 // If the buffer gets big (according to our estimate), send.
701711 if (
702712 sendReportsImmediately ||
703- report . sizeEstimator . bytes >=
713+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
714+ . sizeEstimator . bytes >=
704715 ( options . maxUncompressedReportSize || 4 * 1024 * 1024 )
705716 ) {
706717 await sendReportAndReportErrors ( executableSchemaId ) ;
0 commit comments