@fluidframework/container-loader 2.74.0 → 2.81.0-374083
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/CHANGELOG.md +4 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +2 -0
- package/dist/connectionManager.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +3 -3
- package/dist/container.js.map +1 -1
- package/dist/deltaManager.d.ts +10 -0
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +44 -1
- package/dist/deltaManager.js.map +1 -1
- package/dist/loaderLayerCompatState.d.ts +4 -4
- package/dist/loaderLayerCompatState.d.ts.map +1 -1
- package/dist/loaderLayerCompatState.js +6 -5
- package/dist/loaderLayerCompatState.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/serializedStateManager.d.ts +10 -15
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +20 -83
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/snapshotRefresher.d.ts +68 -0
- package/dist/snapshotRefresher.d.ts.map +1 -0
- package/dist/snapshotRefresher.js +167 -0
- package/dist/snapshotRefresher.js.map +1 -0
- package/eslint.config.mts +4 -4
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +2 -0
- package/lib/connectionManager.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +3 -3
- package/lib/container.js.map +1 -1
- package/lib/deltaManager.d.ts +10 -0
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +44 -1
- package/lib/deltaManager.js.map +1 -1
- package/lib/loaderLayerCompatState.d.ts +4 -4
- package/lib/loaderLayerCompatState.d.ts.map +1 -1
- package/lib/loaderLayerCompatState.js +6 -5
- package/lib/loaderLayerCompatState.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/serializedStateManager.d.ts +10 -15
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +21 -84
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/snapshotRefresher.d.ts +68 -0
- package/lib/snapshotRefresher.d.ts.map +1 -0
- package/lib/snapshotRefresher.js +163 -0
- package/lib/snapshotRefresher.js.map +1 -0
- package/package.json +20 -20
- package/src/connectionManager.ts +2 -0
- package/src/container.ts +3 -6
- package/src/deltaManager.ts +54 -1
- package/src/loaderLayerCompatState.ts +10 -9
- package/src/packageVersion.ts +1 -1
- package/src/serializedStateManager.ts +29 -105
- package/src/snapshotRefresher.ts +201 -0
- package/.eslintrc.cjs +0 -24
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
6
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
7
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
8
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
9
|
+
};
|
|
10
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
11
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
14
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
15
|
+
};
|
|
16
|
+
var _RefreshPromiseTracker_promise, _SnapshotRefresher_disposed, _SnapshotRefresher_snapshotRefreshEnabled;
|
|
17
|
+
import { assert, Timer } from "@fluidframework/core-utils/internal";
|
|
18
|
+
import { FetchSource } from "@fluidframework/driver-definitions/internal";
|
|
19
|
+
import { createChildMonitoringContext, } from "@fluidframework/telemetry-utils/internal";
|
|
20
|
+
import { getLatestSnapshotInfo, } from "./serializedStateManager.js";
|
|
21
|
+
class RefreshPromiseTracker {
|
|
22
|
+
get hasPromise() {
|
|
23
|
+
return __classPrivateFieldGet(this, _RefreshPromiseTracker_promise, "f") !== undefined;
|
|
24
|
+
}
|
|
25
|
+
get promise() {
|
|
26
|
+
return __classPrivateFieldGet(this, _RefreshPromiseTracker_promise, "f");
|
|
27
|
+
}
|
|
28
|
+
constructor(catchHandler) {
|
|
29
|
+
this.catchHandler = catchHandler;
|
|
30
|
+
_RefreshPromiseTracker_promise.set(this, void 0);
|
|
31
|
+
}
|
|
32
|
+
setPromise(p) {
|
|
33
|
+
if (this.hasPromise) {
|
|
34
|
+
throw new Error("Cannot start new snapshot refresh while a refresh is already in progress");
|
|
35
|
+
}
|
|
36
|
+
__classPrivateFieldSet(this, _RefreshPromiseTracker_promise, p.finally(() => {
|
|
37
|
+
__classPrivateFieldSet(this, _RefreshPromiseTracker_promise, undefined, "f");
|
|
38
|
+
}), "f");
|
|
39
|
+
p.catch(this.catchHandler);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
_RefreshPromiseTracker_promise = new WeakMap();
|
|
43
|
+
/**
|
|
44
|
+
* Manages periodic refresh of the latest snapshot for a document.
|
|
45
|
+
*
|
|
46
|
+
* `SnapshotRefresher` polls the storage service for the most recent snapshot and, when a newer
|
|
47
|
+
* snapshot is discovered, invokes the provided `onSnapshotRefreshed` callback with the updated
|
|
48
|
+
* snapshot metadata. It is responsible for:
|
|
49
|
+
*
|
|
50
|
+
* - Tracking the most recent snapshot that has been observed.
|
|
51
|
+
* - Scheduling and managing refresh attempts via an internal timer.
|
|
52
|
+
* - Emitting telemetry for successful and failed refresh attempts.
|
|
53
|
+
*
|
|
54
|
+
* The refresh behavior can be configured via constructor arguments, including whether offline
|
|
55
|
+
* loading and the `getSnapshot` API are supported, as well as the refresh timeout. Callers
|
|
56
|
+
* should dispose this instance when snapshot refresh is no longer needed to stop any pending
|
|
57
|
+
* timers and prevent further refresh attempts.
|
|
58
|
+
*/
|
|
59
|
+
export class SnapshotRefresher {
|
|
60
|
+
get disposed() {
|
|
61
|
+
return __classPrivateFieldGet(this, _SnapshotRefresher_disposed, "f");
|
|
62
|
+
}
|
|
63
|
+
constructor(subLogger, storageAdapter, offlineLoadEnabled, supportGetSnapshotApi, onSnapshotRefreshed, snapshotRefreshTimeoutMs) {
|
|
64
|
+
this.storageAdapter = storageAdapter;
|
|
65
|
+
this.offlineLoadEnabled = offlineLoadEnabled;
|
|
66
|
+
this.supportGetSnapshotApi = supportGetSnapshotApi;
|
|
67
|
+
this.onSnapshotRefreshed = onSnapshotRefreshed;
|
|
68
|
+
_SnapshotRefresher_disposed.set(this, false);
|
|
69
|
+
this.refreshTracker = new RefreshPromiseTracker(
|
|
70
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
71
|
+
(error) => this.mc.logger.sendErrorEvent({
|
|
72
|
+
eventName: "RefreshLatestSnapshotFailed",
|
|
73
|
+
}, error));
|
|
74
|
+
this.snapshotRefreshTimeoutMs = 60 * 60 * 24 * 1000;
|
|
75
|
+
_SnapshotRefresher_snapshotRefreshEnabled.set(this, void 0);
|
|
76
|
+
this.mc = createChildMonitoringContext({
|
|
77
|
+
logger: subLogger,
|
|
78
|
+
namespace: "serializedStateManager",
|
|
79
|
+
});
|
|
80
|
+
this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
|
|
81
|
+
__classPrivateFieldSet(this, _SnapshotRefresher_snapshotRefreshEnabled, this.offlineLoadEnabled &&
|
|
82
|
+
(this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
|
|
83
|
+
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true, "f");
|
|
84
|
+
this.refreshTimer = __classPrivateFieldGet(this, _SnapshotRefresher_snapshotRefreshEnabled, "f")
|
|
85
|
+
? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())
|
|
86
|
+
: undefined;
|
|
87
|
+
}
|
|
88
|
+
tryRefreshSnapshot() {
|
|
89
|
+
if (__classPrivateFieldGet(this, _SnapshotRefresher_snapshotRefreshEnabled, "f") &&
|
|
90
|
+
!__classPrivateFieldGet(this, _SnapshotRefresher_disposed, "f") &&
|
|
91
|
+
!this.refreshTracker.hasPromise &&
|
|
92
|
+
this.latestSnapshot === undefined) {
|
|
93
|
+
// Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
|
|
94
|
+
this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.
|
|
99
|
+
* Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)
|
|
100
|
+
*
|
|
101
|
+
* @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)
|
|
102
|
+
*/
|
|
103
|
+
async refreshLatestSnapshot(supportGetSnapshotApi) {
|
|
104
|
+
this.latestSnapshot = await getLatestSnapshotInfo(this.mc, this.storageAdapter, supportGetSnapshotApi);
|
|
105
|
+
if (__classPrivateFieldGet(this, _SnapshotRefresher_disposed, "f")) {
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
// These are loading groupIds that the containerRuntime has requested over its lifetime.
|
|
109
|
+
// We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
|
|
110
|
+
const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
|
|
111
|
+
if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
|
|
112
|
+
assert(this.storageAdapter.getSnapshot !== undefined, 0x972 /* getSnapshot should exist */);
|
|
113
|
+
// (This is a separate network call from above because it requires work for storage to add a special base groupId)
|
|
114
|
+
const snapshot = await this.storageAdapter.getSnapshot({
|
|
115
|
+
versionId: undefined,
|
|
116
|
+
scenarioName: "getLatestSnapshotInfo",
|
|
117
|
+
cacheSnapshot: false,
|
|
118
|
+
loadingGroupIds: downloadedGroupIds,
|
|
119
|
+
fetchSource: FetchSource.noCache,
|
|
120
|
+
});
|
|
121
|
+
assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
|
|
122
|
+
}
|
|
123
|
+
// Notify the manager about the fetched snapshot - let it decide what to do with it
|
|
124
|
+
const result = this.latestSnapshot === undefined ? -1 : this.onSnapshotRefreshed(this.latestSnapshot);
|
|
125
|
+
this.refreshTimer?.restart();
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clears the latest snapshot after it's been consumed by the manager.
|
|
130
|
+
* This allows the next refresh cycle to proceed.
|
|
131
|
+
*/
|
|
132
|
+
clearLatestSnapshot() {
|
|
133
|
+
this.latestSnapshot = undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Starts the refresh timer.
|
|
137
|
+
*/
|
|
138
|
+
startTimer() {
|
|
139
|
+
this.refreshTimer?.start();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Restarts the refresh timer.
|
|
143
|
+
*/
|
|
144
|
+
restartTimer() {
|
|
145
|
+
this.refreshTimer?.restart();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Gets the current refresh promise for testing purposes.
|
|
149
|
+
* @returns The snapshot sequence number promise, or undefined if no refresh is in progress
|
|
150
|
+
*/
|
|
151
|
+
get refreshSnapshotP() {
|
|
152
|
+
return this.refreshTracker.promise;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Disposes the refresher and clears the timer.
|
|
156
|
+
*/
|
|
157
|
+
dispose() {
|
|
158
|
+
__classPrivateFieldSet(this, _SnapshotRefresher_disposed, true, "f");
|
|
159
|
+
this.refreshTimer?.clear();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
_SnapshotRefresher_disposed = new WeakMap(), _SnapshotRefresher_snapshotRefreshEnabled = new WeakMap();
|
|
163
|
+
//# sourceMappingURL=snapshotRefresher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotRefresher.js","sourceRoot":"","sources":["../src/snapshotRefresher.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 type { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces/internal\";\nimport { assert, Timer } from \"@fluidframework/core-utils/internal\";\nimport { FetchSource } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\tcreateChildMonitoringContext,\n\ttype MonitoringContext,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport {\n\tgetLatestSnapshotInfo,\n\ttype ISerializedStateManagerDocumentStorageService,\n\ttype ISnapshotInfo,\n} from \"./serializedStateManager.js\";\n\nclass RefreshPromiseTracker {\n\tpublic get hasPromise(): boolean {\n\t\treturn this.#promise !== undefined;\n\t}\n\tpublic get promise(): Promise<number> | undefined {\n\t\treturn this.#promise;\n\t}\n\tconstructor(private readonly catchHandler: (error: Error) => void) {}\n\n\t#promise: Promise<number> | undefined;\n\tsetPromise(p: Promise<number>): void {\n\t\tif (this.hasPromise) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cannot start new snapshot refresh while a refresh is already in progress\",\n\t\t\t);\n\t\t}\n\t\tthis.#promise = p.finally(() => {\n\t\t\tthis.#promise = undefined;\n\t\t});\n\t\tp.catch(this.catchHandler);\n\t}\n}\n\n/**\n * Manages periodic refresh of the latest snapshot for a document.\n *\n * `SnapshotRefresher` polls the storage service for the most recent snapshot and, when a newer\n * snapshot is discovered, invokes the provided `onSnapshotRefreshed` callback with the updated\n * snapshot metadata. It is responsible for:\n *\n * - Tracking the most recent snapshot that has been observed.\n * - Scheduling and managing refresh attempts via an internal timer.\n * - Emitting telemetry for successful and failed refresh attempts.\n *\n * The refresh behavior can be configured via constructor arguments, including whether offline\n * loading and the `getSnapshot` API are supported, as well as the refresh timeout. Callers\n * should dispose this instance when snapshot refresh is no longer needed to stop any pending\n * timers and prevent further refresh attempts.\n */\nexport class SnapshotRefresher implements IDisposable {\n\tprivate readonly mc: MonitoringContext;\n\tprivate latestSnapshot: ISnapshotInfo | undefined;\n\t#disposed: boolean = false;\n\n\tpublic get disposed(): boolean {\n\t\treturn this.#disposed;\n\t}\n\n\tprivate readonly refreshTracker = new RefreshPromiseTracker(\n\t\t// eslint-disable-next-line unicorn/consistent-function-scoping\n\t\t(error) =>\n\t\t\tthis.mc.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: \"RefreshLatestSnapshotFailed\",\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t),\n\t);\n\tprivate readonly refreshTimer: Timer | undefined;\n\tprivate readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;\n\treadonly #snapshotRefreshEnabled: boolean;\n\n\tconstructor(\n\t\tsubLogger: ITelemetryBaseLogger,\n\t\tprivate readonly storageAdapter: ISerializedStateManagerDocumentStorageService,\n\t\tprivate readonly offlineLoadEnabled: boolean,\n\t\tprivate readonly supportGetSnapshotApi: () => boolean,\n\t\tprivate readonly onSnapshotRefreshed: (snapshot: ISnapshotInfo) => number,\n\t\tsnapshotRefreshTimeoutMs?: number,\n\t) {\n\t\tthis.mc = createChildMonitoringContext({\n\t\t\tlogger: subLogger,\n\t\t\tnamespace: \"serializedStateManager\",\n\t\t});\n\n\t\tthis.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;\n\n\t\tthis.#snapshotRefreshEnabled =\n\t\t\tthis.offlineLoadEnabled &&\n\t\t\t(this.mc.config.getBoolean(\"Fluid.Container.enableOfflineSnapshotRefresh\") ??\n\t\t\t\tthis.mc.config.getBoolean(\"Fluid.Container.enableOfflineFull\")) === true;\n\n\t\tthis.refreshTimer = this.#snapshotRefreshEnabled\n\t\t\t? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())\n\t\t\t: undefined;\n\t}\n\n\tpublic tryRefreshSnapshot(): void {\n\t\tif (\n\t\t\tthis.#snapshotRefreshEnabled &&\n\t\t\t!this.#disposed &&\n\t\t\t!this.refreshTracker.hasPromise &&\n\t\t\tthis.latestSnapshot === undefined\n\t\t) {\n\t\t\t// Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation\n\t\t\tthis.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));\n\t\t}\n\t}\n\n\t/**\n\t * Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.\n\t * Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)\n\t *\n\t * @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)\n\t */\n\tprivate async refreshLatestSnapshot(supportGetSnapshotApi: boolean): Promise<number> {\n\t\tthis.latestSnapshot = await getLatestSnapshotInfo(\n\t\t\tthis.mc,\n\t\t\tthis.storageAdapter,\n\t\t\tsupportGetSnapshotApi,\n\t\t);\n\n\t\tif (this.#disposed) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t// These are loading groupIds that the containerRuntime has requested over its lifetime.\n\t\t// We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache\n\t\tconst downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);\n\t\tif (supportGetSnapshotApi && downloadedGroupIds.length > 0) {\n\t\t\tassert(\n\t\t\t\tthis.storageAdapter.getSnapshot !== undefined,\n\t\t\t\t0x972 /* getSnapshot should exist */,\n\t\t\t);\n\t\t\t// (This is a separate network call from above because it requires work for storage to add a special base groupId)\n\t\t\tconst snapshot = await this.storageAdapter.getSnapshot({\n\t\t\t\tversionId: undefined,\n\t\t\t\tscenarioName: \"getLatestSnapshotInfo\",\n\t\t\t\tcacheSnapshot: false,\n\t\t\t\tloadingGroupIds: downloadedGroupIds,\n\t\t\t\tfetchSource: FetchSource.noCache,\n\t\t\t});\n\t\t\tassert(snapshot !== undefined, 0x973 /* Snapshot should exist */);\n\t\t}\n\n\t\t// Notify the manager about the fetched snapshot - let it decide what to do with it\n\t\tconst result =\n\t\t\tthis.latestSnapshot === undefined ? -1 : this.onSnapshotRefreshed(this.latestSnapshot);\n\n\t\tthis.refreshTimer?.restart();\n\t\treturn result;\n\t}\n\n\t/**\n\t * Clears the latest snapshot after it's been consumed by the manager.\n\t * This allows the next refresh cycle to proceed.\n\t */\n\tpublic clearLatestSnapshot(): void {\n\t\tthis.latestSnapshot = undefined;\n\t}\n\n\t/**\n\t * Starts the refresh timer.\n\t */\n\tpublic startTimer(): void {\n\t\tthis.refreshTimer?.start();\n\t}\n\n\t/**\n\t * Restarts the refresh timer.\n\t */\n\tpublic restartTimer(): void {\n\t\tthis.refreshTimer?.restart();\n\t}\n\n\t/**\n\t * Gets the current refresh promise for testing purposes.\n\t * @returns The snapshot sequence number promise, or undefined if no refresh is in progress\n\t */\n\tpublic get refreshSnapshotP(): Promise<number> | undefined {\n\t\treturn this.refreshTracker.promise;\n\t}\n\n\t/**\n\t * Disposes the refresher and clears the timer.\n\t */\n\tpublic dispose(): void {\n\t\tthis.#disposed = true;\n\t\tthis.refreshTimer?.clear();\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/container-loader",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.81.0-374083",
|
|
4
4
|
"description": "Fluid container loader",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -129,13 +129,13 @@
|
|
|
129
129
|
"temp-directory": "nyc/.nyc_output"
|
|
130
130
|
},
|
|
131
131
|
"dependencies": {
|
|
132
|
-
"@fluid-internal/client-utils": "
|
|
133
|
-
"@fluidframework/container-definitions": "
|
|
134
|
-
"@fluidframework/core-interfaces": "
|
|
135
|
-
"@fluidframework/core-utils": "
|
|
136
|
-
"@fluidframework/driver-definitions": "
|
|
137
|
-
"@fluidframework/driver-utils": "
|
|
138
|
-
"@fluidframework/telemetry-utils": "
|
|
132
|
+
"@fluid-internal/client-utils": "2.81.0-374083",
|
|
133
|
+
"@fluidframework/container-definitions": "2.81.0-374083",
|
|
134
|
+
"@fluidframework/core-interfaces": "2.81.0-374083",
|
|
135
|
+
"@fluidframework/core-utils": "2.81.0-374083",
|
|
136
|
+
"@fluidframework/driver-definitions": "2.81.0-374083",
|
|
137
|
+
"@fluidframework/driver-utils": "2.81.0-374083",
|
|
138
|
+
"@fluidframework/telemetry-utils": "2.81.0-374083",
|
|
139
139
|
"@types/events_pkg": "npm:@types/events@^3.0.0",
|
|
140
140
|
"@ungap/structured-clone": "^1.2.0",
|
|
141
141
|
"debug": "^4.3.4",
|
|
@@ -144,16 +144,16 @@
|
|
|
144
144
|
"uuid": "^11.1.0"
|
|
145
145
|
},
|
|
146
146
|
"devDependencies": {
|
|
147
|
-
"@arethetypeswrong/cli": "^0.
|
|
147
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
148
148
|
"@biomejs/biome": "~1.9.3",
|
|
149
|
-
"@fluid-internal/client-utils": "
|
|
150
|
-
"@fluid-internal/mocha-test-setup": "
|
|
151
|
-
"@fluid-private/test-loader-utils": "
|
|
152
|
-
"@fluid-tools/build-cli": "^0.
|
|
149
|
+
"@fluid-internal/client-utils": "2.81.0-374083",
|
|
150
|
+
"@fluid-internal/mocha-test-setup": "2.81.0-374083",
|
|
151
|
+
"@fluid-private/test-loader-utils": "2.81.0-374083",
|
|
152
|
+
"@fluid-tools/build-cli": "^0.63.0",
|
|
153
153
|
"@fluidframework/build-common": "^2.0.3",
|
|
154
|
-
"@fluidframework/build-tools": "^0.
|
|
155
|
-
"@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.
|
|
156
|
-
"@fluidframework/eslint-config-fluid": "
|
|
154
|
+
"@fluidframework/build-tools": "^0.63.0",
|
|
155
|
+
"@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.80.0",
|
|
156
|
+
"@fluidframework/eslint-config-fluid": "2.81.0-374083",
|
|
157
157
|
"@microsoft/api-extractor": "7.52.11",
|
|
158
158
|
"@types/debug": "^4.1.5",
|
|
159
159
|
"@types/double-ended-queue": "^2.1.0",
|
|
@@ -162,14 +162,14 @@
|
|
|
162
162
|
"@types/sinon": "^17.0.3",
|
|
163
163
|
"@types/ungap__structured-clone": "^1.2.0",
|
|
164
164
|
"c8": "^10.1.3",
|
|
165
|
-
"concurrently": "^
|
|
165
|
+
"concurrently": "^9.2.1",
|
|
166
166
|
"copyfiles": "^2.4.1",
|
|
167
|
-
"cross-env": "^
|
|
168
|
-
"eslint": "~
|
|
167
|
+
"cross-env": "^10.1.0",
|
|
168
|
+
"eslint": "~9.39.1",
|
|
169
169
|
"jiti": "^2.6.1",
|
|
170
170
|
"mocha": "^10.8.2",
|
|
171
171
|
"mocha-multi-reporters": "^1.5.1",
|
|
172
|
-
"rimraf": "^
|
|
172
|
+
"rimraf": "^6.1.2",
|
|
173
173
|
"sinon": "^18.0.1",
|
|
174
174
|
"typescript": "~5.4.5"
|
|
175
175
|
},
|
package/src/connectionManager.ts
CHANGED
|
@@ -564,6 +564,7 @@ export class ConnectionManager implements IConnectionManager {
|
|
|
564
564
|
this.logger.sendTelemetryEvent(
|
|
565
565
|
{
|
|
566
566
|
eventName: "ConnectionReceived",
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- using ?. could change behavior
|
|
567
568
|
connected: connection !== undefined && connection.disposed === false,
|
|
568
569
|
},
|
|
569
570
|
undefined,
|
|
@@ -573,6 +574,7 @@ export class ConnectionManager implements IConnectionManager {
|
|
|
573
574
|
this.logger.sendTelemetryEvent(
|
|
574
575
|
{
|
|
575
576
|
eventName: "ConnectToDeltaStreamException",
|
|
577
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- using ?. could change behavior
|
|
576
578
|
connected: connection !== undefined && connection.disposed === false,
|
|
577
579
|
},
|
|
578
580
|
undefined,
|
package/src/container.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/* eslint-disable unicorn/consistent-function-scoping */
|
|
6
|
+
/* eslint-disable unicorn/consistent-function-scoping, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/prefer-optional-chain */
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
TypedEventEmitter,
|
|
@@ -751,7 +751,7 @@ export class Container
|
|
|
751
751
|
validateDriverCompatibility(
|
|
752
752
|
maybeDriverCompatDetails.ILayerCompatDetails,
|
|
753
753
|
(error) => {} /* disposeFn */, // There is nothing to dispose here, so just ignore the error.
|
|
754
|
-
subLogger,
|
|
754
|
+
createChildMonitoringContext({ logger: subLogger, namespace: "Container" }),
|
|
755
755
|
);
|
|
756
756
|
|
|
757
757
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performanceNow();
|
|
@@ -2440,10 +2440,7 @@ export class Container
|
|
|
2440
2440
|
|
|
2441
2441
|
// Validate that the Runtime is compatible with this Loader.
|
|
2442
2442
|
const maybeRuntimeCompatDetails = runtime as FluidObject<ILayerCompatDetails>;
|
|
2443
|
-
validateRuntimeCompatibility(
|
|
2444
|
-
maybeRuntimeCompatDetails.ILayerCompatDetails,
|
|
2445
|
-
this.mc.logger,
|
|
2446
|
-
);
|
|
2443
|
+
validateRuntimeCompatibility(maybeRuntimeCompatDetails.ILayerCompatDetails, this.mc);
|
|
2447
2444
|
|
|
2448
2445
|
this._runtime = runtime;
|
|
2449
2446
|
this._lifecycleEvents.emit("runtimeInstantiated");
|
package/src/deltaManager.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type ISignalMessage,
|
|
31
31
|
type IClientDetails,
|
|
32
32
|
type IClientConfiguration,
|
|
33
|
+
type ISequencedDocumentSystemMessage,
|
|
33
34
|
} from "@fluidframework/driver-definitions/internal";
|
|
34
35
|
import { NonRetryableError, isRuntimeMessage } from "@fluidframework/driver-utils/internal";
|
|
35
36
|
import {
|
|
@@ -189,6 +190,12 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
189
190
|
private lastProcessedSequenceNumber: number = 0;
|
|
190
191
|
private lastProcessedMessage: ISequencedDocumentMessage | undefined;
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Map of clientId to the last observed message from that client. This is used to validate
|
|
195
|
+
* that clientSequenceNumbers are always increasing for a given clientId.
|
|
196
|
+
*/
|
|
197
|
+
private readonly lastObservedMessageByClient = new Map<string, ISequencedDocumentMessage>();
|
|
198
|
+
|
|
192
199
|
/**
|
|
193
200
|
* Count the number of noops sent by the client which may not be acked
|
|
194
201
|
*/
|
|
@@ -661,6 +668,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
661
668
|
throw new Error("Delta manager is not attached");
|
|
662
669
|
}
|
|
663
670
|
|
|
671
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
|
|
664
672
|
if (this.deltaStorage === undefined) {
|
|
665
673
|
this.deltaStorage = await docService.connectToDeltaStorage();
|
|
666
674
|
}
|
|
@@ -861,7 +869,50 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
861
869
|
// Also payload goes to telemetry, so no content or anything else that shouldn't be logged for privacy reasons
|
|
862
870
|
// Note: It's possible for a duplicate op to be broadcasted and have everything the same except the timestamp.
|
|
863
871
|
private comparableMessagePayload(m: ISequencedDocumentMessage): string {
|
|
864
|
-
return `${m.clientId}-${m.type}-${m.minimumSequenceNumber}-${m.referenceSequenceNumber}-${m.timestamp}`;
|
|
872
|
+
return `${m.clientId}-${m.type}-${m.sequenceNumber}-${m.minimumSequenceNumber}-${m.referenceSequenceNumber}-${m.timestamp}`;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Validates that the clientSequenceNumber for a given clientId is always increasing.
|
|
877
|
+
* @param message - The message to validate.
|
|
878
|
+
*/
|
|
879
|
+
private validateClientSequenceNumberConsistency(message: ISequencedDocumentMessage): void {
|
|
880
|
+
// Once client leaves, we no longer need to track its last observed message as the client won't send messages.
|
|
881
|
+
if (message.type === MessageType.ClientLeave) {
|
|
882
|
+
const systemLeaveMessage = message as ISequencedDocumentSystemMessage;
|
|
883
|
+
const clientId = JSON.parse(systemLeaveMessage.data) as string;
|
|
884
|
+
this.lastObservedMessageByClient.delete(clientId);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (message.clientId === null) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const lastObservedClientMessage = this.lastObservedMessageByClient.get(message.clientId);
|
|
892
|
+
if (
|
|
893
|
+
lastObservedClientMessage !== undefined &&
|
|
894
|
+
message.clientSequenceNumber <= lastObservedClientMessage.clientSequenceNumber
|
|
895
|
+
) {
|
|
896
|
+
// This looks like a data corruption issue where the clientSequenceNumber for a given clientId is not
|
|
897
|
+
// increasing. The Fluid Service ensures that it only processes ops with contiguous increasing
|
|
898
|
+
// clientSequenceNumbers for a given clientId.
|
|
899
|
+
// So, if we see this error, it is likely a service issue.
|
|
900
|
+
// One example of this is if the service sequences the same op more than once. In this case, all the
|
|
901
|
+
// properties except the sequenceNumber will be the same.
|
|
902
|
+
// Note that we are not checking for gaps in clientSequenceNumber because very old clients may have gaps
|
|
903
|
+
// as per the op stream in the snapshot tests under test/snapshots/content.
|
|
904
|
+
const error = new DataCorruptionError(
|
|
905
|
+
"Found two messages with non-increasing clientSequenceNumber for a given client. Likely to be a service issue",
|
|
906
|
+
{
|
|
907
|
+
clientId: this.connectionManager.clientId,
|
|
908
|
+
sequenceNumber: message.sequenceNumber,
|
|
909
|
+
message1: this.comparableMessagePayload(lastObservedClientMessage),
|
|
910
|
+
message2: this.comparableMessagePayload(message),
|
|
911
|
+
},
|
|
912
|
+
);
|
|
913
|
+
this.close(error);
|
|
914
|
+
}
|
|
915
|
+
this.lastObservedMessageByClient.set(message.clientId, message);
|
|
865
916
|
}
|
|
866
917
|
|
|
867
918
|
private enqueueMessages(
|
|
@@ -914,6 +965,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
914
965
|
duplicate++;
|
|
915
966
|
} else if (message.sequenceNumber !== prev + 1) {
|
|
916
967
|
gap++;
|
|
968
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
|
|
917
969
|
if (firstMissing === undefined) {
|
|
918
970
|
firstMissing = prev + 1;
|
|
919
971
|
}
|
|
@@ -1002,6 +1054,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
1002
1054
|
}
|
|
1003
1055
|
}
|
|
1004
1056
|
} else if (message.sequenceNumber === this.lastQueuedSequenceNumber + 1) {
|
|
1057
|
+
this.validateClientSequenceNumberConsistency(message);
|
|
1005
1058
|
this.lastQueuedSequenceNumber = message.sequenceNumber;
|
|
1006
1059
|
this.previouslyProcessedMessage = message;
|
|
1007
1060
|
this._inbound.push(message);
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
generation,
|
|
8
|
+
type ILayerCompatDetails,
|
|
9
|
+
type ILayerCompatSupportRequirements,
|
|
9
10
|
} from "@fluid-internal/client-utils";
|
|
10
11
|
import type { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
11
12
|
import {
|
|
12
13
|
validateLayerCompatibility,
|
|
13
|
-
type
|
|
14
|
+
type MonitoringContext,
|
|
14
15
|
} from "@fluidframework/telemetry-utils/internal";
|
|
15
16
|
|
|
16
17
|
import { pkgVersion } from "./packageVersion.js";
|
|
@@ -27,7 +28,7 @@ export const loaderCoreCompatDetails = {
|
|
|
27
28
|
/**
|
|
28
29
|
* The current generation of the Loader layer.
|
|
29
30
|
*/
|
|
30
|
-
generation
|
|
31
|
+
generation,
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -80,7 +81,7 @@ export const driverSupportRequirementsForLoader: ILayerCompatSupportRequirements
|
|
|
80
81
|
*/
|
|
81
82
|
export function validateRuntimeCompatibility(
|
|
82
83
|
maybeRuntimeCompatDetails: ILayerCompatDetails | undefined,
|
|
83
|
-
|
|
84
|
+
mc: MonitoringContext,
|
|
84
85
|
): void {
|
|
85
86
|
validateLayerCompatibility(
|
|
86
87
|
"loader",
|
|
@@ -89,7 +90,7 @@ export function validateRuntimeCompatibility(
|
|
|
89
90
|
runtimeSupportRequirementsForLoader,
|
|
90
91
|
maybeRuntimeCompatDetails,
|
|
91
92
|
() => {} /* disposeFn - no op. This will be handled by the caller */,
|
|
92
|
-
|
|
93
|
+
mc,
|
|
93
94
|
);
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -100,7 +101,7 @@ export function validateRuntimeCompatibility(
|
|
|
100
101
|
export function validateDriverCompatibility(
|
|
101
102
|
maybeDriverCompatDetails: ILayerCompatDetails | undefined,
|
|
102
103
|
disposeFn: (error?: ICriticalContainerError) => void,
|
|
103
|
-
|
|
104
|
+
mc: MonitoringContext,
|
|
104
105
|
): void {
|
|
105
106
|
validateLayerCompatibility(
|
|
106
107
|
"loader",
|
|
@@ -109,6 +110,6 @@ export function validateDriverCompatibility(
|
|
|
109
110
|
driverSupportRequirementsForLoader,
|
|
110
111
|
maybeDriverCompatDetails,
|
|
111
112
|
disposeFn,
|
|
112
|
-
|
|
113
|
+
mc,
|
|
113
114
|
);
|
|
114
115
|
}
|
package/src/packageVersion.ts
CHANGED