@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
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
ITelemetryBaseLogger,
|
|
12
12
|
} from "@fluidframework/core-interfaces";
|
|
13
13
|
import type { IDisposable } from "@fluidframework/core-interfaces/internal";
|
|
14
|
-
import {
|
|
14
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
15
15
|
import {
|
|
16
16
|
FetchSource,
|
|
17
17
|
type IDocumentStorageService,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type ContainerStorageAdapter,
|
|
36
36
|
type ISerializableBlobContents,
|
|
37
37
|
} from "./containerStorageAdapter.js";
|
|
38
|
+
import { SnapshotRefresher } from "./snapshotRefresher.js";
|
|
38
39
|
import {
|
|
39
40
|
convertISnapshotToSnapshotWithBlobs,
|
|
40
41
|
convertSnapshotToSnapshotInfo,
|
|
@@ -127,7 +128,7 @@ export interface SerializedSnapshotInfo extends SnapshotWithBlobs {
|
|
|
127
128
|
snapshotSequenceNumber: number;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
interface ISnapshotInfo {
|
|
131
|
+
export interface ISnapshotInfo {
|
|
131
132
|
snapshotSequenceNumber: number;
|
|
132
133
|
snapshotFetchedTime?: number | undefined;
|
|
133
134
|
snapshot: ISnapshot | ISnapshotTree;
|
|
@@ -144,27 +145,6 @@ interface ISerializerEvent extends IEvent {
|
|
|
144
145
|
(event: "saved", listener: (dirty: boolean) => void): void;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
class RefreshPromiseTracker {
|
|
148
|
-
public get hasPromise(): boolean {
|
|
149
|
-
return this.#promise !== undefined;
|
|
150
|
-
}
|
|
151
|
-
public get Promise(): Promise<number> | undefined {
|
|
152
|
-
return this.#promise;
|
|
153
|
-
}
|
|
154
|
-
constructor(private readonly catchHandler: (error: Error) => void) {}
|
|
155
|
-
|
|
156
|
-
#promise: Promise<number> | undefined;
|
|
157
|
-
setPromise(p: Promise<number>): void {
|
|
158
|
-
if (this.hasPromise) {
|
|
159
|
-
throw new Error("Cannot set promise while promise exists");
|
|
160
|
-
}
|
|
161
|
-
this.#promise = p.finally(() => {
|
|
162
|
-
this.#promise = undefined;
|
|
163
|
-
});
|
|
164
|
-
p.catch(this.catchHandler);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
148
|
/**
|
|
169
149
|
* Helper class to manage the state of the container needed for proper serialization.
|
|
170
150
|
*
|
|
@@ -177,33 +157,21 @@ export class SerializedStateManager implements IDisposable {
|
|
|
177
157
|
private readonly mc: MonitoringContext;
|
|
178
158
|
private snapshotInfo: ISnapshotInfo | undefined;
|
|
179
159
|
private latestSnapshot: ISnapshotInfo | undefined;
|
|
180
|
-
private readonly refreshTracker = new RefreshPromiseTracker(
|
|
181
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
182
|
-
(error) =>
|
|
183
|
-
this.mc.logger.sendErrorEvent(
|
|
184
|
-
{
|
|
185
|
-
eventName: "RefreshLatestSnapshotFailed",
|
|
186
|
-
},
|
|
187
|
-
error,
|
|
188
|
-
),
|
|
189
|
-
);
|
|
190
160
|
private lastSavedOpSequenceNumber: number = 0;
|
|
191
|
-
private readonly
|
|
192
|
-
private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
|
|
193
|
-
readonly #snapshotRefreshEnabled: boolean;
|
|
161
|
+
private readonly snapshotRefresher: SnapshotRefresher | undefined;
|
|
194
162
|
#disposed: boolean = false;
|
|
195
163
|
|
|
196
164
|
/**
|
|
197
165
|
* @param subLogger - Container's logger to use as parent for our logger
|
|
198
166
|
* @param storageAdapter - Storage adapter for fetching snapshots
|
|
199
|
-
* @param
|
|
167
|
+
* @param offlineLoadEnabled - Is serializing/rehydrating containers allowed?
|
|
200
168
|
* @param containerEvent - Source of the "saved" event when the container has all its pending state uploaded
|
|
201
169
|
* @param containerDirty - Is the container "dirty"? That's the opposite of "saved" - there is pending state that may not have been received yet by the service.
|
|
202
170
|
*/
|
|
203
171
|
constructor(
|
|
204
172
|
subLogger: ITelemetryBaseLogger,
|
|
205
173
|
private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
|
|
206
|
-
private readonly
|
|
174
|
+
private readonly offlineLoadEnabled: boolean,
|
|
207
175
|
containerEvent: IEventProvider<ISerializerEvent>,
|
|
208
176
|
private readonly containerDirty: () => boolean,
|
|
209
177
|
private readonly supportGetSnapshotApi: () => boolean,
|
|
@@ -214,16 +182,17 @@ export class SerializedStateManager implements IDisposable {
|
|
|
214
182
|
namespace: "serializedStateManager",
|
|
215
183
|
});
|
|
216
184
|
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
185
|
+
this.snapshotRefresher = this.offlineLoadEnabled
|
|
186
|
+
? new SnapshotRefresher(
|
|
187
|
+
subLogger,
|
|
188
|
+
this.storageAdapter,
|
|
189
|
+
this.offlineLoadEnabled,
|
|
190
|
+
this.supportGetSnapshotApi,
|
|
191
|
+
(snapshot: ISnapshotInfo) => this.handleSnapshotRefreshed(snapshot),
|
|
192
|
+
snapshotRefreshTimeoutMs,
|
|
193
|
+
)
|
|
226
194
|
: undefined;
|
|
195
|
+
|
|
227
196
|
containerEvent.on("saved", () => this.updateSnapshotAndProcessedOpsMaybe());
|
|
228
197
|
}
|
|
229
198
|
public get disposed(): boolean {
|
|
@@ -231,7 +200,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
231
200
|
}
|
|
232
201
|
dispose(): void {
|
|
233
202
|
this.#disposed = true;
|
|
234
|
-
this.
|
|
203
|
+
this.snapshotRefresher?.dispose();
|
|
235
204
|
}
|
|
236
205
|
|
|
237
206
|
private verifyNotDisposed(): void {
|
|
@@ -240,17 +209,13 @@ export class SerializedStateManager implements IDisposable {
|
|
|
240
209
|
}
|
|
241
210
|
}
|
|
242
211
|
|
|
243
|
-
public get offlineLoadEnabled(): boolean {
|
|
244
|
-
return this._offlineLoadEnabled;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
212
|
/**
|
|
248
213
|
* Promise that will resolve (or reject) once we've tried to download the latest snapshot(s) from storage
|
|
249
214
|
* only intended to be used for testing purposes.
|
|
250
215
|
* @returns The snapshot sequence number associated with the latest fetched snapshot
|
|
251
216
|
*/
|
|
252
|
-
public get refreshSnapshotP(): Promise<number
|
|
253
|
-
return this.
|
|
217
|
+
public get refreshSnapshotP(): Promise<number> | undefined {
|
|
218
|
+
return this.snapshotRefresher?.refreshSnapshotP;
|
|
254
219
|
}
|
|
255
220
|
|
|
256
221
|
/**
|
|
@@ -292,7 +257,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
292
257
|
const baseSnapshotTree: ISnapshotTree | undefined = getSnapshotTree(snapshot);
|
|
293
258
|
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
|
|
294
259
|
if (this.offlineLoadEnabled) {
|
|
295
|
-
this.
|
|
260
|
+
this.snapshotRefresher?.startTimer();
|
|
296
261
|
this.snapshotInfo = {
|
|
297
262
|
snapshot,
|
|
298
263
|
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
@@ -330,60 +295,19 @@ export class SerializedStateManager implements IDisposable {
|
|
|
330
295
|
snapshot,
|
|
331
296
|
snapshotSequenceNumber: attributes.sequenceNumber,
|
|
332
297
|
};
|
|
333
|
-
this.tryRefreshSnapshot();
|
|
298
|
+
this.snapshotRefresher?.tryRefreshSnapshot();
|
|
334
299
|
}
|
|
335
300
|
return { snapshot, version: undefined, attributes };
|
|
336
301
|
}
|
|
337
302
|
}
|
|
338
303
|
|
|
339
|
-
private tryRefreshSnapshot(): void {
|
|
340
|
-
if (
|
|
341
|
-
this.#snapshotRefreshEnabled &&
|
|
342
|
-
!this.#disposed &&
|
|
343
|
-
!this.refreshTracker.hasPromise &&
|
|
344
|
-
this.latestSnapshot === undefined
|
|
345
|
-
) {
|
|
346
|
-
// Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
|
|
347
|
-
this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
304
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)
|
|
305
|
+
* Handles the snapshotRefreshed event from SnapshotRefresher.
|
|
306
|
+
* Decides whether to accept the new snapshot based on processed ops.
|
|
307
|
+
* @returns The snapshot sequence number if updated, -1 otherwise
|
|
356
308
|
*/
|
|
357
|
-
private
|
|
358
|
-
this.latestSnapshot =
|
|
359
|
-
this.mc,
|
|
360
|
-
this.storageAdapter,
|
|
361
|
-
supportGetSnapshotApi,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
if (this.#disposed) {
|
|
365
|
-
return -1;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// These are loading groupIds that the containerRuntime has requested over its lifetime.
|
|
369
|
-
// We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
|
|
370
|
-
const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
|
|
371
|
-
if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
|
|
372
|
-
assert(
|
|
373
|
-
this.storageAdapter.getSnapshot !== undefined,
|
|
374
|
-
0x972 /* getSnapshot should exist */,
|
|
375
|
-
);
|
|
376
|
-
// (This is a separate network call from above because it requires work for storage to add a special base groupId)
|
|
377
|
-
const snapshot = await this.storageAdapter.getSnapshot({
|
|
378
|
-
versionId: undefined,
|
|
379
|
-
scenarioName: "getLatestSnapshotInfo",
|
|
380
|
-
cacheSnapshot: false,
|
|
381
|
-
loadingGroupIds: downloadedGroupIds,
|
|
382
|
-
fetchSource: FetchSource.noCache,
|
|
383
|
-
});
|
|
384
|
-
assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
|
|
385
|
-
}
|
|
386
|
-
|
|
309
|
+
private handleSnapshotRefreshed(latestSnapshot: ISnapshotInfo): number {
|
|
310
|
+
this.latestSnapshot = latestSnapshot;
|
|
387
311
|
return this.updateSnapshotAndProcessedOpsMaybe();
|
|
388
312
|
}
|
|
389
313
|
|
|
@@ -419,14 +343,14 @@ export class SerializedStateManager implements IDisposable {
|
|
|
419
343
|
stashedSnapshotSequenceNumber: this.snapshotInfo?.snapshotSequenceNumber,
|
|
420
344
|
});
|
|
421
345
|
this.latestSnapshot = undefined;
|
|
422
|
-
this.
|
|
346
|
+
this.snapshotRefresher?.clearLatestSnapshot();
|
|
423
347
|
} else if (snapshotSequenceNumber <= lastProcessedOpSequenceNumber) {
|
|
424
348
|
// Snapshot seq num is between the first and last processed op.
|
|
425
349
|
// Remove the ops that are already part of the snapshot
|
|
426
350
|
this.processedOps.splice(0, snapshotSequenceNumber - firstProcessedOpSequenceNumber + 1);
|
|
427
351
|
this.snapshotInfo = this.latestSnapshot;
|
|
428
352
|
this.latestSnapshot = undefined;
|
|
429
|
-
this.
|
|
353
|
+
this.snapshotRefresher?.clearLatestSnapshot();
|
|
430
354
|
this.mc.logger.sendTelemetryEvent({
|
|
431
355
|
eventName: "SnapshotRefreshed",
|
|
432
356
|
snapshotSequenceNumber,
|
|
@@ -452,7 +376,7 @@ export class SerializedStateManager implements IDisposable {
|
|
|
452
376
|
snapshotSequenceNumber: snapshot.sequenceNumber ?? 0,
|
|
453
377
|
snapshotFetchedTime: Date.now(),
|
|
454
378
|
};
|
|
455
|
-
this.
|
|
379
|
+
this.snapshotRefresher?.startTimer();
|
|
456
380
|
}
|
|
457
381
|
}
|
|
458
382
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
7
|
+
import type { IDisposable } from "@fluidframework/core-interfaces/internal";
|
|
8
|
+
import { assert, Timer } from "@fluidframework/core-utils/internal";
|
|
9
|
+
import { FetchSource } from "@fluidframework/driver-definitions/internal";
|
|
10
|
+
import {
|
|
11
|
+
createChildMonitoringContext,
|
|
12
|
+
type MonitoringContext,
|
|
13
|
+
} from "@fluidframework/telemetry-utils/internal";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getLatestSnapshotInfo,
|
|
17
|
+
type ISerializedStateManagerDocumentStorageService,
|
|
18
|
+
type ISnapshotInfo,
|
|
19
|
+
} from "./serializedStateManager.js";
|
|
20
|
+
|
|
21
|
+
class RefreshPromiseTracker {
|
|
22
|
+
public get hasPromise(): boolean {
|
|
23
|
+
return this.#promise !== undefined;
|
|
24
|
+
}
|
|
25
|
+
public get promise(): Promise<number> | undefined {
|
|
26
|
+
return this.#promise;
|
|
27
|
+
}
|
|
28
|
+
constructor(private readonly catchHandler: (error: Error) => void) {}
|
|
29
|
+
|
|
30
|
+
#promise: Promise<number> | undefined;
|
|
31
|
+
setPromise(p: Promise<number>): void {
|
|
32
|
+
if (this.hasPromise) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Cannot start new snapshot refresh while a refresh is already in progress",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
this.#promise = p.finally(() => {
|
|
38
|
+
this.#promise = undefined;
|
|
39
|
+
});
|
|
40
|
+
p.catch(this.catchHandler);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Manages periodic refresh of the latest snapshot for a document.
|
|
46
|
+
*
|
|
47
|
+
* `SnapshotRefresher` polls the storage service for the most recent snapshot and, when a newer
|
|
48
|
+
* snapshot is discovered, invokes the provided `onSnapshotRefreshed` callback with the updated
|
|
49
|
+
* snapshot metadata. It is responsible for:
|
|
50
|
+
*
|
|
51
|
+
* - Tracking the most recent snapshot that has been observed.
|
|
52
|
+
* - Scheduling and managing refresh attempts via an internal timer.
|
|
53
|
+
* - Emitting telemetry for successful and failed refresh attempts.
|
|
54
|
+
*
|
|
55
|
+
* The refresh behavior can be configured via constructor arguments, including whether offline
|
|
56
|
+
* loading and the `getSnapshot` API are supported, as well as the refresh timeout. Callers
|
|
57
|
+
* should dispose this instance when snapshot refresh is no longer needed to stop any pending
|
|
58
|
+
* timers and prevent further refresh attempts.
|
|
59
|
+
*/
|
|
60
|
+
export class SnapshotRefresher implements IDisposable {
|
|
61
|
+
private readonly mc: MonitoringContext;
|
|
62
|
+
private latestSnapshot: ISnapshotInfo | undefined;
|
|
63
|
+
#disposed: boolean = false;
|
|
64
|
+
|
|
65
|
+
public get disposed(): boolean {
|
|
66
|
+
return this.#disposed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private readonly refreshTracker = new RefreshPromiseTracker(
|
|
70
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
71
|
+
(error) =>
|
|
72
|
+
this.mc.logger.sendErrorEvent(
|
|
73
|
+
{
|
|
74
|
+
eventName: "RefreshLatestSnapshotFailed",
|
|
75
|
+
},
|
|
76
|
+
error,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
private readonly refreshTimer: Timer | undefined;
|
|
80
|
+
private readonly snapshotRefreshTimeoutMs: number = 60 * 60 * 24 * 1000;
|
|
81
|
+
readonly #snapshotRefreshEnabled: boolean;
|
|
82
|
+
|
|
83
|
+
constructor(
|
|
84
|
+
subLogger: ITelemetryBaseLogger,
|
|
85
|
+
private readonly storageAdapter: ISerializedStateManagerDocumentStorageService,
|
|
86
|
+
private readonly offlineLoadEnabled: boolean,
|
|
87
|
+
private readonly supportGetSnapshotApi: () => boolean,
|
|
88
|
+
private readonly onSnapshotRefreshed: (snapshot: ISnapshotInfo) => number,
|
|
89
|
+
snapshotRefreshTimeoutMs?: number,
|
|
90
|
+
) {
|
|
91
|
+
this.mc = createChildMonitoringContext({
|
|
92
|
+
logger: subLogger,
|
|
93
|
+
namespace: "serializedStateManager",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.snapshotRefreshTimeoutMs = snapshotRefreshTimeoutMs ?? this.snapshotRefreshTimeoutMs;
|
|
97
|
+
|
|
98
|
+
this.#snapshotRefreshEnabled =
|
|
99
|
+
this.offlineLoadEnabled &&
|
|
100
|
+
(this.mc.config.getBoolean("Fluid.Container.enableOfflineSnapshotRefresh") ??
|
|
101
|
+
this.mc.config.getBoolean("Fluid.Container.enableOfflineFull")) === true;
|
|
102
|
+
|
|
103
|
+
this.refreshTimer = this.#snapshotRefreshEnabled
|
|
104
|
+
? new Timer(this.snapshotRefreshTimeoutMs, () => this.tryRefreshSnapshot())
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public tryRefreshSnapshot(): void {
|
|
109
|
+
if (
|
|
110
|
+
this.#snapshotRefreshEnabled &&
|
|
111
|
+
!this.#disposed &&
|
|
112
|
+
!this.refreshTracker.hasPromise &&
|
|
113
|
+
this.latestSnapshot === undefined
|
|
114
|
+
) {
|
|
115
|
+
// Don't block on the refresh snapshot call - it is for the next time we serialize, not booting this incarnation
|
|
116
|
+
this.refreshTracker.setPromise(this.refreshLatestSnapshot(this.supportGetSnapshotApi()));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fetch the latest snapshot for the container, including delay-loaded groupIds if pendingLocalState was provided and contained any groupIds.
|
|
122
|
+
* Note that this will update the StorageAdapter's cached snapshots for the groupIds (if present)
|
|
123
|
+
*
|
|
124
|
+
* @param supportGetSnapshotApi - a boolean indicating whether to use the fetchISnapshot or fetchISnapshotTree (must be true to fetch by groupIds)
|
|
125
|
+
*/
|
|
126
|
+
private async refreshLatestSnapshot(supportGetSnapshotApi: boolean): Promise<number> {
|
|
127
|
+
this.latestSnapshot = await getLatestSnapshotInfo(
|
|
128
|
+
this.mc,
|
|
129
|
+
this.storageAdapter,
|
|
130
|
+
supportGetSnapshotApi,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (this.#disposed) {
|
|
134
|
+
return -1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// These are loading groupIds that the containerRuntime has requested over its lifetime.
|
|
138
|
+
// We will fetch the latest snapshot for the groupIds, which will update storageAdapter.loadedGroupIdSnapshots's cache
|
|
139
|
+
const downloadedGroupIds = Object.keys(this.storageAdapter.loadedGroupIdSnapshots);
|
|
140
|
+
if (supportGetSnapshotApi && downloadedGroupIds.length > 0) {
|
|
141
|
+
assert(
|
|
142
|
+
this.storageAdapter.getSnapshot !== undefined,
|
|
143
|
+
0x972 /* getSnapshot should exist */,
|
|
144
|
+
);
|
|
145
|
+
// (This is a separate network call from above because it requires work for storage to add a special base groupId)
|
|
146
|
+
const snapshot = await this.storageAdapter.getSnapshot({
|
|
147
|
+
versionId: undefined,
|
|
148
|
+
scenarioName: "getLatestSnapshotInfo",
|
|
149
|
+
cacheSnapshot: false,
|
|
150
|
+
loadingGroupIds: downloadedGroupIds,
|
|
151
|
+
fetchSource: FetchSource.noCache,
|
|
152
|
+
});
|
|
153
|
+
assert(snapshot !== undefined, 0x973 /* Snapshot should exist */);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Notify the manager about the fetched snapshot - let it decide what to do with it
|
|
157
|
+
const result =
|
|
158
|
+
this.latestSnapshot === undefined ? -1 : this.onSnapshotRefreshed(this.latestSnapshot);
|
|
159
|
+
|
|
160
|
+
this.refreshTimer?.restart();
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clears the latest snapshot after it's been consumed by the manager.
|
|
166
|
+
* This allows the next refresh cycle to proceed.
|
|
167
|
+
*/
|
|
168
|
+
public clearLatestSnapshot(): void {
|
|
169
|
+
this.latestSnapshot = undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Starts the refresh timer.
|
|
174
|
+
*/
|
|
175
|
+
public startTimer(): void {
|
|
176
|
+
this.refreshTimer?.start();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Restarts the refresh timer.
|
|
181
|
+
*/
|
|
182
|
+
public restartTimer(): void {
|
|
183
|
+
this.refreshTimer?.restart();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Gets the current refresh promise for testing purposes.
|
|
188
|
+
* @returns The snapshot sequence number promise, or undefined if no refresh is in progress
|
|
189
|
+
*/
|
|
190
|
+
public get refreshSnapshotP(): Promise<number> | undefined {
|
|
191
|
+
return this.refreshTracker.promise;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Disposes the refresher and clears the timer.
|
|
196
|
+
*/
|
|
197
|
+
public dispose(): void {
|
|
198
|
+
this.#disposed = true;
|
|
199
|
+
this.refreshTimer?.clear();
|
|
200
|
+
}
|
|
201
|
+
}
|
package/.eslintrc.cjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
module.exports = {
|
|
7
|
-
extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"],
|
|
8
|
-
parserOptions: {
|
|
9
|
-
project: ["./tsconfig.json", "./src/test/tsconfig.json"],
|
|
10
|
-
},
|
|
11
|
-
rules: {
|
|
12
|
-
"@fluid-internal/fluid/no-unchecked-record-access": "warn",
|
|
13
|
-
},
|
|
14
|
-
overrides: [
|
|
15
|
-
{
|
|
16
|
-
// Rules only for test files
|
|
17
|
-
files: ["*.spec.ts", "src/test/**"],
|
|
18
|
-
rules: {
|
|
19
|
-
// Test files are run in node only so additional node libraries can be used.
|
|
20
|
-
"import-x/no-nodejs-modules": ["error", { allow: ["node:assert"] }],
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
};
|