@fluidframework/container-runtime 2.0.0-internal.2.2.0 → 2.0.0-internal.2.3.0
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/.eslintrc.js +19 -8
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +44 -33
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +130 -97
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +39 -8
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +117 -61
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +4 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +9 -6
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +30 -24
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -20
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +205 -151
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +6 -3
- package/dist/garbageCollectionConstants.d.ts.map +1 -1
- package/dist/garbageCollectionConstants.js +7 -7
- package/dist/garbageCollectionConstants.js.map +1 -1
- package/dist/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/dist/garbageCollectionTombstoneUtils.js +28 -0
- package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +13 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +35 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +25 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +24 -10
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +30 -17
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts +34 -2
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +114 -5
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +5 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +24 -14
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
- package/dist/opLifecycle/remoteMessageProcessor.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/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +0 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +0 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +9 -20
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.d.ts +0 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +2 -1
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +1 -0
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +1 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +44 -33
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +131 -98
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +39 -8
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +115 -59
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +5 -4
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +9 -6
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +32 -26
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -20
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +201 -147
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +6 -3
- package/lib/garbageCollectionConstants.d.ts.map +1 -1
- package/lib/garbageCollectionConstants.js +6 -6
- package/lib/garbageCollectionConstants.js.map +1 -1
- package/lib/garbageCollectionTombstoneUtils.d.ts +13 -0
- package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/lib/garbageCollectionTombstoneUtils.js +24 -0
- package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
- 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/opLifecycle/batchManager.d.ts +13 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +25 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +24 -10
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +30 -17
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts +34 -2
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +112 -4
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +5 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +24 -14
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
- package/lib/opLifecycle/remoteMessageProcessor.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/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +0 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +0 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +9 -20
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.d.ts +0 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +2 -1
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +1 -0
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +1 -2
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +31 -27
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +146 -103
- package/src/containerRuntime.ts +166 -65
- package/src/dataStoreContext.ts +5 -5
- package/src/dataStores.ts +40 -30
- package/src/garbageCollection.ts +254 -183
- package/src/garbageCollectionConstants.ts +7 -6
- package/src/garbageCollectionTombstoneUtils.ts +31 -0
- package/src/index.ts +0 -5
- package/src/opLifecycle/batchManager.ts +59 -1
- package/src/opLifecycle/definitions.ts +27 -1
- package/src/opLifecycle/index.ts +2 -1
- package/src/opLifecycle/opCompressor.ts +29 -12
- package/src/opLifecycle/opDecompressor.ts +39 -18
- package/src/opLifecycle/opSplitter.ts +141 -7
- package/src/opLifecycle/outbox.ts +32 -16
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +0 -1
- package/src/scheduleManager.ts +19 -30
- package/src/summarizer.ts +1 -1
- package/src/summarizerTypes.ts +1 -0
- package/src/summaryFormat.ts +1 -2
package/src/containerRuntime.ts
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
} from "@fluidframework/protocol-definitions";
|
|
70
70
|
import {
|
|
71
71
|
FlushMode,
|
|
72
|
+
gcTreeKey,
|
|
72
73
|
InboundAttachMessage,
|
|
73
74
|
IFluidDataStoreContextDetached,
|
|
74
75
|
IFluidDataStoreRegistry,
|
|
@@ -155,9 +156,6 @@ import {
|
|
|
155
156
|
IGarbageCollector,
|
|
156
157
|
IGCStats,
|
|
157
158
|
} from "./garbageCollection";
|
|
158
|
-
import {
|
|
159
|
-
gcTreeKey,
|
|
160
|
-
} from "./garbageCollectionConstants";
|
|
161
159
|
import {
|
|
162
160
|
channelToDataStore,
|
|
163
161
|
IDataStoreAliasMessage,
|
|
@@ -482,6 +480,25 @@ export interface IContainerRuntimeOptions {
|
|
|
482
480
|
* @experimental This config should be driven by the connection with the service and will be moved in the future.
|
|
483
481
|
*/
|
|
484
482
|
readonly maxBatchSizeInBytes?: number;
|
|
483
|
+
/**
|
|
484
|
+
* If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
|
|
485
|
+
* how large the individual chunks will be. This is only supported when compression is enabled.
|
|
486
|
+
*
|
|
487
|
+
* If unspecified, if a batch exceeds `maxBatchSizeInBytes` after compression, the container will close with an instance
|
|
488
|
+
* of `GenericError` with the `BatchTooLarge` message.
|
|
489
|
+
*
|
|
490
|
+
* @experimental Not ready for use.
|
|
491
|
+
*/
|
|
492
|
+
readonly chunkSizeInBytes?: number;
|
|
493
|
+
/**
|
|
494
|
+
* If enabled, the runtime will block all attempts to send an op with a different reference sequence number
|
|
495
|
+
* from the previous ops submitted in the same JS turn. This happens when ops are reentrant (an op is created as a
|
|
496
|
+
* response to another op, likely from an event handler).
|
|
497
|
+
*
|
|
498
|
+
* By default, the feature is disabled. If enabled from options, the `Fluid.ContainerRuntime.DisableOpReentryCheck`
|
|
499
|
+
* can be used to disable it at runtime.
|
|
500
|
+
*/
|
|
501
|
+
readonly enableOpReentryCheck?: boolean;
|
|
485
502
|
}
|
|
486
503
|
|
|
487
504
|
/**
|
|
@@ -662,6 +679,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
662
679
|
compressionAlgorithm: CompressionAlgorithms.lz4
|
|
663
680
|
},
|
|
664
681
|
maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
|
|
682
|
+
chunkSizeInBytes = Number.POSITIVE_INFINITY,
|
|
683
|
+
enableOpReentryCheck = false,
|
|
665
684
|
} = runtimeOptions;
|
|
666
685
|
|
|
667
686
|
const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
@@ -719,7 +738,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
719
738
|
if (loadSequenceNumberVerification === "log") {
|
|
720
739
|
logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
|
|
721
740
|
} else {
|
|
741
|
+
// Call both close and dispose as close implementation will no longer dispose runtime in future (2.0.0-internal.3.0.0)
|
|
722
742
|
context.closeFn(error);
|
|
743
|
+
context.disposeFn?.(error);
|
|
723
744
|
}
|
|
724
745
|
}
|
|
725
746
|
}
|
|
@@ -739,6 +760,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
739
760
|
enableOfflineLoad,
|
|
740
761
|
compressionOptions,
|
|
741
762
|
maxBatchSizeInBytes,
|
|
763
|
+
chunkSizeInBytes,
|
|
764
|
+
enableOpReentryCheck,
|
|
742
765
|
},
|
|
743
766
|
containerScope,
|
|
744
767
|
logger,
|
|
@@ -790,8 +813,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
790
813
|
return this.reSubmit;
|
|
791
814
|
}
|
|
792
815
|
|
|
816
|
+
public get disposeFn(): (error?: ICriticalContainerError) => void {
|
|
817
|
+
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
818
|
+
return this.context.disposeFn ?? this.context.closeFn;
|
|
819
|
+
}
|
|
820
|
+
|
|
793
821
|
public get closeFn(): (error?: ICriticalContainerError) => void {
|
|
794
|
-
|
|
822
|
+
// Also call disposeFn to retain functionality of runtime being disposed on close
|
|
823
|
+
return (error?: ICriticalContainerError) => {
|
|
824
|
+
this.context.closeFn(error);
|
|
825
|
+
this.context.disposeFn?.(error);
|
|
826
|
+
};
|
|
795
827
|
}
|
|
796
828
|
|
|
797
829
|
public get flushMode(): FlushMode {
|
|
@@ -863,6 +895,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
863
895
|
|
|
864
896
|
private dirtyContainer: boolean;
|
|
865
897
|
private emitDirtyDocumentEvent = true;
|
|
898
|
+
private readonly enableOpReentryCheck: boolean;
|
|
866
899
|
|
|
867
900
|
private readonly defaultTelemetrySignalSampleCount = 100;
|
|
868
901
|
private _perfSignalData: IPerfSignalReport = {
|
|
@@ -1010,17 +1043,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1010
1043
|
this.messageAtLastSummary = metadata?.message;
|
|
1011
1044
|
|
|
1012
1045
|
this._connected = this.context.connected;
|
|
1013
|
-
this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
|
|
1014
1046
|
|
|
1015
|
-
this.
|
|
1047
|
+
this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
1048
|
+
|
|
1049
|
+
const opSplitter = new OpSplitter(
|
|
1050
|
+
chunks,
|
|
1051
|
+
this.context.submitBatchFn,
|
|
1052
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompressionChunking") === true ?
|
|
1053
|
+
Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
|
|
1054
|
+
runtimeOptions.maxBatchSizeInBytes,
|
|
1055
|
+
this.mc.logger);
|
|
1056
|
+
this.remoteMessageProcessor = new RemoteMessageProcessor(opSplitter, new OpDecompressor());
|
|
1016
1057
|
|
|
1017
|
-
this.
|
|
1018
|
-
ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
1058
|
+
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
1019
1059
|
|
|
1020
1060
|
if (this.summaryConfiguration.state === "enabled") {
|
|
1021
1061
|
this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
|
|
1022
1062
|
}
|
|
1023
1063
|
|
|
1064
|
+
this.enableOpReentryCheck = runtimeOptions.enableOpReentryCheck === true
|
|
1065
|
+
// Allow for a break-glass config to override the options
|
|
1066
|
+
&& this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableOpReentryCheck") !== true;
|
|
1067
|
+
|
|
1024
1068
|
this.summariesDisabled = this.isSummariesDisabled();
|
|
1025
1069
|
this.heuristicsDisabled = this.isHeuristicsDisabled();
|
|
1026
1070
|
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
@@ -1079,6 +1123,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1079
1123
|
// If GC should not run, let the summarizer node know so that it does not track GC state.
|
|
1080
1124
|
gcDisabled: !this.garbageCollector.shouldRunGC,
|
|
1081
1125
|
},
|
|
1126
|
+
// Function to get GC data if needed. This will always be called by the root summarizer node to get GC data.
|
|
1127
|
+
async (fullGC?: boolean) => this.getGCDataInternal(fullGC),
|
|
1128
|
+
// Function to get the GC details from the base snapshot we loaded from.
|
|
1129
|
+
async () => this.garbageCollector.getBaseGCDetails(),
|
|
1082
1130
|
);
|
|
1083
1131
|
|
|
1084
1132
|
if (baseSnapshot) {
|
|
@@ -1092,7 +1140,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1092
1140
|
(id: string, createParam: CreateChildSummarizerNodeParam) => (
|
|
1093
1141
|
summarizeInternal: SummarizeInternalFn,
|
|
1094
1142
|
getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
|
|
1095
|
-
getBaseGCDetailsFn
|
|
1143
|
+
getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
|
|
1096
1144
|
) => this.summarizerNode.createChild(
|
|
1097
1145
|
summarizeInternal,
|
|
1098
1146
|
id,
|
|
@@ -1117,12 +1165,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1117
1165
|
this.handleContext,
|
|
1118
1166
|
blobManagerSnapshot,
|
|
1119
1167
|
() => this.storage,
|
|
1120
|
-
(
|
|
1168
|
+
(localId: string, blobId?: string) => {
|
|
1121
1169
|
if (!this.disposed) {
|
|
1122
|
-
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, {
|
|
1170
|
+
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { localId, blobId });
|
|
1123
1171
|
}
|
|
1124
1172
|
},
|
|
1125
1173
|
(blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
|
|
1174
|
+
(fromPath: string, toPath: string) => this.garbageCollector.addedOutboundReference(fromPath, toPath),
|
|
1126
1175
|
this,
|
|
1127
1176
|
pendingRuntimeState?.pendingAttachmentBlobs,
|
|
1128
1177
|
);
|
|
@@ -1147,15 +1196,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1147
1196
|
},
|
|
1148
1197
|
pendingRuntimeState?.pending);
|
|
1149
1198
|
|
|
1199
|
+
const compressionOptions = this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompression") === true ?
|
|
1200
|
+
{
|
|
1201
|
+
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
|
|
1202
|
+
compressionAlgorithm: CompressionAlgorithms.lz4
|
|
1203
|
+
} : runtimeOptions.compressionOptions;
|
|
1204
|
+
|
|
1150
1205
|
this.outbox = new Outbox({
|
|
1151
1206
|
shouldSend: () => this.canSendOps(),
|
|
1152
1207
|
pendingStateManager: this.pendingStateManager,
|
|
1153
1208
|
containerContext: this.context,
|
|
1154
1209
|
compressor: new OpCompressor(this.mc.logger),
|
|
1210
|
+
splitter: opSplitter,
|
|
1155
1211
|
config: {
|
|
1156
|
-
compressionOptions
|
|
1212
|
+
compressionOptions,
|
|
1157
1213
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1214
|
+
enableOpReentryCheck: this.enableOpReentryCheck,
|
|
1158
1215
|
},
|
|
1216
|
+
logger: this.mc.logger,
|
|
1159
1217
|
});
|
|
1160
1218
|
|
|
1161
1219
|
this.context.quorum.on("removeMember", (clientId: string) => {
|
|
@@ -1638,7 +1696,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1638
1696
|
if (!this.shouldContinueReconnecting()) {
|
|
1639
1697
|
this.closeFn(
|
|
1640
1698
|
DataProcessingError.create(
|
|
1641
|
-
// eslint-disable-next-line max-len
|
|
1642
1699
|
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)",
|
|
1643
1700
|
"setConnectionState",
|
|
1644
1701
|
undefined,
|
|
@@ -1685,7 +1742,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1685
1742
|
|
|
1686
1743
|
try {
|
|
1687
1744
|
let localOpMetadata: unknown;
|
|
1688
|
-
if (local && runtimeMessage) {
|
|
1745
|
+
if (local && runtimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
|
|
1689
1746
|
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1690
1747
|
}
|
|
1691
1748
|
|
|
@@ -1820,9 +1877,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1820
1877
|
assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
|
|
1821
1878
|
}
|
|
1822
1879
|
|
|
1823
|
-
public orderSequentially(callback: () =>
|
|
1880
|
+
public orderSequentially<T>(callback: () => T): T {
|
|
1824
1881
|
let checkpoint: IBatchCheckpoint | undefined;
|
|
1825
|
-
|
|
1882
|
+
let result: T;
|
|
1826
1883
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1827
1884
|
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1828
1885
|
// 1. It would not help, as we flush attach ops as they become available.
|
|
@@ -1831,7 +1888,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1831
1888
|
}
|
|
1832
1889
|
try {
|
|
1833
1890
|
this._orderSequentiallyCalls++;
|
|
1834
|
-
callback();
|
|
1891
|
+
result = callback();
|
|
1835
1892
|
} catch (error) {
|
|
1836
1893
|
if (checkpoint) {
|
|
1837
1894
|
// This will throw and close the container if rollback fails
|
|
@@ -1863,6 +1920,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1863
1920
|
if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
|
|
1864
1921
|
this.flush();
|
|
1865
1922
|
}
|
|
1923
|
+
return result;
|
|
1866
1924
|
}
|
|
1867
1925
|
|
|
1868
1926
|
public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
|
|
@@ -2112,6 +2170,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2112
2170
|
return this.dataStores.updateStateBeforeGC();
|
|
2113
2171
|
}
|
|
2114
2172
|
|
|
2173
|
+
private async getGCDataInternal(fullGC?: boolean): Promise<IGarbageCollectionData> {
|
|
2174
|
+
return this.dataStores.getGCData(fullGC);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2115
2177
|
/**
|
|
2116
2178
|
* Implementation of IGarbageCollectionRuntime::getGCData.
|
|
2117
2179
|
* Generates and returns the GC data for this container.
|
|
@@ -2119,7 +2181,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2119
2181
|
*/
|
|
2120
2182
|
public async getGCData(fullGC?: boolean): Promise<IGarbageCollectionData> {
|
|
2121
2183
|
const builder = new GCDataBuilder();
|
|
2122
|
-
const dsGCData = await this.
|
|
2184
|
+
const dsGCData = await this.summarizerNode.getGCData(fullGC);
|
|
2123
2185
|
builder.addNodes(dsGCData.gcNodes);
|
|
2124
2186
|
|
|
2125
2187
|
const blobsGCData = this.blobManager.getGCData(fullGC);
|
|
@@ -2138,40 +2200,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2138
2200
|
// always referenced, so the used routes is only self-route (empty string).
|
|
2139
2201
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
2140
2202
|
|
|
2141
|
-
const
|
|
2142
|
-
|
|
2143
|
-
for (const route of usedRoutes) {
|
|
2144
|
-
if (this.isBlobPath(route)) {
|
|
2145
|
-
blobManagerUsedRoutes.push(route);
|
|
2146
|
-
} else {
|
|
2147
|
-
dataStoreUsedRoutes.push(route);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
|
|
2152
|
-
this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
|
|
2203
|
+
const { dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(usedRoutes);
|
|
2204
|
+
this.dataStores.updateUsedRoutes(dataStoreRoutes);
|
|
2153
2205
|
}
|
|
2154
2206
|
|
|
2155
2207
|
/**
|
|
2156
|
-
* This is called to update objects whose routes are unused.
|
|
2157
|
-
*
|
|
2158
|
-
* @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
|
|
2159
|
-
* @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
|
|
2160
|
-
* are deleted.
|
|
2208
|
+
* This is called to update objects whose routes are unused.
|
|
2209
|
+
* @param unusedRoutes - Data store and attachment blob routes that are unused in this Container.
|
|
2161
2210
|
*/
|
|
2162
|
-
public updateUnusedRoutes(unusedRoutes: string[]
|
|
2163
|
-
const
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
blobManagerUnusedRoutes.push(route);
|
|
2168
|
-
} else {
|
|
2169
|
-
dataStoreUnusedRoutes.push(route);
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2211
|
+
public updateUnusedRoutes(unusedRoutes: string[]) {
|
|
2212
|
+
const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(unusedRoutes);
|
|
2213
|
+
this.blobManager.updateUnusedRoutes(blobManagerRoutes);
|
|
2214
|
+
this.dataStores.updateUnusedRoutes(dataStoreRoutes);
|
|
2215
|
+
}
|
|
2172
2216
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2217
|
+
/**
|
|
2218
|
+
* This is called to update objects that are tombstones.
|
|
2219
|
+
* @param tombstonedRoutes - Data store and attachment blob routes that are tombstones in this Container.
|
|
2220
|
+
*/
|
|
2221
|
+
public updateTombstonedRoutes(tombstonedRoutes: string[]) {
|
|
2222
|
+
const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(tombstonedRoutes);
|
|
2223
|
+
this.blobManager.updateTombstonedRoutes(blobManagerRoutes);
|
|
2224
|
+
this.dataStores.updateTombstonedRoutes(dataStoreRoutes);
|
|
2175
2225
|
}
|
|
2176
2226
|
|
|
2177
2227
|
/**
|
|
@@ -2201,7 +2251,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2201
2251
|
public async getGCNodePackagePath(nodePath: string): Promise<readonly string[] | undefined> {
|
|
2202
2252
|
switch (this.getNodeType(nodePath)) {
|
|
2203
2253
|
case GCNodeType.Blob:
|
|
2204
|
-
return [
|
|
2254
|
+
return [BlobManager.basePath];
|
|
2205
2255
|
case GCNodeType.DataStore:
|
|
2206
2256
|
case GCNodeType.SubDataStore:
|
|
2207
2257
|
return this.dataStores.getDataStorePackagePath(nodePath);
|
|
@@ -2221,6 +2271,25 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2221
2271
|
return true;
|
|
2222
2272
|
}
|
|
2223
2273
|
|
|
2274
|
+
/**
|
|
2275
|
+
* From a given list of routes, separate and return routes that belong to blob manager and data stores.
|
|
2276
|
+
* @param routes - A list of routes that can belong to data stores or blob manager.
|
|
2277
|
+
* @returns - Two route lists - One that contains routes for blob manager and another one that contains routes
|
|
2278
|
+
* for data stores.
|
|
2279
|
+
*/
|
|
2280
|
+
private getDataStoreAndBlobManagerRoutes(routes: string[]) {
|
|
2281
|
+
const blobManagerRoutes: string[] = [];
|
|
2282
|
+
const dataStoreRoutes: string[] = [];
|
|
2283
|
+
for (const route of routes) {
|
|
2284
|
+
if (this.isBlobPath(route)) {
|
|
2285
|
+
blobManagerRoutes.push(route);
|
|
2286
|
+
} else {
|
|
2287
|
+
dataStoreRoutes.push(route);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return { blobManagerRoutes, dataStoreRoutes };
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2224
2293
|
/**
|
|
2225
2294
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
2226
2295
|
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
@@ -2315,7 +2384,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2315
2384
|
if (this.deltaManager.lastSequenceNumber !== summaryRefSeqNum) {
|
|
2316
2385
|
return {
|
|
2317
2386
|
continue: false,
|
|
2318
|
-
// eslint-disable-next-line max-len
|
|
2319
2387
|
error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
|
|
2320
2388
|
};
|
|
2321
2389
|
}
|
|
@@ -2325,7 +2393,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2325
2393
|
if (lastAck !== this.summaryCollection.latestAck) {
|
|
2326
2394
|
return {
|
|
2327
2395
|
continue: false,
|
|
2328
|
-
// eslint-disable-next-line max-len
|
|
2329
2396
|
error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
|
|
2330
2397
|
};
|
|
2331
2398
|
}
|
|
@@ -2587,7 +2654,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2587
2654
|
} else if (!this.flushMicroTaskExists) {
|
|
2588
2655
|
this.flushMicroTaskExists = true;
|
|
2589
2656
|
// Queue a microtask to detect the end of the turn and force a flush.
|
|
2590
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2591
2657
|
Promise.resolve().then(() => {
|
|
2592
2658
|
this.flushMicroTaskExists = false;
|
|
2593
2659
|
this.flush();
|
|
@@ -2708,17 +2774,42 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2708
2774
|
// It should only be done by the summarizerNode, if required.
|
|
2709
2775
|
// When fetching from storage we will always get the latest version and do not use the ackHandle.
|
|
2710
2776
|
const snapshotTreeFetcher = async () => {
|
|
2711
|
-
const fetchResult = await this.
|
|
2712
|
-
null,
|
|
2777
|
+
const fetchResult = await this.fetchLatestSnapshotFromStorage(
|
|
2713
2778
|
summaryLogger,
|
|
2714
2779
|
{
|
|
2715
2780
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2716
2781
|
ackHandle,
|
|
2717
2782
|
summaryRefSeq,
|
|
2718
2783
|
fetchLatest: true,
|
|
2719
|
-
}
|
|
2784
|
+
},
|
|
2785
|
+
);
|
|
2720
2786
|
|
|
2721
2787
|
const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
|
|
2788
|
+
/**
|
|
2789
|
+
* If the fetched snapshot is older than the one for which the ack was received, close the container.
|
|
2790
|
+
* This should never happen because an ack should be sent after the latest summary is updated in the server.
|
|
2791
|
+
* However, there are couple of scenarios where it's possible:
|
|
2792
|
+
* 1. A file was modified externally resulting in modifying the snapshot's sequence number. This can lead to
|
|
2793
|
+
* the document being unusable and we should not proceed.
|
|
2794
|
+
* 2. The server DB failed after the ack was sent which may delete the corresponding snapshot. Ideally, in
|
|
2795
|
+
* such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
|
|
2796
|
+
* state.
|
|
2797
|
+
*/
|
|
2798
|
+
if (latestSnapshotRefSeq < summaryRefSeq) {
|
|
2799
|
+
const error = DataProcessingError.create(
|
|
2800
|
+
"Fetched snapshot is older than the received ack",
|
|
2801
|
+
"RefreshLatestSummaryAck",
|
|
2802
|
+
undefined /* sequencedMessage */,
|
|
2803
|
+
{
|
|
2804
|
+
ackHandle,
|
|
2805
|
+
summaryRefSeq,
|
|
2806
|
+
latestSnapshotRefSeq,
|
|
2807
|
+
},
|
|
2808
|
+
);
|
|
2809
|
+
this.closeFn(error);
|
|
2810
|
+
throw error;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2722
2813
|
summaryLogger.sendTelemetryEvent(
|
|
2723
2814
|
{
|
|
2724
2815
|
eventName: "LatestSummaryRetrieved",
|
|
@@ -2744,7 +2835,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2744
2835
|
);
|
|
2745
2836
|
|
|
2746
2837
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2747
|
-
await this.garbageCollector.
|
|
2838
|
+
await this.garbageCollector.refreshLatestSummary(
|
|
2839
|
+
result,
|
|
2840
|
+
proposalHandle,
|
|
2841
|
+
summaryRefSeq,
|
|
2842
|
+
readAndParseBlob,
|
|
2843
|
+
);
|
|
2748
2844
|
}
|
|
2749
2845
|
|
|
2750
2846
|
/**
|
|
@@ -2756,11 +2852,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2756
2852
|
private async refreshLatestSummaryAckFromServer(
|
|
2757
2853
|
summaryLogger: ITelemetryLogger,
|
|
2758
2854
|
): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
|
|
2759
|
-
const { snapshotTree, versionId } = await this.
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2855
|
+
const { snapshotTree, versionId } = await this.fetchLatestSnapshotFromStorage(
|
|
2856
|
+
summaryLogger,
|
|
2857
|
+
{
|
|
2858
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2859
|
+
fetchLatest: true,
|
|
2860
|
+
},
|
|
2764
2861
|
);
|
|
2765
2862
|
|
|
2766
2863
|
const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
|
|
@@ -2775,16 +2872,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2775
2872
|
);
|
|
2776
2873
|
|
|
2777
2874
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2778
|
-
await this.garbageCollector.
|
|
2875
|
+
await this.garbageCollector.refreshLatestSummary(
|
|
2876
|
+
result,
|
|
2877
|
+
undefined,
|
|
2878
|
+
latestSnapshotRefSeq,
|
|
2879
|
+
readAndParseBlob,
|
|
2880
|
+
)
|
|
2779
2881
|
|
|
2780
2882
|
return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
|
|
2781
2883
|
}
|
|
2782
2884
|
|
|
2783
|
-
private async
|
|
2784
|
-
versionId: string | null,
|
|
2885
|
+
private async fetchLatestSnapshotFromStorage(
|
|
2785
2886
|
logger: ITelemetryLogger,
|
|
2786
2887
|
event: ITelemetryGenericEvent,
|
|
2787
|
-
fetchSource?: FetchSource,
|
|
2788
2888
|
): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
|
|
2789
2889
|
return PerformanceEvent.timedExecAsync(
|
|
2790
2890
|
logger, event, async (perfEvent: {
|
|
@@ -2797,7 +2897,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2797
2897
|
const trace = Trace.start();
|
|
2798
2898
|
|
|
2799
2899
|
const versions = await this.storage.getVersions(
|
|
2800
|
-
|
|
2900
|
+
null, 1, "refreshLatestSummaryAckFromServer", FetchSource.noCache);
|
|
2801
2901
|
assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
|
|
2802
2902
|
stats.getVersionDuration = trace.trace().duration;
|
|
2803
2903
|
|
|
@@ -2960,6 +3060,7 @@ const waitForSeq = async (
|
|
|
2960
3060
|
): Promise<void> => new Promise<void>((resolve, reject) => {
|
|
2961
3061
|
// TODO: remove cast to any when actual event is determined
|
|
2962
3062
|
deltaManager.on("closed" as any, reject);
|
|
3063
|
+
deltaManager.on("disposed" as any, reject);
|
|
2963
3064
|
|
|
2964
3065
|
// If we already reached target sequence number, simply resolve the promise.
|
|
2965
3066
|
if (deltaManager.lastSequenceNumber >= targetSeq) {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -61,7 +61,6 @@ import {
|
|
|
61
61
|
import {
|
|
62
62
|
addBlobToSummary,
|
|
63
63
|
convertSummaryTreeToITree,
|
|
64
|
-
packagePathToTelemetryProperty,
|
|
65
64
|
} from "@fluidframework/runtime-utils";
|
|
66
65
|
import {
|
|
67
66
|
ChildLogger,
|
|
@@ -88,6 +87,7 @@ import {
|
|
|
88
87
|
getFluidDataStoreAttributes,
|
|
89
88
|
} from "./summaryFormat";
|
|
90
89
|
import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
|
|
90
|
+
import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
|
|
91
91
|
import { summarizerClientType } from "./summarizerClientElection";
|
|
92
92
|
|
|
93
93
|
function createAttributes(
|
|
@@ -782,11 +782,11 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
782
782
|
|
|
783
783
|
// Always log an error when tombstoned data store is used. However, throw an error only if
|
|
784
784
|
// throwOnTombstoneUsage is set.
|
|
785
|
-
|
|
785
|
+
const event = {
|
|
786
786
|
eventName: "GC_Tombstone_DataStore_Changed",
|
|
787
787
|
callSite,
|
|
788
|
-
|
|
789
|
-
|
|
788
|
+
};
|
|
789
|
+
sendGCTombstoneEvent(this.mc, event, this.clientDetails.type === summarizerClientType, this.pkg, error);
|
|
790
790
|
// Always log an error when tombstoned data store is used. However, throw an error only if
|
|
791
791
|
// throwOnTombstoneUsage is set and the client is not a summarizer.
|
|
792
792
|
if (this.throwOnTombstoneUsage) {
|
|
@@ -799,7 +799,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
799
799
|
return (
|
|
800
800
|
summarizeInternal: SummarizeInternalFn,
|
|
801
801
|
getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
|
|
802
|
-
getBaseGCDetailsFn
|
|
802
|
+
getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
|
|
803
803
|
) => this.summarizerNode.createChild(
|
|
804
804
|
summarizeInternal,
|
|
805
805
|
id,
|
package/src/dataStores.ts
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
create404Response,
|
|
35
35
|
createResponseError,
|
|
36
36
|
responseToException,
|
|
37
|
-
packagePathToTelemetryProperty,
|
|
38
37
|
SummaryTreeBuilder,
|
|
39
38
|
} from "@fluidframework/runtime-utils";
|
|
40
39
|
import { ChildLogger, loggerToMonitoringContext, LoggingError, MonitoringContext, TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
@@ -42,7 +41,7 @@ import { AttachState } from "@fluidframework/container-definitions";
|
|
|
42
41
|
import { BlobCacheStorageService, buildSnapshotTree } from "@fluidframework/driver-utils";
|
|
43
42
|
import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
|
|
44
43
|
import { v4 as uuid } from "uuid";
|
|
45
|
-
import { GCDataBuilder, unpackChildNodesUsedRoutes } from "@fluidframework/garbage-collector";
|
|
44
|
+
import { GCDataBuilder, unpackChildNodesGCDetails, unpackChildNodesUsedRoutes } from "@fluidframework/garbage-collector";
|
|
46
45
|
import { DataStoreContexts } from "./dataStoreContexts";
|
|
47
46
|
import { ContainerRuntime } from "./containerRuntime";
|
|
48
47
|
import {
|
|
@@ -57,6 +56,7 @@ import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
|
|
|
57
56
|
import { GCNodeType } from "./garbageCollection";
|
|
58
57
|
import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
|
|
59
58
|
import { summarizerClientType } from "./summarizerClientElection";
|
|
59
|
+
import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
|
|
60
60
|
|
|
61
61
|
type PendingAliasResolve = (success: boolean) => void;
|
|
62
62
|
|
|
@@ -99,7 +99,7 @@ export class DataStores implements IDisposable {
|
|
|
99
99
|
(id: string, createParam: CreateChildSummarizerNodeParam) => CreateChildSummarizerNodeFn,
|
|
100
100
|
private readonly deleteChildSummarizerNodeFn: (id: string) => void,
|
|
101
101
|
baseLogger: ITelemetryBaseLogger,
|
|
102
|
-
getBaseGCDetails: () => Promise<
|
|
102
|
+
getBaseGCDetails: () => Promise<IGarbageCollectionDetailsBase>,
|
|
103
103
|
private readonly gcNodeUpdated: (
|
|
104
104
|
nodePath: string, timestampMs: number, packagePath?: readonly string[]) => void,
|
|
105
105
|
private readonly aliasMap: Map<string, string>,
|
|
@@ -109,7 +109,8 @@ export class DataStores implements IDisposable {
|
|
|
109
109
|
this.containerRuntimeHandle = new FluidObjectHandle(this.runtime, "/", this.runtime.IFluidHandleContext);
|
|
110
110
|
|
|
111
111
|
const baseGCDetailsP = new LazyPromise(async () => {
|
|
112
|
-
|
|
112
|
+
const baseGCDetails = await getBaseGCDetails();
|
|
113
|
+
return unpackChildNodesGCDetails(baseGCDetails);
|
|
113
114
|
});
|
|
114
115
|
// Returns the base GC details for the data store with the given id.
|
|
115
116
|
const dataStoreBaseGCDetails = async (dataStoreId: string) => {
|
|
@@ -441,12 +442,18 @@ export class DataStores implements IDisposable {
|
|
|
441
442
|
// The requested data store is removed by gc. Create a 404 gc response exception.
|
|
442
443
|
const error = responseToException(createResponseError(404, "Datastore removed by gc", request), request);
|
|
443
444
|
// Note: if a user writes a request to look like it's viaHandle, we will also send this telemetry event
|
|
444
|
-
|
|
445
|
+
const event = {
|
|
445
446
|
eventName: "GC_Tombstone_DataStore_Requested",
|
|
446
447
|
url: request.url,
|
|
447
|
-
pkg: packagePathToTelemetryProperty(context.isLoaded ? context.packagePath : undefined),
|
|
448
448
|
viaHandle,
|
|
449
|
-
}
|
|
449
|
+
};
|
|
450
|
+
sendGCTombstoneEvent(
|
|
451
|
+
this.mc,
|
|
452
|
+
event,
|
|
453
|
+
this.runtime.clientDetails.type === summarizerClientType,
|
|
454
|
+
context.isLoaded ? context.packagePath : undefined,
|
|
455
|
+
error,
|
|
456
|
+
);
|
|
450
457
|
// Always log an error when tombstoned data store is used. However, throw an error only if
|
|
451
458
|
// throwOnTombstoneUsage is set.
|
|
452
459
|
if (this.throwOnTombstoneUsage) {
|
|
@@ -624,11 +631,6 @@ export class DataStores implements IDisposable {
|
|
|
624
631
|
// Verify that the used routes are correct.
|
|
625
632
|
for (const [id] of usedDataStoreRoutes) {
|
|
626
633
|
assert(this.contexts.has(id), 0x167 /* "Used route does not belong to any known data store" */);
|
|
627
|
-
|
|
628
|
-
// Revive datastores regardless of whether or not tombstone the tombstone flag is flipped
|
|
629
|
-
const dataStore = this.contexts.get(id);
|
|
630
|
-
assert(dataStore !== undefined, 0x46e /* No data store retrieved with specified id */);
|
|
631
|
-
dataStore.setTombstone(false /* tombstone */);
|
|
632
634
|
}
|
|
633
635
|
|
|
634
636
|
// Update the used routes in each data store. Used routes is empty for unused data stores.
|
|
@@ -638,13 +640,10 @@ export class DataStores implements IDisposable {
|
|
|
638
640
|
}
|
|
639
641
|
|
|
640
642
|
/**
|
|
641
|
-
* This is called to update objects whose routes are unused. The unused objects are
|
|
642
|
-
* tombstones.
|
|
643
|
+
* This is called to update objects whose routes are unused. The unused objects are deleted.
|
|
643
644
|
* @param unusedRoutes - The routes that are unused in all data stores in this Container.
|
|
644
|
-
* @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
|
|
645
|
-
* are deleted.
|
|
646
645
|
*/
|
|
647
|
-
public updateUnusedRoutes(unusedRoutes: string[]
|
|
646
|
+
public updateUnusedRoutes(unusedRoutes: string[]) {
|
|
648
647
|
for (const route of unusedRoutes) {
|
|
649
648
|
const pathParts = route.split("/");
|
|
650
649
|
// Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
|
|
@@ -654,19 +653,6 @@ export class DataStores implements IDisposable {
|
|
|
654
653
|
}
|
|
655
654
|
const dataStoreId = pathParts[1];
|
|
656
655
|
assert(this.contexts.has(dataStoreId), 0x2d7 /* No data store with specified id */);
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* When running GC in tombstone mode, datastore contexts are tombstoned. Tombstoned datastore contexts
|
|
660
|
-
* enable testing scenarios with accessing deleted content without actually deleting content from
|
|
661
|
-
* summaries.
|
|
662
|
-
*/
|
|
663
|
-
if (tombstone) {
|
|
664
|
-
const dataStore = this.contexts.get(dataStoreId);
|
|
665
|
-
assert(dataStore !== undefined, 0x442 /* No data store retrieved with specified id */);
|
|
666
|
-
dataStore.setTombstone(true /* tombstone */);
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
656
|
// Delete the contexts of unused data stores.
|
|
671
657
|
this.contexts.delete(dataStoreId);
|
|
672
658
|
// Delete the summarizer node of the unused data stores.
|
|
@@ -674,6 +660,30 @@ export class DataStores implements IDisposable {
|
|
|
674
660
|
}
|
|
675
661
|
}
|
|
676
662
|
|
|
663
|
+
/**
|
|
664
|
+
* This is called to update objects whose routes are tombstones. Tombstoned datastore contexts enable testing
|
|
665
|
+
* scenarios with accessing deleted content without actually deleting content from summaries.
|
|
666
|
+
* @param tombstonedRoutes - The routes that are tombstones in all data stores in this Container.
|
|
667
|
+
*/
|
|
668
|
+
public updateTombstonedRoutes(tombstonedRoutes: string[]) {
|
|
669
|
+
const tombstonedDataStoresSet: Set<string> = new Set();
|
|
670
|
+
for (const route of tombstonedRoutes) {
|
|
671
|
+
const pathParts = route.split("/");
|
|
672
|
+
// Tombstone data store only if its route (/datastoreId) is directly in tombstoneRoutes.
|
|
673
|
+
if (pathParts.length > 2) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const dataStoreId = pathParts[1];
|
|
677
|
+
assert(this.contexts.has(dataStoreId), 0x510 /* No data store with specified id */);
|
|
678
|
+
tombstonedDataStoresSet.add(dataStoreId);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Update the used routes in each data store. Used routes is empty for unused data stores.
|
|
682
|
+
for (const [contextId, context] of this.contexts) {
|
|
683
|
+
context.setTombstone(tombstonedDataStoresSet.has(contextId));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
677
687
|
/**
|
|
678
688
|
* Returns the outbound routes of this channel. Only root data stores are considered referenced and their paths are
|
|
679
689
|
* part of outbound routes.
|