@fluidframework/container-runtime 1.2.3-84921 → 1.2.5
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/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +36 -15
- package/dist/containerRuntime.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 +14 -0
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +25 -0
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +33 -3
- package/dist/summarizer.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +36 -15
- package/lib/containerRuntime.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 +14 -0
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +25 -0
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +35 -5
- package/lib/summarizer.js.map +1 -1
- package/package.json +14 -14
- package/src/containerRuntime.ts +60 -32
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +33 -1
- package/src/summarizer.ts +46 -10
package/src/containerRuntime.ts
CHANGED
|
@@ -2534,21 +2534,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2534
2534
|
},
|
|
2535
2535
|
);
|
|
2536
2536
|
|
|
2537
|
+
let latestSnapshotVersionId: string | undefined;
|
|
2537
2538
|
if (refreshLatestAck) {
|
|
2538
|
-
const
|
|
2539
|
+
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
|
|
2539
2540
|
ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
2541
|
+
const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
|
|
2542
|
+
latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
|
|
2540
2543
|
|
|
2541
|
-
if (
|
|
2544
|
+
if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
|
|
2542
2545
|
// We need to catch up to the latest summary's reference sequence number before pausing.
|
|
2543
2546
|
await PerformanceEvent.timedExecAsync(
|
|
2544
2547
|
summaryNumberLogger,
|
|
2545
2548
|
{
|
|
2546
2549
|
eventName: "WaitingForSeq",
|
|
2547
2550
|
lastSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
2548
|
-
targetSequenceNumber:
|
|
2551
|
+
targetSequenceNumber: latestSnapshotRefSeq,
|
|
2549
2552
|
lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
|
|
2550
2553
|
},
|
|
2551
|
-
async () => waitForSeq(this.deltaManager,
|
|
2554
|
+
async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
|
|
2552
2555
|
{ start: true, end: true, cancel: "error" }, // definitely want start event
|
|
2553
2556
|
);
|
|
2554
2557
|
}
|
|
@@ -2673,19 +2676,33 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2673
2676
|
return { stage: "generate", ...generateSummaryData, error: continueResult.error };
|
|
2674
2677
|
}
|
|
2675
2678
|
|
|
2679
|
+
// It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
|
|
2680
|
+
// summary. So if the previous summarizer closes just after submitting the summary and before
|
|
2681
|
+
// submitting the summaryOp then we can't rely on summaryAck. So in case we have
|
|
2682
|
+
// latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
|
|
2683
|
+
// the one fetched from storage as parent as that is the latest.
|
|
2676
2684
|
const lastAck = this.summaryCollection.latestAck;
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2685
|
+
let summaryContext: ISummaryContext;
|
|
2686
|
+
if (lastAck?.summaryAck.contents.handle !== latestSnapshotVersionId
|
|
2687
|
+
&& latestSnapshotVersionId !== undefined) {
|
|
2688
|
+
summaryContext = {
|
|
2689
|
+
proposalHandle: undefined,
|
|
2690
|
+
ackHandle: latestSnapshotVersionId,
|
|
2691
|
+
referenceSequenceNumber: summaryRefSeqNum,
|
|
2692
|
+
};
|
|
2693
|
+
} else if (lastAck === undefined) {
|
|
2694
|
+
summaryContext = {
|
|
2695
|
+
proposalHandle: undefined,
|
|
2696
|
+
ackHandle: this.context.getLoadedFromVersion()?.id,
|
|
2697
|
+
referenceSequenceNumber: summaryRefSeqNum,
|
|
2698
|
+
};
|
|
2699
|
+
} else {
|
|
2700
|
+
summaryContext = {
|
|
2701
|
+
proposalHandle: lastAck.summaryOp.contents.handle,
|
|
2702
|
+
ackHandle: lastAck.summaryAck.contents.handle,
|
|
2703
|
+
referenceSequenceNumber: summaryRefSeqNum,
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2689
2706
|
|
|
2690
2707
|
let handle: string;
|
|
2691
2708
|
try {
|
|
@@ -3039,15 +3056,20 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
3039
3056
|
summaryLogger: ITelemetryLogger,
|
|
3040
3057
|
) {
|
|
3041
3058
|
const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
|
|
3042
|
-
const
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3059
|
+
const { snapshotTree } = await this.fetchSnapshotFromStorage(
|
|
3060
|
+
ackHandle,
|
|
3061
|
+
summaryLogger,
|
|
3062
|
+
{
|
|
3046
3063
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
3047
3064
|
ackHandle,
|
|
3048
3065
|
summaryRefSeq,
|
|
3049
3066
|
fetchLatest: false,
|
|
3050
|
-
}
|
|
3067
|
+
},
|
|
3068
|
+
);
|
|
3069
|
+
const result = await this.summarizerNode.refreshLatestSummary(
|
|
3070
|
+
proposalHandle,
|
|
3071
|
+
summaryRefSeq,
|
|
3072
|
+
async () => snapshotTree,
|
|
3051
3073
|
readAndParseBlob,
|
|
3052
3074
|
summaryLogger,
|
|
3053
3075
|
);
|
|
@@ -3062,19 +3084,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
3062
3084
|
* @param summaryLogger - logger to use when fetching snapshot from storage
|
|
3063
3085
|
* @returns downloaded snapshot's reference sequence number
|
|
3064
3086
|
*/
|
|
3065
|
-
private async refreshLatestSummaryAckFromServer(
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3087
|
+
private async refreshLatestSummaryAckFromServer(
|
|
3088
|
+
summaryLogger: ITelemetryLogger,
|
|
3089
|
+
): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
|
|
3090
|
+
const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
|
|
3091
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
3092
|
+
fetchLatest: true,
|
|
3093
|
+
},
|
|
3094
|
+
);
|
|
3070
3095
|
|
|
3071
3096
|
const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
|
|
3072
|
-
const
|
|
3097
|
+
const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
|
|
3073
3098
|
|
|
3074
3099
|
const result = await this.summarizerNode.refreshLatestSummary(
|
|
3075
3100
|
undefined,
|
|
3076
|
-
|
|
3077
|
-
async () =>
|
|
3101
|
+
latestSnapshotRefSeq,
|
|
3102
|
+
async () => snapshotTree,
|
|
3078
3103
|
readAndParseBlob,
|
|
3079
3104
|
summaryLogger,
|
|
3080
3105
|
);
|
|
@@ -3082,11 +3107,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
3082
3107
|
// Notify the garbage collector so it can update its latest summary state.
|
|
3083
3108
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
3084
3109
|
|
|
3085
|
-
return
|
|
3110
|
+
return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
|
|
3086
3111
|
}
|
|
3087
3112
|
|
|
3088
3113
|
private async fetchSnapshotFromStorage(
|
|
3089
|
-
versionId: string | null,
|
|
3114
|
+
versionId: string | null,
|
|
3115
|
+
logger: ITelemetryLogger,
|
|
3116
|
+
event: ITelemetryGenericEvent,
|
|
3117
|
+
): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
|
|
3090
3118
|
return PerformanceEvent.timedExecAsync(
|
|
3091
3119
|
logger, event, async (perfEvent: {
|
|
3092
3120
|
end: (arg0: {
|
|
@@ -3106,7 +3134,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
3106
3134
|
stats.getSnapshotDuration = trace.trace().duration;
|
|
3107
3135
|
|
|
3108
3136
|
perfEvent.end(stats);
|
|
3109
|
-
return maybeSnapshot;
|
|
3137
|
+
return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
|
|
3110
3138
|
});
|
|
3111
3139
|
}
|
|
3112
3140
|
|
package/src/packageVersion.ts
CHANGED
package/src/runningSummarizer.ts
CHANGED
|
@@ -78,10 +78,10 @@ export class RunningSummarizer implements IDisposable {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
public get disposed() { return this._disposed; }
|
|
81
|
-
|
|
82
81
|
private stopping = false;
|
|
83
82
|
private _disposed = false;
|
|
84
83
|
private summarizingLock: Promise<void> | undefined;
|
|
84
|
+
private refreshSummaryAckLock: Promise<void> | undefined;
|
|
85
85
|
private tryWhileSummarizing = false;
|
|
86
86
|
private readonly pendingAckTimer: PromiseTimer;
|
|
87
87
|
private heuristicRunner?: ISummarizeHeuristicRunner;
|
|
@@ -276,6 +276,31 @@ export class RunningSummarizer implements IDisposable {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Blocks a new summarizer from running in case RefreshSummaryAck is being processed.
|
|
281
|
+
* Assumes that caller checked upfront for lack of concurrent action (this.refreshSummaryAckLock)
|
|
282
|
+
* before calling this API. I.e. caller is responsible for either erroring out or waiting on this promise.
|
|
283
|
+
* Note: The refreshSummaryAckLock makes sure no summarizer gets enqueued or processed
|
|
284
|
+
* until the refresh has completed. One can't rely uniquely on the summarizingLock as the
|
|
285
|
+
* refreshLatestSummaryAck also happens during the time summarizingLock !== undefined.
|
|
286
|
+
* Ex. Summarizer submits a summay + op and then waits for the Summary Ack to proceed
|
|
287
|
+
* with the refreshLatestSummaryAck and complete the summary.
|
|
288
|
+
* @param action - action to perform.
|
|
289
|
+
* @returns - result of action.
|
|
290
|
+
*/
|
|
291
|
+
public async lockedRefreshSummaryAckAction<T>(action: () => Promise<T>) {
|
|
292
|
+
assert(this.refreshSummaryAckLock === undefined,
|
|
293
|
+
"Refresh Summary Ack - Caller is responsible for checking lock");
|
|
294
|
+
|
|
295
|
+
const refreshSummaryAckLock = new Deferred<void>();
|
|
296
|
+
this.refreshSummaryAckLock = refreshSummaryAckLock.promise;
|
|
297
|
+
|
|
298
|
+
return action().finally(() => {
|
|
299
|
+
refreshSummaryAckLock.resolve();
|
|
300
|
+
this.refreshSummaryAckLock = undefined;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
279
304
|
/**
|
|
280
305
|
* Runs single summary action that prevents any other concurrent actions.
|
|
281
306
|
* Assumes that caller checked upfront for lack of concurrent action (this.summarizingLock)
|
|
@@ -290,6 +315,8 @@ export class RunningSummarizer implements IDisposable {
|
|
|
290
315
|
this.summarizingLock = summarizingLock.promise;
|
|
291
316
|
|
|
292
317
|
this.summarizeCount++;
|
|
318
|
+
// Make sure the RefreshLatestSummaryAck is not being executed.
|
|
319
|
+
await this.refreshSummaryAckLock;
|
|
293
320
|
|
|
294
321
|
return action().finally(() => {
|
|
295
322
|
summarizingLock.resolve();
|
|
@@ -393,6 +420,10 @@ export class RunningSummarizer implements IDisposable {
|
|
|
393
420
|
});
|
|
394
421
|
await delay(delaySeconds * 1000);
|
|
395
422
|
}
|
|
423
|
+
|
|
424
|
+
// Make sure the refresh Summary Ack is not being executed.
|
|
425
|
+
await this.refreshSummaryAckLock;
|
|
426
|
+
|
|
396
427
|
// Note: no need to account for cancellationToken.waitCancelled here, as
|
|
397
428
|
// this is accounted SummaryGenerator.summarizeCore that controls receivedSummaryAckOrNack.
|
|
398
429
|
const resultSummarize = this.generator.summarize(summarizeProps, options, cancellationToken);
|
|
@@ -441,6 +472,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
441
472
|
// The heuristics are blocking concurrent summarize attempts.
|
|
442
473
|
throw new UsageError("Attempted to run an already-running summarizer on demand");
|
|
443
474
|
}
|
|
475
|
+
|
|
444
476
|
const result = this.trySummarizeOnce(
|
|
445
477
|
{ reason: `onDemand/${reason}` },
|
|
446
478
|
options,
|
package/src/summarizer.ts
CHANGED
|
@@ -8,9 +8,15 @@ import { Deferred } from "@fluidframework/common-utils";
|
|
|
8
8
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
9
9
|
import { ILoader, LoaderHeader } from "@fluidframework/container-definitions";
|
|
10
10
|
import { UsageError } from "@fluidframework/container-utils";
|
|
11
|
-
import { DriverHeader } from "@fluidframework/driver-definitions";
|
|
11
|
+
import { DriverErrorType, DriverHeader } from "@fluidframework/driver-definitions";
|
|
12
12
|
import { requestFluidObject } from "@fluidframework/runtime-utils";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ChildLogger,
|
|
15
|
+
IFluidErrorBase,
|
|
16
|
+
isFluidError,
|
|
17
|
+
LoggingError,
|
|
18
|
+
wrapErrorAndLog,
|
|
19
|
+
} from "@fluidframework/telemetry-utils";
|
|
14
20
|
import {
|
|
15
21
|
FluidObject,
|
|
16
22
|
IFluidHandleContext,
|
|
@@ -23,7 +29,7 @@ import {
|
|
|
23
29
|
import { ISummaryConfiguration } from "./containerRuntime";
|
|
24
30
|
import { ICancellableSummarizerController } from "./runWhileConnectedCoordinator";
|
|
25
31
|
import { summarizerClientType } from "./summarizerClientElection";
|
|
26
|
-
import { SummaryCollection } from "./summaryCollection";
|
|
32
|
+
import { IAckedSummary, SummaryCollection } from "./summaryCollection";
|
|
27
33
|
import { SummarizerHandle } from "./summarizerHandle";
|
|
28
34
|
import { RunningSummarizer } from "./runningSummarizer";
|
|
29
35
|
import {
|
|
@@ -378,22 +384,52 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
378
384
|
|
|
379
385
|
private async handleSummaryAcks() {
|
|
380
386
|
let refSequenceNumber = this.runtime.deltaManager.initialSequenceNumber;
|
|
387
|
+
let ack: IAckedSummary | undefined;
|
|
381
388
|
while (this.runningSummarizer) {
|
|
382
389
|
const summaryLogger = this.runningSummarizer.tryGetCorrelatedLogger(refSequenceNumber) ?? this.logger;
|
|
383
390
|
try {
|
|
384
|
-
|
|
391
|
+
// Initialize ack with undefined if exception happens inside of waitSummaryAck on second iteration,
|
|
392
|
+
// we record undefined, not previous handles.
|
|
393
|
+
ack = undefined;
|
|
394
|
+
ack = await this.summaryCollection.waitSummaryAck(refSequenceNumber);
|
|
385
395
|
refSequenceNumber = ack.summaryOp.referenceSequenceNumber;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
396
|
+
const summaryOpHandle = ack.summaryOp.contents.handle;
|
|
397
|
+
const summaryAckHandle = ack.summaryAck.contents.handle;
|
|
398
|
+
// Make sure we block any summarizer from being executed/enqueued while
|
|
399
|
+
// executing the refreshLatestSummaryAck.
|
|
400
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/779
|
|
401
|
+
await this.runningSummarizer.lockedRefreshSummaryAckAction(async () =>
|
|
402
|
+
this.internalsProvider.refreshLatestSummaryAck(
|
|
403
|
+
summaryOpHandle,
|
|
404
|
+
summaryAckHandle,
|
|
405
|
+
refSequenceNumber,
|
|
406
|
+
summaryLogger,
|
|
407
|
+
).catch(async (error) => {
|
|
408
|
+
// If the error is 404, so maybe the fetched version no longer exists on server. We just
|
|
409
|
+
// ignore this error in that case, as that means we will have another summaryAck for the
|
|
410
|
+
// latest version with which we will refresh the state. However in case of single commit
|
|
411
|
+
// summary, we might me missing a summary ack, so in that case we are still fine as the
|
|
412
|
+
// code in `submitSummary` function in container runtime, will refresh the latest state
|
|
413
|
+
// by calling `refreshLatestSummaryAckFromServer` and we will be fine.
|
|
414
|
+
if (isFluidError(error)
|
|
415
|
+
&& error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError) {
|
|
416
|
+
summaryLogger.sendTelemetryEvent({
|
|
417
|
+
eventName: "HandleSummaryAckErrorIgnored",
|
|
418
|
+
referenceSequenceNumber: refSequenceNumber,
|
|
419
|
+
proposalHandle: summaryOpHandle,
|
|
420
|
+
ackHandle: summaryAckHandle,
|
|
421
|
+
}, error);
|
|
422
|
+
} else {
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
}),
|
|
392
426
|
);
|
|
393
427
|
} catch (error) {
|
|
394
428
|
summaryLogger.sendErrorEvent({
|
|
395
429
|
eventName: "HandleSummaryAckError",
|
|
396
430
|
referenceSequenceNumber: refSequenceNumber,
|
|
431
|
+
handle: ack?.summaryOp?.contents?.handle,
|
|
432
|
+
ackHandle: ack?.summaryAck?.contents?.handle,
|
|
397
433
|
}, error);
|
|
398
434
|
}
|
|
399
435
|
refSequenceNumber++;
|