@fluidframework/runtime-utils 1.2.7 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/dist/objectstorageutils.d.ts.map +1 -1
- package/dist/objectstorageutils.js +2 -12
- package/dist/objectstorageutils.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/requestParser.d.ts.map +1 -1
- package/dist/requestParser.js +1 -6
- package/dist/requestParser.js.map +1 -1
- package/dist/runtimeFactoryHelper.d.ts.map +1 -1
- package/dist/runtimeFactoryHelper.js +1 -6
- package/dist/runtimeFactoryHelper.js.map +1 -1
- package/dist/summarizerNode/summarizerNode.d.ts +18 -16
- package/dist/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summarizerNode/summarizerNode.js +63 -87
- package/dist/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summarizerNode/summarizerNodeUtils.d.ts +5 -38
- package/dist/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summarizerNode/summarizerNodeUtils.js +1 -94
- package/dist/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summarizerNode/summarizerNodeWithGc.d.ts +7 -3
- package/dist/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summarizerNode/summarizerNodeWithGc.js +7 -3
- package/dist/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/dist/summaryUtils.d.ts.map +1 -1
- package/dist/summaryUtils.js +4 -13
- package/dist/summaryUtils.js.map +1 -1
- package/lib/objectstorageutils.d.ts.map +1 -1
- package/lib/objectstorageutils.js +2 -12
- package/lib/objectstorageutils.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/requestParser.d.ts.map +1 -1
- package/lib/requestParser.js +1 -6
- package/lib/requestParser.js.map +1 -1
- package/lib/runtimeFactoryHelper.d.ts.map +1 -1
- package/lib/runtimeFactoryHelper.js +1 -6
- package/lib/runtimeFactoryHelper.js.map +1 -1
- package/lib/summarizerNode/summarizerNode.d.ts +18 -16
- package/lib/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summarizerNode/summarizerNode.js +64 -88
- package/lib/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summarizerNode/summarizerNodeUtils.d.ts +5 -38
- package/lib/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summarizerNode/summarizerNodeUtils.js +0 -91
- package/lib/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summarizerNode/summarizerNodeWithGc.d.ts +7 -3
- package/lib/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summarizerNode/summarizerNodeWithGc.js +7 -3
- package/lib/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/summaryUtils.d.ts.map +1 -1
- package/lib/summaryUtils.js +4 -13
- package/lib/summaryUtils.js.map +1 -1
- package/package.json +18 -18
- package/src/objectstorageutils.ts +2 -10
- package/src/packageVersion.ts +1 -1
- package/src/requestParser.ts +1 -5
- package/src/runtimeFactoryHelper.ts +1 -5
- package/src/summarizerNode/summarizerNode.ts +64 -111
- package/src/summarizerNode/summarizerNodeUtils.ts +4 -127
- package/src/summarizerNode/summarizerNodeWithGc.ts +7 -3
- package/src/summaryUtils.ts +4 -11
|
@@ -25,21 +25,13 @@ export async function listBlobsAtTreePath(inputTree: ITree | undefined, path: st
|
|
|
25
25
|
while (tree?.entries !== undefined && pathParts.length > 0) {
|
|
26
26
|
const part = pathParts.shift();
|
|
27
27
|
const treeEntry = tree.entries.find((value) => {
|
|
28
|
-
|
|
29
|
-
return true;
|
|
30
|
-
} else {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
28
|
+
return value.type === "Tree" && value.path === part ? true : false;
|
|
33
29
|
});
|
|
34
30
|
|
|
35
31
|
// this check is largely superfluous due to the same check being done
|
|
36
32
|
// immediately above. the type system, however, is not aware of this.
|
|
37
33
|
// so we must redundantly determine that the entry's type is "Tree"
|
|
38
|
-
|
|
39
|
-
tree = treeEntry.value;
|
|
40
|
-
} else {
|
|
41
|
-
tree = undefined;
|
|
42
|
-
}
|
|
34
|
+
tree = treeEntry?.type === "Tree" ? treeEntry.value : undefined;
|
|
43
35
|
}
|
|
44
36
|
if (tree?.entries === undefined || pathParts.length !== 0) {
|
|
45
37
|
throw new Error("path does not exist");
|
package/src/packageVersion.ts
CHANGED
package/src/requestParser.ts
CHANGED
|
@@ -40,11 +40,7 @@ export class RequestParser implements IRequest {
|
|
|
40
40
|
|
|
41
41
|
protected constructor(private readonly request: Readonly<IRequest>) {
|
|
42
42
|
const queryStartIndex = this.request.url.indexOf("?");
|
|
43
|
-
|
|
44
|
-
this.query = this.request.url.substring(queryStartIndex);
|
|
45
|
-
} else {
|
|
46
|
-
this.query = "";
|
|
47
|
-
}
|
|
43
|
+
this.query = queryStartIndex >= 0 ? this.request.url.substring(queryStartIndex) : "";
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
public get url(): string {
|
|
@@ -22,11 +22,7 @@ export abstract class RuntimeFactoryHelper<T = IContainerRuntime> implements IRu
|
|
|
22
22
|
: existing;
|
|
23
23
|
const runtime = await this.preInitialize(context, fromExisting);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
await this.instantiateFromExisting(runtime);
|
|
27
|
-
} else {
|
|
28
|
-
await this.instantiateFirstTime(runtime);
|
|
29
|
-
}
|
|
25
|
+
await (fromExisting ? this.instantiateFromExisting(runtime) : this.instantiateFirstTime(runtime));
|
|
30
26
|
|
|
31
27
|
await this.hasInitialized(runtime);
|
|
32
28
|
return runtime;
|
|
@@ -24,9 +24,6 @@ import { assert, unreachableCase } from "@fluidframework/common-utils";
|
|
|
24
24
|
import { mergeStats, convertToSummaryTree, calculateStats } from "../summaryUtils";
|
|
25
25
|
import { ReadAndParseBlob } from "../utils";
|
|
26
26
|
import {
|
|
27
|
-
decodeSummary,
|
|
28
|
-
encodeSummary,
|
|
29
|
-
EncodeSummaryParam,
|
|
30
27
|
EscapedPath,
|
|
31
28
|
ICreateChildDetails,
|
|
32
29
|
IInitialSummary,
|
|
@@ -63,7 +60,6 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
63
60
|
|
|
64
61
|
protected readonly children = new Map<string, SummarizerNode>();
|
|
65
62
|
protected readonly pendingSummaries = new Map<string, SummaryNode>();
|
|
66
|
-
private readonly outstandingOps: ISequencedDocumentMessage[] = [];
|
|
67
63
|
private wipReferenceSequenceNumber: number | undefined;
|
|
68
64
|
private wipLocalPaths: { localPath: EscapedPath; additionalPath?: EscapedPath; } | undefined;
|
|
69
65
|
private wipSkipRecursion = false;
|
|
@@ -112,52 +108,12 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
112
108
|
}
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.wipLocalPaths.additionalPath = EscapedPath.createAndConcat(result.pathPartsForChildren);
|
|
120
|
-
}
|
|
121
|
-
return { summary: result.summary, stats: result.stats };
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (this.throwOnError || this.trackingSequenceNumber < this._changeSequenceNumber) {
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
const latestSummary = this._latestSummary;
|
|
127
|
-
const initialSummary = this.initialSummary;
|
|
128
|
-
|
|
129
|
-
let encodeParam: EncodeSummaryParam;
|
|
130
|
-
let localPath: EscapedPath;
|
|
131
|
-
if (latestSummary !== undefined) {
|
|
132
|
-
// Create using handle of latest acked summary
|
|
133
|
-
encodeParam = {
|
|
134
|
-
fromSummary: true,
|
|
135
|
-
summaryNode: latestSummary,
|
|
136
|
-
};
|
|
137
|
-
localPath = latestSummary.localPath;
|
|
138
|
-
} else if (initialSummary?.summary !== undefined) {
|
|
139
|
-
// Create using initial summary from attach op
|
|
140
|
-
encodeParam = {
|
|
141
|
-
fromSummary: false,
|
|
142
|
-
initialSummary: initialSummary.summary,
|
|
143
|
-
};
|
|
144
|
-
localPath = EscapedPath.create(initialSummary.id);
|
|
145
|
-
} else {
|
|
146
|
-
// No base summary to reference
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
this.wipSummaryLogger.sendErrorEvent({
|
|
150
|
-
eventName: "SummarizingWithBasePlusOps",
|
|
151
|
-
},
|
|
152
|
-
error);
|
|
153
|
-
const summary = encodeSummary(encodeParam, this.outstandingOps);
|
|
154
|
-
this.wipLocalPaths = {
|
|
155
|
-
localPath,
|
|
156
|
-
additionalPath: summary.additionalPath,
|
|
157
|
-
};
|
|
158
|
-
this.wipSkipRecursion = true;
|
|
159
|
-
return { summary: summary.summary, stats: summary.stats };
|
|
111
|
+
const result = await this.summarizeInternalFn(fullTree, true, telemetryContext);
|
|
112
|
+
this.wipLocalPaths = { localPath: EscapedPath.create(result.id) };
|
|
113
|
+
if (result.pathPartsForChildren !== undefined) {
|
|
114
|
+
this.wipLocalPaths.additionalPath = EscapedPath.createAndConcat(result.pathPartsForChildren);
|
|
160
115
|
}
|
|
116
|
+
return { summary: result.summary, stats: result.stats };
|
|
161
117
|
}
|
|
162
118
|
|
|
163
119
|
/**
|
|
@@ -210,10 +166,15 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
210
166
|
// If there is no latestSummary, clearSummary and return before reaching this code.
|
|
211
167
|
assert(!!localPathsToUse, 0x1a5 /* "Tracked summary local paths not set" */);
|
|
212
168
|
|
|
169
|
+
// DataStore can be realized out-of-order during the summary execution (ex. the mixinSummaryHandler
|
|
170
|
+
// in order to provide search capability to the summaries). If that happens,
|
|
171
|
+
// a child node will not have the handle paths with ".channels".
|
|
172
|
+
// By using the _latestSummary's basePath we have a safe path to compose its pendingSummary.
|
|
173
|
+
// PR: https://github.com/microsoft/FluidFramework/pull/11697
|
|
213
174
|
const summary = new SummaryNode({
|
|
214
175
|
...localPathsToUse,
|
|
215
176
|
referenceSequenceNumber: this.wipReferenceSequenceNumber,
|
|
216
|
-
basePath: parentPath,
|
|
177
|
+
basePath: parentSkipRecursion ? this._latestSummary?.basePath ?? parentPath : parentPath,
|
|
217
178
|
});
|
|
218
179
|
const fullPathForChildren = summary.fullPathForChildren;
|
|
219
180
|
for (const child of this.children.values()) {
|
|
@@ -248,11 +209,15 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
248
209
|
* it becomes the latest summary. If the current summary is already ahead (e.g., loaded from a service summary),
|
|
249
210
|
* we skip the update. Otherwise, we get the snapshot by calling `getSnapshot` and update latest
|
|
250
211
|
* summary based off of that.
|
|
212
|
+
*
|
|
251
213
|
* @returns A RefreshSummaryResult type which returns information based on the following three scenarios:
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
214
|
+
*
|
|
215
|
+
* 1. The latest summary was not udpated.
|
|
216
|
+
*
|
|
217
|
+
* 2. The latest summary was updated and the summary corresponding to the params was being tracked.
|
|
218
|
+
*
|
|
219
|
+
* 3. The latest summary was updated but the summary corresponding to the params was not tracked. In this
|
|
220
|
+
* case, the latest summary is updated based on the downloaded snapshot which is also returned.
|
|
256
221
|
*/
|
|
257
222
|
public async refreshLatestSummary(
|
|
258
223
|
proposalHandle: string | undefined,
|
|
@@ -268,6 +233,17 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
268
233
|
this.refreshLatestSummaryFromPending(proposalHandle, maybeSummaryNode.referenceSequenceNumber);
|
|
269
234
|
return { latestSummaryUpdated: true, wasSummaryTracked: true };
|
|
270
235
|
}
|
|
236
|
+
|
|
237
|
+
const props = {
|
|
238
|
+
summaryRefSeq,
|
|
239
|
+
pendingSize: this.pendingSummaries.size ?? undefined,
|
|
240
|
+
};
|
|
241
|
+
this.defaultLogger.sendTelemetryEvent({
|
|
242
|
+
eventName: "PendingSummaryNotFound",
|
|
243
|
+
proposalHandle,
|
|
244
|
+
referenceSequenceNumber: this.referenceSequenceNumber,
|
|
245
|
+
details: JSON.stringify(props),
|
|
246
|
+
});
|
|
271
247
|
}
|
|
272
248
|
|
|
273
249
|
// If we have seen a summary same or later as the current one, ignore it.
|
|
@@ -286,7 +262,12 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
286
262
|
);
|
|
287
263
|
return { latestSummaryUpdated: true, wasSummaryTracked: false, snapshot: snapshotTree };
|
|
288
264
|
}
|
|
289
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Called when we get an ack from the server for a summary we've just sent. Updates the reference state of this node
|
|
267
|
+
* from the state in the pending summary queue.
|
|
268
|
+
* @param proposalHandle - Handle for the current proposal.
|
|
269
|
+
* @param referenceSequenceNumber - reference sequence number of sent summary.
|
|
270
|
+
*/
|
|
290
271
|
protected refreshLatestSummaryFromPending(
|
|
291
272
|
proposalHandle: string,
|
|
292
273
|
referenceSequenceNumber: number,
|
|
@@ -312,7 +293,6 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
312
293
|
this.refreshLatestSummaryCore(referenceSequenceNumber);
|
|
313
294
|
|
|
314
295
|
this._latestSummary = summaryNode;
|
|
315
|
-
|
|
316
296
|
// Propagate update to all child nodes
|
|
317
297
|
for (const child of this.children.values()) {
|
|
318
298
|
child.refreshLatestSummaryFromPending(proposalHandle, referenceSequenceNumber);
|
|
@@ -334,15 +314,14 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
334
314
|
|
|
335
315
|
this.refreshLatestSummaryCore(referenceSequenceNumber);
|
|
336
316
|
|
|
337
|
-
const { baseSummary, pathParts } = decodeSummary(snapshotTree, correlatedSummaryLogger);
|
|
338
|
-
|
|
339
317
|
this._latestSummary = new SummaryNode({
|
|
340
318
|
referenceSequenceNumber,
|
|
341
319
|
basePath,
|
|
342
320
|
localPath,
|
|
343
321
|
});
|
|
344
322
|
|
|
345
|
-
const
|
|
323
|
+
const pathParts: string[] = [];
|
|
324
|
+
const { childrenTree, childrenPathPart } = parseSummaryForSubtrees(snapshotTree);
|
|
346
325
|
if (childrenPathPart !== undefined) {
|
|
347
326
|
pathParts.push(childrenPathPart);
|
|
348
327
|
}
|
|
@@ -376,14 +355,6 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
376
355
|
this.pendingSummaries.delete(key);
|
|
377
356
|
}
|
|
378
357
|
}
|
|
379
|
-
|
|
380
|
-
// Clear earlier outstanding ops
|
|
381
|
-
while (
|
|
382
|
-
this.outstandingOps.length > 0
|
|
383
|
-
&& this.outstandingOps[0].sequenceNumber <= referenceSequenceNumber
|
|
384
|
-
) {
|
|
385
|
-
this.outstandingOps.shift();
|
|
386
|
-
}
|
|
387
358
|
}
|
|
388
359
|
|
|
389
360
|
public loadBaseSummaryWithoutDifferential(snapshot: ISnapshotTree) {
|
|
@@ -398,46 +369,22 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
398
369
|
public async loadBaseSummary(
|
|
399
370
|
snapshot: ISnapshotTree,
|
|
400
371
|
readAndParseBlob: ReadAndParseBlob,
|
|
401
|
-
): Promise<
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
const { childrenPathPart } = parseSummaryForSubtrees(decodedSummary.baseSummary);
|
|
372
|
+
): Promise<ISnapshotTree> {
|
|
373
|
+
const pathParts: string[] = [];
|
|
374
|
+
const { childrenPathPart } = parseSummaryForSubtrees(snapshot);
|
|
406
375
|
if (childrenPathPart !== undefined) {
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (decodedSummary.pathParts.length > 0 && this._latestSummary !== undefined) {
|
|
411
|
-
this._latestSummary.additionalPath = EscapedPath.createAndConcat(decodedSummary.pathParts);
|
|
376
|
+
pathParts.push(childrenPathPart);
|
|
412
377
|
}
|
|
413
378
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (outstandingOps.length > 0) {
|
|
417
|
-
const newOpsLatestSeq = outstandingOps[outstandingOps.length - 1].sequenceNumber;
|
|
418
|
-
assert(
|
|
419
|
-
newOpsLatestSeq <= this.trackingSequenceNumber,
|
|
420
|
-
0x1a9 /* "When loading base summary, expected outstanding ops <= tracking sequence number" */,
|
|
421
|
-
);
|
|
379
|
+
if (pathParts.length > 0 && this._latestSummary !== undefined) {
|
|
380
|
+
this._latestSummary.additionalPath = EscapedPath.createAndConcat(pathParts);
|
|
422
381
|
}
|
|
423
382
|
|
|
424
|
-
return
|
|
425
|
-
baseSummary: decodedSummary.baseSummary,
|
|
426
|
-
outstandingOps,
|
|
427
|
-
};
|
|
383
|
+
return snapshot;
|
|
428
384
|
}
|
|
429
385
|
|
|
430
386
|
public recordChange(op: ISequencedDocumentMessage): void {
|
|
431
|
-
const lastOp = this.outstandingOps[this.outstandingOps.length - 1];
|
|
432
|
-
if (lastOp !== undefined) {
|
|
433
|
-
assert(
|
|
434
|
-
lastOp.sequenceNumber < op.sequenceNumber,
|
|
435
|
-
0x1aa /* Out of order change recorded */,
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
387
|
this.invalidate(op.sequenceNumber);
|
|
439
|
-
this.trackingSequenceNumber = op.sequenceNumber;
|
|
440
|
-
this.outstandingOps.push(op);
|
|
441
388
|
}
|
|
442
389
|
|
|
443
390
|
public invalidate(sequenceNumber: number): void {
|
|
@@ -460,13 +407,6 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
460
407
|
}
|
|
461
408
|
|
|
462
409
|
private readonly canReuseHandle: boolean;
|
|
463
|
-
private readonly throwOnError: boolean;
|
|
464
|
-
/**
|
|
465
|
-
* Sequence number of latest tracked op. This updates during recordChange,
|
|
466
|
-
* but not for invalidate since we don't have the op. If this drifts from
|
|
467
|
-
* changeSequenceNumber and we try to create a differential summary we assert.
|
|
468
|
-
*/
|
|
469
|
-
private trackingSequenceNumber: number;
|
|
470
410
|
|
|
471
411
|
/**
|
|
472
412
|
* Do not call constructor directly.
|
|
@@ -483,11 +423,6 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
483
423
|
protected wipSummaryLogger?: ITelemetryLogger,
|
|
484
424
|
) {
|
|
485
425
|
this.canReuseHandle = config.canReuseHandle ?? true;
|
|
486
|
-
// BUGBUG: Seeing issues with differential summaries.
|
|
487
|
-
// this will disable them, and throw instead
|
|
488
|
-
// while we continue to investigate
|
|
489
|
-
this.throwOnError = true; // config.throwOnFailure ?? false;
|
|
490
|
-
this.trackingSequenceNumber = this._changeSequenceNumber;
|
|
491
426
|
}
|
|
492
427
|
|
|
493
428
|
public createChild(
|
|
@@ -517,7 +452,8 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
517
452
|
);
|
|
518
453
|
|
|
519
454
|
// There may be additional state that has to be updated in this child. For example, if a summary is being
|
|
520
|
-
// tracked, the child's summary tracking state needs to be updated too.
|
|
455
|
+
// tracked, the child's summary tracking state needs to be updated too. Same goes for pendingSummaries we might
|
|
456
|
+
// have outstanding on the parent in case we realize nodes in between Summary Op and Summary Ack.
|
|
521
457
|
this.maybeUpdateChildState(child);
|
|
522
458
|
|
|
523
459
|
this.children.set(id, child);
|
|
@@ -620,6 +556,8 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
620
556
|
/**
|
|
621
557
|
* Updates the state of the child if required. For example, if a summary is currently being tracked, the child's
|
|
622
558
|
* summary tracking state needs to be updated too.
|
|
559
|
+
* Also, in case a child node gets realized in between Summary Op and Summary Ack, let's initialize the child's
|
|
560
|
+
* pending summary as well.
|
|
623
561
|
* @param child - The child node whose state is to be updated.
|
|
624
562
|
*/
|
|
625
563
|
protected maybeUpdateChildState(child: SummarizerNode) {
|
|
@@ -628,8 +566,23 @@ export class SummarizerNode implements IRootSummarizerNode {
|
|
|
628
566
|
if (this.isTrackingInProgress()) {
|
|
629
567
|
child.wipReferenceSequenceNumber = this.wipReferenceSequenceNumber;
|
|
630
568
|
}
|
|
569
|
+
// In case we have pending summaries on the parent, let's initialize it on the child.
|
|
570
|
+
if (child._latestSummary !== undefined) {
|
|
571
|
+
for (const [key, value] of this.pendingSummaries.entries()) {
|
|
572
|
+
const newLatestSummaryNode = new SummaryNode({
|
|
573
|
+
referenceSequenceNumber: value.referenceSequenceNumber,
|
|
574
|
+
basePath: child._latestSummary.basePath,
|
|
575
|
+
localPath: child._latestSummary.localPath,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
child.addPendingSummary(key, newLatestSummaryNode);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
631
581
|
}
|
|
632
582
|
|
|
583
|
+
protected addPendingSummary(key: string, summary: SummaryNode) {
|
|
584
|
+
this.pendingSummaries.set(key, summary);
|
|
585
|
+
}
|
|
633
586
|
/**
|
|
634
587
|
* Tells whether summary tracking is in progress. True if "startSummary" API is called before summarize.
|
|
635
588
|
*/
|
|
@@ -4,28 +4,23 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import { assert } from "@fluidframework/common-utils";
|
|
8
7
|
import {
|
|
9
8
|
ISnapshotTree,
|
|
10
|
-
ISequencedDocumentMessage,
|
|
11
|
-
SummaryType,
|
|
12
9
|
ISummaryTree,
|
|
13
10
|
SummaryObject,
|
|
14
11
|
} from "@fluidframework/protocol-definitions";
|
|
15
12
|
import { channelsTreeName, ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
|
|
16
|
-
import { SummaryTreeBuilder } from "../summaryUtils";
|
|
17
13
|
import { ReadAndParseBlob } from "../utils";
|
|
18
14
|
|
|
19
|
-
const baseSummaryTreeKey = "_baseSummary";
|
|
20
|
-
const outstandingOpsBlobKey = "_outstandingOps";
|
|
21
|
-
const maxDecodeDepth = 100;
|
|
22
|
-
|
|
23
15
|
/**
|
|
24
16
|
* Return value of refreshSummaryAck function. There can be three different scenarios based on the passed params:
|
|
17
|
+
*
|
|
25
18
|
* 1. The latest summary was not udpated.
|
|
19
|
+
*
|
|
26
20
|
* 2. The latest summary was updated and the summary corresponding to the params was tracked by this client.
|
|
21
|
+
*
|
|
27
22
|
* 3. The latest summary was updated but the summary corresponding to the params was not tracked. In this case, the
|
|
28
|
-
*
|
|
23
|
+
* latest summary is updated based on the downloaded snapshot which is also returned.
|
|
29
24
|
*/
|
|
30
25
|
export type RefreshSummaryResult = {
|
|
31
26
|
latestSummaryUpdated: false;
|
|
@@ -137,88 +132,6 @@ export class SummaryNode {
|
|
|
137
132
|
}
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
/** Result from decoding summary which may have been a differential summary. */
|
|
141
|
-
interface IDecodedSummary {
|
|
142
|
-
/** The innermost base summary which is not itself a differential summary */
|
|
143
|
-
readonly baseSummary: ISnapshotTree;
|
|
144
|
-
/** The entire path name to the innermost base summary */
|
|
145
|
-
readonly pathParts: string[];
|
|
146
|
-
/** Function to fetch all outstanding ops since the innermost base summary */
|
|
147
|
-
getOutstandingOps(readAndParseBlob: ReadAndParseBlob): Promise<ISequencedDocumentMessage[]>;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Checks if the snapshot is created by referencing a previous successful
|
|
152
|
-
* summary plus outstanding ops. If so, it will recursively "decode" it until
|
|
153
|
-
* it gets to the last successful summary (the base summary) and returns that
|
|
154
|
-
* as well as a function for fetching the outstanding ops. Also returns the
|
|
155
|
-
* full path to the previous base summary for child summarizer nodes to use as
|
|
156
|
-
* their base path when necessary.
|
|
157
|
-
* @param snapshot - snapshot tree to decode
|
|
158
|
-
*/
|
|
159
|
-
export function decodeSummary(
|
|
160
|
-
snapshot: ISnapshotTree,
|
|
161
|
-
logger: Pick<ITelemetryLogger, "sendTelemetryEvent">,
|
|
162
|
-
): IDecodedSummary {
|
|
163
|
-
let baseSummary = snapshot;
|
|
164
|
-
const pathParts: string[] = [];
|
|
165
|
-
const opsBlobs: string[] = [];
|
|
166
|
-
|
|
167
|
-
for (let i = 0; ; i++) {
|
|
168
|
-
if (i > maxDecodeDepth) {
|
|
169
|
-
logger.sendTelemetryEvent({
|
|
170
|
-
eventName: "DecodeSummaryMaxDepth",
|
|
171
|
-
maxDecodeDepth,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
const outstandingOpsBlob = baseSummary.blobs[outstandingOpsBlobKey];
|
|
175
|
-
const newBaseSummary = baseSummary.trees[baseSummaryTreeKey];
|
|
176
|
-
if (outstandingOpsBlob === undefined && newBaseSummary === undefined) {
|
|
177
|
-
return {
|
|
178
|
-
baseSummary,
|
|
179
|
-
pathParts,
|
|
180
|
-
async getOutstandingOps(readAndParseBlob: ReadAndParseBlob) {
|
|
181
|
-
let outstandingOps: ISequencedDocumentMessage[] = [];
|
|
182
|
-
for (const opsBlob of opsBlobs) {
|
|
183
|
-
const newOutstandingOps = await readAndParseBlob<ISequencedDocumentMessage[]>(opsBlob);
|
|
184
|
-
if (outstandingOps.length > 0 && newOutstandingOps.length > 0) {
|
|
185
|
-
const latestSeq = outstandingOps[outstandingOps.length - 1].sequenceNumber;
|
|
186
|
-
const newEarliestSeq = newOutstandingOps[0].sequenceNumber;
|
|
187
|
-
if (newEarliestSeq <= latestSeq) {
|
|
188
|
-
logger.sendTelemetryEvent({
|
|
189
|
-
eventName: "DuplicateOutstandingOps",
|
|
190
|
-
// eslint-disable-next-line max-len
|
|
191
|
-
message: `newEarliestSeq <= latestSeq in decodeSummary: ${newEarliestSeq} <= ${latestSeq}`,
|
|
192
|
-
});
|
|
193
|
-
while (newOutstandingOps.length > 0
|
|
194
|
-
&& newOutstandingOps[0].sequenceNumber <= latestSeq) {
|
|
195
|
-
newOutstandingOps.shift();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
outstandingOps = outstandingOps.concat(newOutstandingOps);
|
|
200
|
-
}
|
|
201
|
-
return outstandingOps;
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
assert(!!outstandingOpsBlob, 0x1af /* "Outstanding ops blob missing, but base summary tree exists" */);
|
|
207
|
-
assert(newBaseSummary !== undefined, 0x1b0 /* "Base summary tree missing, but outstanding ops blob exists" */);
|
|
208
|
-
baseSummary = newBaseSummary;
|
|
209
|
-
pathParts.push(baseSummaryTreeKey);
|
|
210
|
-
opsBlobs.unshift(outstandingOpsBlob);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Summary tree which is a handle of the previous successfully acked summary
|
|
216
|
-
* and a blob of the outstanding ops since that summary.
|
|
217
|
-
*/
|
|
218
|
-
interface IEncodedSummary extends ISummaryTreeWithStats {
|
|
219
|
-
readonly additionalPath: EscapedPath;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
135
|
/**
|
|
223
136
|
* Parameter to help encode summary with conditional behavior.
|
|
224
137
|
* When fromSummary is true, it will contain the SummaryNode of
|
|
@@ -234,42 +147,6 @@ export type EncodeSummaryParam = {
|
|
|
234
147
|
initialSummary: ISummaryTreeWithStats;
|
|
235
148
|
};
|
|
236
149
|
|
|
237
|
-
/**
|
|
238
|
-
* Creates a summary tree which is a handle of the previous successfully acked summary
|
|
239
|
-
* and a blob of the outstanding ops since that summary. If there is no acked summary yet,
|
|
240
|
-
* it will create with the tree found in the initial attach op and the blob of outstanding ops.
|
|
241
|
-
* @param summaryParam - information about last acked summary and paths to encode if from summary,
|
|
242
|
-
* otherwise the initial summary from the attach op.
|
|
243
|
-
* @param outstandingOps - outstanding ops since last acked summary
|
|
244
|
-
*/
|
|
245
|
-
export function encodeSummary(
|
|
246
|
-
summaryParam: EncodeSummaryParam,
|
|
247
|
-
outstandingOps: ISequencedDocumentMessage[],
|
|
248
|
-
): IEncodedSummary {
|
|
249
|
-
let additionalPath = EscapedPath.create(baseSummaryTreeKey);
|
|
250
|
-
|
|
251
|
-
const builder = new SummaryTreeBuilder();
|
|
252
|
-
builder.addBlob(outstandingOpsBlobKey, JSON.stringify(outstandingOps));
|
|
253
|
-
|
|
254
|
-
if (summaryParam.fromSummary) {
|
|
255
|
-
// Create using handle of latest acked summary
|
|
256
|
-
const summaryNode = summaryParam.summaryNode;
|
|
257
|
-
if (summaryNode.additionalPath !== undefined) {
|
|
258
|
-
additionalPath = additionalPath.concat(summaryNode.additionalPath);
|
|
259
|
-
}
|
|
260
|
-
builder.addHandle(baseSummaryTreeKey, SummaryType.Tree, summaryNode.fullPath.path);
|
|
261
|
-
} else {
|
|
262
|
-
// Create using initial summary from attach op
|
|
263
|
-
builder.addWithStats(baseSummaryTreeKey, summaryParam.initialSummary);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const summary = builder.getSummaryTree();
|
|
267
|
-
return {
|
|
268
|
-
...summary,
|
|
269
|
-
additionalPath,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
150
|
/**
|
|
274
151
|
* Information about the initial summary tree found from an attach op.
|
|
275
152
|
*/
|
|
@@ -49,12 +49,16 @@ class SummaryNodeWithGC extends SummaryNode {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Extends the functionality of SummarizerNode to manage this node's garbage collection data:
|
|
52
|
+
*
|
|
52
53
|
* - Adds a new API `getGCData` to return GC data of this node.
|
|
54
|
+
*
|
|
53
55
|
* - Caches the result of `getGCData` to be used if nothing changes between summaries.
|
|
56
|
+
*
|
|
54
57
|
* - Manages the used routes of this node. These are used to identify if this node is referenced in the document
|
|
55
|
-
*
|
|
58
|
+
* and to determine if the node's used state changed since last summary.
|
|
59
|
+
*
|
|
56
60
|
* - Adds trackState param to summarize. If trackState is false, it bypasses the SummarizerNode and calls
|
|
57
|
-
*
|
|
61
|
+
* directly into summarizeInternal method.
|
|
58
62
|
*/
|
|
59
63
|
export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
|
|
60
64
|
// Tracks the work-in-progress used routes during summary.
|
|
@@ -121,7 +125,7 @@ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummari
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
/**
|
|
124
|
-
* @deprecated
|
|
128
|
+
* @deprecated Renamed to {@link SummarizerNodeWithGC.getBaseGCDetails}.
|
|
125
129
|
*/
|
|
126
130
|
public getGCSummaryDetails(): IGarbageCollectionSummaryDetails {
|
|
127
131
|
return this.getBaseGCDetails();
|
package/src/summaryUtils.ts
CHANGED
|
@@ -70,11 +70,7 @@ export function utf8ByteLength(str: string): number {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function getBlobSize(content: ISummaryBlob["content"]): number {
|
|
73
|
-
|
|
74
|
-
return utf8ByteLength(content);
|
|
75
|
-
} else {
|
|
76
|
-
return content.byteLength;
|
|
77
|
-
}
|
|
73
|
+
return typeof content === "string" ? utf8ByteLength(content) : content.byteLength;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
function calculateStatsCore(summaryObject: SummaryObject, stats: ISummaryStats): void {
|
|
@@ -202,12 +198,9 @@ export function convertToSummaryTreeWithStats(
|
|
|
202
198
|
switch (entry.type) {
|
|
203
199
|
case TreeEntry.Blob: {
|
|
204
200
|
const blob = entry.value;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} else {
|
|
209
|
-
content = blob.contents;
|
|
210
|
-
}
|
|
201
|
+
const content = blob.encoding === "base64"
|
|
202
|
+
? IsoBuffer.from(blob.contents, "base64")
|
|
203
|
+
: blob.contents;
|
|
211
204
|
builder.addBlob(entry.path, content);
|
|
212
205
|
break;
|
|
213
206
|
}
|