@fluidframework/container-runtime 2.51.0 → 2.52.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/CHANGELOG.md +10 -0
- package/api-report/container-runtime.legacy.alpha.api.md +1 -2
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +15 -7
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +72 -186
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/containerCompatibility.d.ts +34 -0
- package/dist/containerCompatibility.d.ts.map +1 -0
- package/dist/containerCompatibility.js +125 -0
- package/dist/containerCompatibility.js.map +1 -0
- package/dist/containerRuntime.d.ts +27 -15
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +175 -136
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +6 -6
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +3 -2
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +7 -1
- package/dist/metadata.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/storageServiceWithAttachBlobs.d.ts +40 -5
- package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -1
- package/dist/storageServiceWithAttachBlobs.js +56 -5
- package/dist/storageServiceWithAttachBlobs.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +1 -1
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +3 -3
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +15 -7
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +39 -153
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/containerCompatibility.d.ts +34 -0
- package/lib/containerCompatibility.d.ts.map +1 -0
- package/lib/containerCompatibility.js +120 -0
- package/lib/containerCompatibility.js.map +1 -0
- package/lib/containerRuntime.d.ts +27 -15
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +103 -64
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +6 -6
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/metadata.d.ts +3 -2
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +5 -0
- package/lib/metadata.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/storageServiceWithAttachBlobs.d.ts +40 -5
- package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -1
- package/lib/storageServiceWithAttachBlobs.js +56 -5
- package/lib/storageServiceWithAttachBlobs.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +1 -1
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +3 -3
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +20 -20
- package/src/blobManager/blobManager.ts +53 -195
- package/src/containerCompatibility.ts +176 -0
- package/src/containerRuntime.ts +157 -122
- package/src/dataStoreContext.ts +13 -5
- package/src/index.ts +6 -1
- package/src/metadata.ts +10 -2
- package/src/packageVersion.ts +1 -1
- package/src/storageServiceWithAttachBlobs.ts +92 -10
- package/src/summary/documentSchema.ts +1 -1
- package/src/summary/summaryFormat.ts +2 -2
- package/dist/compatUtils.d.ts +0 -106
- package/dist/compatUtils.d.ts.map +0 -1
- package/dist/compatUtils.js +0 -251
- package/dist/compatUtils.js.map +0 -1
- package/lib/compatUtils.d.ts +0 -106
- package/lib/compatUtils.d.ts.map +0 -1
- package/lib/compatUtils.js +0 -242
- package/lib/compatUtils.js.map +0 -1
- package/src/compatUtils.ts +0 -365
package/dist/containerRuntime.js
CHANGED
|
@@ -15,15 +15,16 @@ const internal_4 = require("@fluidframework/driver-utils/internal");
|
|
|
15
15
|
const internal_5 = require("@fluidframework/id-compressor/internal");
|
|
16
16
|
const internal_6 = require("@fluidframework/runtime-definitions/internal");
|
|
17
17
|
const internal_7 = require("@fluidframework/runtime-utils/internal");
|
|
18
|
-
const internal_8 = require("@fluidframework/
|
|
18
|
+
const internal_8 = require("@fluidframework/runtime-utils/internal");
|
|
19
|
+
const internal_9 = require("@fluidframework/telemetry-utils/internal");
|
|
19
20
|
const semver_ts_1 = require("semver-ts");
|
|
20
21
|
const uuid_1 = require("uuid");
|
|
21
22
|
const batchTracker_js_1 = require("./batchTracker.js");
|
|
22
23
|
const index_js_1 = require("./blobManager/index.js");
|
|
23
24
|
const channelCollection_js_1 = require("./channelCollection.js");
|
|
24
|
-
const compatUtils_js_1 = require("./compatUtils.js");
|
|
25
25
|
const compressionDefinitions_js_1 = require("./compressionDefinitions.js");
|
|
26
26
|
const connectionTelemetry_js_1 = require("./connectionTelemetry.js");
|
|
27
|
+
const containerCompatibility_js_1 = require("./containerCompatibility.js");
|
|
27
28
|
const containerHandleContext_js_1 = require("./containerHandleContext.js");
|
|
28
29
|
const dataStore_js_1 = require("./dataStore.js");
|
|
29
30
|
const dataStoreRegistry_js_1 = require("./dataStoreRegistry.js");
|
|
@@ -53,7 +54,7 @@ const throttler_js_1 = require("./throttler.js");
|
|
|
53
54
|
*
|
|
54
55
|
*/
|
|
55
56
|
function getUnknownMessageTypeError(unknownContainerRuntimeMessageType, codePath, sequencedMessage) {
|
|
56
|
-
return
|
|
57
|
+
return internal_9.DataProcessingError.create("Runtime message of unknown type", codePath, sequencedMessage, {
|
|
57
58
|
messageDetails: {
|
|
58
59
|
type: unknownContainerRuntimeMessageType,
|
|
59
60
|
},
|
|
@@ -186,6 +187,12 @@ let getSingleUseLegacyLogCallback = (logger, type) => {
|
|
|
186
187
|
};
|
|
187
188
|
};
|
|
188
189
|
exports.getSingleUseLegacyLogCallback = getSingleUseLegacyLogCallback;
|
|
190
|
+
/**
|
|
191
|
+
* Does nothing helper to apply unverified branding to a value.
|
|
192
|
+
*/
|
|
193
|
+
function markUnverified(value) {
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
189
196
|
/**
|
|
190
197
|
* This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
|
|
191
198
|
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
|
|
@@ -236,14 +243,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
236
243
|
* This object should provide all the functionality that the Container is expected to provide to the loader layer.
|
|
237
244
|
*/
|
|
238
245
|
static async loadRuntime(params) {
|
|
239
|
-
const { context, registryEntries, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab =
|
|
246
|
+
const { context, registryEntries, existing, requestHandler, provideEntryPoint, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, minVersionForCollab = internal_7.defaultMinVersionForCollab, } = params;
|
|
240
247
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
241
248
|
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
|
|
242
249
|
const backCompatContext = context;
|
|
243
250
|
const passLogger = backCompatContext.taggedLogger ??
|
|
244
251
|
// eslint-disable-next-line import/no-deprecated
|
|
245
|
-
new
|
|
246
|
-
const logger = (0,
|
|
252
|
+
new internal_9.TaggedLoggerAdapter(backCompatContext.logger);
|
|
253
|
+
const logger = (0, internal_9.createChildLogger)({
|
|
247
254
|
logger: passLogger,
|
|
248
255
|
properties: {
|
|
249
256
|
all: {
|
|
@@ -251,19 +258,19 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
251
258
|
},
|
|
252
259
|
},
|
|
253
260
|
});
|
|
254
|
-
const mc = (0,
|
|
261
|
+
const mc = (0, internal_9.loggerToMonitoringContext)(logger);
|
|
255
262
|
// Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
|
|
256
263
|
// based on the minVersionForCollab.
|
|
257
264
|
// For example, if minVersionForCollab is set to "1.0.0", the default configs will ensure compatibility with FF runtime
|
|
258
265
|
// 1.0.0 or later. If the minVersionForCollab is set to "2.10.0", the default values will be generated to ensure compatibility
|
|
259
266
|
// with FF runtime 2.10.0 or later.
|
|
260
|
-
if (!(0,
|
|
261
|
-
throw new
|
|
267
|
+
if (!(0, internal_7.isValidMinVersionForCollab)(minVersionForCollab)) {
|
|
268
|
+
throw new internal_9.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
|
|
262
269
|
}
|
|
263
270
|
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
264
271
|
// were manually set.
|
|
265
|
-
(0,
|
|
266
|
-
const defaultsAffectingDocSchema = (0,
|
|
272
|
+
(0, containerCompatibility_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
|
|
273
|
+
const defaultsAffectingDocSchema = (0, containerCompatibility_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
|
|
267
274
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
268
275
|
const defaultsNotAffectingDocSchema = {
|
|
269
276
|
summaryOptions: {},
|
|
@@ -326,7 +333,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
326
333
|
// Older runtimes do not understand new schema, and thus could corrupt document if they proceed, thus we are using
|
|
327
334
|
// this poison pill to prevent them from proceeding.
|
|
328
335
|
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
329
|
-
const error = new
|
|
336
|
+
const error = new internal_9.DataCorruptionError(
|
|
330
337
|
// pre-0.58 error message: SummaryMetadataMismatch
|
|
331
338
|
"Summary metadata mismatch", { runtimeVersion: packageVersion_js_1.pkgVersion, runtimeSequenceNumber, protocolSequenceNumber });
|
|
332
339
|
if (loadSequenceNumberVerification === "log") {
|
|
@@ -397,7 +404,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
397
404
|
},
|
|
398
405
|
};
|
|
399
406
|
})();
|
|
400
|
-
const compressorLogger = (0,
|
|
407
|
+
const compressorLogger = (0, internal_9.createSampledLogger)(logger, idCompressorEventSampler);
|
|
401
408
|
const pendingLocalState = context.pendingLocalState;
|
|
402
409
|
if (pendingLocalState?.pendingIdCompressorState !== undefined) {
|
|
403
410
|
return (0, internal_5.deserializeIdCompressor)(pendingLocalState.pendingIdCompressorState, compressorLogger);
|
|
@@ -428,7 +435,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
428
435
|
? minVersionForCollab
|
|
429
436
|
: existingMinVersionForCollab;
|
|
430
437
|
if (compressionLz4 && !enableGroupedBatching) {
|
|
431
|
-
throw new
|
|
438
|
+
throw new internal_9.UsageError("If compression is enabled, op grouping must be enabled too");
|
|
432
439
|
}
|
|
433
440
|
const featureGatesForTelemetry = {};
|
|
434
441
|
// Make sure we've got all the options including internal ones
|
|
@@ -447,13 +454,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
447
454
|
};
|
|
448
455
|
const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, updatedMinVersionForCollab, requestHandler, undefined, // summaryConfiguration
|
|
449
456
|
recentBatchInfo);
|
|
450
|
-
runtime.blobManager.stashedBlobsUploadP.then(() => {
|
|
451
|
-
// make sure we didn't reconnect before the promise resolved
|
|
452
|
-
if (runtime.delayConnectClientId !== undefined && !runtime.disposed) {
|
|
453
|
-
runtime.delayConnectClientId = undefined;
|
|
454
|
-
runtime.setConnectionStateCore(true, runtime.delayConnectClientId);
|
|
455
|
-
}
|
|
456
|
-
}, (error) => runtime.closeFn(error));
|
|
457
457
|
// Initialize the base state of the runtime before it's returned.
|
|
458
458
|
await runtime.initializeBaseState(context.loader);
|
|
459
459
|
// Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
|
|
@@ -597,7 +597,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
597
597
|
this.minVersionForCollab = minVersionForCollab;
|
|
598
598
|
this.requestHandler = requestHandler;
|
|
599
599
|
this.summaryConfiguration = summaryConfiguration;
|
|
600
|
-
this.imminentClosure = false;
|
|
601
600
|
this.isReadOnly = () => this.deltaManager.readOnlyInfo.readonly === true;
|
|
602
601
|
// We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
|
|
603
602
|
// Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
|
|
@@ -627,10 +626,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
627
626
|
// eslint-disable-next-line import/no-deprecated
|
|
628
627
|
this.enterStagingMode = () => {
|
|
629
628
|
if (this.stageControls !== undefined) {
|
|
630
|
-
throw new
|
|
629
|
+
throw new internal_9.UsageError("Already in staging mode");
|
|
631
630
|
}
|
|
632
631
|
if (this.attachState === container_definitions_1.AttachState.Detached) {
|
|
633
|
-
throw new
|
|
632
|
+
throw new internal_9.UsageError("Cannot enter staging mode while Detached");
|
|
634
633
|
}
|
|
635
634
|
// Make sure Outbox is empty before entering staging mode,
|
|
636
635
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
@@ -648,7 +647,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
648
647
|
this.channelCollection.notifyStagingMode(false);
|
|
649
648
|
}
|
|
650
649
|
catch (error) {
|
|
651
|
-
const normalizedError = (0,
|
|
650
|
+
const normalizedError = (0, internal_9.normalizeError)(error);
|
|
652
651
|
this.closeFn(normalizedError);
|
|
653
652
|
throw normalizedError;
|
|
654
653
|
}
|
|
@@ -684,17 +683,29 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
684
683
|
// It is lazily create to avoid listeners (old events) that ultimately go nowhere.
|
|
685
684
|
this.lazyEventsForExtensions = new internal_2.Lazy(() => {
|
|
686
685
|
const eventEmitter = (0, client_utils_1.createEmitter)();
|
|
687
|
-
this.
|
|
688
|
-
|
|
686
|
+
if (this.getConnectionState) {
|
|
687
|
+
this.on("connectedToService", (clientId, canWrite) => {
|
|
688
|
+
eventEmitter.emit("joined", { clientId, canWrite });
|
|
689
|
+
});
|
|
690
|
+
this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
|
|
691
|
+
this.on("connectionTypeChanged", (canWrite) => eventEmitter.emit("connectionTypeChanged", canWrite));
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.on("connected", (clientId) => {
|
|
695
|
+
eventEmitter.emit("joined", { clientId, canWrite: true });
|
|
696
|
+
});
|
|
697
|
+
this.on("disconnected", () => eventEmitter.emit("disconnected"));
|
|
698
|
+
}
|
|
689
699
|
return eventEmitter;
|
|
690
700
|
});
|
|
691
|
-
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
|
|
701
|
+
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, getConnectionState, } = context;
|
|
702
|
+
this.getConnectionState = getConnectionState;
|
|
692
703
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
693
704
|
this.disposeFn = disposeFn ?? closeFn;
|
|
694
705
|
// Validate that the Loader is compatible with this Runtime.
|
|
695
|
-
const
|
|
696
|
-
(0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(
|
|
697
|
-
this.mc = (0,
|
|
706
|
+
const maybeLoaderCompatDetailsForRuntime = context;
|
|
707
|
+
(0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
|
|
708
|
+
this.mc = (0, internal_9.createChildMonitoringContext)({
|
|
698
709
|
logger: this.baseLogger,
|
|
699
710
|
namespace: "ContainerRuntime",
|
|
700
711
|
properties: {
|
|
@@ -804,6 +815,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
804
815
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
805
816
|
// Later updates come through calls to setConnectionState.
|
|
806
817
|
this.canSendOps = connected;
|
|
818
|
+
this.canSendSignals = this.getConnectionState
|
|
819
|
+
? this.getConnectionState() === internal_1.ConnectionState.Connected
|
|
820
|
+
: undefined;
|
|
807
821
|
this.mc.logger.sendTelemetryEvent({
|
|
808
822
|
eventName: "GCFeatureMatrix",
|
|
809
823
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -852,7 +866,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
852
866
|
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
|
|
853
867
|
// If the context has ILayerCompatDetails, it supports referenceSequenceNumbers since that features
|
|
854
868
|
// predates ILayerCompatDetails.
|
|
855
|
-
const referenceSequenceNumbersSupported =
|
|
869
|
+
const referenceSequenceNumbersSupported = maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
|
|
856
870
|
? supportedFeatures?.get("referenceSequenceNumbers") === true
|
|
857
871
|
: true;
|
|
858
872
|
if (runtimeOptions.flushMode === internal_6.FlushModeExperimental.Async &&
|
|
@@ -867,7 +881,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
867
881
|
this.offlineEnabled =
|
|
868
882
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
|
|
869
883
|
if (this.offlineEnabled && this._flushMode !== internal_6.FlushMode.TurnBased) {
|
|
870
|
-
const error = new
|
|
884
|
+
const error = new internal_9.UsageError("Offline mode is only supported in turn-based mode");
|
|
871
885
|
this.closeFn(error);
|
|
872
886
|
throw error;
|
|
873
887
|
}
|
|
@@ -885,7 +899,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
885
899
|
// This is a runtime enforcement of what's already explicit in the policy's type itself,
|
|
886
900
|
// which dictates the value is either undefined or exactly 5 days in ms.
|
|
887
901
|
// As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
|
|
888
|
-
throw new
|
|
902
|
+
throw new internal_9.UsageError("Driver's maximumCacheDurationMs policy cannot exceed 5 days");
|
|
889
903
|
}
|
|
890
904
|
}
|
|
891
905
|
this.garbageCollector = index_js_2.GarbageCollector.create({
|
|
@@ -910,7 +924,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
910
924
|
const summaryReferenceSequenceNumber = baseSnapshot === undefined || metadata?.disableIsolatedChannels === true
|
|
911
925
|
? undefined
|
|
912
926
|
: loadedFromSequenceNumber;
|
|
913
|
-
this.summarizerNode = (0, index_js_4.createRootSummarizerNodeWithGC)((0,
|
|
927
|
+
this.summarizerNode = (0, index_js_4.createRootSummarizerNodeWithGC)((0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "SummarizerNode" }),
|
|
914
928
|
// Summarize function to call when summarize is called. Summarizer node always tracks summary state.
|
|
915
929
|
async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
|
|
916
930
|
// Latest change sequence number, no changes since summary applied yet
|
|
@@ -973,8 +987,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
973
987
|
stashedBlobs: pendingRuntimeState?.pendingAttachmentBlobs,
|
|
974
988
|
createBlobPayloadPending: this.sessionSchema.createBlobPayloadPending === true,
|
|
975
989
|
});
|
|
976
|
-
this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0,
|
|
977
|
-
this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0,
|
|
990
|
+
this.deltaScheduler = new deltaScheduler_js_1.DeltaScheduler(this.innerDeltaManager, this, (0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
|
|
991
|
+
this.inboundBatchAggregator = new inboundBatchAggregator_js_1.InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, (0, internal_9.createChildLogger)({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
|
|
978
992
|
const legacySendBatchFn = (0, exports.makeLegacySendBatchFn)(submitFn, this.innerDeltaManager);
|
|
979
993
|
this.skipSafetyFlushDuringProcessStack =
|
|
980
994
|
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
@@ -1118,7 +1132,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1118
1132
|
async initializeBaseState(loader) {
|
|
1119
1133
|
if (this.sessionSchema.idCompressorMode === "on" ||
|
|
1120
1134
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
|
|
1121
|
-
|
|
1135
|
+
internal_9.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
|
|
1122
1136
|
this._idCompressor = this.createIdCompressorFn();
|
|
1123
1137
|
event.end({
|
|
1124
1138
|
details: {
|
|
@@ -1148,7 +1162,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1148
1162
|
this.summaryConfiguration.initialSummarizerDelayMs,
|
|
1149
1163
|
};
|
|
1150
1164
|
const summaryCollection = new index_js_4.SummaryCollection(this.deltaManager, this.baseLogger);
|
|
1151
|
-
const orderedClientLogger = (0,
|
|
1165
|
+
const orderedClientLogger = (0, internal_9.createChildLogger)({
|
|
1152
1166
|
logger: this.baseLogger,
|
|
1153
1167
|
namespace: "OrderedClientElection",
|
|
1154
1168
|
});
|
|
@@ -1272,7 +1286,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1272
1286
|
// Snapshots should only move forward. If we observe an older snapshot than the one we loaded from, then likely
|
|
1273
1287
|
// the file has been overwritten or service lost data.
|
|
1274
1288
|
if (snapshotSeqNumber < this.deltaManager.initialSequenceNumber) {
|
|
1275
|
-
throw
|
|
1289
|
+
throw internal_9.DataProcessingError.create("Downloaded snapshot older than snapshot we loaded from", "getSnapshotForLoadingGroupId", undefined, {
|
|
1276
1290
|
loadingGroupIds: sortedLoadingGroupIds.join(","),
|
|
1277
1291
|
snapshotSeqNumber,
|
|
1278
1292
|
initialSequenceNumber: this.deltaManager.initialSequenceNumber,
|
|
@@ -1295,7 +1309,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1295
1309
|
targetSequenceNumber: snapshotSeqNumber, // This is so we reuse some columns in telemetry
|
|
1296
1310
|
sequenceNumber: this.deltaManager.lastSequenceNumber, // This is so we reuse some columns in telemetry
|
|
1297
1311
|
};
|
|
1298
|
-
const event =
|
|
1312
|
+
const event = internal_9.PerformanceEvent.start(this.mc.logger, {
|
|
1299
1313
|
...props,
|
|
1300
1314
|
});
|
|
1301
1315
|
// If the inbound deltas queue is paused or disconnected, we expect a reconnect and unpause
|
|
@@ -1340,7 +1354,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1340
1354
|
// @ts-expect-error expected to be used by LTS Loaders and Containers
|
|
1341
1355
|
async request(request) {
|
|
1342
1356
|
try {
|
|
1343
|
-
const parser =
|
|
1357
|
+
const parser = internal_8.RequestParser.create(request);
|
|
1344
1358
|
const id = parser.pathParts[0];
|
|
1345
1359
|
if (id === index_js_4.summarizerRequestUrl && parser.pathParts.length === 1) {
|
|
1346
1360
|
if (this._summarizer !== undefined) {
|
|
@@ -1350,16 +1364,16 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1350
1364
|
value: this._summarizer,
|
|
1351
1365
|
};
|
|
1352
1366
|
}
|
|
1353
|
-
return (0,
|
|
1367
|
+
return (0, internal_8.create404Response)(request);
|
|
1354
1368
|
}
|
|
1355
1369
|
if (this.requestHandler !== undefined) {
|
|
1356
1370
|
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1357
1371
|
return this.requestHandler(parser, this);
|
|
1358
1372
|
}
|
|
1359
|
-
return (0,
|
|
1373
|
+
return (0, internal_8.create404Response)(request);
|
|
1360
1374
|
}
|
|
1361
1375
|
catch (error) {
|
|
1362
|
-
return (0,
|
|
1376
|
+
return (0, internal_8.exceptionToResponse)(error);
|
|
1363
1377
|
}
|
|
1364
1378
|
}
|
|
1365
1379
|
/**
|
|
@@ -1368,7 +1382,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1368
1382
|
*/
|
|
1369
1383
|
async resolveHandle(request) {
|
|
1370
1384
|
try {
|
|
1371
|
-
const requestParser =
|
|
1385
|
+
const requestParser = internal_8.RequestParser.create(request);
|
|
1372
1386
|
const id = requestParser.pathParts[0];
|
|
1373
1387
|
if (id === "_channels") {
|
|
1374
1388
|
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
@@ -1376,10 +1390,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1376
1390
|
}
|
|
1377
1391
|
if (id === index_js_1.blobManagerBasePath && requestParser.isLeaf(2)) {
|
|
1378
1392
|
const localId = requestParser.pathParts[1];
|
|
1379
|
-
const payloadPending = requestParser.headers?.[
|
|
1393
|
+
const payloadPending = requestParser.headers?.[internal_8.RuntimeHeaders.payloadPending] === true;
|
|
1380
1394
|
if (!this.blobManager.hasBlob(localId) &&
|
|
1381
|
-
requestParser.headers?.[
|
|
1382
|
-
return (0,
|
|
1395
|
+
requestParser.headers?.[internal_8.RuntimeHeaders.wait] === false) {
|
|
1396
|
+
return (0, internal_8.create404Response)(request);
|
|
1383
1397
|
}
|
|
1384
1398
|
const blob = await this.blobManager.getBlob(localId, payloadPending);
|
|
1385
1399
|
return {
|
|
@@ -1391,10 +1405,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1391
1405
|
else if (requestParser.pathParts.length > 0) {
|
|
1392
1406
|
return await this.channelCollection.request(request);
|
|
1393
1407
|
}
|
|
1394
|
-
return (0,
|
|
1408
|
+
return (0, internal_8.create404Response)(request);
|
|
1395
1409
|
}
|
|
1396
1410
|
catch (error) {
|
|
1397
|
-
return (0,
|
|
1411
|
+
return (0, internal_8.exceptionToResponse)(error);
|
|
1398
1412
|
}
|
|
1399
1413
|
}
|
|
1400
1414
|
/**
|
|
@@ -1435,39 +1449,39 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1435
1449
|
lastMessage: explicitSchemaControl ? message : undefined,
|
|
1436
1450
|
documentSchema,
|
|
1437
1451
|
};
|
|
1438
|
-
(0,
|
|
1452
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.metadataBlobName, JSON.stringify(metadata));
|
|
1439
1453
|
}
|
|
1440
1454
|
addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
|
|
1441
1455
|
this.addMetadataToSummary(summaryTree);
|
|
1442
1456
|
if (this._idCompressor) {
|
|
1443
1457
|
const idCompressorState = JSON.stringify(this._idCompressor.serialize(false));
|
|
1444
|
-
(0,
|
|
1458
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.idCompressorBlobName, idCompressorState);
|
|
1445
1459
|
}
|
|
1446
1460
|
if (this.remoteMessageProcessor.partialMessages.size > 0) {
|
|
1447
1461
|
const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
|
|
1448
|
-
(0,
|
|
1462
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.chunksBlobName, content);
|
|
1449
1463
|
}
|
|
1450
1464
|
const recentBatchInfo = this.duplicateBatchDetector?.getRecentBatchInfoForSummary(telemetryContext);
|
|
1451
1465
|
if (recentBatchInfo !== undefined) {
|
|
1452
|
-
(0,
|
|
1466
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.recentBatchInfoBlobName, JSON.stringify(recentBatchInfo));
|
|
1453
1467
|
}
|
|
1454
1468
|
const dataStoreAliases = this.channelCollection.aliases;
|
|
1455
1469
|
if (dataStoreAliases.size > 0) {
|
|
1456
|
-
(0,
|
|
1470
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.aliasBlobName, JSON.stringify([...dataStoreAliases]));
|
|
1457
1471
|
}
|
|
1458
1472
|
if (this.summarizerClientElection) {
|
|
1459
1473
|
const electedSummarizerContent = JSON.stringify(this.summarizerClientElection?.serialize());
|
|
1460
|
-
(0,
|
|
1474
|
+
(0, internal_8.addBlobToSummary)(summaryTree, index_js_4.electedSummarizerBlobName, electedSummarizerContent);
|
|
1461
1475
|
}
|
|
1462
1476
|
const blobManagerSummary = this.blobManager.summarize();
|
|
1463
1477
|
// Some storage (like git) doesn't allow empty tree, so we can omit it.
|
|
1464
1478
|
// and the blob manager can handle the tree not existing when loading
|
|
1465
1479
|
if (Object.keys(blobManagerSummary.summary.tree).length > 0) {
|
|
1466
|
-
(0,
|
|
1480
|
+
(0, internal_8.addSummarizeResultToSummary)(summaryTree, index_js_1.blobsTreeName, blobManagerSummary);
|
|
1467
1481
|
}
|
|
1468
1482
|
const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
|
|
1469
1483
|
if (gcSummary !== undefined) {
|
|
1470
|
-
(0,
|
|
1484
|
+
(0, internal_8.addSummarizeResultToSummary)(summaryTree, internal_6.gcTreeKey, gcSummary);
|
|
1471
1485
|
}
|
|
1472
1486
|
}
|
|
1473
1487
|
// Track how many times the container tries to reconnect with pending messages.
|
|
@@ -1573,7 +1587,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1573
1587
|
}
|
|
1574
1588
|
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
1575
1589
|
// GC op is only sent in summarizer which should never have stashed ops.
|
|
1576
|
-
throw new
|
|
1590
|
+
throw new internal_9.LoggingError("GC op not expected to be stashed in summarizer");
|
|
1577
1591
|
}
|
|
1578
1592
|
default: {
|
|
1579
1593
|
const error = getUnknownMessageTypeError(opContents.type, "applyStashedOp" /* codePath */);
|
|
@@ -1585,7 +1599,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1585
1599
|
loadIdCompressor() {
|
|
1586
1600
|
if (this._idCompressor === undefined &&
|
|
1587
1601
|
this.sessionSchema.idCompressorMode !== undefined) {
|
|
1588
|
-
|
|
1602
|
+
internal_9.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
|
|
1589
1603
|
this._idCompressor = this.createIdCompressorFn();
|
|
1590
1604
|
// Finalize any ranges we received while the compressor was turned off.
|
|
1591
1605
|
const ops = this.pendingIdCompressorOps;
|
|
@@ -1613,24 +1627,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1613
1627
|
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
1614
1628
|
this.loadIdCompressor();
|
|
1615
1629
|
}
|
|
1616
|
-
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
1617
|
-
this.delayConnectClientId = undefined;
|
|
1618
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1619
|
-
eventName: "UnsuccessfulConnectedTransition",
|
|
1620
|
-
});
|
|
1621
|
-
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
// If there are stashed blobs in the pending state, we need to delay
|
|
1625
|
-
// propagation of the "connected" event until we have uploaded them to
|
|
1626
|
-
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1627
|
-
const connecting = canSendOps && !this.canSendOps;
|
|
1628
|
-
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
1629
|
-
(0, internal_2.assert)(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
|
|
1630
|
-
(0, internal_2.assert)(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
1631
|
-
this.delayConnectClientId = clientId;
|
|
1632
|
-
return;
|
|
1633
|
-
}
|
|
1634
1630
|
this.setConnectionStateCore(canSendOps, clientId);
|
|
1635
1631
|
}
|
|
1636
1632
|
/**
|
|
@@ -1639,7 +1635,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1639
1635
|
* @remarks The connection state from container context used here when raising connected events.
|
|
1640
1636
|
*/
|
|
1641
1637
|
setConnectionStateCore(canSendOps, clientId) {
|
|
1642
|
-
(0, internal_2.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1643
1638
|
this.verifyNotClosed();
|
|
1644
1639
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1645
1640
|
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
@@ -1661,7 +1656,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1661
1656
|
if (reconnection) {
|
|
1662
1657
|
this.consecutiveReconnects++;
|
|
1663
1658
|
if (!this.shouldContinueReconnecting()) {
|
|
1664
|
-
this.closeFn(
|
|
1659
|
+
this.closeFn(internal_9.DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops.", "setConnectionState", undefined, {
|
|
1665
1660
|
dataLoss: 1,
|
|
1666
1661
|
attempts: this.consecutiveReconnects,
|
|
1667
1662
|
pendingMessages: this.pendingMessagesCount,
|
|
@@ -1674,7 +1669,40 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1674
1669
|
}
|
|
1675
1670
|
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
1676
1671
|
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
1677
|
-
|
|
1672
|
+
// Emit "connected" and "disconnected" events based on ability to send ops
|
|
1673
|
+
(0, internal_9.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
1674
|
+
// Emit "connectedToService" and "disconnectedFromService" events based on service connection status
|
|
1675
|
+
this.emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId);
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Emits service connection events based on connection state changes.
|
|
1679
|
+
*
|
|
1680
|
+
* @remarks
|
|
1681
|
+
* "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
|
|
1682
|
+
* "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
|
|
1683
|
+
*/
|
|
1684
|
+
emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId) {
|
|
1685
|
+
if (!this.getConnectionState) {
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const canSendSignals = this.getConnectionState() === internal_1.ConnectionState.Connected;
|
|
1689
|
+
const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
|
|
1690
|
+
this.canSendSignals = canSendSignals;
|
|
1691
|
+
if (canSendSignalsChanged) {
|
|
1692
|
+
// If canSendSignals changed, we either transitioned from Connected to Disconnected or CatchingUp to Connected
|
|
1693
|
+
if (canSendSignals) {
|
|
1694
|
+
// Emit for CatchingUp to Connected transition
|
|
1695
|
+
this.emit("connectedToService", clientId, canSendOps);
|
|
1696
|
+
}
|
|
1697
|
+
else {
|
|
1698
|
+
// Emit for Connected to Disconnected transition
|
|
1699
|
+
this.emit("disconnectedFromService");
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
else if (canSendOpsChanged) {
|
|
1703
|
+
// If canSendSignals did not change but canSendOps did, then connection type has changed.
|
|
1704
|
+
this.emit("connectionTypeChanged", canSendOps);
|
|
1705
|
+
}
|
|
1678
1706
|
}
|
|
1679
1707
|
async notifyOpReplay(message) {
|
|
1680
1708
|
await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
|
|
@@ -1727,7 +1755,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1727
1755
|
const batchStart = inboundResult.batchStart;
|
|
1728
1756
|
const result = this.duplicateBatchDetector?.processInboundBatch(batchStart);
|
|
1729
1757
|
if (result?.duplicate) {
|
|
1730
|
-
const error = new
|
|
1758
|
+
const error = new internal_9.DataCorruptionError("Duplicate batch - The same batch was sequenced twice", { batchId: batchStart.batchId });
|
|
1731
1759
|
this.mc.logger.sendTelemetryEvent({
|
|
1732
1760
|
eventName: "DuplicateBatch",
|
|
1733
1761
|
details: {
|
|
@@ -1736,7 +1764,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1736
1764
|
batchStartCsn: batchStart.batchStartCsn,
|
|
1737
1765
|
size: inboundResult.length,
|
|
1738
1766
|
duplicateBatchSequenceNumber: result.otherSequenceNumber,
|
|
1739
|
-
...(0,
|
|
1767
|
+
...(0, internal_9.extractSafePropertiesFromMessage)(batchStart.keyMessage),
|
|
1740
1768
|
},
|
|
1741
1769
|
}, error);
|
|
1742
1770
|
throw error;
|
|
@@ -1984,12 +2012,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1984
2012
|
}
|
|
1985
2013
|
processSignal(message, local) {
|
|
1986
2014
|
const envelope = message.content;
|
|
1987
|
-
const transformed = {
|
|
2015
|
+
const transformed = markUnverified({
|
|
1988
2016
|
clientId: message.clientId,
|
|
1989
2017
|
content: envelope.contents.content,
|
|
1990
2018
|
type: envelope.contents.type,
|
|
1991
2019
|
targetClientId: message.targetClientId,
|
|
1992
|
-
};
|
|
2020
|
+
});
|
|
1993
2021
|
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
1994
2022
|
if (message.clientId === this.clientId) {
|
|
1995
2023
|
this.signalTelemetryManager.trackReceivedSignal(envelope, this.mc.logger, this.consecutiveReconnects);
|
|
@@ -2008,12 +2036,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2008
2036
|
// Due to a mismatch between different layers in terms of
|
|
2009
2037
|
// what is the interface of passing signals, we need to adjust
|
|
2010
2038
|
// the signal envelope before sending it to the datastores to be processed
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2039
|
+
const channelSignalMessage = {
|
|
2040
|
+
...signalMessage,
|
|
2041
|
+
content: {
|
|
2042
|
+
address,
|
|
2043
|
+
contents: signalMessage.content,
|
|
2044
|
+
},
|
|
2014
2045
|
};
|
|
2015
|
-
|
|
2016
|
-
this.channelCollection.processSignal(signalMessage, local);
|
|
2046
|
+
this.channelCollection.processSignal(channelSignalMessage, local);
|
|
2017
2047
|
return;
|
|
2018
2048
|
}
|
|
2019
2049
|
const addresses = address.split("/");
|
|
@@ -2028,7 +2058,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2028
2058
|
(0, internal_2.assert)(!local, 0xba0 /* No recipient found for local signal */);
|
|
2029
2059
|
this.mc.logger.sendTelemetryEvent({
|
|
2030
2060
|
eventName: "SignalAddressNotFound",
|
|
2031
|
-
...(0,
|
|
2061
|
+
...(0, internal_9.tagCodeArtifacts)({
|
|
2032
2062
|
address,
|
|
2033
2063
|
}),
|
|
2034
2064
|
});
|
|
@@ -2048,7 +2078,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2048
2078
|
(0, internal_2.assert)(this.outbox.isEmpty, 0x3cf /* reentrancy */);
|
|
2049
2079
|
}
|
|
2050
2080
|
catch (error) {
|
|
2051
|
-
const error2 = (0,
|
|
2081
|
+
const error2 = (0, internal_9.normalizeError)(error, {
|
|
2052
2082
|
props: {
|
|
2053
2083
|
orderSequentiallyCalls: this.batchRunner.runs,
|
|
2054
2084
|
},
|
|
@@ -2089,15 +2119,15 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2089
2119
|
stageControls = undefined;
|
|
2090
2120
|
}
|
|
2091
2121
|
catch (error_) {
|
|
2092
|
-
const error2 = (0,
|
|
2093
|
-
return
|
|
2122
|
+
const error2 = (0, internal_9.wrapError)(error_, (message) => {
|
|
2123
|
+
return internal_9.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
|
|
2094
2124
|
});
|
|
2095
2125
|
this.closeFn(error2);
|
|
2096
2126
|
throw error2;
|
|
2097
2127
|
}
|
|
2098
2128
|
}
|
|
2099
2129
|
else {
|
|
2100
|
-
this.closeFn((0,
|
|
2130
|
+
this.closeFn((0, internal_9.wrapError)(error, (errorMessage) => new internal_9.GenericError(`orderSequentially callback exception: ${errorMessage}`, error, {
|
|
2101
2131
|
orderSequentiallyCalls: this.batchRunner.runs,
|
|
2102
2132
|
})));
|
|
2103
2133
|
}
|
|
@@ -2143,7 +2173,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2143
2173
|
}
|
|
2144
2174
|
const channel = await context.realize();
|
|
2145
2175
|
if (channel.entryPoint === undefined) {
|
|
2146
|
-
throw new
|
|
2176
|
+
throw new internal_9.UsageError("entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint");
|
|
2147
2177
|
}
|
|
2148
2178
|
this.garbageCollector.nodeUpdated({
|
|
2149
2179
|
node: { type: "DataStore", path: `/${internalId}` },
|
|
@@ -2163,7 +2193,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2163
2193
|
shouldSendOps() {
|
|
2164
2194
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
2165
2195
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2166
|
-
return
|
|
2196
|
+
return this.connected && !this.innerDeltaManager.readOnlyInfo.readonly;
|
|
2167
2197
|
}
|
|
2168
2198
|
getQuorum() {
|
|
2169
2199
|
return this._quorum;
|
|
@@ -2265,7 +2295,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2265
2295
|
async summarize(options) {
|
|
2266
2296
|
this.verifyNotClosed();
|
|
2267
2297
|
const { fullTree = false, trackState = true, summaryLogger = this.mc.logger, runGC = this.garbageCollector.shouldRunGC, runSweep, fullGC, } = options;
|
|
2268
|
-
const telemetryContext = new
|
|
2298
|
+
const telemetryContext = new internal_8.TelemetryContext();
|
|
2269
2299
|
// Add the options that are used to generate this summary to the telemetry context.
|
|
2270
2300
|
telemetryContext.setMultiple("fluid_Summarize", "Options", {
|
|
2271
2301
|
fullTree,
|
|
@@ -2298,7 +2328,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2298
2328
|
* @see IGarbageCollectionRuntime.getGCData
|
|
2299
2329
|
*/
|
|
2300
2330
|
async getGCData(fullGC) {
|
|
2301
|
-
const builder = new
|
|
2331
|
+
const builder = new internal_8.GCDataBuilder();
|
|
2302
2332
|
const dsGCData = await this.summarizerNode.getGCData(fullGC);
|
|
2303
2333
|
builder.addNodes(dsGCData.gcNodes);
|
|
2304
2334
|
const blobsGCData = this.blobManager.getGCData(fullGC);
|
|
@@ -2425,7 +2455,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2425
2455
|
if (timestampMs === undefined) {
|
|
2426
2456
|
this.mc.logger.sendTelemetryEvent({
|
|
2427
2457
|
eventName: "NoTimestampInGCOutboundRoute",
|
|
2428
|
-
...(0,
|
|
2458
|
+
...(0, internal_9.tagCodeArtifacts)({
|
|
2429
2459
|
id: toPath,
|
|
2430
2460
|
fromId: fromPath,
|
|
2431
2461
|
}),
|
|
@@ -2448,7 +2478,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2448
2478
|
// use it for all events logged during this summary.
|
|
2449
2479
|
const summaryNumber = this.nextSummaryNumber;
|
|
2450
2480
|
let summaryRefSeqNum;
|
|
2451
|
-
const summaryNumberLogger = (0,
|
|
2481
|
+
const summaryNumberLogger = (0, internal_9.createChildLogger)({
|
|
2452
2482
|
logger: summaryLogger,
|
|
2453
2483
|
properties: {
|
|
2454
2484
|
all: {
|
|
@@ -2459,7 +2489,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2459
2489
|
});
|
|
2460
2490
|
// legacy: assert 0x3d1
|
|
2461
2491
|
if (!this.outbox.isEmpty) {
|
|
2462
|
-
throw
|
|
2492
|
+
throw internal_9.DataProcessingError.create("Can't trigger summary in the middle of a batch", "submitSummary", undefined, {
|
|
2463
2493
|
summaryNumber,
|
|
2464
2494
|
pendingMessages: this.pendingMessagesCount,
|
|
2465
2495
|
outboxLength: this.outbox.messageCount,
|
|
@@ -2596,7 +2626,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2596
2626
|
stage: "base",
|
|
2597
2627
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
2598
2628
|
minimumSequenceNumber,
|
|
2599
|
-
error: (0,
|
|
2629
|
+
error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
|
|
2600
2630
|
};
|
|
2601
2631
|
}
|
|
2602
2632
|
// Validate that the summary generated by summarizer nodes is correct before uploading.
|
|
@@ -2627,7 +2657,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2627
2657
|
(0, internal_2.assert)(dataStoreTree.type === driver_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
2628
2658
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === driver_definitions_1.SummaryType.Handle).length;
|
|
2629
2659
|
const gcSummaryTreeStats = summaryTree.tree[internal_6.gcTreeKey]
|
|
2630
|
-
? (0,
|
|
2660
|
+
? (0, internal_8.calculateStats)(summaryTree.tree[internal_6.gcTreeKey])
|
|
2631
2661
|
: undefined;
|
|
2632
2662
|
const summaryStats = {
|
|
2633
2663
|
dataStoreCount: this.channelCollection.size,
|
|
@@ -2666,7 +2696,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2666
2696
|
return {
|
|
2667
2697
|
stage: "generate",
|
|
2668
2698
|
...generateSummaryData,
|
|
2669
|
-
error: (0,
|
|
2699
|
+
error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
|
|
2670
2700
|
};
|
|
2671
2701
|
}
|
|
2672
2702
|
const parent = summaryContext.ackHandle;
|
|
@@ -2698,7 +2728,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2698
2728
|
return {
|
|
2699
2729
|
stage: "upload",
|
|
2700
2730
|
...uploadData,
|
|
2701
|
-
error: (0,
|
|
2731
|
+
error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
|
|
2702
2732
|
};
|
|
2703
2733
|
}
|
|
2704
2734
|
const submitData = {
|
|
@@ -2714,7 +2744,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2714
2744
|
return {
|
|
2715
2745
|
stage: "upload",
|
|
2716
2746
|
...uploadData,
|
|
2717
|
-
error: (0,
|
|
2747
|
+
error: (0, internal_9.wrapError)(error, (msg) => new index_js_4.RetriableSummaryError(msg)),
|
|
2718
2748
|
};
|
|
2719
2749
|
}
|
|
2720
2750
|
return submitData;
|
|
@@ -2750,7 +2780,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2750
2780
|
// the summarizer.
|
|
2751
2781
|
if (finalAttempt &&
|
|
2752
2782
|
this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary")) {
|
|
2753
|
-
const error =
|
|
2783
|
+
const error = internal_9.DataProcessingError.create("Pending ops during summarization", "submitSummary", undefined, { pendingMessages: this.pendingMessagesCount });
|
|
2754
2784
|
logger.sendErrorEvent({
|
|
2755
2785
|
eventName: "SkipFailingIncorrectSummary",
|
|
2756
2786
|
referenceSequenceNumber,
|
|
@@ -2897,7 +2927,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2897
2927
|
this.scheduleFlush();
|
|
2898
2928
|
}
|
|
2899
2929
|
catch (error) {
|
|
2900
|
-
const dpe =
|
|
2930
|
+
const dpe = internal_9.DataProcessingError.wrapIfUnrecognized(error, "ContainerRuntime.submit", {
|
|
2901
2931
|
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
2902
2932
|
});
|
|
2903
2933
|
this.closeFn(dpe);
|
|
@@ -3142,7 +3172,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3142
3172
|
* happen in scenarios where the snapshot for the ack was lost in storage in scenarios like DB rollback, etc.
|
|
3143
3173
|
*/
|
|
3144
3174
|
async fetchLatestSnapshotAndMaybeClose(targetRefSeq, targetAckHandle, logger) {
|
|
3145
|
-
const fetchedSnapshotRefSeq = await
|
|
3175
|
+
const fetchedSnapshotRefSeq = await internal_9.PerformanceEvent.timedExecAsync(logger, { eventName: "RefreshLatestSummaryAckFetch" }, async (perfEvent) => {
|
|
3146
3176
|
const props = { targetRefSeq, targetAckHandle };
|
|
3147
3177
|
const trace = client_utils_1.Trace.start();
|
|
3148
3178
|
let snapshotTree;
|
|
@@ -3173,7 +3203,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3173
3203
|
props.snapshotVersion = versions[0].id;
|
|
3174
3204
|
}
|
|
3175
3205
|
props.getSnapshotDuration = trace.trace().duration;
|
|
3176
|
-
const snapshotRefSeq = await (0,
|
|
3206
|
+
const snapshotRefSeq = await (0, internal_8.seqFromTree)(snapshotTree, this.readAndParseBlob);
|
|
3177
3207
|
props.snapshotRefSeq = snapshotRefSeq;
|
|
3178
3208
|
props.newerSnapshotPresent = snapshotRefSeq >= targetRefSeq;
|
|
3179
3209
|
perfEvent.end({ details: props });
|
|
@@ -3191,39 +3221,35 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3191
3221
|
}
|
|
3192
3222
|
getPendingLocalState(props) {
|
|
3193
3223
|
this.verifyNotClosed();
|
|
3224
|
+
if (props?.notifyImminentClosure) {
|
|
3225
|
+
throw new internal_9.UsageError("notifyImminentClosure is no longer supported in ContainerRuntime");
|
|
3226
|
+
}
|
|
3194
3227
|
if (this.batchRunner.running) {
|
|
3195
|
-
throw new
|
|
3228
|
+
throw new internal_9.UsageError("can't get state while manually accumulating a batch");
|
|
3196
3229
|
}
|
|
3197
|
-
|
|
3198
|
-
|
|
3230
|
+
// Flush pending batch.
|
|
3231
|
+
// getPendingLocalState() is only exposed through Container.getPendingLocalState(), so it's safe
|
|
3232
|
+
// to close current batch.
|
|
3233
|
+
this.flush();
|
|
3234
|
+
return internal_9.PerformanceEvent.timedExec(this.mc.logger, {
|
|
3235
|
+
eventName: "getPendingLocalState",
|
|
3236
|
+
}, (event) => {
|
|
3199
3237
|
const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
3200
3238
|
const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
3201
3239
|
const pendingIdCompressorState = this._idCompressor?.serialize(true);
|
|
3202
|
-
|
|
3240
|
+
const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
|
|
3241
|
+
const pendingRuntimeState = {
|
|
3203
3242
|
pending,
|
|
3204
3243
|
pendingIdCompressorState,
|
|
3205
3244
|
pendingAttachmentBlobs,
|
|
3206
3245
|
sessionExpiryTimerStarted,
|
|
3207
3246
|
};
|
|
3208
|
-
};
|
|
3209
|
-
const perfEvent = {
|
|
3210
|
-
eventName: "getPendingLocalState",
|
|
3211
|
-
notifyImminentClosure: props?.notifyImminentClosure,
|
|
3212
|
-
};
|
|
3213
|
-
const logAndReturnPendingState = (event, pendingState) => {
|
|
3214
3247
|
event.end({
|
|
3215
|
-
attachmentBlobsSize: Object.keys(
|
|
3216
|
-
pendingOpsSize:
|
|
3248
|
+
attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
|
|
3249
|
+
pendingOpsSize: pendingRuntimeState?.pending?.pendingStates.length,
|
|
3217
3250
|
});
|
|
3218
|
-
return
|
|
3219
|
-
};
|
|
3220
|
-
// Flush pending batch.
|
|
3221
|
-
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
3222
|
-
// to close current batch.
|
|
3223
|
-
this.flush();
|
|
3224
|
-
return props?.notifyImminentClosure === true
|
|
3225
|
-
? internal_8.PerformanceEvent.timedExecAsync(this.mc.logger, perfEvent, async (event) => logAndReturnPendingState(event, getSyncState(await this.blobManager.attachAndGetPendingBlobs(props?.stopBlobAttachingSignal))))
|
|
3226
|
-
: internal_8.PerformanceEvent.timedExec(this.mc.logger, perfEvent, (event) => logAndReturnPendingState(event, getSyncState()));
|
|
3251
|
+
return pendingRuntimeState;
|
|
3252
|
+
});
|
|
3227
3253
|
}
|
|
3228
3254
|
summarizeOnDemand(options) {
|
|
3229
3255
|
if (this._summarizer !== undefined) {
|
|
@@ -3233,7 +3259,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3233
3259
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
3234
3260
|
// disableSummaries is turned on. We are throwing instead of returning a failure here,
|
|
3235
3261
|
// because it is a misuse of the API rather than an expected failure.
|
|
3236
|
-
throw new
|
|
3262
|
+
throw new internal_9.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
|
|
3237
3263
|
}
|
|
3238
3264
|
else {
|
|
3239
3265
|
return this.summaryManager.summarizeOnDemand(options);
|
|
@@ -3247,17 +3273,30 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3247
3273
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
3248
3274
|
// generateSummaries is turned off. We are throwing instead of returning a failure here,
|
|
3249
3275
|
// because it is a misuse of the API rather than an expected failure.
|
|
3250
|
-
throw new
|
|
3276
|
+
throw new internal_9.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
|
|
3251
3277
|
}
|
|
3252
3278
|
else {
|
|
3253
3279
|
return this.summaryManager.enqueueSummarize(options);
|
|
3254
3280
|
}
|
|
3255
3281
|
}
|
|
3282
|
+
getJoinedStatus() {
|
|
3283
|
+
const getConnectionState = this.getConnectionState;
|
|
3284
|
+
if (getConnectionState) {
|
|
3285
|
+
const connectionState = getConnectionState();
|
|
3286
|
+
if (connectionState === internal_1.ConnectionState.Connected) {
|
|
3287
|
+
return this.canSendOps ? "joinedForWriting" : "joinedForReading";
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
else if (this.canSendOps) {
|
|
3291
|
+
return "joinedForWriting";
|
|
3292
|
+
}
|
|
3293
|
+
return "disconnected";
|
|
3294
|
+
}
|
|
3256
3295
|
acquireExtension(id, factory, ...useContext) {
|
|
3257
3296
|
let entry = this.extensions.get(id);
|
|
3258
3297
|
if (entry === undefined) {
|
|
3259
3298
|
const runtime = {
|
|
3260
|
-
|
|
3299
|
+
getJoinedStatus: this.getJoinedStatus.bind(this),
|
|
3261
3300
|
getClientId: () => this.clientId,
|
|
3262
3301
|
events: this.lazyEventsForExtensions.value,
|
|
3263
3302
|
logger: this.baseLogger,
|