@fluidframework/container-runtime 0.57.0-51086 → 0.57.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/containerRuntime.d.ts +10 -6
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +64 -30
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +1 -36
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -27
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +5 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +11 -6
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +2 -2
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +24 -11
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +94 -53
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/containerRuntime.d.ts +10 -6
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +65 -31
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +1 -36
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +4 -26
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +5 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +11 -6
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +2 -2
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +24 -11
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +94 -53
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +13 -13
- package/src/containerRuntime.ts +98 -34
- package/src/dataStore.ts +6 -42
- package/src/dataStoreContext.ts +15 -13
- package/src/dataStores.ts +8 -3
- package/src/garbageCollection.ts +114 -57
- package/src/index.ts +0 -1
- package/src/packageVersion.ts +1 -1
package/src/garbageCollection.ts
CHANGED
|
@@ -93,6 +93,7 @@ interface IUnreferencedEvent {
|
|
|
93
93
|
id: string;
|
|
94
94
|
age: number;
|
|
95
95
|
timeout: number;
|
|
96
|
+
lastSummaryTime?: number;
|
|
96
97
|
externalRequest?: boolean;
|
|
97
98
|
viaHandle?: boolean;
|
|
98
99
|
};
|
|
@@ -105,6 +106,8 @@ export interface IGarbageCollectionRuntime {
|
|
|
105
106
|
getGCData(fullGC?: boolean): Promise<IGarbageCollectionData>;
|
|
106
107
|
/** After GC has run, called to notify the runtime of routes that are used in it. */
|
|
107
108
|
updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number): void;
|
|
109
|
+
/** Called when the runtime should close because of an error. */
|
|
110
|
+
closeFn(error?: ICriticalContainerError): void;
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
/** Defines the contract for the garbage collector. */
|
|
@@ -137,6 +140,7 @@ export interface IGarbageCollector {
|
|
|
137
140
|
nodeUpdated(
|
|
138
141
|
nodePath: string,
|
|
139
142
|
reason: "Loaded" | "Changed",
|
|
143
|
+
timestampMs?: number,
|
|
140
144
|
packagePath?: readonly string[],
|
|
141
145
|
requestHeaders?: IRequestHeader,
|
|
142
146
|
): void;
|
|
@@ -155,23 +159,43 @@ class UnreferencedStateTracker {
|
|
|
155
159
|
return this._inactive;
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
private
|
|
162
|
+
private timer: Timer | undefined;
|
|
159
163
|
|
|
160
164
|
constructor(
|
|
161
165
|
public readonly unreferencedTimestampMs: number,
|
|
162
|
-
inactiveTimeoutMs: number,
|
|
166
|
+
private readonly inactiveTimeoutMs: number,
|
|
167
|
+
currentReferenceTimestampMs?: number,
|
|
163
168
|
) {
|
|
164
|
-
// If
|
|
165
|
-
//
|
|
166
|
-
if (
|
|
169
|
+
// If there is no current reference timestamp, don't track the node's inactive state. This will happen later
|
|
170
|
+
// when updateTracking is called with a reference timestamp.
|
|
171
|
+
if (currentReferenceTimestampMs !== undefined) {
|
|
172
|
+
this.updateTracking(currentReferenceTimestampMs);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Updates the tracking state based on the provided timestamp.
|
|
178
|
+
*/
|
|
179
|
+
public updateTracking(currentReferenceTimestampMs: number) {
|
|
180
|
+
const unreferencedDurationMs = currentReferenceTimestampMs - this.unreferencedTimestampMs;
|
|
181
|
+
// If the timeout has already expired, the node has become inactive.
|
|
182
|
+
if (unreferencedDurationMs > this.inactiveTimeoutMs) {
|
|
167
183
|
this._inactive = true;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
this.timer?.clear();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// The node isn't inactive yet. Restart a timer for the duration remaining for it to become inactive.
|
|
189
|
+
const remainingDurationMs = this.inactiveTimeoutMs - unreferencedDurationMs;
|
|
190
|
+
if (this.timer === undefined) {
|
|
191
|
+
this.timer = new Timer(remainingDurationMs, () => { this._inactive = true; });
|
|
171
192
|
}
|
|
193
|
+
this.timer.restart(remainingDurationMs);
|
|
172
194
|
}
|
|
173
195
|
|
|
174
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Stop tracking this node. Reset the unreferenced timer, if any, and reset inactive state.
|
|
198
|
+
*/
|
|
175
199
|
public stopTracking() {
|
|
176
200
|
this.timer?.clear();
|
|
177
201
|
this._inactive = false;
|
|
@@ -188,8 +212,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
188
212
|
gcOptions: IGCRuntimeOptions,
|
|
189
213
|
deleteUnusedRoutes: (unusedRoutes: string[]) => void,
|
|
190
214
|
getNodePackagePath: (nodeId: string) => readonly string[] | undefined,
|
|
191
|
-
|
|
192
|
-
|
|
215
|
+
getCurrentReferenceTimestampMs: () => number | undefined,
|
|
216
|
+
getLastSummaryTimestampMs: () => number | undefined,
|
|
193
217
|
baseSnapshot: ISnapshotTree | undefined,
|
|
194
218
|
readAndParseBlob: ReadAndParseBlob,
|
|
195
219
|
baseLogger: ITelemetryLogger,
|
|
@@ -201,8 +225,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
201
225
|
gcOptions,
|
|
202
226
|
deleteUnusedRoutes,
|
|
203
227
|
getNodePackagePath,
|
|
204
|
-
|
|
205
|
-
|
|
228
|
+
getCurrentReferenceTimestampMs,
|
|
229
|
+
getLastSummaryTimestampMs,
|
|
206
230
|
baseSnapshot,
|
|
207
231
|
readAndParseBlob,
|
|
208
232
|
baseLogger,
|
|
@@ -307,9 +331,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
307
331
|
private readonly deleteUnusedRoutes: (unusedRoutes: string[]) => void,
|
|
308
332
|
/** For a given node path, returns the node's package path. */
|
|
309
333
|
private readonly getNodePackagePath: (nodePath: string) => readonly string[] | undefined,
|
|
310
|
-
/**
|
|
311
|
-
|
|
312
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Returns a referenced timestamp to be used to track unreferenced nodes. This is a server generated timestamp
|
|
336
|
+
* and may not be available if there aren't any ops processed yet. If so, we skip tracking unreferenced state
|
|
337
|
+
* such as time when node becomes unreferenced or inactive.
|
|
338
|
+
*/
|
|
339
|
+
private readonly getCurrentReferenceTimestampMs: () => number | undefined,
|
|
340
|
+
/** Returns the timestamp of the last summary generated for this container. */
|
|
341
|
+
private readonly getLastSummaryTimestampMs: () => number | undefined,
|
|
313
342
|
baseSnapshot: ISnapshotTree | undefined,
|
|
314
343
|
readAndParseBlob: ReadAndParseBlob,
|
|
315
344
|
baseLogger: ITelemetryLogger,
|
|
@@ -346,7 +375,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
346
375
|
const timeoutMs = this.sessionExpiryTimeoutMs;
|
|
347
376
|
setLongTimeout(timeoutMs,
|
|
348
377
|
() => {
|
|
349
|
-
this.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
|
|
378
|
+
this.provider.closeFn(new ClientSessionExpiredError(`Client session expired.`, timeoutMs));
|
|
350
379
|
},
|
|
351
380
|
(timer) => {
|
|
352
381
|
this.sessionExpiryTimer = timer;
|
|
@@ -448,10 +477,13 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
448
477
|
return Object.keys(gcState.gcNodes).length === 1 ? undefined : gcState;
|
|
449
478
|
});
|
|
450
479
|
|
|
451
|
-
|
|
452
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Set up the initializer which initializes the base GC state from the base snapshot. Note that the reference
|
|
482
|
+
* timestamp maybe from old ops which were not summarized and stored in the file. So, the unreferenced state
|
|
483
|
+
* may be out of date. This is fine because the state is updated every time GC runs based on the time then.
|
|
484
|
+
*/
|
|
453
485
|
this.initializeBaseStateP = new LazyPromise<void>(async () => {
|
|
454
|
-
const
|
|
486
|
+
const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
|
|
455
487
|
const baseState = await baseSummaryStateP;
|
|
456
488
|
if (baseState === undefined) {
|
|
457
489
|
return;
|
|
@@ -459,16 +491,13 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
459
491
|
|
|
460
492
|
const gcNodes: { [ id: string ]: string[] } = {};
|
|
461
493
|
for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
|
|
462
|
-
|
|
463
|
-
if (unreferencedTimestampMs !== undefined) {
|
|
464
|
-
// Get how long it has been since the node was unreferenced. Start a timeout for the remaining time
|
|
465
|
-
// left for it to be eligible for deletion.
|
|
466
|
-
const unreferencedDurationMs = currentTimestampMs - unreferencedTimestampMs;
|
|
494
|
+
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
467
495
|
this.unreferencedNodesState.set(
|
|
468
496
|
nodeId,
|
|
469
497
|
new UnreferencedStateTracker(
|
|
470
|
-
unreferencedTimestampMs,
|
|
471
|
-
this.deleteTimeoutMs
|
|
498
|
+
nodeData.unreferencedTimestampMs,
|
|
499
|
+
this.deleteTimeoutMs,
|
|
500
|
+
currentReferenceTimestampMs,
|
|
472
501
|
),
|
|
473
502
|
);
|
|
474
503
|
}
|
|
@@ -571,10 +600,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
571
600
|
this.updateStateSinceLastRun(gcData);
|
|
572
601
|
|
|
573
602
|
// Update the current state of the system based on the GC run.
|
|
574
|
-
const
|
|
575
|
-
this.updateCurrentState(gcData, gcResult,
|
|
603
|
+
const currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs();
|
|
604
|
+
this.updateCurrentState(gcData, gcResult, currentReferenceTimestampMs);
|
|
576
605
|
|
|
577
|
-
this.provider.updateUsedRoutes(gcResult.referencedNodeIds,
|
|
606
|
+
this.provider.updateUsedRoutes(gcResult.referencedNodeIds, currentReferenceTimestampMs);
|
|
578
607
|
|
|
579
608
|
if (runSweep) {
|
|
580
609
|
// Placeholder for running sweep logic.
|
|
@@ -653,12 +682,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
653
682
|
* Called when a node with the given id is updated. If the node is inactive, log an error.
|
|
654
683
|
* @param nodePath - The id of the node that changed.
|
|
655
684
|
* @param reason - Whether the node was loaded or changed.
|
|
685
|
+
* @param timestampMs - The timestamp when the node changed.
|
|
656
686
|
* @param packagePath - The package path of the node. This may not be available if the node hasn't been loaded yet.
|
|
657
687
|
* @param requestHeaders - If the node was loaded via request path, the headers in the request.
|
|
658
688
|
*/
|
|
659
689
|
public nodeUpdated(
|
|
660
690
|
nodePath: string,
|
|
661
691
|
reason: "Loaded" | "Changed",
|
|
692
|
+
timestampMs?: number,
|
|
662
693
|
packagePath?: readonly string[],
|
|
663
694
|
requestHeaders?: IRequestHeader,
|
|
664
695
|
) {
|
|
@@ -669,7 +700,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
669
700
|
this.logIfInactive(
|
|
670
701
|
reason,
|
|
671
702
|
nodePath,
|
|
672
|
-
|
|
703
|
+
timestampMs,
|
|
673
704
|
packagePath,
|
|
674
705
|
requestHeaders,
|
|
675
706
|
);
|
|
@@ -695,8 +726,6 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
695
726
|
this.logIfInactive(
|
|
696
727
|
"Revived",
|
|
697
728
|
toNodePath,
|
|
698
|
-
this.getCurrentTimestampMs(),
|
|
699
|
-
this.getNodePackagePath(toNodePath),
|
|
700
729
|
);
|
|
701
730
|
}
|
|
702
731
|
|
|
@@ -725,28 +754,16 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
725
754
|
* 3. Clears tracking for nodes that were unreferenced but became referenced in this run.
|
|
726
755
|
* @param gcData - The data representing the reference graph on which GC is run.
|
|
727
756
|
* @param gcResult - The result of the GC run on the gcData.
|
|
728
|
-
* @param
|
|
757
|
+
* @param currentReferenceTimestampMs - The timestamp to be used for unreferenced nodes' timestamp.
|
|
729
758
|
*/
|
|
730
|
-
private updateCurrentState(
|
|
759
|
+
private updateCurrentState(
|
|
760
|
+
gcData: IGarbageCollectionData,
|
|
761
|
+
gcResult: IGCResult,
|
|
762
|
+
currentReferenceTimestampMs?: number,
|
|
763
|
+
) {
|
|
731
764
|
this.gcDataFromLastRun = cloneGCData(gcData);
|
|
732
765
|
this.referencesSinceLastRun.clear();
|
|
733
766
|
|
|
734
|
-
// Iterate through the deleted nodes and start tracking if they became unreferenced in this run.
|
|
735
|
-
for (const nodeId of gcResult.deletedNodeIds) {
|
|
736
|
-
// The time when the node became unreferenced. This is added to the current GC state.
|
|
737
|
-
let unreferencedTimestampMs: number = currentTimestampMs;
|
|
738
|
-
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
739
|
-
if (nodeStateTracker !== undefined) {
|
|
740
|
-
unreferencedTimestampMs = nodeStateTracker.unreferencedTimestampMs;
|
|
741
|
-
} else {
|
|
742
|
-
// Start tracking this node as it became unreferenced in this run.
|
|
743
|
-
this.unreferencedNodesState.set(
|
|
744
|
-
nodeId,
|
|
745
|
-
new UnreferencedStateTracker(unreferencedTimestampMs, this.deleteTimeoutMs),
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
767
|
// Iterate through the referenced nodes and stop tracking if they were unreferenced before.
|
|
751
768
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
752
769
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
@@ -757,6 +774,36 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
757
774
|
this.unreferencedNodesState.delete(nodeId);
|
|
758
775
|
}
|
|
759
776
|
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* If there is no current reference time, skip tracking when a node becomes unreferenced. This would happen
|
|
780
|
+
* if no ops have been processed ever and we still try to run GC. If so, there is nothing interesting to track
|
|
781
|
+
* anyway.
|
|
782
|
+
*/
|
|
783
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* If a node became unreferenced in this run, start tracking it.
|
|
789
|
+
* If a node was already unreferenced, update its tracking information. Since the current reference time is
|
|
790
|
+
* from the ops seen, this will ensure that we keep updating the unreferenced state as time moves forward.
|
|
791
|
+
*/
|
|
792
|
+
for (const nodeId of gcResult.deletedNodeIds) {
|
|
793
|
+
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
794
|
+
if (nodeStateTracker === undefined) {
|
|
795
|
+
this.unreferencedNodesState.set(
|
|
796
|
+
nodeId,
|
|
797
|
+
new UnreferencedStateTracker(
|
|
798
|
+
currentReferenceTimestampMs,
|
|
799
|
+
this.deleteTimeoutMs,
|
|
800
|
+
currentReferenceTimestampMs,
|
|
801
|
+
),
|
|
802
|
+
);
|
|
803
|
+
} else {
|
|
804
|
+
nodeStateTracker.updateTracking(currentReferenceTimestampMs);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
760
807
|
}
|
|
761
808
|
|
|
762
809
|
/**
|
|
@@ -939,16 +986,21 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
939
986
|
}
|
|
940
987
|
|
|
941
988
|
/**
|
|
942
|
-
* Logs an event if a node is inactive and is used.
|
|
943
|
-
* queue it and it will be logged the next time GC runs as the package data should be available then.
|
|
989
|
+
* Logs an event if a node is inactive and is used.
|
|
944
990
|
*/
|
|
945
991
|
private logIfInactive(
|
|
946
992
|
eventSuffix: "Changed" | "Loaded" | "Revived",
|
|
947
993
|
nodeId: string,
|
|
948
|
-
|
|
994
|
+
currentReferenceTimestampMs = this.getCurrentReferenceTimestampMs(),
|
|
949
995
|
packagePath?: readonly string[],
|
|
950
996
|
requestHeaders?: IRequestHeader,
|
|
951
997
|
) {
|
|
998
|
+
// If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
|
|
999
|
+
// logging as nothing interesting would have happened worth logging.
|
|
1000
|
+
if (currentReferenceTimestampMs === undefined) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
952
1004
|
const eventName = `inactiveObject_${eventSuffix}`;
|
|
953
1005
|
// We log a particular event for a given node only once so that it is not too noisy.
|
|
954
1006
|
const uniqueEventId = `${nodeId}-${eventName}`;
|
|
@@ -958,15 +1010,20 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
958
1010
|
const event: IUnreferencedEvent = {
|
|
959
1011
|
eventName,
|
|
960
1012
|
id: nodeId,
|
|
961
|
-
age:
|
|
1013
|
+
age: currentReferenceTimestampMs - nodeState.unreferencedTimestampMs,
|
|
962
1014
|
timeout: this.deleteTimeoutMs,
|
|
1015
|
+
lastSummaryTime: this.getLastSummaryTimestampMs(),
|
|
963
1016
|
externalRequest: requestHeaders?.[RuntimeHeaders.externalRequest],
|
|
964
1017
|
viaHandle: requestHeaders?.[RuntimeHeaders.viaHandle],
|
|
965
1018
|
};
|
|
966
|
-
|
|
1019
|
+
|
|
1020
|
+
// If the package data for the node exists, log immediately. Otherwise, queue it and it will be logged the
|
|
1021
|
+
// next time GC runs as the package data should be available then.
|
|
1022
|
+
const pkg = packagePath ?? this.getNodePackagePath(nodeId);
|
|
1023
|
+
if (pkg !== undefined) {
|
|
967
1024
|
this.mc.logger.sendErrorEvent({
|
|
968
1025
|
...event,
|
|
969
|
-
pkg: { value: `/${
|
|
1026
|
+
pkg: { value: `/${pkg.join("/")}`, tag: TelemetryDataTag.PackageData },
|
|
970
1027
|
});
|
|
971
1028
|
} else {
|
|
972
1029
|
this.pendingEventsQueue.push(event);
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,6 @@ export {
|
|
|
19
19
|
ContainerRuntime,
|
|
20
20
|
RuntimeHeaders,
|
|
21
21
|
} from "./containerRuntime";
|
|
22
|
-
export { IDataStore, AliasResult } from "./dataStore";
|
|
23
22
|
export { DeltaScheduler } from "./deltaScheduler";
|
|
24
23
|
export { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
25
24
|
export {
|
package/src/packageVersion.ts
CHANGED