@fluidframework/container-runtime 0.51.0-43124 → 0.51.3
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 +31 -31
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +61 -144
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.js +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +3 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -4
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +116 -0
- package/dist/garbageCollection.d.ts.map +1 -0
- package/dist/garbageCollection.js +148 -0
- package/dist/garbageCollection.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -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/pendingStateManager.d.ts +0 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +0 -36
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +31 -31
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +62 -145
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +3 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -4
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +116 -0
- package/lib/garbageCollection.d.ts.map +1 -0
- package/lib/garbageCollection.js +144 -0
- package/lib/garbageCollection.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -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/pendingStateManager.d.ts +0 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +0 -36
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +11 -11
- package/src/containerRuntime.ts +89 -188
- package/src/dataStoreContext.ts +1 -1
- package/src/dataStores.ts +5 -5
- package/src/garbageCollection.ts +269 -0
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +0 -43
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
6
|
+
import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
|
|
7
|
+
import { ReadAndParseBlob, RefreshSummaryResult } from "@fluidframework/runtime-utils";
|
|
8
|
+
import { IGCRuntimeOptions } from "./containerRuntime";
|
|
9
|
+
import { IContainerRuntimeMetadata } from "./summaryFormat";
|
|
10
|
+
/** The used state statistics of a node. */
|
|
11
|
+
export interface IUsedStateStats {
|
|
12
|
+
totalNodeCount: number;
|
|
13
|
+
unusedNodeCount: number;
|
|
14
|
+
}
|
|
15
|
+
/** The statistics of the system state after a garbage collection run. */
|
|
16
|
+
export interface IGCStats {
|
|
17
|
+
totalNodes: number;
|
|
18
|
+
deletedNodes: number;
|
|
19
|
+
totalDataStores: number;
|
|
20
|
+
deletedDataStores: number;
|
|
21
|
+
}
|
|
22
|
+
/** Defines the APIs for the runtime object to be passed to the garbage collector. */
|
|
23
|
+
export interface IGarbageCollectionRuntime {
|
|
24
|
+
/** Returns the garbage collection data of the runtime. */
|
|
25
|
+
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
26
|
+
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
27
|
+
updateUsedRoutes(usedRoutes: string[]): IUsedStateStats;
|
|
28
|
+
}
|
|
29
|
+
/** Defines the contract for the garbage collector. */
|
|
30
|
+
export interface IGarbageCollector {
|
|
31
|
+
/** Tells whether GC should run or not. */
|
|
32
|
+
readonly shouldRunGC: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* This tracks two things:
|
|
35
|
+
* 1. Whether GC is enabled - If this is 0, GC is disabled. If this is > 0, GC is enabled.
|
|
36
|
+
* 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.
|
|
37
|
+
*/
|
|
38
|
+
readonly gcSummaryFeatureVersion: number;
|
|
39
|
+
/** Tells whether the GC version has changed compared to the version in the latest summary. */
|
|
40
|
+
readonly hasGCVersionChanged: boolean;
|
|
41
|
+
/** Run garbage collection and update the reference / used state of the system. */
|
|
42
|
+
collectGarbage(options: {
|
|
43
|
+
logger?: ITelemetryLogger;
|
|
44
|
+
runGC?: boolean;
|
|
45
|
+
runSweep?: boolean;
|
|
46
|
+
fullGC?: boolean;
|
|
47
|
+
}): Promise<IGCStats>;
|
|
48
|
+
/** Called when the latest summary of the system has been refreshed. */
|
|
49
|
+
latestSummaryStateRefreshed(result: RefreshSummaryResult, readAndParseBlob: ReadAndParseBlob): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The garbage collector for the container runtime. It consolidates the garbage collection functionality and maintains
|
|
53
|
+
* its state across summaries.
|
|
54
|
+
*/
|
|
55
|
+
export declare class GarbageCollector implements IGarbageCollector {
|
|
56
|
+
private readonly provider;
|
|
57
|
+
private readonly gcOptions;
|
|
58
|
+
/**
|
|
59
|
+
* After GC has run, called to delete objects in the runtime whose routes are unused. This is not part of the
|
|
60
|
+
* provider because its specific to this garbage collector implementation and is not part of the contract.
|
|
61
|
+
*/
|
|
62
|
+
private readonly deleteUnusedRoutes;
|
|
63
|
+
static create(provider: IGarbageCollectionRuntime, gcOptions: IGCRuntimeOptions, deleteUnusedRoutes: (unusedRoutes: string[]) => void, baseLogger: ITelemetryLogger, existing: boolean, metadata?: IContainerRuntimeMetadata): IGarbageCollector;
|
|
64
|
+
/**
|
|
65
|
+
* Tells whether GC should be run based on the GC options and local storage flags.
|
|
66
|
+
*/
|
|
67
|
+
readonly shouldRunGC: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* This tracks two things:
|
|
70
|
+
* 1. Whether GC is enabled - If this is 0, GC is disabled. If this is > 0, GC is enabled.
|
|
71
|
+
* 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.
|
|
72
|
+
*/
|
|
73
|
+
get gcSummaryFeatureVersion(): number;
|
|
74
|
+
/**
|
|
75
|
+
* Tells whether the GC version has changed compared to the version in the latest summary.
|
|
76
|
+
*/
|
|
77
|
+
get hasGCVersionChanged(): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Tracks if GC is enabled for this document. This is specified during document creation and doesn't change
|
|
80
|
+
* throughout its lifetime.
|
|
81
|
+
*/
|
|
82
|
+
private readonly gcEnabled;
|
|
83
|
+
private readonly shouldRunSweep;
|
|
84
|
+
private readonly testMode;
|
|
85
|
+
private readonly logger;
|
|
86
|
+
private readonly currentGCVersion;
|
|
87
|
+
private latestSummaryGCVersion;
|
|
88
|
+
protected constructor(provider: IGarbageCollectionRuntime, gcOptions: IGCRuntimeOptions,
|
|
89
|
+
/**
|
|
90
|
+
* After GC has run, called to delete objects in the runtime whose routes are unused. This is not part of the
|
|
91
|
+
* provider because its specific to this garbage collector implementation and is not part of the contract.
|
|
92
|
+
*/
|
|
93
|
+
deleteUnusedRoutes: (unusedRoutes: string[]) => void, baseLogger: ITelemetryLogger, existing: boolean, metadata?: IContainerRuntimeMetadata);
|
|
94
|
+
/**
|
|
95
|
+
* Runs garbage collection and udpates the reference / used state of the nodes in the container.
|
|
96
|
+
* @returns the number of data stores that have been marked as unreferenced.
|
|
97
|
+
*/
|
|
98
|
+
collectGarbage(options: {
|
|
99
|
+
/** Logger to use for logging GC events */
|
|
100
|
+
logger?: ITelemetryLogger;
|
|
101
|
+
/** True to run GC sweep phase after the mark phase */
|
|
102
|
+
runSweep?: boolean;
|
|
103
|
+
/** True to generate full GC data */
|
|
104
|
+
fullGC?: boolean;
|
|
105
|
+
}): Promise<IGCStats>;
|
|
106
|
+
/**
|
|
107
|
+
* Called when the latest summary of the system has been refreshed. This will be used to update the state of the
|
|
108
|
+
* latest summary tracked.
|
|
109
|
+
*/
|
|
110
|
+
latestSummaryStateRefreshed(result: RefreshSummaryResult, readAndParseBlob: ReadAndParseBlob): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Update the latest summary GC version from the metadata blob in the given snapshot.
|
|
113
|
+
*/
|
|
114
|
+
private updateSummaryGCVersionFromSnapshot;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=garbageCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"garbageCollection.d.ts","sourceRoot":"","sources":["../src/garbageCollection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAGvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAGH,yBAAyB,EAE5B,MAAM,iBAAiB,CAAC;AAYzB,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,qFAAqF;AACrF,MAAM,WAAW,yBAAyB;IACtC,0DAA0D;IAC1D,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC7D,oFAAoF;IACpF,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC;CAC3D;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,8FAA8F;IAC9F,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,kFAAkF;IAClF,cAAc,CACV,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9F,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB,uEAAuE;IACvE,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChH;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IAmDlD,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB;WAxDzB,MAAM,CAChB,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,iBAAiB,EAC5B,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,EACpD,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,yBAAyB,GACrC,iBAAiB;IAIpB;;OAEG;IACH,SAAgB,WAAW,EAAE,OAAO,CAAC;IAErC;;;;OAIG;IACH,IAAW,uBAAuB,IAAI,MAAM,CAE3C;IAED;;OAEG;IACH,IAAW,mBAAmB,IAAI,OAAO,CAKxC;IAED;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAG1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAE9C,OAAO,CAAC,sBAAsB,CAAY;IAE1C,SAAS,aACY,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,iBAAiB;IAC7C;;;OAGG;IACc,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,EACrE,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,yBAAyB;IAqCxC;;;OAGG;IACU,cAAc,CACvB,OAAO,EAAE;QACL,0CAA0C;QAC1C,MAAM,CAAC,EAAE,gBAAgB,CAAC;QAC1B,sDAAsD;QACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,oCAAoC;QACpC,MAAM,CAAC,EAAE,OAAO,CAAC;KACpB,GACF,OAAO,CAAC,QAAQ,CAAC;IAgDpB;;;OAGG;IACU,2BAA2B,CACpC,MAAM,EAAE,oBAAoB,EAC5B,gBAAgB,EAAE,gBAAgB,GACnC,OAAO,CAAC,IAAI,CAAC;IAgBhB;;OAEG;YACW,kCAAkC;CAOnD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { runGarbageCollection } from "@fluidframework/garbage-collector";
|
|
6
|
+
import { ChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
7
|
+
import { getLocalStorageFeatureGate } from "./localStorageFeatureGates";
|
|
8
|
+
import { getGCVersion, metadataBlobName, } from "./summaryFormat";
|
|
9
|
+
/** This is the current version of garbage collection. */
|
|
10
|
+
const GCVersion = 1;
|
|
11
|
+
// Local storage key to turn GC on / off.
|
|
12
|
+
const runGCKey = "FluidRunGC";
|
|
13
|
+
// Local storage key to turn GC test mode on / off.
|
|
14
|
+
const gcTestModeKey = "FluidGCTestMode";
|
|
15
|
+
// Local storage key to turn GC sweep on / off.
|
|
16
|
+
const runSweepKey = "FluidRunSweep";
|
|
17
|
+
/**
|
|
18
|
+
* The garbage collector for the container runtime. It consolidates the garbage collection functionality and maintains
|
|
19
|
+
* its state across summaries.
|
|
20
|
+
*/
|
|
21
|
+
export class GarbageCollector {
|
|
22
|
+
constructor(provider, gcOptions,
|
|
23
|
+
/**
|
|
24
|
+
* After GC has run, called to delete objects in the runtime whose routes are unused. This is not part of the
|
|
25
|
+
* provider because its specific to this garbage collector implementation and is not part of the contract.
|
|
26
|
+
*/
|
|
27
|
+
deleteUnusedRoutes, baseLogger, existing, metadata) {
|
|
28
|
+
var _a, _b, _c;
|
|
29
|
+
this.provider = provider;
|
|
30
|
+
this.gcOptions = gcOptions;
|
|
31
|
+
this.deleteUnusedRoutes = deleteUnusedRoutes;
|
|
32
|
+
// The current GC version that this container is running.
|
|
33
|
+
this.currentGCVersion = GCVersion;
|
|
34
|
+
this.logger = ChildLogger.create(baseLogger, "GarbageCollector");
|
|
35
|
+
let prevSummaryGCVersion;
|
|
36
|
+
// GC can only be enabled during creation. After that, it can never be enabled again. So, for existing
|
|
37
|
+
// documents, we get this information from the metadata blob.
|
|
38
|
+
if (existing) {
|
|
39
|
+
prevSummaryGCVersion = getGCVersion(metadata);
|
|
40
|
+
// Existing documents which did not have metadata blob or had GC disabled have version as 0. For all
|
|
41
|
+
// other exsiting documents, GC is enabled.
|
|
42
|
+
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// For new documents, GC has to be exlicitly enabled via the gcAllowed flag in GC options.
|
|
46
|
+
this.gcEnabled = gcOptions.gcAllowed === true;
|
|
47
|
+
}
|
|
48
|
+
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
49
|
+
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
50
|
+
this.latestSummaryGCVersion = prevSummaryGCVersion !== null && prevSummaryGCVersion !== void 0 ? prevSummaryGCVersion : this.currentGCVersion;
|
|
51
|
+
// Whether GC should run or not. Can override with localStorage flag.
|
|
52
|
+
this.shouldRunGC = (_a = getLocalStorageFeatureGate(runGCKey)) !== null && _a !== void 0 ? _a : (
|
|
53
|
+
// GC must be enabled for the document.
|
|
54
|
+
this.gcEnabled
|
|
55
|
+
// GC must not be disabled via GC options.
|
|
56
|
+
&& !gcOptions.disableGC);
|
|
57
|
+
// Whether GC sweep phase should run or not. If this is false, only GC mark phase is run. Can override with
|
|
58
|
+
// localStorage flag.
|
|
59
|
+
this.shouldRunSweep = this.shouldRunGC &&
|
|
60
|
+
((_b = getLocalStorageFeatureGate(runSweepKey)) !== null && _b !== void 0 ? _b : gcOptions.runSweep === true);
|
|
61
|
+
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
62
|
+
this.testMode = (_c = getLocalStorageFeatureGate(gcTestModeKey)) !== null && _c !== void 0 ? _c : gcOptions.runGCInTestMode === true;
|
|
63
|
+
}
|
|
64
|
+
static create(provider, gcOptions, deleteUnusedRoutes, baseLogger, existing, metadata) {
|
|
65
|
+
return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, baseLogger, existing, metadata);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* This tracks two things:
|
|
69
|
+
* 1. Whether GC is enabled - If this is 0, GC is disabled. If this is > 0, GC is enabled.
|
|
70
|
+
* 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.
|
|
71
|
+
*/
|
|
72
|
+
get gcSummaryFeatureVersion() {
|
|
73
|
+
return this.gcEnabled ? this.currentGCVersion : 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tells whether the GC version has changed compared to the version in the latest summary.
|
|
77
|
+
*/
|
|
78
|
+
get hasGCVersionChanged() {
|
|
79
|
+
// The current version can differ from the latest summary version in two cases:
|
|
80
|
+
// 1. The summary this client loaded with has data from a different GC version.
|
|
81
|
+
// 2. This client's latest summary was updated from a snapshot that has a different GC version.
|
|
82
|
+
return this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs garbage collection and udpates the reference / used state of the nodes in the container.
|
|
86
|
+
* @returns the number of data stores that have been marked as unreferenced.
|
|
87
|
+
*/
|
|
88
|
+
async collectGarbage(options) {
|
|
89
|
+
const { logger = this.logger, runSweep = this.shouldRunSweep, fullGC = this.gcOptions.runFullGC === true || this.hasGCVersionChanged, } = options;
|
|
90
|
+
return PerformanceEvent.timedExecAsync(logger, { eventName: "GarbageCollection" }, async (event) => {
|
|
91
|
+
const gcStats = {};
|
|
92
|
+
// Get the runtime's GC data and run GC on the reference graph in it.
|
|
93
|
+
const gcData = await this.provider.getGCData(fullGC);
|
|
94
|
+
const { referencedNodeIds, deletedNodeIds } = runGarbageCollection(gcData.gcNodes, ["/"], logger);
|
|
95
|
+
// Remove this node's route ("/") and notify data stores of routes that are used in it.
|
|
96
|
+
const usedRoutes = referencedNodeIds.filter((id) => { return id !== "/"; });
|
|
97
|
+
const dataStoreUsedStateStats = this.provider.updateUsedRoutes(usedRoutes);
|
|
98
|
+
if (runSweep) {
|
|
99
|
+
// Placeholder for running sweep logic.
|
|
100
|
+
}
|
|
101
|
+
// Update stats to be reported in the peformance event.
|
|
102
|
+
gcStats.deletedNodes = deletedNodeIds.length;
|
|
103
|
+
gcStats.totalNodes = referencedNodeIds.length + deletedNodeIds.length;
|
|
104
|
+
gcStats.deletedDataStores = dataStoreUsedStateStats.unusedNodeCount;
|
|
105
|
+
gcStats.totalDataStores = dataStoreUsedStateStats.totalNodeCount;
|
|
106
|
+
// If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios
|
|
107
|
+
// involving access to deleted data.
|
|
108
|
+
if (this.testMode) {
|
|
109
|
+
this.deleteUnusedRoutes(deletedNodeIds);
|
|
110
|
+
}
|
|
111
|
+
event.end(gcStats);
|
|
112
|
+
return gcStats;
|
|
113
|
+
}, { end: true, cancel: "error" });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Called when the latest summary of the system has been refreshed. This will be used to update the state of the
|
|
117
|
+
* latest summary tracked.
|
|
118
|
+
*/
|
|
119
|
+
async latestSummaryStateRefreshed(result, readAndParseBlob) {
|
|
120
|
+
if (!this.shouldRunGC || !result.latestSummaryUpdated) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// If the summary was tracked by this client, it was the one that generated the summary in the first place.
|
|
124
|
+
// Basically, it was written in the current GC version.
|
|
125
|
+
if (result.wasSummaryTracked) {
|
|
126
|
+
this.latestSummaryGCVersion = this.currentGCVersion;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// If the summary was not tracked by this client, update latest GC version from the snapshot in the result as
|
|
130
|
+
// that is now the latest summary.
|
|
131
|
+
await this.updateSummaryGCVersionFromSnapshot(result.snapshot, readAndParseBlob);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Update the latest summary GC version from the metadata blob in the given snapshot.
|
|
135
|
+
*/
|
|
136
|
+
async updateSummaryGCVersionFromSnapshot(snapshot, readAndParseBlob) {
|
|
137
|
+
const metadataBlobId = snapshot.blobs[metadataBlobName];
|
|
138
|
+
if (metadataBlobId) {
|
|
139
|
+
const metadata = await readAndParseBlob(metadataBlobId);
|
|
140
|
+
this.latestSummaryGCVersion = getGCVersion(metadata);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=garbageCollection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"garbageCollection.js","sourceRoot":"","sources":["../src/garbageCollection.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { runGarbageCollection } from \"@fluidframework/garbage-collector\";\nimport { ISnapshotTree } from \"@fluidframework/protocol-definitions\";\nimport { IGarbageCollectionData } from \"@fluidframework/runtime-definitions\";\nimport { ReadAndParseBlob, RefreshSummaryResult } from \"@fluidframework/runtime-utils\";\nimport { ChildLogger, PerformanceEvent } from \"@fluidframework/telemetry-utils\";\n\nimport { IGCRuntimeOptions } from \"./containerRuntime\";\nimport { getLocalStorageFeatureGate } from \"./localStorageFeatureGates\";\nimport {\n getGCVersion,\n GCVersion,\n IContainerRuntimeMetadata,\n metadataBlobName,\n} from \"./summaryFormat\";\n\n/** This is the current version of garbage collection. */\nconst GCVersion = 1;\n\n// Local storage key to turn GC on / off.\nconst runGCKey = \"FluidRunGC\";\n// Local storage key to turn GC test mode on / off.\nconst gcTestModeKey = \"FluidGCTestMode\";\n// Local storage key to turn GC sweep on / off.\nconst runSweepKey = \"FluidRunSweep\";\n\n/** The used state statistics of a node. */\nexport interface IUsedStateStats {\n totalNodeCount: number;\n unusedNodeCount: number;\n}\n\n/** The statistics of the system state after a garbage collection run. */\nexport interface IGCStats {\n totalNodes: number;\n deletedNodes: number;\n totalDataStores: number;\n deletedDataStores: number;\n}\n\n/** Defines the APIs for the runtime object to be passed to the garbage collector. */\nexport interface IGarbageCollectionRuntime {\n /** Returns the garbage collection data of the runtime. */\n getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;\n /** After GC has run, called to notify the runtime of routes that are used in it. */\n updateUsedRoutes(usedRoutes: string[]): IUsedStateStats;\n}\n\n/** Defines the contract for the garbage collector. */\nexport interface IGarbageCollector {\n /** Tells whether GC should run or not. */\n readonly shouldRunGC: boolean;\n /**\n * This tracks two things:\n * 1. Whether GC is enabled - If this is 0, GC is disabled. If this is > 0, GC is enabled.\n * 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.\n */\n readonly gcSummaryFeatureVersion: number;\n /** Tells whether the GC version has changed compared to the version in the latest summary. */\n readonly hasGCVersionChanged: boolean;\n /** Run garbage collection and update the reference / used state of the system. */\n collectGarbage(\n options: { logger?: ITelemetryLogger, runGC?: boolean, runSweep?: boolean, fullGC?: boolean },\n ): Promise<IGCStats>;\n /** Called when the latest summary of the system has been refreshed. */\n latestSummaryStateRefreshed(result: RefreshSummaryResult, readAndParseBlob: ReadAndParseBlob): Promise<void>;\n}\n\n/**\n * The garbage collector for the container runtime. It consolidates the garbage collection functionality and maintains\n * its state across summaries.\n */\nexport class GarbageCollector implements IGarbageCollector {\n public static create(\n provider: IGarbageCollectionRuntime,\n gcOptions: IGCRuntimeOptions,\n deleteUnusedRoutes: (unusedRoutes: string[]) => void,\n baseLogger: ITelemetryLogger,\n existing: boolean,\n metadata?: IContainerRuntimeMetadata,\n ): IGarbageCollector {\n return new GarbageCollector(provider, gcOptions, deleteUnusedRoutes, baseLogger, existing, metadata);\n }\n\n /**\n * Tells whether GC should be run based on the GC options and local storage flags.\n */\n public readonly shouldRunGC: boolean;\n\n /**\n * This tracks two things:\n * 1. Whether GC is enabled - If this is 0, GC is disabled. If this is > 0, GC is enabled.\n * 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.\n */\n public get gcSummaryFeatureVersion(): number {\n return this.gcEnabled ? this.currentGCVersion : 0;\n }\n\n /**\n * Tells whether the GC version has changed compared to the version in the latest summary.\n */\n public get hasGCVersionChanged(): boolean {\n // The current version can differ from the latest summary version in two cases:\n // 1. The summary this client loaded with has data from a different GC version.\n // 2. This client's latest summary was updated from a snapshot that has a different GC version.\n return this.shouldRunGC && this.latestSummaryGCVersion !== this.currentGCVersion;\n }\n\n /**\n * Tracks if GC is enabled for this document. This is specified during document creation and doesn't change\n * throughout its lifetime.\n */\n private readonly gcEnabled: boolean;\n private readonly shouldRunSweep: boolean;\n private readonly testMode: boolean;\n private readonly logger: ITelemetryLogger;\n\n // The current GC version that this container is running.\n private readonly currentGCVersion = GCVersion;\n // This is the version of GC data in the latest summary being tracked.\n private latestSummaryGCVersion: GCVersion;\n\n protected constructor(\n private readonly provider: IGarbageCollectionRuntime,\n private readonly gcOptions: IGCRuntimeOptions,\n /**\n * After GC has run, called to delete objects in the runtime whose routes are unused. This is not part of the\n * provider because its specific to this garbage collector implementation and is not part of the contract.\n */\n private readonly deleteUnusedRoutes: (unusedRoutes: string[]) => void,\n baseLogger: ITelemetryLogger,\n existing: boolean,\n metadata?: IContainerRuntimeMetadata,\n ) {\n this.logger = ChildLogger.create(baseLogger, \"GarbageCollector\");\n\n let prevSummaryGCVersion: number | undefined;\n // GC can only be enabled during creation. After that, it can never be enabled again. So, for existing\n // documents, we get this information from the metadata blob.\n if (existing) {\n prevSummaryGCVersion = getGCVersion(metadata);\n // Existing documents which did not have metadata blob or had GC disabled have version as 0. For all\n // other exsiting documents, GC is enabled.\n this.gcEnabled = prevSummaryGCVersion > 0;\n } else {\n // For new documents, GC has to be exlicitly enabled via the gcAllowed flag in GC options.\n this.gcEnabled = gcOptions.gcAllowed === true;\n }\n // For existing document, the latest summary is the one that we loaded from. So, use its GC version as the\n // latest tracked GC version. For new documents, we will be writing the first summary with the current version.\n this.latestSummaryGCVersion = prevSummaryGCVersion ?? this.currentGCVersion;\n\n // Whether GC should run or not. Can override with localStorage flag.\n this.shouldRunGC = getLocalStorageFeatureGate(runGCKey) ?? (\n // GC must be enabled for the document.\n this.gcEnabled\n // GC must not be disabled via GC options.\n && !gcOptions.disableGC\n );\n\n // Whether GC sweep phase should run or not. If this is false, only GC mark phase is run. Can override with\n // localStorage flag.\n this.shouldRunSweep = this.shouldRunGC &&\n (getLocalStorageFeatureGate(runSweepKey) ?? gcOptions.runSweep === true);\n\n // Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.\n this.testMode = getLocalStorageFeatureGate(gcTestModeKey) ?? gcOptions.runGCInTestMode === true;\n }\n\n /**\n * Runs garbage collection and udpates the reference / used state of the nodes in the container.\n * @returns the number of data stores that have been marked as unreferenced.\n */\n public async collectGarbage(\n options: {\n /** Logger to use for logging GC events */\n logger?: ITelemetryLogger,\n /** True to run GC sweep phase after the mark phase */\n runSweep?: boolean,\n /** True to generate full GC data */\n fullGC?: boolean,\n },\n ): Promise<IGCStats> {\n const {\n logger = this.logger,\n runSweep = this.shouldRunSweep,\n fullGC = this.gcOptions.runFullGC === true || this.hasGCVersionChanged,\n } = options;\n\n return PerformanceEvent.timedExecAsync(logger, { eventName: \"GarbageCollection\" }, async (event) => {\n const gcStats: {\n deletedNodes?: number,\n totalNodes?: number,\n deletedDataStores?: number,\n totalDataStores?: number,\n } = {};\n\n // Get the runtime's GC data and run GC on the reference graph in it.\n const gcData = await this.provider.getGCData(fullGC);\n const { referencedNodeIds, deletedNodeIds } = runGarbageCollection(\n gcData.gcNodes,\n [ \"/\" ],\n logger,\n );\n\n // Remove this node's route (\"/\") and notify data stores of routes that are used in it.\n const usedRoutes = referencedNodeIds.filter((id: string) => { return id !== \"/\"; });\n const dataStoreUsedStateStats = this.provider.updateUsedRoutes(usedRoutes);\n\n if (runSweep) {\n // Placeholder for running sweep logic.\n }\n\n // Update stats to be reported in the peformance event.\n gcStats.deletedNodes = deletedNodeIds.length;\n gcStats.totalNodes = referencedNodeIds.length + deletedNodeIds.length;\n gcStats.deletedDataStores = dataStoreUsedStateStats.unusedNodeCount;\n gcStats.totalDataStores = dataStoreUsedStateStats.totalNodeCount;\n\n // If we are running in GC test mode, delete objects for unused routes. This enables testing scenarios\n // involving access to deleted data.\n if (this.testMode) {\n this.deleteUnusedRoutes(deletedNodeIds);\n }\n event.end(gcStats);\n return gcStats as IGCStats;\n },\n { end: true, cancel: \"error\" });\n }\n\n /**\n * Called when the latest summary of the system has been refreshed. This will be used to update the state of the\n * latest summary tracked.\n */\n public async latestSummaryStateRefreshed(\n result: RefreshSummaryResult,\n readAndParseBlob: ReadAndParseBlob,\n ): Promise<void> {\n if (!this.shouldRunGC || !result.latestSummaryUpdated) {\n return;\n }\n\n // If the summary was tracked by this client, it was the one that generated the summary in the first place.\n // Basically, it was written in the current GC version.\n if (result.wasSummaryTracked) {\n this.latestSummaryGCVersion = this.currentGCVersion;\n return;\n }\n // If the summary was not tracked by this client, update latest GC version from the snapshot in the result as\n // that is now the latest summary.\n await this.updateSummaryGCVersionFromSnapshot(result.snapshot, readAndParseBlob);\n }\n\n /**\n * Update the latest summary GC version from the metadata blob in the given snapshot.\n */\n private async updateSummaryGCVersionFromSnapshot(snapshot: ISnapshotTree, readAndParseBlob: ReadAndParseBlob) {\n const metadataBlobId = snapshot.blobs[metadataBlobName];\n if (metadataBlobId) {\n const metadata = await readAndParseBlob<IContainerRuntimeMetadata>(metadataBlobId);\n this.latestSummaryGCVersion = getGCVersion(metadata);\n }\n }\n}\n"]}
|
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from "./containerRuntime";
|
|
6
6
|
export * from "./deltaScheduler";
|
|
7
7
|
export * from "./dataStoreRegistry";
|
|
8
|
+
export { IGarbageCollectionRuntime, IGCStats, IUsedStateStats } from "./garbageCollection";
|
|
8
9
|
export * from "./pendingStateManager";
|
|
9
10
|
export * from "./summarizer";
|
|
10
11
|
export * from "./summarizerTypes";
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3F,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC"}
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAoC,0BAA0B,EAAE,MAAM,gCAAgC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport * from \"./containerRuntime\";\nexport * from \"./deltaScheduler\";\nexport * from \"./dataStoreRegistry\";\nexport { IGarbageCollectionRuntime, IGCStats, IUsedStateStats } from \"./garbageCollection\";\nexport * from \"./pendingStateManager\";\nexport * from \"./summarizer\";\nexport * from \"./summarizerTypes\";\nexport * from \"./summaryCollection\";\nexport { ICancellableSummarizerController, neverCancelledSummaryToken } from \"./runWhileConnectedCoordinator\";\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/container-runtime";
|
|
8
|
-
export declare const pkgVersion = "0.51.
|
|
8
|
+
export declare const pkgVersion = "0.51.3";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAC3D,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAC3D,eAAO,MAAM,UAAU,WAAW,CAAC"}
|
package/lib/packageVersion.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"0.51.3\";\n"]}
|
|
@@ -61,7 +61,6 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
61
61
|
private readonly initialStates;
|
|
62
62
|
private readonly previousClientIds;
|
|
63
63
|
private readonly firstStashedCSN;
|
|
64
|
-
private stashedCount;
|
|
65
64
|
private readonly disposeOnce;
|
|
66
65
|
private pendingMessagesCount;
|
|
67
66
|
private isProcessingBatch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAGjE,OAAO,EACH,yBAAyB,EAC5B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAEhE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAoB,MAAM,oBAAoB,CAAC;AAE9F;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,oBAAoB,CAAC;IAClC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,OAAO,EAAE,GAAG,CAAC;IACb,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACnD;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,OAAO,CAAC;CACjB;AAED,oBAAY,aAAa,GAAG,eAAe,GAAG,iBAAiB,GAAG,aAAa,CAAC;AAEhF,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,aAAa,EAAE,aAAa,EAAE,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,YAAW,WAAW;
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,6 @@ export class PendingStateManager {
|
|
|
24
24
|
this.pendingStates = new Deque();
|
|
25
25
|
this.previousClientIds = new Set();
|
|
26
26
|
this.firstStashedCSN = -1;
|
|
27
|
-
this.stashedCount = 0;
|
|
28
27
|
this.disposeOnce = new Lazy(() => {
|
|
29
28
|
this.initialStates.clear();
|
|
30
29
|
this.pendingStates.clear();
|
|
@@ -42,7 +41,6 @@ export class PendingStateManager {
|
|
|
42
41
|
// get stashed op count and client sequence number of first op
|
|
43
42
|
const messages = initialState.pendingStates
|
|
44
43
|
.filter((state) => state.type === "message");
|
|
45
|
-
this.stashedCount = messages.length;
|
|
46
44
|
this.firstStashedCSN = messages[0].clientSequenceNumber;
|
|
47
45
|
}
|
|
48
46
|
}
|
|
@@ -202,7 +200,6 @@ export class PendingStateManager {
|
|
|
202
200
|
// if it's not a message just drop it and keep looking
|
|
203
201
|
if (nextState.type === "message") {
|
|
204
202
|
this.assertOpMatch(nextState, message, isOriginalClientId);
|
|
205
|
-
--this.stashedCount;
|
|
206
203
|
return { localAck: true, localOpMetadata: nextState.localOpMetadata };
|
|
207
204
|
}
|
|
208
205
|
}
|
|
@@ -334,45 +331,12 @@ export class PendingStateManager {
|
|
|
334
331
|
assert(this.connected, 0x172 /* "The connection state is not consistent with the runtime" */);
|
|
335
332
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
336
333
|
assert(this.clientId !== this.containerRuntime.clientId, 0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
337
|
-
const prevClientId = this.clientId;
|
|
338
334
|
this.clientId = this.containerRuntime.clientId;
|
|
339
335
|
assert(this.initialStates.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
|
|
340
336
|
let pendingStatesCount = this.pendingStates.length;
|
|
341
337
|
if (pendingStatesCount === 0) {
|
|
342
338
|
return;
|
|
343
339
|
}
|
|
344
|
-
if (!prevClientId && this.stashedCount > 0) {
|
|
345
|
-
// this is first connect, verify we are about to "resubmit" only stashed ops
|
|
346
|
-
assert(this.pendingStates.toArray().filter((s) => s.type === "message").length === this.stashedCount, 0x290 /* "unexpected message queued before first connect" */);
|
|
347
|
-
Array.from(this.previousClientIds).map((id) => assert(this.containerRuntime.getQuorum().getMember(id) === undefined, 0x291 /* "client with stashed ops already connected" */));
|
|
348
|
-
// send rejoin op with stashed client ID if we have it
|
|
349
|
-
if (this.previousClientIds.size > 0) {
|
|
350
|
-
const clientId = Array.from(this.previousClientIds)[0];
|
|
351
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
352
|
-
this.pendingStates.unshift({
|
|
353
|
-
type: "message",
|
|
354
|
-
messageType: ContainerMessageType.Rejoin,
|
|
355
|
-
content: { clientId },
|
|
356
|
-
});
|
|
357
|
-
++pendingStatesCount;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (prevClientId) {
|
|
361
|
-
// add a rejoin op so future clients provided with our stashed pending ops can recognize them
|
|
362
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
363
|
-
const firstState = this.pendingStates.peekFront();
|
|
364
|
-
if (firstState.type !== "message" || firstState.messageType !== ContainerMessageType.Rejoin) {
|
|
365
|
-
// if there is already a rejoin op in the queue, just resubmit same op under new client ID
|
|
366
|
-
// otherwise, add one to the queue
|
|
367
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
368
|
-
this.pendingStates.unshift({
|
|
369
|
-
type: "message",
|
|
370
|
-
messageType: ContainerMessageType.Rejoin,
|
|
371
|
-
content: { clientId: prevClientId },
|
|
372
|
-
});
|
|
373
|
-
++pendingStatesCount;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
340
|
// Reset the pending message count because all these messages will be removed from the queue.
|
|
377
341
|
this.pendingMessagesCount = 0;
|
|
378
342
|
// Save the current FlushMode so that we can revert it back after replaying the states.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert, Lazy } from \"@fluidframework/common-utils\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport {\n ISequencedDocumentMessage,\n} from \"@fluidframework/protocol-definitions\";\nimport { FlushMode } from \"@fluidframework/runtime-definitions\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerRuntime, ContainerMessageType, isRuntimeMessage } from \"./containerRuntime\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n */\nexport interface IPendingMessage {\n type: \"message\";\n messageType: ContainerMessageType;\n clientSequenceNumber: number;\n referenceSequenceNumber: number;\n content: any;\n localOpMetadata: unknown;\n opMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * This represents a FlushMode update and is added to the pending queue when `setFlushMode` is called on the\n * ContainerRuntime and the FlushMode changes.\n */\nexport interface IPendingFlushMode {\n type: \"flushMode\";\n flushMode: FlushMode;\n}\n\n/**\n * This represents a manual flush and is added to the pending queue when `flush` is called on the ContainerRuntime to\n * flush any pending messages. This is applicable only when the FlushMode is Manual.\n */\nexport interface IPendingFlush {\n type: \"flush\";\n}\n\nexport type IPendingState = IPendingMessage | IPendingFlushMode | IPendingFlush;\n\nexport interface IPendingLocalState {\n /**\n * client ID we most recently connected with, or undefined if we never connected\n */\n clientId?: string;\n /**\n * list of pending states, including ops and batch information\n */\n pendingStates: IPendingState[];\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes setting the FlushMode, manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n private readonly pendingStates = new Deque<IPendingState>();\n private readonly initialStates: Deque<IPendingState>;\n private readonly previousClientIds = new Set<string>();\n private readonly firstStashedCSN: number = -1;\n private stashedCount = 0;\n private readonly disposeOnce = new Lazy<void>(() => {\n this.initialStates.clear();\n this.pendingStates.clear();\n });\n\n // Maintains the count of messages that are currently unacked.\n private pendingMessagesCount: number = 0;\n\n // Indicates whether we are processing a batch.\n private isProcessingBatch: boolean = false;\n\n // This stores the first message in the batch that we are processing. This is used to verify that we get\n // the correct batch metadata.\n private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n private clientId: string | undefined;\n\n private get connected(): boolean {\n return this.containerRuntime.connected;\n }\n\n /**\n * Called to check if there are any pending messages in the pending state queue.\n * @returns A boolean indicating whether there are messages or not.\n */\n public hasPendingMessages(): boolean {\n return this.pendingMessagesCount !== 0;\n }\n\n public getLocalState(): IPendingLocalState | undefined {\n if (this.hasPendingMessages()) {\n return {\n clientId: this.clientId,\n pendingStates: this.pendingStates.toArray().map(\n // delete localOpMetadata since it may not be serializable\n // and will be regenerated by applyStashedOp()\n (state) => state.type === \"message\" ? {...state, localOpMetadata: undefined } : state),\n };\n }\n }\n\n constructor(\n private readonly containerRuntime: ContainerRuntime,\n private readonly applyStashedOp: (type, content) => Promise<unknown>,\n initialState: IPendingLocalState | undefined,\n ) {\n this.initialStates = new Deque<IPendingState>(initialState?.pendingStates ?? []);\n\n if (initialState) {\n if (initialState?.clientId) {\n this.previousClientIds.add(initialState.clientId);\n }\n // get stashed op count and client sequence number of first op\n const messages = initialState.pendingStates\n .filter((state) => state.type === \"message\") as IPendingMessage[];\n this.stashedCount = messages.length;\n this.firstStashedCSN = messages[0].clientSequenceNumber;\n }\n }\n\n public get disposed() { return this.disposeOnce.evaluated; }\n public readonly dispose = () => this.disposeOnce.value;\n\n /**\n * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n * queue.\n * @param type - The container message type.\n * @param clientSequenceNumber - The clientSequenceNumber associated with the message.\n * @param content - The message content.\n * @param localOpMetadata - The local metadata associated with the message.\n */\n public onSubmitMessage(\n type: ContainerMessageType,\n clientSequenceNumber: number,\n referenceSequenceNumber: number,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined,\n ) {\n const pendingMessage: IPendingMessage = {\n type: \"message\",\n messageType: type,\n clientSequenceNumber,\n referenceSequenceNumber,\n content,\n localOpMetadata,\n opMetadata,\n };\n\n this.pendingStates.push(pendingMessage);\n\n this.pendingMessagesCount++;\n }\n\n /**\n * Called when the FlushMode is updated. Adds the FlushMode to the pending state queue.\n * @param flushMode - The flushMode that was updated.\n */\n public onFlushModeUpdated(flushMode: FlushMode) {\n if (flushMode === FlushMode.Immediate) {\n const previousState = this.pendingStates.peekBack();\n\n // We don't have to track a previous \"flush\" state because FlushMode.Immediate flushes the messages. So,\n // just tracking this FlushMode.Immediate is enough.\n if (previousState?.type === \"flush\") {\n this.pendingStates.removeBack();\n }\n\n // If no messages were sent between FlushMode.TurnBased and FlushMode.Immediate,\n // then we do not have to track both these states.\n // Remove FlushMode.TurnBased from the pending queue and return.\n if (previousState?.type === \"flushMode\" && previousState.flushMode === FlushMode.TurnBased) {\n this.pendingStates.removeBack();\n return;\n }\n }\n\n const pendingFlushMode: IPendingFlushMode = {\n type: \"flushMode\",\n flushMode,\n };\n this.pendingStates.push(pendingFlushMode);\n }\n\n /**\n * Called when flush() is called on the ContainerRuntime to manually flush messages.\n */\n public onFlush() {\n // If the FlushMode is Immediate, we should not track this flush call as it is only applicable when FlushMode\n // is TurnBased.\n if (this.containerRuntime.flushMode === FlushMode.Immediate) {\n return;\n }\n\n // If the previous state is not a message, we don't have to track this flush call as there is nothing to flush.\n const previousState = this.pendingStates.peekBack();\n if (previousState?.type !== \"message\") {\n return;\n }\n\n // Note that because of the checks above and the checks in onFlushModeUpdated(), we can be sure that a \"flush\"\n // state always has a \"message\" before and after it. So, it marks the end of a batch and the beginning of a\n // new one.\n const pendingFlush: IPendingFlush = {\n type: \"flush\",\n };\n this.pendingStates.push(pendingFlush);\n }\n\n /**\n * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n */\n public async applyStashedOpsAt(seqNum: number) {\n // apply stashed ops at sequence number\n while (!this.initialStates.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextState = this.initialStates.peekFront()!;\n if (nextState.type === \"message\") {\n if (nextState.referenceSequenceNumber > seqNum) {\n break; // nothing left to do at this sequence number\n } else if (nextState.referenceSequenceNumber > 0 && nextState.referenceSequenceNumber < seqNum) {\n throw new Error(\"loaded from snapshot too recent to apply stashed ops\");\n }\n\n // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n const localOpMetadata = await this.applyStashedOp(nextState.messageType, nextState.content);\n nextState.localOpMetadata = localOpMetadata;\n }\n\n // then we push onto pendingStates which will cause PendingStateManager to resubmit when we connect\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.pendingStates.push(this.initialStates.shift()!);\n }\n }\n\n /**\n * Processes a local message once it's ack'd by the server to verify that there was no data corruption and that\n * the batch information was preserved for batch messages. Also process remote messages that might have been\n * sent from a previous container.\n * @param message - The messsage that got ack'd and needs to be processed.\n */\n public processMessage(message: ISequencedDocumentMessage, local: boolean) {\n // Do not process chunked ops until all pieces are available.\n if (message.type === ContainerMessageType.ChunkedOp) {\n return { localAck: false, localOpMetadata: undefined };\n }\n\n if (local) {\n return { localAck: false, localOpMetadata: this.processPendingLocalMessage(message) };\n } else {\n return this.processRemoteMessage(message);\n }\n }\n\n /**\n * Listens for ACKs of stashed ops\n */\n private processRemoteMessage(message: ISequencedDocumentMessage) {\n if (!isRuntimeMessage(message)) {\n return { localAck: false, localOpMetadata: undefined };\n }\n\n // this message was a pending op that was actually sent successfully\n const isOriginalClientId = message.clientId === Array.from(this.previousClientIds)[0] &&\n message.clientSequenceNumber >= this.firstStashedCSN;\n // this message is a pending or stashed op that was resubmitted\n const isNewClientId = Array.from(this.previousClientIds).indexOf(message.clientId) > 0;\n\n // if this is an ack for a stashed op, dequeue one message.\n // we should have seen its ref seq num by now and the DDS should be ready for it to be ACKed\n if (isOriginalClientId || isNewClientId) {\n assert(this.clientId === undefined, 0x28b /* \"multiple clients connected with stashed ops\" */);\n while (!this.pendingStates.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextState = this.pendingStates.shift()!;\n // if it's not a message just drop it and keep looking\n if (nextState.type === \"message\") {\n this.assertOpMatch(nextState, message, isOriginalClientId);\n --this.stashedCount;\n return { localAck: true, localOpMetadata: nextState.localOpMetadata };\n }\n }\n }\n\n if (message.type === ContainerMessageType.Rejoin && this.previousClientIds.has(message.contents?.clientId)) {\n this.previousClientIds.add(message.clientId);\n }\n\n return { localAck: false, localOpMetadata: undefined };\n }\n\n private assertOpMatch(state: IPendingMessage, message: ISequencedDocumentMessage, isOriginalClientId: boolean) {\n assert(message.type === state.messageType, 0x28c /* \"different message type\" */);\n assert(message.clientSequenceNumber === state.clientSequenceNumber || !isOriginalClientId,\n 0x28d /* \"client sequence number doesn't match\" */);\n switch(message.type) {\n case ContainerMessageType.Attach:\n assert(message.contents.id === state.content.id, 0x28e /* \"datastore ID doesn't match\" */);\n break;\n case ContainerMessageType.FluidDataStoreOp:\n assert(message.contents.address === state.content.address, 0x28f /* \"address doesn't match\" */);\n break;\n case ContainerMessageType.BlobAttach:\n // todo: assert we have blob storage, assert blob IDs match, remove blob from blob storage since it made\n // it through successfully\n break;\n case ContainerMessageType.Rejoin:\n default:\n throw new Error(`${message.type} not expected`);\n }\n }\n\n /**\n * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n * the batch information was preserved for batch messages.\n * @param message - The messsage that got ack'd and needs to be processed.\n */\n private processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n // Pre-processing part - This may be the start of a batch.\n this.maybeProcessBatchBegin(message);\n\n // Get the next state from the pending queue and verify that it is of type \"message\".\n const pendingState = this.peekNextPendingState();\n assert(pendingState.type === \"message\", 0x169 /* \"No pending message found for this remote message\" */);\n this.pendingStates.shift();\n\n // Processing part - Verify that there has been no data corruption.\n // The clientSequenceNumber of the incoming message must match that of the pending message.\n if (pendingState.clientSequenceNumber !== message.clientSequenceNumber) {\n // Close the container because this could indicate data corruption.\n const error = new DataProcessingError(\n \"unexpectedAckReceived\",\n \"unexpectedAckReceived\",\n {\n clientId: message.clientId,\n sequenceNumber: message.sequenceNumber,\n clientSequenceNumber: message.clientSequenceNumber,\n expectedClientSequenceNumber: pendingState.clientSequenceNumber,\n },\n );\n\n this.containerRuntime.closeFn(error);\n return;\n }\n\n this.pendingMessagesCount--;\n\n // Post-processing part - If we are processing a batch then this could be the last message in the batch.\n if (this.isProcessingBatch) {\n this.maybeProcessBatchEnd(message);\n }\n\n return pendingState.localOpMetadata;\n }\n\n /**\n * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n const pendingState = this.peekNextPendingState();\n if (pendingState.type !== \"flush\" && pendingState.type !== \"flushMode\") {\n return;\n }\n\n // If the pending state is of type \"flushMode\", it must be Manual since Automatic flush mode is processed\n // after a message is processed and not before.\n if (pendingState.type === \"flushMode\") {\n assert(pendingState.flushMode === FlushMode.TurnBased,\n 0x16a /* \"Flush mode should be manual when processing batch begin\" */);\n }\n\n // We should not already be processing a batch and there should be no pending batch begin message.\n assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n 0x16b /* \"The pending batch state indicates we are already processing a batch\" */);\n\n // Set the pending batch state indicating we have started processing a batch.\n this.pendingBatchBeginMessage = message;\n this.isProcessingBatch = true;\n\n // Remove this pending state from the queue as we have processed it.\n this.pendingStates.shift();\n }\n\n private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n const nextPendingState = this.peekNextPendingState();\n if (nextPendingState.type !== \"flush\" && nextPendingState.type !== \"flushMode\") {\n return;\n }\n\n // If the next pending state is of type \"flushMode\", it must be Immediate and if so, we need to remove it from\n // the queue.\n // Note that we do not remove the type \"flush\" from the queue because it indicates the end of one batch and the\n // beginning of a new one. So, it will removed when the next batch begin is processed.\n if (nextPendingState.type === \"flushMode\") {\n assert(nextPendingState.flushMode === FlushMode.Immediate,\n 0x16c /* \"Flush mode is set to TurnBased in the middle of processing a batch\" */);\n this.pendingStates.shift();\n }\n\n // There should be a pending batch begin message.\n assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* \"There is no pending batch begin message\" */);\n\n // Get the batch begin metadata from the first message in the batch.\n const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;\n\n // There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n if (this.pendingBatchBeginMessage === message) {\n assert(batchBeginMetadata === undefined,\n 0x16e /* \"Batch with single message should not have batch metadata\" */);\n } else {\n // Get the batch metadata from the last message in the batch.\n const batchEndMetadata = message.metadata?.batch;\n assert(batchBeginMetadata === true, 0x16f /* \"Did not receive batch begin metadata\" */);\n assert(batchEndMetadata === false, 0x170 /* \"Did not receive batch end metadata\" */);\n }\n\n // Clear the pending batch state now that we have processed the entire batch.\n this.pendingBatchBeginMessage = undefined;\n this.isProcessingBatch = false;\n }\n\n /**\n * Returns the next pending state from the pending state queue.\n */\n private peekNextPendingState(): IPendingState {\n const nextPendingState = this.pendingStates.peekFront();\n assert(!!nextPendingState, 0x171 /* \"No pending state found for the remote message\" */);\n return nextPendingState;\n }\n\n /**\n * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n * states in its queue. This includes setting the FlushMode and triggering resubmission of unacked ops.\n */\n public replayPendingStates() {\n assert(this.connected, 0x172 /* \"The connection state is not consistent with the runtime\" */);\n\n // This assert suggests we are about to send same ops twice, which will result in data loss.\n assert(this.clientId !== this.containerRuntime.clientId,\n 0x173 /* \"replayPendingStates called twice for same clientId!\" */);\n const prevClientId = this.clientId;\n this.clientId = this.containerRuntime.clientId;\n\n assert(this.initialStates.isEmpty(), 0x174 /* \"initial states should be empty before replaying pending\" */);\n\n let pendingStatesCount = this.pendingStates.length;\n if (pendingStatesCount === 0) {\n return;\n }\n\n if (!prevClientId && this.stashedCount > 0) {\n // this is first connect, verify we are about to \"resubmit\" only stashed ops\n assert(this.pendingStates.toArray().filter((s) => s.type === \"message\").length === this.stashedCount,\n 0x290 /* \"unexpected message queued before first connect\" */);\n\n Array.from(this.previousClientIds).map((id) =>\n assert(this.containerRuntime.getQuorum().getMember(id) === undefined,\n 0x291 /* \"client with stashed ops already connected\" */));\n\n // send rejoin op with stashed client ID if we have it\n if (this.previousClientIds.size > 0) {\n const clientId = Array.from(this.previousClientIds)[0];\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n this.pendingStates.unshift({\n type: \"message\",\n messageType: ContainerMessageType.Rejoin,\n content: { clientId },\n } as IPendingMessage);\n ++pendingStatesCount;\n }\n }\n\n if (prevClientId) {\n // add a rejoin op so future clients provided with our stashed pending ops can recognize them\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const firstState = this.pendingStates.peekFront()!;\n if (firstState.type !== \"message\" || firstState.messageType !== ContainerMessageType.Rejoin) {\n // if there is already a rejoin op in the queue, just resubmit same op under new client ID\n // otherwise, add one to the queue\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n this.pendingStates.unshift({\n type: \"message\",\n messageType: ContainerMessageType.Rejoin,\n content: { clientId: prevClientId },\n } as IPendingMessage);\n ++pendingStatesCount;\n }\n }\n\n // Reset the pending message count because all these messages will be removed from the queue.\n this.pendingMessagesCount = 0;\n\n // Save the current FlushMode so that we can revert it back after replaying the states.\n const savedFlushMode = this.containerRuntime.flushMode;\n\n // Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were\n // pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n // which must not be replayed.\n while (pendingStatesCount > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const pendingState = this.pendingStates.shift()!;\n switch (pendingState.type) {\n case \"message\":\n {\n this.containerRuntime.reSubmitFn(\n pendingState.messageType,\n pendingState.content,\n pendingState.localOpMetadata,\n pendingState.opMetadata);\n }\n break;\n case \"flushMode\":\n {\n this.containerRuntime.setFlushMode(pendingState.flushMode);\n }\n break;\n case \"flush\":\n {\n this.containerRuntime.flush();\n }\n break;\n default:\n break;\n }\n pendingStatesCount--;\n }\n\n // Revert the FlushMode.\n this.containerRuntime.setFlushMode(savedFlushMode);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable } from \"@fluidframework/common-definitions\";\nimport { assert, Lazy } from \"@fluidframework/common-utils\";\nimport { DataProcessingError } from \"@fluidframework/container-utils\";\nimport {\n ISequencedDocumentMessage,\n} from \"@fluidframework/protocol-definitions\";\nimport { FlushMode } from \"@fluidframework/runtime-definitions\";\nimport Deque from \"double-ended-queue\";\nimport { ContainerRuntime, ContainerMessageType, isRuntimeMessage } from \"./containerRuntime\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n */\nexport interface IPendingMessage {\n type: \"message\";\n messageType: ContainerMessageType;\n clientSequenceNumber: number;\n referenceSequenceNumber: number;\n content: any;\n localOpMetadata: unknown;\n opMetadata: Record<string, unknown> | undefined;\n}\n\n/**\n * This represents a FlushMode update and is added to the pending queue when `setFlushMode` is called on the\n * ContainerRuntime and the FlushMode changes.\n */\nexport interface IPendingFlushMode {\n type: \"flushMode\";\n flushMode: FlushMode;\n}\n\n/**\n * This represents a manual flush and is added to the pending queue when `flush` is called on the ContainerRuntime to\n * flush any pending messages. This is applicable only when the FlushMode is Manual.\n */\nexport interface IPendingFlush {\n type: \"flush\";\n}\n\nexport type IPendingState = IPendingMessage | IPendingFlushMode | IPendingFlush;\n\nexport interface IPendingLocalState {\n /**\n * client ID we most recently connected with, or undefined if we never connected\n */\n clientId?: string;\n /**\n * list of pending states, including ops and batch information\n */\n pendingStates: IPendingState[];\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes setting the FlushMode, manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n private readonly pendingStates = new Deque<IPendingState>();\n private readonly initialStates: Deque<IPendingState>;\n private readonly previousClientIds = new Set<string>();\n private readonly firstStashedCSN: number = -1;\n private readonly disposeOnce = new Lazy<void>(() => {\n this.initialStates.clear();\n this.pendingStates.clear();\n });\n\n // Maintains the count of messages that are currently unacked.\n private pendingMessagesCount: number = 0;\n\n // Indicates whether we are processing a batch.\n private isProcessingBatch: boolean = false;\n\n // This stores the first message in the batch that we are processing. This is used to verify that we get\n // the correct batch metadata.\n private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n private clientId: string | undefined;\n\n private get connected(): boolean {\n return this.containerRuntime.connected;\n }\n\n /**\n * Called to check if there are any pending messages in the pending state queue.\n * @returns A boolean indicating whether there are messages or not.\n */\n public hasPendingMessages(): boolean {\n return this.pendingMessagesCount !== 0;\n }\n\n public getLocalState(): IPendingLocalState | undefined {\n if (this.hasPendingMessages()) {\n return {\n clientId: this.clientId,\n pendingStates: this.pendingStates.toArray().map(\n // delete localOpMetadata since it may not be serializable\n // and will be regenerated by applyStashedOp()\n (state) => state.type === \"message\" ? {...state, localOpMetadata: undefined } : state),\n };\n }\n }\n\n constructor(\n private readonly containerRuntime: ContainerRuntime,\n private readonly applyStashedOp: (type, content) => Promise<unknown>,\n initialState: IPendingLocalState | undefined,\n ) {\n this.initialStates = new Deque<IPendingState>(initialState?.pendingStates ?? []);\n\n if (initialState) {\n if (initialState?.clientId) {\n this.previousClientIds.add(initialState.clientId);\n }\n // get stashed op count and client sequence number of first op\n const messages = initialState.pendingStates\n .filter((state) => state.type === \"message\") as IPendingMessage[];\n this.firstStashedCSN = messages[0].clientSequenceNumber;\n }\n }\n\n public get disposed() { return this.disposeOnce.evaluated; }\n public readonly dispose = () => this.disposeOnce.value;\n\n /**\n * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n * queue.\n * @param type - The container message type.\n * @param clientSequenceNumber - The clientSequenceNumber associated with the message.\n * @param content - The message content.\n * @param localOpMetadata - The local metadata associated with the message.\n */\n public onSubmitMessage(\n type: ContainerMessageType,\n clientSequenceNumber: number,\n referenceSequenceNumber: number,\n content: any,\n localOpMetadata: unknown,\n opMetadata: Record<string, unknown> | undefined,\n ) {\n const pendingMessage: IPendingMessage = {\n type: \"message\",\n messageType: type,\n clientSequenceNumber,\n referenceSequenceNumber,\n content,\n localOpMetadata,\n opMetadata,\n };\n\n this.pendingStates.push(pendingMessage);\n\n this.pendingMessagesCount++;\n }\n\n /**\n * Called when the FlushMode is updated. Adds the FlushMode to the pending state queue.\n * @param flushMode - The flushMode that was updated.\n */\n public onFlushModeUpdated(flushMode: FlushMode) {\n if (flushMode === FlushMode.Immediate) {\n const previousState = this.pendingStates.peekBack();\n\n // We don't have to track a previous \"flush\" state because FlushMode.Immediate flushes the messages. So,\n // just tracking this FlushMode.Immediate is enough.\n if (previousState?.type === \"flush\") {\n this.pendingStates.removeBack();\n }\n\n // If no messages were sent between FlushMode.TurnBased and FlushMode.Immediate,\n // then we do not have to track both these states.\n // Remove FlushMode.TurnBased from the pending queue and return.\n if (previousState?.type === \"flushMode\" && previousState.flushMode === FlushMode.TurnBased) {\n this.pendingStates.removeBack();\n return;\n }\n }\n\n const pendingFlushMode: IPendingFlushMode = {\n type: \"flushMode\",\n flushMode,\n };\n this.pendingStates.push(pendingFlushMode);\n }\n\n /**\n * Called when flush() is called on the ContainerRuntime to manually flush messages.\n */\n public onFlush() {\n // If the FlushMode is Immediate, we should not track this flush call as it is only applicable when FlushMode\n // is TurnBased.\n if (this.containerRuntime.flushMode === FlushMode.Immediate) {\n return;\n }\n\n // If the previous state is not a message, we don't have to track this flush call as there is nothing to flush.\n const previousState = this.pendingStates.peekBack();\n if (previousState?.type !== \"message\") {\n return;\n }\n\n // Note that because of the checks above and the checks in onFlushModeUpdated(), we can be sure that a \"flush\"\n // state always has a \"message\" before and after it. So, it marks the end of a batch and the beginning of a\n // new one.\n const pendingFlush: IPendingFlush = {\n type: \"flush\",\n };\n this.pendingStates.push(pendingFlush);\n }\n\n /**\n * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n */\n public async applyStashedOpsAt(seqNum: number) {\n // apply stashed ops at sequence number\n while (!this.initialStates.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextState = this.initialStates.peekFront()!;\n if (nextState.type === \"message\") {\n if (nextState.referenceSequenceNumber > seqNum) {\n break; // nothing left to do at this sequence number\n } else if (nextState.referenceSequenceNumber > 0 && nextState.referenceSequenceNumber < seqNum) {\n throw new Error(\"loaded from snapshot too recent to apply stashed ops\");\n }\n\n // applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n const localOpMetadata = await this.applyStashedOp(nextState.messageType, nextState.content);\n nextState.localOpMetadata = localOpMetadata;\n }\n\n // then we push onto pendingStates which will cause PendingStateManager to resubmit when we connect\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.pendingStates.push(this.initialStates.shift()!);\n }\n }\n\n /**\n * Processes a local message once it's ack'd by the server to verify that there was no data corruption and that\n * the batch information was preserved for batch messages. Also process remote messages that might have been\n * sent from a previous container.\n * @param message - The messsage that got ack'd and needs to be processed.\n */\n public processMessage(message: ISequencedDocumentMessage, local: boolean) {\n // Do not process chunked ops until all pieces are available.\n if (message.type === ContainerMessageType.ChunkedOp) {\n return { localAck: false, localOpMetadata: undefined };\n }\n\n if (local) {\n return { localAck: false, localOpMetadata: this.processPendingLocalMessage(message) };\n } else {\n return this.processRemoteMessage(message);\n }\n }\n\n /**\n * Listens for ACKs of stashed ops\n */\n private processRemoteMessage(message: ISequencedDocumentMessage) {\n if (!isRuntimeMessage(message)) {\n return { localAck: false, localOpMetadata: undefined };\n }\n\n // this message was a pending op that was actually sent successfully\n const isOriginalClientId = message.clientId === Array.from(this.previousClientIds)[0] &&\n message.clientSequenceNumber >= this.firstStashedCSN;\n // this message is a pending or stashed op that was resubmitted\n const isNewClientId = Array.from(this.previousClientIds).indexOf(message.clientId) > 0;\n\n // if this is an ack for a stashed op, dequeue one message.\n // we should have seen its ref seq num by now and the DDS should be ready for it to be ACKed\n if (isOriginalClientId || isNewClientId) {\n assert(this.clientId === undefined, 0x28b /* \"multiple clients connected with stashed ops\" */);\n while (!this.pendingStates.isEmpty()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextState = this.pendingStates.shift()!;\n // if it's not a message just drop it and keep looking\n if (nextState.type === \"message\") {\n this.assertOpMatch(nextState, message, isOriginalClientId);\n return { localAck: true, localOpMetadata: nextState.localOpMetadata };\n }\n }\n }\n\n if (message.type === ContainerMessageType.Rejoin && this.previousClientIds.has(message.contents?.clientId)) {\n this.previousClientIds.add(message.clientId);\n }\n\n return { localAck: false, localOpMetadata: undefined };\n }\n\n private assertOpMatch(state: IPendingMessage, message: ISequencedDocumentMessage, isOriginalClientId: boolean) {\n assert(message.type === state.messageType, 0x28c /* \"different message type\" */);\n assert(message.clientSequenceNumber === state.clientSequenceNumber || !isOriginalClientId,\n 0x28d /* \"client sequence number doesn't match\" */);\n switch(message.type) {\n case ContainerMessageType.Attach:\n assert(message.contents.id === state.content.id, 0x28e /* \"datastore ID doesn't match\" */);\n break;\n case ContainerMessageType.FluidDataStoreOp:\n assert(message.contents.address === state.content.address, 0x28f /* \"address doesn't match\" */);\n break;\n case ContainerMessageType.BlobAttach:\n // todo: assert we have blob storage, assert blob IDs match, remove blob from blob storage since it made\n // it through successfully\n break;\n case ContainerMessageType.Rejoin:\n default:\n throw new Error(`${message.type} not expected`);\n }\n }\n\n /**\n * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n * the batch information was preserved for batch messages.\n * @param message - The messsage that got ack'd and needs to be processed.\n */\n private processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {\n // Pre-processing part - This may be the start of a batch.\n this.maybeProcessBatchBegin(message);\n\n // Get the next state from the pending queue and verify that it is of type \"message\".\n const pendingState = this.peekNextPendingState();\n assert(pendingState.type === \"message\", 0x169 /* \"No pending message found for this remote message\" */);\n this.pendingStates.shift();\n\n // Processing part - Verify that there has been no data corruption.\n // The clientSequenceNumber of the incoming message must match that of the pending message.\n if (pendingState.clientSequenceNumber !== message.clientSequenceNumber) {\n // Close the container because this could indicate data corruption.\n const error = new DataProcessingError(\n \"unexpectedAckReceived\",\n \"unexpectedAckReceived\",\n {\n clientId: message.clientId,\n sequenceNumber: message.sequenceNumber,\n clientSequenceNumber: message.clientSequenceNumber,\n expectedClientSequenceNumber: pendingState.clientSequenceNumber,\n },\n );\n\n this.containerRuntime.closeFn(error);\n return;\n }\n\n this.pendingMessagesCount--;\n\n // Post-processing part - If we are processing a batch then this could be the last message in the batch.\n if (this.isProcessingBatch) {\n this.maybeProcessBatchEnd(message);\n }\n\n return pendingState.localOpMetadata;\n }\n\n /**\n * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n * @param message - The message that is being processed.\n */\n private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n const pendingState = this.peekNextPendingState();\n if (pendingState.type !== \"flush\" && pendingState.type !== \"flushMode\") {\n return;\n }\n\n // If the pending state is of type \"flushMode\", it must be Manual since Automatic flush mode is processed\n // after a message is processed and not before.\n if (pendingState.type === \"flushMode\") {\n assert(pendingState.flushMode === FlushMode.TurnBased,\n 0x16a /* \"Flush mode should be manual when processing batch begin\" */);\n }\n\n // We should not already be processing a batch and there should be no pending batch begin message.\n assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n 0x16b /* \"The pending batch state indicates we are already processing a batch\" */);\n\n // Set the pending batch state indicating we have started processing a batch.\n this.pendingBatchBeginMessage = message;\n this.isProcessingBatch = true;\n\n // Remove this pending state from the queue as we have processed it.\n this.pendingStates.shift();\n }\n\n private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n const nextPendingState = this.peekNextPendingState();\n if (nextPendingState.type !== \"flush\" && nextPendingState.type !== \"flushMode\") {\n return;\n }\n\n // If the next pending state is of type \"flushMode\", it must be Immediate and if so, we need to remove it from\n // the queue.\n // Note that we do not remove the type \"flush\" from the queue because it indicates the end of one batch and the\n // beginning of a new one. So, it will removed when the next batch begin is processed.\n if (nextPendingState.type === \"flushMode\") {\n assert(nextPendingState.flushMode === FlushMode.Immediate,\n 0x16c /* \"Flush mode is set to TurnBased in the middle of processing a batch\" */);\n this.pendingStates.shift();\n }\n\n // There should be a pending batch begin message.\n assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* \"There is no pending batch begin message\" */);\n\n // Get the batch begin metadata from the first message in the batch.\n const batchBeginMetadata = this.pendingBatchBeginMessage.metadata?.batch;\n\n // There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n // are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n if (this.pendingBatchBeginMessage === message) {\n assert(batchBeginMetadata === undefined,\n 0x16e /* \"Batch with single message should not have batch metadata\" */);\n } else {\n // Get the batch metadata from the last message in the batch.\n const batchEndMetadata = message.metadata?.batch;\n assert(batchBeginMetadata === true, 0x16f /* \"Did not receive batch begin metadata\" */);\n assert(batchEndMetadata === false, 0x170 /* \"Did not receive batch end metadata\" */);\n }\n\n // Clear the pending batch state now that we have processed the entire batch.\n this.pendingBatchBeginMessage = undefined;\n this.isProcessingBatch = false;\n }\n\n /**\n * Returns the next pending state from the pending state queue.\n */\n private peekNextPendingState(): IPendingState {\n const nextPendingState = this.pendingStates.peekFront();\n assert(!!nextPendingState, 0x171 /* \"No pending state found for the remote message\" */);\n return nextPendingState;\n }\n\n /**\n * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n * states in its queue. This includes setting the FlushMode and triggering resubmission of unacked ops.\n */\n public replayPendingStates() {\n assert(this.connected, 0x172 /* \"The connection state is not consistent with the runtime\" */);\n\n // This assert suggests we are about to send same ops twice, which will result in data loss.\n assert(this.clientId !== this.containerRuntime.clientId,\n 0x173 /* \"replayPendingStates called twice for same clientId!\" */);\n this.clientId = this.containerRuntime.clientId;\n\n assert(this.initialStates.isEmpty(), 0x174 /* \"initial states should be empty before replaying pending\" */);\n\n let pendingStatesCount = this.pendingStates.length;\n if (pendingStatesCount === 0) {\n return;\n }\n\n // Reset the pending message count because all these messages will be removed from the queue.\n this.pendingMessagesCount = 0;\n\n // Save the current FlushMode so that we can revert it back after replaying the states.\n const savedFlushMode = this.containerRuntime.flushMode;\n\n // Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were\n // pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n // which must not be replayed.\n while (pendingStatesCount > 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const pendingState = this.pendingStates.shift()!;\n switch (pendingState.type) {\n case \"message\":\n {\n this.containerRuntime.reSubmitFn(\n pendingState.messageType,\n pendingState.content,\n pendingState.localOpMetadata,\n pendingState.opMetadata);\n }\n break;\n case \"flushMode\":\n {\n this.containerRuntime.setFlushMode(pendingState.flushMode);\n }\n break;\n case \"flush\":\n {\n this.containerRuntime.flush();\n }\n break;\n default:\n break;\n }\n pendingStatesCount--;\n }\n\n // Revert the FlushMode.\n this.containerRuntime.setFlushMode(savedFlushMode);\n }\n}\n"]}
|