@fluidframework/container-runtime 0.56.0 → 0.57.0-51086
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +9 -1
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +6 -6
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +65 -25
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +149 -79
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +62 -0
- package/dist/dataStore.d.ts.map +1 -0
- package/dist/dataStore.js +135 -0
- package/dist/dataStore.js.map +1 -0
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +9 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +14 -19
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +47 -21
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +195 -61
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/runningSummarizer.d.ts +1 -0
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +23 -15
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +6 -6
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryGenerator.d.ts +2 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +46 -28
- package/dist/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +9 -1
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +6 -6
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +65 -25
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +150 -80
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +62 -0
- package/lib/dataStore.d.ts.map +1 -0
- package/lib/dataStore.js +130 -0
- package/lib/dataStore.js.map +1 -0
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +9 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +13 -18
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +47 -21
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +197 -63
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runningSummarizer.d.ts +1 -0
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +23 -15
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +6 -6
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryGenerator.d.ts +2 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +46 -28
- package/lib/summaryGenerator.js.map +1 -1
- package/package.json +13 -13
- package/src/blobManager.ts +12 -1
- package/src/connectionTelemetry.ts +7 -6
- package/src/containerRuntime.ts +231 -103
- package/src/dataStore.ts +187 -0
- package/src/dataStoreContext.ts +1 -1
- package/src/dataStores.ts +18 -38
- package/src/garbageCollection.ts +283 -105
- package/src/index.ts +3 -1
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +25 -16
- package/src/summarizerTypes.ts +6 -8
- package/src/summaryGenerator.ts +72 -23
package/src/runningSummarizer.ts
CHANGED
|
@@ -93,6 +93,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
93
93
|
readonly resultsBuilder: SummarizeResultBuilder;
|
|
94
94
|
} | undefined;
|
|
95
95
|
private summarizeCount = 0;
|
|
96
|
+
private totalSuccessfulAttempts = 0;
|
|
96
97
|
|
|
97
98
|
private constructor(
|
|
98
99
|
baseLogger: ITelemetryLogger,
|
|
@@ -107,7 +108,14 @@ export class RunningSummarizer implements IDisposable {
|
|
|
107
108
|
{ disableHeuristics = false }: Readonly<Partial<ISummarizerOptions>> = {},
|
|
108
109
|
) {
|
|
109
110
|
this.logger = ChildLogger.create(
|
|
110
|
-
baseLogger, "Running",
|
|
111
|
+
baseLogger, "Running",
|
|
112
|
+
{
|
|
113
|
+
all: {
|
|
114
|
+
summarizeCount: () => this.summarizeCount,
|
|
115
|
+
summarizerSuccessfulAttempts: () => this.totalSuccessfulAttempts,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
);
|
|
111
119
|
|
|
112
120
|
if (!disableHeuristics) {
|
|
113
121
|
this.heuristicRunner = new SummarizeHeuristicRunner(
|
|
@@ -124,7 +132,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
124
132
|
maxAckWaitTime,
|
|
125
133
|
() => {
|
|
126
134
|
this.raiseSummarizingError("summaryAckWaitTimeout");
|
|
127
|
-
// Note:
|
|
135
|
+
// Note: summarizeCount (from ChildLogger definition) may be 0,
|
|
128
136
|
// since this code path is hit when RunningSummarizer first starts up,
|
|
129
137
|
// before this instance has kicked off a new summarize run.
|
|
130
138
|
this.logger.sendErrorEvent({
|
|
@@ -152,6 +160,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
152
160
|
this.heuristicData,
|
|
153
161
|
this.submitSummaryCallback,
|
|
154
162
|
this.raiseSummarizingError,
|
|
163
|
+
() => { this.totalSuccessfulAttempts++; },
|
|
155
164
|
this.summaryWatcher,
|
|
156
165
|
this.logger,
|
|
157
166
|
);
|
|
@@ -183,8 +192,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
183
192
|
switch (op.type) {
|
|
184
193
|
case MessageType.ClientLeave:
|
|
185
194
|
case MessageType.ClientJoin:
|
|
186
|
-
case MessageType.Propose:
|
|
187
|
-
case MessageType.Reject: {
|
|
195
|
+
case MessageType.Propose: {
|
|
188
196
|
// Synchronously handle quorum ops like regular ops
|
|
189
197
|
this.handleOp(undefined, op);
|
|
190
198
|
return;
|
|
@@ -338,27 +346,27 @@ export class RunningSummarizer implements IDisposable {
|
|
|
338
346
|
{ refreshLatestAck: true, fullTree: true, delaySeconds: 10 * 60 },
|
|
339
347
|
];
|
|
340
348
|
let overrideDelaySeconds: number | undefined;
|
|
341
|
-
let
|
|
342
|
-
let
|
|
349
|
+
let summaryAttempts = 0;
|
|
350
|
+
let summaryAttemptsPerPhase = 0;
|
|
343
351
|
|
|
344
352
|
let lastResult: { message: string; error: any; } | undefined;
|
|
345
353
|
|
|
346
|
-
for (let
|
|
354
|
+
for (let summaryAttemptPhase = 0; summaryAttemptPhase < attempts.length;) {
|
|
347
355
|
if (this.cancellationToken.cancelled) {
|
|
348
356
|
return;
|
|
349
357
|
}
|
|
350
358
|
|
|
351
|
-
|
|
352
|
-
|
|
359
|
+
summaryAttempts++;
|
|
360
|
+
summaryAttemptsPerPhase++;
|
|
353
361
|
|
|
354
|
-
const { delaySeconds: regularDelaySeconds = 0, ...options } = attempts[
|
|
362
|
+
const { delaySeconds: regularDelaySeconds = 0, ...options } = attempts[summaryAttemptPhase];
|
|
355
363
|
const delaySeconds = overrideDelaySeconds ?? regularDelaySeconds;
|
|
356
364
|
|
|
357
365
|
const summarizeProps: ITelemetryProperties = {
|
|
358
366
|
summarizeReason,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
367
|
+
summaryAttempts,
|
|
368
|
+
summaryAttemptsPerPhase,
|
|
369
|
+
summaryAttemptPhase: summaryAttemptPhase + 1, // make everything 1-based
|
|
362
370
|
...options,
|
|
363
371
|
};
|
|
364
372
|
|
|
@@ -377,14 +385,15 @@ export class RunningSummarizer implements IDisposable {
|
|
|
377
385
|
const result = await resultSummarize.receivedSummaryAckOrNack;
|
|
378
386
|
|
|
379
387
|
if (result.success) {
|
|
388
|
+
this.totalSuccessfulAttempts++;
|
|
380
389
|
return;
|
|
381
390
|
}
|
|
382
391
|
// Check for retryDelay that can come from summaryNack or upload summary flow.
|
|
383
392
|
// Retry the same step only once per retryAfter response.
|
|
384
393
|
overrideDelaySeconds = result.retryAfterSeconds;
|
|
385
|
-
if (overrideDelaySeconds === undefined ||
|
|
386
|
-
|
|
387
|
-
|
|
394
|
+
if (overrideDelaySeconds === undefined || summaryAttemptsPerPhase > 1) {
|
|
395
|
+
summaryAttemptPhase++;
|
|
396
|
+
summaryAttemptsPerPhase = 0;
|
|
388
397
|
}
|
|
389
398
|
lastResult = result;
|
|
390
399
|
}
|
package/src/summarizerTypes.ts
CHANGED
|
@@ -20,14 +20,6 @@ import {
|
|
|
20
20
|
import { ISummaryStats } from "@fluidframework/runtime-definitions";
|
|
21
21
|
import { ISummaryAckMessage, ISummaryNackMessage, ISummaryOpMessage } from "./summaryCollection";
|
|
22
22
|
|
|
23
|
-
declare module "@fluidframework/core-interfaces" {
|
|
24
|
-
export interface IFluidObject {
|
|
25
|
-
/** @deprecated - use `FluidObject<ISummarizer>` instead */
|
|
26
|
-
readonly ISummarizer?: ISummarizer;
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
/**
|
|
32
24
|
* @deprecated - This will be removed in a later release.
|
|
33
25
|
*/
|
|
@@ -144,8 +136,12 @@ export interface IEnqueueSummarizeOptions extends IOnDemandSummarizeOptions {
|
|
|
144
136
|
* only relevant at the root of the tree.
|
|
145
137
|
*/
|
|
146
138
|
export interface IGeneratedSummaryStats extends ISummaryStats {
|
|
139
|
+
/** The total number of data stores in the container. */
|
|
147
140
|
readonly dataStoreCount: number;
|
|
141
|
+
/** The number of data stores that were summarized in this summary. */
|
|
148
142
|
readonly summarizedDataStoreCount: number;
|
|
143
|
+
/** The number of data stores whose GC reference state was updated in this summary. */
|
|
144
|
+
readonly gcStateUpdatedDataStoreCount?: number;
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
/** Base results for all submitSummary attempts. */
|
|
@@ -166,6 +162,8 @@ export interface IGenerateSummaryTreeResult extends Omit<IBaseSummarizeResult, "
|
|
|
166
162
|
readonly summaryStats: IGeneratedSummaryStats;
|
|
167
163
|
/** Time it took to generate the summary tree and stats. */
|
|
168
164
|
readonly generateDuration: number;
|
|
165
|
+
/** True if the full tree regeneration with no handle reuse optimizations was forced. */
|
|
166
|
+
readonly forcedFullTree: boolean;
|
|
169
167
|
}
|
|
170
168
|
|
|
171
169
|
/** Results of submitSummary after uploading the tree to storage. */
|
package/src/summaryGenerator.ts
CHANGED
|
@@ -153,6 +153,7 @@ export class SummaryGenerator {
|
|
|
153
153
|
private readonly heuristicData: ISummarizeHeuristicData,
|
|
154
154
|
private readonly submitSummaryCallback: (options: ISubmitSummaryOptions) => Promise<SubmitSummaryResult>,
|
|
155
155
|
private readonly raiseSummarizingError: (errorCode: string) => void,
|
|
156
|
+
private readonly successfulSummaryCallback: () => void,
|
|
156
157
|
private readonly summaryWatcher: Pick<IClientSummaryWatcher, "watchSummary">,
|
|
157
158
|
private readonly logger: ITelemetryLogger,
|
|
158
159
|
) {
|
|
@@ -193,13 +194,21 @@ export class SummaryGenerator {
|
|
|
193
194
|
): Promise<void> {
|
|
194
195
|
const { refreshLatestAck, fullTree } = options;
|
|
195
196
|
const logger = ChildLogger.create(this.logger, undefined, { all: summarizeProps });
|
|
197
|
+
|
|
198
|
+
const timeSinceLastAttempt = Date.now() - this.heuristicData.lastAttempt.summaryTime;
|
|
199
|
+
const timeSinceLastSummary = Date.now() - this.heuristicData.lastSuccessfulSummary.summaryTime;
|
|
200
|
+
let summarizeTelemetryProps: Record<string, string | number | boolean | undefined> = {
|
|
201
|
+
fullTree,
|
|
202
|
+
timeSinceLastAttempt,
|
|
203
|
+
timeSinceLastSummary,
|
|
204
|
+
}
|
|
205
|
+
|
|
196
206
|
const summarizeEvent = PerformanceEvent.start(logger, {
|
|
197
207
|
eventName: "Summarize",
|
|
198
208
|
refreshLatestAck,
|
|
199
|
-
|
|
200
|
-
timeSinceLastAttempt: Date.now() - this.heuristicData.lastAttempt.summaryTime,
|
|
201
|
-
timeSinceLastSummary: Date.now() - this.heuristicData.lastSuccessfulSummary.summaryTime,
|
|
209
|
+
...summarizeTelemetryProps,
|
|
202
210
|
});
|
|
211
|
+
|
|
203
212
|
// Helper functions to report failures and return.
|
|
204
213
|
const getFailMessage =
|
|
205
214
|
(errorCode: keyof typeof summarizeErrors) => `${errorCode}: ${summarizeErrors[errorCode]}`;
|
|
@@ -231,10 +240,15 @@ export class SummaryGenerator {
|
|
|
231
240
|
|
|
232
241
|
// Wait to generate and send summary
|
|
233
242
|
this.summarizeTimer.start();
|
|
243
|
+
|
|
234
244
|
// Use record type to prevent unexpected value types
|
|
235
245
|
let summaryData: SubmitSummaryResult | undefined;
|
|
236
|
-
let generateTelemetryProps: Record<string, string | number | boolean | undefined> = {};
|
|
237
246
|
try {
|
|
247
|
+
const generateSummaryEvent = PerformanceEvent.start(logger, {
|
|
248
|
+
eventName: "Summarize",
|
|
249
|
+
...summarizeTelemetryProps,
|
|
250
|
+
});
|
|
251
|
+
|
|
238
252
|
summaryData = await this.submitSummaryCallback({
|
|
239
253
|
fullTree,
|
|
240
254
|
refreshLatestAck,
|
|
@@ -243,29 +257,32 @@ export class SummaryGenerator {
|
|
|
243
257
|
});
|
|
244
258
|
|
|
245
259
|
// Cumulatively add telemetry properties based on how far generateSummary went.
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
referenceSequenceNumber
|
|
249
|
-
|
|
250
|
-
|
|
260
|
+
const referenceSequenceNumber = summaryData.referenceSequenceNumber;
|
|
261
|
+
const opsSinceLastSummary =
|
|
262
|
+
referenceSequenceNumber - this.heuristicData.lastSuccessfulSummary.refSequenceNumber;
|
|
263
|
+
summarizeTelemetryProps = {
|
|
264
|
+
...summarizeTelemetryProps,
|
|
265
|
+
referenceSequenceNumber,
|
|
266
|
+
opsSinceLastAttempt: referenceSequenceNumber - this.heuristicData.lastAttempt.refSequenceNumber,
|
|
267
|
+
opsSinceLastSummary,
|
|
251
268
|
};
|
|
252
269
|
if (summaryData.stage !== "base") {
|
|
253
|
-
|
|
254
|
-
...
|
|
270
|
+
summarizeTelemetryProps = {
|
|
271
|
+
...summarizeTelemetryProps,
|
|
255
272
|
...summaryData.summaryStats,
|
|
256
273
|
generateDuration: summaryData.generateDuration,
|
|
257
274
|
};
|
|
258
275
|
|
|
259
276
|
if (summaryData.stage !== "generate") {
|
|
260
|
-
|
|
261
|
-
...
|
|
277
|
+
summarizeTelemetryProps = {
|
|
278
|
+
...summarizeTelemetryProps,
|
|
262
279
|
handle: summaryData.handle,
|
|
263
280
|
uploadDuration: summaryData.uploadDuration,
|
|
264
281
|
};
|
|
265
282
|
|
|
266
283
|
if (summaryData.stage !== "upload") {
|
|
267
|
-
|
|
268
|
-
...
|
|
284
|
+
summarizeTelemetryProps = {
|
|
285
|
+
...summarizeTelemetryProps,
|
|
269
286
|
clientSequenceNumber: summaryData.clientSequenceNumber,
|
|
270
287
|
};
|
|
271
288
|
}
|
|
@@ -273,11 +290,33 @@ export class SummaryGenerator {
|
|
|
273
290
|
}
|
|
274
291
|
|
|
275
292
|
if (summaryData.stage !== "submit") {
|
|
276
|
-
return fail("submitSummaryFailure", summaryData.error,
|
|
293
|
+
return fail("submitSummaryFailure", summaryData.error, summarizeTelemetryProps);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* With incremental summaries, if the full tree was not summarized, only data stores that changed should
|
|
298
|
+
* be summarized. A data store is considered changed if either or both of the following is true:
|
|
299
|
+
* - It has received an op.
|
|
300
|
+
* - Its reference state changed, i.e., it went from referenced to unreferenced or vice-versa.
|
|
301
|
+
*
|
|
302
|
+
* In the extreme case, every op can be for a different data store and each op can result in the reference
|
|
303
|
+
* state change of multiple data stores. So, the total number of data stores that are summarized should not
|
|
304
|
+
* exceed the number of ops since last summary + number of data store whose reference state changed.
|
|
305
|
+
*/
|
|
306
|
+
if (!fullTree && !summaryData.forcedFullTree) {
|
|
307
|
+
const { summarizedDataStoreCount, gcStateUpdatedDataStoreCount = 0 } = summaryData.summaryStats;
|
|
308
|
+
if (summarizedDataStoreCount > gcStateUpdatedDataStoreCount + opsSinceLastSummary) {
|
|
309
|
+
logger.sendErrorEvent({
|
|
310
|
+
eventName: "IncrementalSummaryViolation",
|
|
311
|
+
summarizedDataStoreCount,
|
|
312
|
+
gcStateUpdatedDataStoreCount,
|
|
313
|
+
opsSinceLastSummary,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
277
316
|
}
|
|
278
317
|
|
|
279
318
|
// Log event here on summary success only, as Summarize_cancel duplicates failure logging.
|
|
280
|
-
|
|
319
|
+
generateSummaryEvent.reportEvent("generate", {...summarizeTelemetryProps});
|
|
281
320
|
resultsBuilder.summarySubmitted.resolve({ success: true, data: summaryData });
|
|
282
321
|
} catch (error) {
|
|
283
322
|
return fail("submitSummaryFailure", error);
|
|
@@ -305,9 +344,10 @@ export class SummaryGenerator {
|
|
|
305
344
|
success: true,
|
|
306
345
|
data: { summarizeOp, broadcastDuration },
|
|
307
346
|
});
|
|
347
|
+
|
|
308
348
|
this.heuristicData.lastAttempt.summarySequenceNumber = summarizeOp.sequenceNumber;
|
|
309
349
|
logger.sendTelemetryEvent({
|
|
310
|
-
eventName: "
|
|
350
|
+
eventName: "Summarize_Op",
|
|
311
351
|
duration: broadcastDuration,
|
|
312
352
|
referenceSequenceNumber: summarizeOp.referenceSequenceNumber,
|
|
313
353
|
summarySequenceNumber: summarizeOp.sequenceNumber,
|
|
@@ -327,14 +367,22 @@ export class SummaryGenerator {
|
|
|
327
367
|
|
|
328
368
|
// Update for success/failure
|
|
329
369
|
const ackNackDuration = Date.now() - this.heuristicData.lastAttempt.summaryTime;
|
|
330
|
-
|
|
370
|
+
|
|
371
|
+
// adding new properties
|
|
372
|
+
summarizeTelemetryProps = {
|
|
331
373
|
ackWaitDuration: ackNackDuration,
|
|
332
|
-
|
|
374
|
+
ackNackSequenceNumber: ackNackOp.sequenceNumber,
|
|
333
375
|
summarySequenceNumber: ackNackOp.contents.summaryProposal.summarySequenceNumber,
|
|
376
|
+
...summarizeTelemetryProps,
|
|
334
377
|
};
|
|
335
378
|
if (ackNackOp.type === MessageType.SummaryAck) {
|
|
336
379
|
this.heuristicData.markLastAttemptAsSuccessful();
|
|
337
|
-
|
|
380
|
+
this.successfulSummaryCallback();
|
|
381
|
+
summarizeEvent.end({
|
|
382
|
+
...summarizeTelemetryProps,
|
|
383
|
+
handle: ackNackOp.contents.handle,
|
|
384
|
+
message: "summaryAck",
|
|
385
|
+
});
|
|
338
386
|
resultsBuilder.receivedSummaryAckOrNack.resolve({ success: true, data: {
|
|
339
387
|
summaryAckOp: ackNackOp,
|
|
340
388
|
ackNackDuration,
|
|
@@ -348,13 +396,14 @@ export class SummaryGenerator {
|
|
|
348
396
|
|
|
349
397
|
const error = new LoggingError(`summaryNack: ${message}`, { retryAfterSeconds });
|
|
350
398
|
logger.sendErrorEvent(
|
|
351
|
-
{ eventName: "SummaryNack", ...
|
|
399
|
+
{ eventName: "SummaryNack", ...summarizeTelemetryProps, retryAfterSeconds }, error);
|
|
400
|
+
|
|
352
401
|
assert(getRetryDelaySecondsFromError(error) === retryAfterSeconds, 0x25f /* "retryAfterSeconds" */);
|
|
353
402
|
// This will only set resultsBuilder.receivedSummaryAckOrNack, as other promises are already set.
|
|
354
403
|
return fail(
|
|
355
404
|
"summaryNack",
|
|
356
405
|
error,
|
|
357
|
-
{ ...
|
|
406
|
+
{ ...summarizeTelemetryProps, nackRetryAfter: retryAfterSeconds },
|
|
358
407
|
{ summaryNackOp: ackNackOp, ackNackDuration },
|
|
359
408
|
);
|
|
360
409
|
}
|