@@ -48,22 +48,6 @@ const reportHeaderDefaults = {
48
48
uname : `${ os . platform ( ) } , ${ os . type ( ) } , ${ os . release ( ) } , ${ os . arch ( ) } )` ,
49
49
} ;
50
50
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
-
67
51
export function ApolloServerPluginUsageReporting < TContext extends BaseContext > (
68
52
options : ApolloServerPluginUsageReportingOptions < TContext > = Object . create (
69
53
null ,
@@ -145,9 +129,45 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
145
129
cache : LRUCache < string , OperationDerivedData > ;
146
130
} | null = null ;
147
131
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
+ } ;
151
171
152
172
const overriddenExecutableSchemaId = options . overrideReportedSchema
153
173
? computeCoreSchemaHash ( options . overrideReportedSchema )
@@ -193,21 +213,10 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
193
213
return id ;
194
214
}
195
215
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
-
206
216
async function sendAllReportsAndReportErrors ( ) : Promise < void > {
207
217
await Promise . all (
208
- Object . keys ( reportDataByExecutableSchemaId ) . map (
209
- ( executableSchemaId ) =>
210
- sendReportAndReportErrors ( executableSchemaId ) ,
218
+ [ ...reportByExecutableSchemaId . keys ( ) ] . map ( ( executableSchemaId ) =>
219
+ sendReportAndReportErrors ( executableSchemaId ) ,
211
220
) ,
212
221
) ;
213
222
}
@@ -229,13 +238,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
229
238
230
239
// Needs to be an arrow function to be confident that key is defined.
231
240
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 ) ;
236
242
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 )
239
246
) {
240
247
return ;
241
248
}
@@ -580,10 +587,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
580
587
const executableSchemaId =
581
588
overriddenExecutableSchemaId ??
582
589
executableSchemaIdForSchema ( schema ) ;
583
- const reportData = getReportData ( executableSchemaId ) ;
584
590
585
591
if ( includeOperationInUsageReporting === false ) {
586
- if ( resolvedOperation ) reportData . report . operationCount ++ ;
592
+ if ( resolvedOperation )
593
+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
594
+ . operationCount ++ ;
587
595
return ;
588
596
}
589
597
@@ -638,8 +646,6 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
638
646
overriddenExecutableSchemaId ??
639
647
executableSchemaIdForSchema ( schema ) ;
640
648
641
- const reportData = getReportData ( executableSchemaId ) ;
642
- const { report } = reportData ;
643
649
const { trace } = treeBuilder ;
644
650
645
651
let statsReportKey : string | undefined = undefined ;
@@ -677,9 +683,12 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
677
683
throw new Error ( `Error encoding trace: ${ protobufError } ` ) ;
678
684
}
679
685
680
- if ( resolvedOperation ) report . operationCount ++ ;
686
+ if ( resolvedOperation ) {
687
+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
688
+ . operationCount ++ ;
689
+ }
681
690
682
- report . addTrace ( {
691
+ getReportWhichMustBeUsedImmediately ( executableSchemaId ) . addTrace ( {
683
692
statsReportKey,
684
693
trace,
685
694
// We include the operation as a trace (rather than aggregated
@@ -705,7 +714,8 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
705
714
// If the buffer gets big (according to our estimate), send.
706
715
if (
707
716
sendReportsImmediately ||
708
- report . sizeEstimator . bytes >=
717
+ getReportWhichMustBeUsedImmediately ( executableSchemaId )
718
+ . sizeEstimator . bytes >=
709
719
( options . maxUncompressedReportSize || 4 * 1024 * 1024 )
710
720
) {
711
721
await sendReportAndReportErrors ( executableSchemaId ) ;
0 commit comments