@fluidframework/container-runtime 2.0.0-internal.1.4.4 → 2.0.0-internal.2.0.1
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/batchManager.d.ts +2 -3
- package/dist/batchManager.d.ts.map +1 -1
- package/dist/batchManager.js +3 -8
- package/dist/batchManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +43 -16
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +107 -82
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +4 -20
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +17 -47
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +2 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -11
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +1 -10
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +43 -51
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +3 -12
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +6 -26
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +42 -62
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.js +7 -2
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +0 -3
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +19 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts +4 -2
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +10 -6
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchManager.d.ts +2 -3
- package/lib/batchManager.d.ts.map +1 -1
- package/lib/batchManager.js +3 -8
- package/lib/batchManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +43 -16
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +108 -83
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +4 -20
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +18 -48
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +2 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -11
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +1 -10
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +42 -50
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +3 -12
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +3 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -2
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +6 -26
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +42 -62
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.js +7 -2
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +0 -3
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +19 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts +4 -2
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +10 -6
- package/lib/summaryManager.js.map +1 -1
- package/package.json +22 -65
- package/src/batchManager.ts +7 -11
- package/src/containerRuntime.ts +149 -100
- package/src/dataStoreContext.ts +20 -62
- package/src/dataStores.ts +2 -10
- package/src/garbageCollection.ts +45 -55
- package/src/gcSweepReadyUsageDetection.ts +2 -10
- package/src/index.ts +2 -3
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +57 -96
- package/src/scheduleManager.ts +1 -0
- package/src/summarizer.ts +6 -6
- package/src/summarizerHeuristics.ts +0 -3
- package/src/summarizerTypes.ts +20 -7
- package/src/summaryFormat.ts +4 -2
- package/src/summaryManager.ts +18 -7
package/src/dataStores.ts
CHANGED
|
@@ -97,7 +97,6 @@ export class DataStores implements IDisposable {
|
|
|
97
97
|
private readonly gcNodeUpdated: (
|
|
98
98
|
nodePath: string, timestampMs: number, packagePath?: readonly string[]) => void,
|
|
99
99
|
private readonly aliasMap: Map<string, string>,
|
|
100
|
-
private readonly writeGCDataAtRoot: boolean,
|
|
101
100
|
private readonly contexts: DataStoreContexts = new DataStoreContexts(baseLogger),
|
|
102
101
|
) {
|
|
103
102
|
this.logger = ChildLogger.create(baseLogger);
|
|
@@ -142,7 +141,6 @@ export class DataStores implements IDisposable {
|
|
|
142
141
|
key,
|
|
143
142
|
{ type: CreateSummarizerNodeSource.FromSummary },
|
|
144
143
|
),
|
|
145
|
-
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
146
144
|
});
|
|
147
145
|
} else {
|
|
148
146
|
if (typeof value !== "object") {
|
|
@@ -162,7 +160,6 @@ export class DataStores implements IDisposable {
|
|
|
162
160
|
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(key),
|
|
163
161
|
snapshotTree,
|
|
164
162
|
isRootDataStore: undefined,
|
|
165
|
-
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
166
163
|
});
|
|
167
164
|
}
|
|
168
165
|
this.contexts.addBoundOrRemoted(dataStoreContext);
|
|
@@ -247,7 +244,6 @@ export class DataStores implements IDisposable {
|
|
|
247
244
|
},
|
|
248
245
|
},
|
|
249
246
|
),
|
|
250
|
-
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
251
247
|
pkg,
|
|
252
248
|
});
|
|
253
249
|
|
|
@@ -351,7 +347,6 @@ export class DataStores implements IDisposable {
|
|
|
351
347
|
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
|
|
352
348
|
snapshotTree: undefined,
|
|
353
349
|
isRootDataStore: isRoot,
|
|
354
|
-
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
355
350
|
});
|
|
356
351
|
this.contexts.addUnbound(context);
|
|
357
352
|
return context;
|
|
@@ -372,7 +367,6 @@ export class DataStores implements IDisposable {
|
|
|
372
367
|
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
|
|
373
368
|
snapshotTree: undefined,
|
|
374
369
|
isRootDataStore: false,
|
|
375
|
-
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
376
370
|
createProps: props,
|
|
377
371
|
});
|
|
378
372
|
this.contexts.addUnbound(context);
|
|
@@ -595,10 +589,8 @@ export class DataStores implements IDisposable {
|
|
|
595
589
|
/**
|
|
596
590
|
* After GC has run, called to notify this Container's data stores of routes that are used in it.
|
|
597
591
|
* @param usedRoutes - The routes that are used in all data stores in this Container.
|
|
598
|
-
* @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
|
|
599
|
-
* unreferenced as part of this GC run, this should be used to update the time when it happens.
|
|
600
592
|
*/
|
|
601
|
-
public updateUsedRoutes(usedRoutes: string[]
|
|
593
|
+
public updateUsedRoutes(usedRoutes: string[]) {
|
|
602
594
|
// Get a map of data store ids to routes used in it.
|
|
603
595
|
const usedDataStoreRoutes = unpackChildNodesUsedRoutes(usedRoutes);
|
|
604
596
|
|
|
@@ -609,7 +601,7 @@ export class DataStores implements IDisposable {
|
|
|
609
601
|
|
|
610
602
|
// Update the used routes in each data store. Used routes is empty for unused data stores.
|
|
611
603
|
for (const [contextId, context] of this.contexts) {
|
|
612
|
-
context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? []
|
|
604
|
+
context.updateUsedRoutes(usedDataStoreRoutes.get(contextId) ?? []);
|
|
613
605
|
}
|
|
614
606
|
}
|
|
615
607
|
|
package/src/garbageCollection.ts
CHANGED
|
@@ -67,12 +67,8 @@ export const runGCKey = "Fluid.GarbageCollection.RunGC";
|
|
|
67
67
|
export const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
68
68
|
// Feature gate key to turn GC test mode on / off.
|
|
69
69
|
export const gcTestModeKey = "Fluid.GarbageCollection.GCTestMode";
|
|
70
|
-
// Feature gate key to write GC data at the root of the summary tree.
|
|
71
|
-
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
72
70
|
// Feature gate key to expire a session after a set period of time.
|
|
73
71
|
export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
74
|
-
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present.
|
|
75
|
-
export const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
76
72
|
// Feature gate key to write the gc blob as a handle if the data is the same.
|
|
77
73
|
export const trackGCStateKey = "Fluid.GarbageCollection.TrackGCState";
|
|
78
74
|
// Feature gate key to turn GC sweep log off.
|
|
@@ -126,7 +122,7 @@ export interface IGarbageCollectionRuntime {
|
|
|
126
122
|
/** Returns the garbage collection data of the runtime. */
|
|
127
123
|
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
128
124
|
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
129
|
-
updateUsedRoutes(usedRoutes: string[]
|
|
125
|
+
updateUsedRoutes(usedRoutes: string[]): void;
|
|
130
126
|
/** After GC has run, called to delete objects in the runtime whose routes are unused. */
|
|
131
127
|
deleteUnusedRoutes(unusedRoutes: string[]): void;
|
|
132
128
|
/** Returns a referenced timestamp to be used to track unreferenced nodes. */
|
|
@@ -143,8 +139,6 @@ export interface IGarbageCollector {
|
|
|
143
139
|
readonly shouldRunGC: boolean;
|
|
144
140
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
145
141
|
readonly summaryStateNeedsReset: boolean;
|
|
146
|
-
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
147
|
-
readonly writeDataAtRoot: boolean;
|
|
148
142
|
readonly trackGCState: boolean;
|
|
149
143
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
150
144
|
collectGarbage(
|
|
@@ -190,7 +184,6 @@ export interface IGarbageCollectorCreateParams {
|
|
|
190
184
|
readonly readAndParseBlob: ReadAndParseBlob;
|
|
191
185
|
readonly activeConnection: () => boolean;
|
|
192
186
|
readonly getContainerDiagnosticId: () => string;
|
|
193
|
-
readonly snapshotCacheExpiryMs?: number;
|
|
194
187
|
}
|
|
195
188
|
|
|
196
189
|
/** The state of node that is unreferenced. */
|
|
@@ -378,14 +371,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
378
371
|
private readonly testMode: boolean;
|
|
379
372
|
private readonly mc: MonitoringContext;
|
|
380
373
|
|
|
381
|
-
/**
|
|
382
|
-
* Tells whether the GC data should be written to the root of the summary tree.
|
|
383
|
-
*/
|
|
384
|
-
private _writeDataAtRoot: boolean = true;
|
|
385
|
-
public get writeDataAtRoot(): boolean {
|
|
386
|
-
return this._writeDataAtRoot;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
374
|
/**
|
|
390
375
|
* Tells whether the initial GC state needs to be reset. This can happen under 2 conditions:
|
|
391
376
|
*
|
|
@@ -462,9 +447,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
462
447
|
sweepEnabled: this.sweepEnabled,
|
|
463
448
|
runGC: this.shouldRunGC,
|
|
464
449
|
runSweep: this.shouldRunSweep,
|
|
465
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
466
450
|
testMode: this.testMode,
|
|
467
451
|
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
452
|
+
sweepTimeout: this.sweepTimeoutMs,
|
|
468
453
|
inactiveTimeout: this.inactiveTimeoutMs,
|
|
469
454
|
trackGCState: this.trackGCState,
|
|
470
455
|
...this.gcOptions,
|
|
@@ -498,6 +483,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
498
483
|
|
|
499
484
|
let prevSummaryGCVersion: number | undefined;
|
|
500
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
488
|
+
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer.
|
|
489
|
+
*
|
|
490
|
+
* The snapshot cache expiry timeout cannot be known precisely but the upper bound is 5 days.
|
|
491
|
+
* The buffer is added to account for any clock skew or other edge cases.
|
|
492
|
+
* We use server timestamps throughout so the skew should be minimal but make it 1 day to be safe.
|
|
493
|
+
*/
|
|
494
|
+
function computeSweepTimeout(sessionExpiryTimeoutMs: number | undefined) {
|
|
495
|
+
const maxSnapshotCacheExpiryMs = 5 * oneDayMs;
|
|
496
|
+
const bufferMs = oneDayMs;
|
|
497
|
+
return sessionExpiryTimeoutMs &&
|
|
498
|
+
(sessionExpiryTimeoutMs + maxSnapshotCacheExpiryMs + bufferMs);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
501
|
/**
|
|
502
502
|
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
503
503
|
* 1. Whether running GC mark phase is allowed or not.
|
|
@@ -512,6 +512,9 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
512
512
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
513
513
|
this.sweepEnabled = metadata?.sweepEnabled ?? false;
|
|
514
514
|
this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
|
|
515
|
+
this.sweepTimeoutMs =
|
|
516
|
+
metadata?.sweepTimeoutMs
|
|
517
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs); // Backfill old documents that didn't persist this
|
|
515
518
|
} else {
|
|
516
519
|
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
517
520
|
// scenario but explicitly failing makes it clearer and promotes correct usage.
|
|
@@ -519,20 +522,28 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
519
522
|
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
520
523
|
}
|
|
521
524
|
|
|
525
|
+
// This Test Override only applies for new containers
|
|
526
|
+
const testOverrideSweepTimeoutMs =
|
|
527
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SweepTimeoutMs");
|
|
528
|
+
|
|
522
529
|
// For new documents, GC is enabled by default. It can be explicitly disabled by setting the gcAllowed
|
|
523
530
|
// flag in GC options to false.
|
|
524
531
|
this.gcEnabled = this.gcOptions.gcAllowed !== false;
|
|
525
532
|
// The sweep phase has to be explicitly enabled by setting the sweepAllowed flag in GC options to true.
|
|
526
|
-
|
|
533
|
+
// ...unless we're using the TestOverride
|
|
534
|
+
this.sweepEnabled = this.gcOptions.sweepAllowed === true || testOverrideSweepTimeoutMs !== undefined;
|
|
527
535
|
|
|
528
536
|
// Set the Session Expiry only if the flag is enabled and GC is enabled.
|
|
529
537
|
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
530
538
|
this.sessionExpiryTimeoutMs = this.gcOptions.sessionExpiryTimeoutMs ?? defaultSessionExpiryDurationMs;
|
|
531
539
|
}
|
|
540
|
+
this.sweepTimeoutMs =
|
|
541
|
+
testOverrideSweepTimeoutMs
|
|
542
|
+
?? computeSweepTimeout(this.sessionExpiryTimeoutMs);
|
|
532
543
|
}
|
|
533
544
|
|
|
534
545
|
// If session expiry is enabled, we need to close the container when the session expiry timeout expires.
|
|
535
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
546
|
+
if (this.sessionExpiryTimeoutMs !== undefined) {
|
|
536
547
|
// If Test Override config is set, override Session Expiry timeout.
|
|
537
548
|
const overrideSessionExpiryTimeoutMs =
|
|
538
549
|
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
@@ -543,21 +554,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
543
554
|
() => { this.runtime.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs)); },
|
|
544
555
|
);
|
|
545
556
|
this.sessionExpiryTimer.start();
|
|
546
|
-
|
|
547
|
-
// TEMPORARY: Hardcode a default of 5 days which will be >= the policy's value in the ODSP driver.
|
|
548
|
-
// This unblocks the Sweep Log (see logSweepEvents function).
|
|
549
|
-
// This will be removed before sweep is fully implemented.
|
|
550
|
-
const snapshotCacheExpiryMs = createParams.snapshotCacheExpiryMs ?? 5 * 24 * 60 * 60 * 1000;
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Sweep timeout is the time after which unreferenced content can be swept.
|
|
554
|
-
* Sweep timeout = session expiry timeout + snapshot cache expiry timeout + one day buffer. The buffer is
|
|
555
|
-
* added to account for any clock skew. We use server timestamps throughout so the skew should be minimal
|
|
556
|
-
* but make it one day to be safe.
|
|
557
|
-
*/
|
|
558
|
-
if (snapshotCacheExpiryMs !== undefined) {
|
|
559
|
-
this.sweepTimeoutMs = this.sessionExpiryTimeoutMs + snapshotCacheExpiryMs + oneDayMs;
|
|
560
|
-
}
|
|
561
557
|
}
|
|
562
558
|
|
|
563
559
|
// For existing document, the latest summary is the one that we loaded from. So, use its GC version as the
|
|
@@ -584,16 +580,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
584
580
|
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
585
581
|
*
|
|
586
582
|
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
587
|
-
*
|
|
588
583
|
* 2. Sweep timeout should be available. Without this, we wouldn't know when an object should be deleted.
|
|
589
|
-
*
|
|
590
|
-
*
|
|
584
|
+
* 3. The driver must implement the policy limiting the age of snapshots used for loading. Otherwise
|
|
585
|
+
* the Sweep Timeout calculation is not valid. We use the persisted value to ensure consistency over time.
|
|
586
|
+
* 4. Sweep should be enabled for this container (this.sweepEnabled). This can be overridden via runSweep
|
|
591
587
|
* feature flag.
|
|
592
588
|
*/
|
|
593
|
-
this.shouldRunSweep =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
589
|
+
this.shouldRunSweep =
|
|
590
|
+
this.shouldRunGC
|
|
591
|
+
&& this.sweepTimeoutMs !== undefined
|
|
592
|
+
&& (this.mc.config.getBoolean(runSweepKey) ?? this.sweepEnabled);
|
|
597
593
|
|
|
598
594
|
this.trackGCState = this.mc.config.getBoolean(trackGCStateKey) === true;
|
|
599
595
|
|
|
@@ -610,15 +606,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
610
606
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
611
607
|
this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? this.gcOptions.runGCInTestMode === true;
|
|
612
608
|
|
|
613
|
-
// GC state
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
618
|
-
// contain GC tree and GC is enabled.
|
|
619
|
-
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
620
|
-
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
621
|
-
}
|
|
609
|
+
// The GC state needs to be reset if the base snapshot contains GC tree and GC is disabled or it doesn't
|
|
610
|
+
// contain GC tree and GC is enabled.
|
|
611
|
+
const gcTreePresent = baseSnapshot?.trees[gcTreeKey] !== undefined;
|
|
612
|
+
this.initialStateNeedsReset = gcTreePresent !== this.shouldRunGC;
|
|
622
613
|
|
|
623
614
|
// Get the GC state from the GC blob in the base snapshot. Use LazyPromise because we only want to do
|
|
624
615
|
// this once since it involves fetching blobs from storage which is expensive.
|
|
@@ -631,8 +622,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
631
622
|
// For newer documents, GC data should be present in the GC tree in the root of the snapshot.
|
|
632
623
|
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
633
624
|
if (gcSnapshotTree !== undefined) {
|
|
634
|
-
// If the GC tree is written at root, we should also do the same.
|
|
635
|
-
this._writeDataAtRoot = true;
|
|
636
625
|
const baseGCState = await getGCStateFromSnapshot(
|
|
637
626
|
gcSnapshotTree,
|
|
638
627
|
readAndParseBlob,
|
|
@@ -855,7 +844,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
855
844
|
const gcResult = runGarbageCollection(gcData.gcNodes, ["/"]);
|
|
856
845
|
|
|
857
846
|
const gcStats = await this.runPostGCSteps(gcData, gcResult, logger, currentReferenceTimestampMs);
|
|
858
|
-
event.end({ ...gcStats });
|
|
847
|
+
event.end({ ...gcStats, timestamp: currentReferenceTimestampMs });
|
|
859
848
|
this.completedRuns++;
|
|
860
849
|
return gcStats;
|
|
861
850
|
}, { end: true, cancel: "error" });
|
|
@@ -884,7 +873,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
884
873
|
|
|
885
874
|
// Update the current state and update the runtime of all routes or ids that used as per the GC run.
|
|
886
875
|
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
887
|
-
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds
|
|
876
|
+
this.runtime.updateUsedRoutes(gcResult.referencedNodeIds);
|
|
888
877
|
|
|
889
878
|
// Log events for objects that are ready to be deleted by sweep. When we have sweep enabled, we will
|
|
890
879
|
// delete these objects here instead.
|
|
@@ -967,6 +956,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
967
956
|
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
968
957
|
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
969
958
|
sweepEnabled: this.sweepEnabled,
|
|
959
|
+
sweepTimeoutMs: this.sweepTimeoutMs,
|
|
970
960
|
};
|
|
971
961
|
}
|
|
972
962
|
|
|
@@ -1155,7 +1145,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1155
1145
|
this.newReferencesSinceLastRun,
|
|
1156
1146
|
);
|
|
1157
1147
|
|
|
1158
|
-
if (
|
|
1148
|
+
if (missingExplicitReferences.length > 0) {
|
|
1159
1149
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
1160
1150
|
const event: ITelemetryPerformanceEvent = {
|
|
1161
1151
|
eventName: "gcUnknownOutboundReferences",
|
|
@@ -74,16 +74,8 @@ export class SweepReadyUsageDetectionHandler {
|
|
|
74
74
|
localStorageOverride?: Pick<Storage, "getItem" | "setItem">,
|
|
75
75
|
) {
|
|
76
76
|
const noopStorage = { getItem: () => null, setItem: () => {} };
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
} else {
|
|
80
|
-
try {
|
|
81
|
-
// localStorage is not defined in Node environment so this throws
|
|
82
|
-
this.localStorage = localStorage ?? noopStorage;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
this.localStorage = noopStorage;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
77
|
+
// localStorage is not defined in Node environment, so fall back to noopStorage if needed.
|
|
78
|
+
this.localStorage = localStorageOverride ?? globalThis.localStorage ?? noopStorage;
|
|
87
79
|
|
|
88
80
|
if (this.localStorage === noopStorage) {
|
|
89
81
|
// This means the Skip Closure Period logic will not work.
|
package/src/index.ts
CHANGED
|
@@ -23,8 +23,8 @@ export {
|
|
|
23
23
|
RuntimeHeaders,
|
|
24
24
|
ISummaryConfiguration,
|
|
25
25
|
DefaultSummaryConfiguration,
|
|
26
|
+
ICompressionRuntimeOptions,
|
|
26
27
|
} from "./containerRuntime";
|
|
27
|
-
export { DeltaScheduler } from "./deltaScheduler";
|
|
28
28
|
export { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
29
29
|
export {
|
|
30
30
|
gcBlobPrefix,
|
|
@@ -34,12 +34,10 @@ export {
|
|
|
34
34
|
} from "./garbageCollection";
|
|
35
35
|
export {
|
|
36
36
|
IPendingFlush,
|
|
37
|
-
IPendingFlushMode,
|
|
38
37
|
IPendingLocalState,
|
|
39
38
|
IPendingMessage,
|
|
40
39
|
IPendingState,
|
|
41
40
|
} from "./pendingStateManager";
|
|
42
|
-
export { ScheduleManager } from "./scheduleManager";
|
|
43
41
|
export { Summarizer } from "./summarizer";
|
|
44
42
|
export {
|
|
45
43
|
EnqueueSummarizeResult,
|
|
@@ -54,6 +52,7 @@ export {
|
|
|
54
52
|
INackSummaryResult,
|
|
55
53
|
IOnDemandSummarizeOptions,
|
|
56
54
|
IProvideSummarizer,
|
|
55
|
+
IRefreshSummaryAckOptions,
|
|
57
56
|
ISubmitSummaryOpResult,
|
|
58
57
|
ISubmitSummaryOptions,
|
|
59
58
|
ISummarizeOptions,
|
package/src/packageVersion.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { DataProcessingError } from "@fluidframework/container-utils";
|
|
|
10
10
|
import {
|
|
11
11
|
ISequencedDocumentMessage,
|
|
12
12
|
} from "@fluidframework/protocol-definitions";
|
|
13
|
-
import { FlushMode } from "@fluidframework/runtime-definitions";
|
|
14
13
|
import Deque from "double-ended-queue";
|
|
15
14
|
import { ContainerMessageType } from "./containerRuntime";
|
|
16
15
|
import { pkgVersion } from "./packageVersion";
|
|
@@ -29,15 +28,6 @@ export interface IPendingMessage {
|
|
|
29
28
|
opMetadata: Record<string, unknown> | undefined;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/**
|
|
33
|
-
* This represents a FlushMode update and is added to the pending queue when `setFlushMode` is called on the
|
|
34
|
-
* ContainerRuntime and the FlushMode changes.
|
|
35
|
-
*/
|
|
36
|
-
export interface IPendingFlushMode {
|
|
37
|
-
type: "flushMode";
|
|
38
|
-
flushMode: FlushMode;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
31
|
/**
|
|
42
32
|
* This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime
|
|
43
33
|
* to flush pending messages.
|
|
@@ -46,7 +36,7 @@ export interface IPendingFlush {
|
|
|
46
36
|
type: "flush";
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
export type IPendingState = IPendingMessage |
|
|
39
|
+
export type IPendingState = IPendingMessage | IPendingFlush;
|
|
50
40
|
|
|
51
41
|
export interface IPendingLocalState {
|
|
52
42
|
/**
|
|
@@ -58,8 +48,6 @@ export interface IPendingLocalState {
|
|
|
58
48
|
export interface IRuntimeStateHandler{
|
|
59
49
|
connected(): boolean;
|
|
60
50
|
clientId(): string | undefined;
|
|
61
|
-
flushMode(): FlushMode;
|
|
62
|
-
setFlushMode(mode: FlushMode): void;
|
|
63
51
|
close(error?: ICriticalContainerError): void;
|
|
64
52
|
applyStashedOp: (type: ContainerMessageType, content: ISequencedDocumentMessage) => Promise<unknown>;
|
|
65
53
|
flush(): void;
|
|
@@ -68,13 +56,18 @@ export interface IRuntimeStateHandler{
|
|
|
68
56
|
content: any,
|
|
69
57
|
localOpMetadata: unknown,
|
|
70
58
|
opMetadata: Record<string, unknown> | undefined): void;
|
|
59
|
+
rollback(
|
|
60
|
+
type: ContainerMessageType,
|
|
61
|
+
content: any,
|
|
62
|
+
localOpMetadata: unknown): void;
|
|
63
|
+
orderSequentially(callback: () => void): void;
|
|
71
64
|
}
|
|
72
65
|
|
|
73
66
|
/**
|
|
74
67
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
75
68
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
76
69
|
* batches along with the messages.
|
|
77
|
-
* When the Container reconnects, it replays the pending states, which includes
|
|
70
|
+
* When the Container reconnects, it replays the pending states, which includes manual flushing
|
|
78
71
|
* of messages and triggering resubmission of unacked ops.
|
|
79
72
|
*
|
|
80
73
|
* It verifies that all the ops are acked, are received in the right order and batch information is correct.
|
|
@@ -100,13 +93,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
100
93
|
// the correct batch metadata.
|
|
101
94
|
private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;
|
|
102
95
|
|
|
103
|
-
/**
|
|
104
|
-
* This tracks the flush mode for the next message in the pending state queue. When replaying messages, we need to
|
|
105
|
-
* first set the flush mode to this value and then send ops. It is important to do this info because the flush
|
|
106
|
-
* mode could have been updated.
|
|
107
|
-
*/
|
|
108
|
-
private flushModeForNextMessage: FlushMode;
|
|
109
|
-
|
|
110
96
|
private clientId: string | undefined;
|
|
111
97
|
|
|
112
98
|
/**
|
|
@@ -131,13 +117,9 @@ export class PendingStateManager implements IDisposable {
|
|
|
131
117
|
|
|
132
118
|
constructor(
|
|
133
119
|
private readonly stateHandler: IRuntimeStateHandler,
|
|
134
|
-
initialFlushMode: FlushMode,
|
|
135
120
|
initialLocalState: IPendingLocalState | undefined,
|
|
136
121
|
) {
|
|
137
122
|
this.initialStates = new Deque<IPendingState>(initialLocalState?.pendingStates ?? []);
|
|
138
|
-
|
|
139
|
-
this.flushModeForNextMessage = initialFlushMode;
|
|
140
|
-
this.onFlushModeUpdated(initialFlushMode);
|
|
141
123
|
}
|
|
142
124
|
|
|
143
125
|
public get disposed() { return this.disposeOnce.evaluated; }
|
|
@@ -174,24 +156,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
174
156
|
this._pendingMessagesCount++;
|
|
175
157
|
}
|
|
176
158
|
|
|
177
|
-
/**
|
|
178
|
-
* Called when the FlushMode is updated. Adds the FlushMode to the pending state queue.
|
|
179
|
-
* @param flushMode - The flushMode that was updated.
|
|
180
|
-
*/
|
|
181
|
-
public onFlushModeUpdated(flushMode: FlushMode) {
|
|
182
|
-
this.pendingStates.push({ type: "flushMode", flushMode });
|
|
183
|
-
}
|
|
184
|
-
|
|
185
159
|
/**
|
|
186
160
|
* Called when flush() is called on the ContainerRuntime to manually flush messages.
|
|
187
161
|
*/
|
|
188
162
|
public onFlush() {
|
|
189
|
-
// If the FlushMode is Immediate, we don't need to track an explicit flush call because every message is
|
|
190
|
-
// automatically flushed. So, flush is a no-op.
|
|
191
|
-
if (this.stateHandler.flushMode() === FlushMode.Immediate) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
163
|
// If the previous state is not a message, flush is a no-op.
|
|
196
164
|
const previousState = this.pendingStates.peekBack();
|
|
197
165
|
if (previousState?.type !== "message") {
|
|
@@ -279,11 +247,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
279
247
|
* @param message - The message that is being processed.
|
|
280
248
|
*/
|
|
281
249
|
private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
|
|
282
|
-
// Tracks the last FlushMode that was set before this message was sent.
|
|
283
|
-
let pendingFlushMode: FlushMode | undefined;
|
|
284
|
-
// Tracks whether a flush was called before this message was sent.
|
|
285
|
-
let pendingFlush: boolean = false;
|
|
286
|
-
|
|
287
250
|
/**
|
|
288
251
|
* We are checking if the next message is the start of a batch. It can happen in the following scenarios:
|
|
289
252
|
*
|
|
@@ -295,34 +258,12 @@ export class PendingStateManager implements IDisposable {
|
|
|
295
258
|
* Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was
|
|
296
259
|
* updated a bunch of times without sending any messages.
|
|
297
260
|
*/
|
|
298
|
-
|
|
299
|
-
while (nextPendingState.type !== "message") {
|
|
300
|
-
if (nextPendingState.type === "flushMode") {
|
|
301
|
-
pendingFlushMode = nextPendingState.flushMode;
|
|
302
|
-
}
|
|
303
|
-
if (nextPendingState.type === "flush") {
|
|
304
|
-
pendingFlush = true;
|
|
305
|
-
}
|
|
261
|
+
while (this.peekNextPendingState().type !== "message") {
|
|
306
262
|
this.pendingStates.shift();
|
|
307
|
-
nextPendingState = this.peekNextPendingState();
|
|
308
263
|
}
|
|
309
264
|
|
|
310
|
-
if
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// If the FlushMode was set to Immediate before this message was sent, this message won't be a batch message
|
|
315
|
-
// because in Immediate mode, every message is flushed individually.
|
|
316
|
-
if (pendingFlushMode === FlushMode.Immediate) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* This message is the first in a batch if before it was sent either the FlushMode was set to TurnBased or there
|
|
322
|
-
* was an explicit flush call. Note that a flush call is tracked only in TurnBased mode and it indicates the end
|
|
323
|
-
* of one batch and beginning of another.
|
|
324
|
-
*/
|
|
325
|
-
if (pendingFlushMode === FlushMode.TurnBased || pendingFlush) {
|
|
265
|
+
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
266
|
+
if (message.metadata?.batch) {
|
|
326
267
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
327
268
|
assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,
|
|
328
269
|
0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
@@ -347,16 +288,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
347
288
|
return;
|
|
348
289
|
}
|
|
349
290
|
|
|
350
|
-
/**
|
|
351
|
-
* We are in the middle of processing a batch. The batch ends when we see an explicit flush. We should never see
|
|
352
|
-
* a FlushMode before flush. This is true because we track batches only when FlushMode is TurnBased and in this
|
|
353
|
-
* mode, a batch ends either by calling flush or by changing the mode to Immediate which also triggers a flush.
|
|
354
|
-
*/
|
|
355
|
-
assert(
|
|
356
|
-
nextPendingState.type !== "flushMode",
|
|
357
|
-
0x2bd /* "We should not see a pending FlushMode until we see a flush when processing a batch" */,
|
|
358
|
-
);
|
|
359
|
-
|
|
360
291
|
// There should be a pending batch begin message.
|
|
361
292
|
assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
|
|
362
293
|
|
|
@@ -406,7 +337,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
406
337
|
|
|
407
338
|
/**
|
|
408
339
|
* Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
|
|
409
|
-
* states in its queue. This includes
|
|
340
|
+
* states in its queue. This includes triggering resubmission of unacked ops.
|
|
410
341
|
*/
|
|
411
342
|
public replayPendingStates() {
|
|
412
343
|
assert(this.stateHandler.connected(), 0x172 /* "The connection state is not consistent with the runtime" */);
|
|
@@ -426,12 +357,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
426
357
|
// Reset the pending message count because all these messages will be removed from the queue.
|
|
427
358
|
this._pendingMessagesCount = 0;
|
|
428
359
|
|
|
429
|
-
|
|
430
|
-
const savedFlushMode = this.stateHandler.flushMode();
|
|
431
|
-
|
|
432
|
-
// Set the flush mode for the next message. This step is important because the flush mode may have been changed
|
|
433
|
-
// after the next pending message was sent.
|
|
434
|
-
this.stateHandler.setFlushMode(this.flushModeForNextMessage);
|
|
360
|
+
const messageBatchQueue = new Deque<IPendingMessage>();
|
|
435
361
|
|
|
436
362
|
// Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were
|
|
437
363
|
// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue
|
|
@@ -441,16 +367,43 @@ export class PendingStateManager implements IDisposable {
|
|
|
441
367
|
const pendingState = this.pendingStates.shift()!;
|
|
442
368
|
switch (pendingState.type) {
|
|
443
369
|
case "message":
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
370
|
+
assert(pendingState.opMetadata?.batch !== false || messageBatchQueue.length > 0,
|
|
371
|
+
0x41b /* We cannot process batches in chunks */);
|
|
372
|
+
/**
|
|
373
|
+
* We want to ensure grouped messages get processed in a batch.
|
|
374
|
+
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
375
|
+
* either receive the whole batch ack or nothing at all.
|
|
376
|
+
*/
|
|
377
|
+
if (messageBatchQueue.length > 0 || pendingState.opMetadata?.batch) {
|
|
378
|
+
messageBatchQueue.enqueue(pendingState);
|
|
379
|
+
} else {
|
|
380
|
+
this.stateHandler.reSubmit(
|
|
381
|
+
pendingState.messageType,
|
|
382
|
+
pendingState.content,
|
|
383
|
+
pendingState.localOpMetadata,
|
|
384
|
+
pendingState.opMetadata);
|
|
385
|
+
}
|
|
452
386
|
break;
|
|
453
387
|
case "flush":
|
|
388
|
+
/**
|
|
389
|
+
* A "flush" call can indicate the end of a batch.
|
|
390
|
+
* We can't rely on the "batch" property in the message metadata as it gets
|
|
391
|
+
* updated elsewhere and it is not the same object instance that gets updated.
|
|
392
|
+
*/
|
|
393
|
+
if (messageBatchQueue.length > 0) {
|
|
394
|
+
this.stateHandler.orderSequentially(() => {
|
|
395
|
+
while (messageBatchQueue.length > 0) {
|
|
396
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
397
|
+
const message = messageBatchQueue.dequeue()!;
|
|
398
|
+
this.stateHandler.reSubmit(
|
|
399
|
+
message.messageType,
|
|
400
|
+
message.content,
|
|
401
|
+
message.localOpMetadata,
|
|
402
|
+
message.opMetadata);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
assert(messageBatchQueue.length === 0, 0x41c /* cannot flush in the middle of a batch */);
|
|
454
407
|
this.stateHandler.flush();
|
|
455
408
|
break;
|
|
456
409
|
default:
|
|
@@ -459,7 +412,15 @@ export class PendingStateManager implements IDisposable {
|
|
|
459
412
|
pendingStatesCount--;
|
|
460
413
|
}
|
|
461
414
|
|
|
462
|
-
//
|
|
463
|
-
|
|
415
|
+
// There are some cases where ops are stashed but not flushed. We need to ensure they are resubmitted
|
|
416
|
+
while (messageBatchQueue.length > 0) {
|
|
417
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
418
|
+
const message = messageBatchQueue.dequeue()!;
|
|
419
|
+
this.stateHandler.reSubmit(
|
|
420
|
+
message.messageType,
|
|
421
|
+
message.content,
|
|
422
|
+
message.localOpMetadata,
|
|
423
|
+
message.opMetadata);
|
|
424
|
+
}
|
|
464
425
|
}
|
|
465
426
|
}
|