@fluidframework/container-runtime 2.41.0-338401 → 2.42.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 +8 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +4 -4
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +22 -1
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +109 -7
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +67 -28
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +332 -186
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +2 -0
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +1 -1
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +5 -4
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +4 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +7 -0
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +6 -5
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +9 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +6 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +2 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +6 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +22 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +34 -11
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runCounter.d.ts.map +1 -1
- package/dist/runCounter.js +1 -1
- package/dist/runCounter.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +42 -18
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +62 -52
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +4 -4
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +22 -1
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +102 -3
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +67 -28
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +333 -188
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +5 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +2 -0
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +1 -1
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +5 -4
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +1 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +4 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +7 -0
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +6 -5
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +9 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +6 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +2 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +6 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +22 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +34 -11
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runCounter.d.ts.map +1 -1
- package/lib/runCounter.js +1 -1
- package/lib/runCounter.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +42 -18
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +62 -52
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js.map +1 -1
- package/package.json +19 -19
- package/src/channelCollection.ts +4 -4
- package/src/compatUtils.ts +145 -10
- package/src/containerRuntime.ts +472 -225
- package/src/dataStore.ts +7 -0
- package/src/gc/garbageCollection.ts +2 -0
- package/src/gc/gcDefinitions.ts +1 -1
- package/src/index.ts +2 -1
- package/src/messageTypes.ts +12 -5
- package/src/metadata.ts +1 -1
- package/src/opLifecycle/batchManager.ts +8 -0
- package/src/opLifecycle/definitions.ts +7 -3
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +17 -4
- package/src/opLifecycle/opSerialization.ts +6 -1
- package/src/opLifecycle/outbox.ts +8 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +64 -20
- package/src/runCounter.ts +4 -1
- package/src/summary/documentSchema.ts +111 -86
- package/src/summary/index.ts +2 -1
package/dist/containerRuntime.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Licensed under the MIT License.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createNewSignalEnvelope = exports.ContainerRuntime = exports.loadContainerRuntime = exports.getSingleUseLegacyLogCallback = exports.makeLegacySendBatchFn = exports.getDeviceSpec = exports.agentSchedulerId = exports.isUnpackedRuntimeMessage = exports.defaultPendingOpsRetryDelayMs = exports.defaultPendingOpsWaitTimeoutMs = exports.defaultRuntimeHeaderData = exports.InactiveResponseHeaderKey = exports.TombstoneResponseHeaderKey = exports.DeletedResponseHeaderKey = void 0;
|
|
7
|
+
exports.isContainerMessageDirtyable = exports.createNewSignalEnvelope = exports.ContainerRuntime = exports.loadContainerRuntime = exports.getSingleUseLegacyLogCallback = exports.makeLegacySendBatchFn = exports.getDeviceSpec = exports.agentSchedulerId = exports.isUnpackedRuntimeMessage = exports.defaultPendingOpsRetryDelayMs = exports.defaultPendingOpsWaitTimeoutMs = exports.defaultRuntimeHeaderData = exports.InactiveResponseHeaderKey = exports.TombstoneResponseHeaderKey = exports.DeletedResponseHeaderKey = void 0;
|
|
8
8
|
const client_utils_1 = require("@fluid-internal/client-utils");
|
|
9
9
|
const container_definitions_1 = require("@fluidframework/container-definitions");
|
|
10
10
|
const internal_1 = require("@fluidframework/container-definitions/internal");
|
|
@@ -197,6 +197,20 @@ async function loadContainerRuntime(params) {
|
|
|
197
197
|
}
|
|
198
198
|
exports.loadContainerRuntime = loadContainerRuntime;
|
|
199
199
|
const defaultMaxConsecutiveReconnects = 7;
|
|
200
|
+
/**
|
|
201
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
202
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
203
|
+
*/
|
|
204
|
+
function canStageMessageOfType(type) {
|
|
205
|
+
return (
|
|
206
|
+
// These are user changes coming up from the runtime's DataStores
|
|
207
|
+
type === messageTypes_js_1.ContainerMessageType.FluidDataStoreOp ||
|
|
208
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
209
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
210
|
+
type === messageTypes_js_1.ContainerMessageType.GC ||
|
|
211
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
212
|
+
type === messageTypes_js_1.ContainerMessageType.DocumentSchemaChange);
|
|
213
|
+
}
|
|
200
214
|
/**
|
|
201
215
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
202
216
|
* It will define the store level mappings.
|
|
@@ -245,6 +259,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
245
259
|
if (!(0, compatUtils_js_1.isValidMinVersionForCollab)(minVersionForCollab)) {
|
|
246
260
|
throw new internal_8.UsageError(`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`);
|
|
247
261
|
}
|
|
262
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
263
|
+
// were manually set.
|
|
264
|
+
(0, compatUtils_js_1.validateRuntimeOptions)(minVersionForCollab, runtimeOptions);
|
|
248
265
|
const defaultsAffectingDocSchema = (0, compatUtils_js_1.getMinVersionForCollabDefaults)(minVersionForCollab);
|
|
249
266
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
250
267
|
const defaultsNotAffectingDocSchema = {
|
|
@@ -503,15 +520,19 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
503
520
|
}
|
|
504
521
|
/**
|
|
505
522
|
* Invokes the given callback and expects that no ops are submitted
|
|
506
|
-
* until execution finishes. If an op is submitted,
|
|
523
|
+
* until execution finishes. If an op is submitted, it will be marked as reentrant.
|
|
507
524
|
*
|
|
508
525
|
* @param callback - the callback to be invoked
|
|
509
526
|
*/
|
|
510
527
|
ensureNoDataModelChanges(callback) {
|
|
511
528
|
return this.dataModelChangeRunner.run(callback);
|
|
512
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Indicates whether the container is in a state where it is able to send
|
|
532
|
+
* ops (connected to op stream and not in readonly mode).
|
|
533
|
+
*/
|
|
513
534
|
get connected() {
|
|
514
|
-
return this.
|
|
535
|
+
return this.canSendOps;
|
|
515
536
|
}
|
|
516
537
|
/**
|
|
517
538
|
* clientId of parent (non-summarizing) container that owns summarizer container
|
|
@@ -575,7 +596,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
575
596
|
// Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
|
|
576
597
|
this.pendingIdCompressorOps = [];
|
|
577
598
|
this.batchRunner = new runCounter_js_1.BatchRunCounter();
|
|
578
|
-
this.
|
|
599
|
+
this.flushScheduled = false;
|
|
579
600
|
this.consecutiveReconnects = 0;
|
|
580
601
|
this.dataModelChangeRunner = new runCounter_js_1.RunCounter();
|
|
581
602
|
this._disposed = false;
|
|
@@ -588,6 +609,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
588
609
|
this.snapshotCacheForLoadingGroupIds = new internal_2.PromiseCache({
|
|
589
610
|
expiry: { policy: "absolute", durationMs: 60000 },
|
|
590
611
|
});
|
|
612
|
+
this.extensions = new Map();
|
|
591
613
|
this.notifyReadOnlyState = (readonly) => this.channelCollection.notifyReadOnlyState(readonly);
|
|
592
614
|
/**
|
|
593
615
|
* Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
|
|
@@ -598,7 +620,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
598
620
|
// eslint-disable-next-line import/no-deprecated
|
|
599
621
|
this.enterStagingMode = () => {
|
|
600
622
|
if (this.stageControls !== undefined) {
|
|
601
|
-
throw new
|
|
623
|
+
throw new internal_8.UsageError("already in staging mode");
|
|
624
|
+
}
|
|
625
|
+
if (this.attachState === container_definitions_1.AttachState.Detached) {
|
|
626
|
+
throw new internal_8.UsageError("cannot enter staging mode while detached");
|
|
602
627
|
}
|
|
603
628
|
// Make sure all BatchManagers are empty before entering staging mode,
|
|
604
629
|
// since we mark whole batches as "staged" or not to indicate whether to submit them.
|
|
@@ -619,11 +644,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
619
644
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
620
645
|
this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
|
|
621
646
|
(0, internal_2.assert)(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
|
|
622
|
-
this.
|
|
647
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
623
648
|
});
|
|
624
|
-
|
|
625
|
-
this.updateDocumentDirtyState(this.pendingMessagesCount !== 0);
|
|
626
|
-
}
|
|
649
|
+
this.updateDocumentDirtyState();
|
|
627
650
|
}),
|
|
628
651
|
commitChanges: (optionsParam) => {
|
|
629
652
|
const options = { ...defaultStagingCommitOptions, ...optionsParam };
|
|
@@ -640,6 +663,15 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
640
663
|
return this.stageControls;
|
|
641
664
|
};
|
|
642
665
|
this.readAndParseBlob = async (id) => (0, internal_4.readAndParse)(this.storage, id);
|
|
666
|
+
// While internal, ContainerRuntime has not been converted to use the new events support.
|
|
667
|
+
// Recreate the required events (new pattern) with injected, wrapper new emitter.
|
|
668
|
+
// It is lazily create to avoid listeners (old events) that ultimately go nowhere.
|
|
669
|
+
this.lazyEventsForExtensions = new internal_2.Lazy(() => {
|
|
670
|
+
const eventEmitter = (0, client_utils_1.createEmitter)();
|
|
671
|
+
this.on("connected", (clientId) => eventEmitter.emit("connected", clientId));
|
|
672
|
+
this.on("disconnected", () => eventEmitter.emit("disconnected"));
|
|
673
|
+
return eventEmitter;
|
|
674
|
+
});
|
|
643
675
|
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
|
|
644
676
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
645
677
|
this.disposeFn = disposeFn ?? closeFn;
|
|
@@ -649,6 +681,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
649
681
|
this.mc = (0, internal_8.createChildMonitoringContext)({
|
|
650
682
|
logger: this.baseLogger,
|
|
651
683
|
namespace: "ContainerRuntime",
|
|
684
|
+
properties: {
|
|
685
|
+
all: {
|
|
686
|
+
inStagingMode: this.inStagingMode,
|
|
687
|
+
},
|
|
688
|
+
},
|
|
652
689
|
});
|
|
653
690
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
654
691
|
// We can use runtimeOptions.compressionOptions.compressionAlgorithm, but only if it's in the schema list!
|
|
@@ -668,12 +705,28 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
668
705
|
this.submitSummaryFn =
|
|
669
706
|
submitSummaryFn ??
|
|
670
707
|
((summaryOp, refseq) => submitFn(internal_3.MessageType.Summarize, summaryOp, false));
|
|
671
|
-
|
|
708
|
+
const sequenceAndSubmitSignal = (envelope, targetClientId) => {
|
|
709
|
+
if (targetClientId === undefined) {
|
|
710
|
+
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope);
|
|
711
|
+
}
|
|
712
|
+
submitSignalFn(envelope, targetClientId);
|
|
713
|
+
};
|
|
714
|
+
this.submitSignalFn = (envelope, targetClientId) => {
|
|
715
|
+
if (envelope.address?.startsWith("/")) {
|
|
716
|
+
throw new Error("General path based addressing is not implemented");
|
|
717
|
+
}
|
|
718
|
+
sequenceAndSubmitSignal(envelope, targetClientId);
|
|
719
|
+
};
|
|
720
|
+
this.submitExtensionSignal = (id, addressChain, message) => {
|
|
721
|
+
this.verifyNotClosed();
|
|
722
|
+
const envelope = createNewSignalEnvelope(`/ext/${id}/${addressChain.join("/")}`, message.type, message.content);
|
|
723
|
+
sequenceAndSubmitSignal(envelope, message.targetClientId);
|
|
724
|
+
};
|
|
672
725
|
// TODO: After IContainerContext.options is removed, we'll just create a new blank object {} here.
|
|
673
726
|
// Values are generally expected to be set from the runtime side.
|
|
674
727
|
this.options = options ?? {};
|
|
675
728
|
this.clientDetails = clientDetails;
|
|
676
|
-
|
|
729
|
+
this.isSummarizerClient = this.clientDetails.type === index_js_4.summarizerClientType;
|
|
677
730
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
678
731
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
679
732
|
this._getClientId = () => context.clientId;
|
|
@@ -700,8 +753,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
700
753
|
this.mc.logger.sendTelemetryEvent({
|
|
701
754
|
eventName: "Attached",
|
|
702
755
|
details: {
|
|
703
|
-
|
|
704
|
-
|
|
756
|
+
lastEmittedDirty: this.lastEmittedDirty,
|
|
757
|
+
currentDirtyState: this.computeCurrentDirtyState(),
|
|
705
758
|
},
|
|
706
759
|
});
|
|
707
760
|
});
|
|
@@ -710,7 +763,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
710
763
|
details: { attachState: this.attachState },
|
|
711
764
|
}));
|
|
712
765
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
713
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
766
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
714
767
|
let loadSummaryNumber;
|
|
715
768
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
716
769
|
// get the values from the metadata blob.
|
|
@@ -734,7 +787,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
734
787
|
this.messageAtLastSummary = lastMessageFromMetadata(metadata);
|
|
735
788
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
736
789
|
// Later updates come through calls to setConnectionState.
|
|
737
|
-
this.
|
|
790
|
+
this.canSendOps = connected;
|
|
738
791
|
this.mc.logger.sendTelemetryEvent({
|
|
739
792
|
eventName: "GCFeatureMatrix",
|
|
740
793
|
metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
|
|
@@ -827,7 +880,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
827
880
|
existing,
|
|
828
881
|
metadata,
|
|
829
882
|
createContainerMetadata: this.createContainerMetadata,
|
|
830
|
-
isSummarizerClient,
|
|
883
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
831
884
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
832
885
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
833
886
|
readAndParseBlob: async (id) => (0, internal_4.readAndParse)(this.storage, id),
|
|
@@ -868,9 +921,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
868
921
|
// verifyNotClosed is called in FluidDataStoreContext, which is *the* expected caller.
|
|
869
922
|
const envelope1 = content;
|
|
870
923
|
const envelope2 = createNewSignalEnvelope(envelope1.address, type, envelope1.contents);
|
|
871
|
-
if (targetClientId === undefined) {
|
|
872
|
-
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope2);
|
|
873
|
-
}
|
|
874
924
|
this.submitSignalFn(envelope2, targetClientId);
|
|
875
925
|
};
|
|
876
926
|
let snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(baseSnapshot, metadata);
|
|
@@ -914,7 +964,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
914
964
|
// Keep the old flag name even though we renamed the class member (it shipped in 2.31.0)
|
|
915
965
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
916
966
|
this.outbox = new index_js_3.Outbox({
|
|
917
|
-
shouldSend: () => this.
|
|
967
|
+
shouldSend: () => this.shouldSendOps(),
|
|
918
968
|
pendingStateManager: this.pendingStateManager,
|
|
919
969
|
submitBatchFn,
|
|
920
970
|
legacySendBatchFn,
|
|
@@ -964,9 +1014,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
964
1014
|
const closeSummarizerDelayOverride = this.mc.config.getNumber("Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs");
|
|
965
1015
|
this.closeSummarizerDelayMs =
|
|
966
1016
|
closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
|
|
967
|
-
this
|
|
968
|
-
|
|
969
|
-
context.updateDirtyContainerState(this.
|
|
1017
|
+
// We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
|
|
1018
|
+
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
1019
|
+
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
970
1020
|
if (!this.skipSafetyFlushDuringProcessStack) {
|
|
971
1021
|
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
972
1022
|
// so we should flush now to clear the way for the next ops.
|
|
@@ -1053,7 +1103,14 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1053
1103
|
await this.initializeSummarizer(loader);
|
|
1054
1104
|
if (this.sessionSchema.idCompressorMode === "on" ||
|
|
1055
1105
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)) {
|
|
1056
|
-
this.
|
|
1106
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnBoot" }, (event) => {
|
|
1107
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1108
|
+
event.end({
|
|
1109
|
+
details: {
|
|
1110
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1111
|
+
},
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1057
1114
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
1058
1115
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
1059
1116
|
}
|
|
@@ -1082,8 +1139,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1082
1139
|
const orderedClientCollection = new index_js_4.OrderedClientCollection(orderedClientLogger, this.innerDeltaManager, this._quorum);
|
|
1083
1140
|
const orderedClientElectionForSummarizer = new index_js_4.OrderedClientElection(orderedClientLogger, orderedClientCollection, this.electedSummarizerData ?? this.innerDeltaManager.lastSequenceNumber, index_js_4.SummarizerClientElection.isClientEligible, this.mc.config.getBoolean("Fluid.ContainerRuntime.OrderedClientElection.EnablePerformanceEvents"));
|
|
1084
1141
|
this.summarizerClientElection = new index_js_4.SummarizerClientElection(orderedClientLogger, summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary);
|
|
1085
|
-
|
|
1086
|
-
if (isSummarizerClient) {
|
|
1142
|
+
if (this.isSummarizerClient) {
|
|
1087
1143
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
1088
1144
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
1089
1145
|
const module = await import(
|
|
@@ -1431,35 +1487,32 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1431
1487
|
}
|
|
1432
1488
|
replayPendingStates() {
|
|
1433
1489
|
// We need to be able to send ops to replay states
|
|
1434
|
-
if (!this.
|
|
1490
|
+
if (!this.shouldSendOps()) {
|
|
1435
1491
|
return;
|
|
1436
1492
|
}
|
|
1437
|
-
//
|
|
1438
|
-
// dirty state change events
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
const oldState = this.dirtyContainer;
|
|
1442
|
-
this.dirtyContainer = false;
|
|
1493
|
+
// Replaying is an internal operation and we don't want to generate noise while doing it.
|
|
1494
|
+
// So temporarily disable dirty state change events, and save the old state.
|
|
1495
|
+
// When we're done, we'll emit the event if the state changed.
|
|
1496
|
+
const oldState = this.lastEmittedDirty;
|
|
1443
1497
|
(0, internal_2.assert)(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
|
|
1444
1498
|
this.emitDirtyDocumentEvent = false;
|
|
1445
|
-
let newState;
|
|
1446
1499
|
try {
|
|
1447
1500
|
// Any ID Allocation ops that failed to submit after the pending state was queued need to have
|
|
1448
1501
|
// the corresponding ranges resubmitted (note this call replaces the typical resubmit flow).
|
|
1449
1502
|
// Since we don't submit ID Allocation ops when staged, any outstanding ranges would be from
|
|
1450
1503
|
// before staging mode so we can simply say staged: false.
|
|
1451
1504
|
this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
|
|
1505
|
+
this.scheduleFlush();
|
|
1452
1506
|
// replay the ops
|
|
1453
1507
|
this.pendingStateManager.replayPendingStates();
|
|
1454
1508
|
}
|
|
1455
1509
|
finally {
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
this.dirtyContainer = oldState;
|
|
1510
|
+
// Restore the old state, re-enable event emit
|
|
1511
|
+
this.lastEmittedDirty = oldState;
|
|
1459
1512
|
this.emitDirtyDocumentEvent = true;
|
|
1460
1513
|
}
|
|
1461
|
-
//
|
|
1462
|
-
this.updateDocumentDirtyState(
|
|
1514
|
+
// This will emit an event if the state changed relative to before replay
|
|
1515
|
+
this.updateDocumentDirtyState();
|
|
1463
1516
|
}
|
|
1464
1517
|
/**
|
|
1465
1518
|
* Parse an op's type and actual content from given serialized content
|
|
@@ -1516,25 +1569,35 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1516
1569
|
loadIdCompressor() {
|
|
1517
1570
|
if (this._idCompressor === undefined &&
|
|
1518
1571
|
this.sessionSchema.idCompressorMode !== undefined) {
|
|
1519
|
-
this.
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1572
|
+
internal_8.PerformanceEvent.timedExec(this.mc.logger, { eventName: "CreateIdCompressorOnDelayedLoad" }, (event) => {
|
|
1573
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
1574
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
1575
|
+
const ops = this.pendingIdCompressorOps;
|
|
1576
|
+
this.pendingIdCompressorOps = [];
|
|
1577
|
+
const trace = client_utils_1.Trace.start();
|
|
1578
|
+
for (const range of ops) {
|
|
1579
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
1580
|
+
}
|
|
1581
|
+
event.end({
|
|
1582
|
+
details: {
|
|
1583
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
1584
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
1585
|
+
pendingIdCompressorOps: ops.length,
|
|
1586
|
+
},
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1526
1589
|
(0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
1527
1590
|
}
|
|
1528
1591
|
}
|
|
1529
|
-
setConnectionState(
|
|
1592
|
+
setConnectionState(canSendOps, clientId) {
|
|
1530
1593
|
// Validate we have consistent state
|
|
1531
1594
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
1532
1595
|
(0, internal_2.assert)(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
1533
1596
|
(0, internal_2.assert)(this.clientId === currentClientId, 0x978 /* this.clientId does not match Audience */);
|
|
1534
|
-
if (
|
|
1597
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
1535
1598
|
this.loadIdCompressor();
|
|
1536
1599
|
}
|
|
1537
|
-
if (
|
|
1600
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
1538
1601
|
this.delayConnectClientId = undefined;
|
|
1539
1602
|
this.mc.logger.sendTelemetryEvent({
|
|
1540
1603
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -1542,37 +1605,39 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1542
1605
|
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
1543
1606
|
return;
|
|
1544
1607
|
}
|
|
1545
|
-
if (!connected) {
|
|
1546
|
-
this.documentsSchemaController.onDisconnect();
|
|
1547
|
-
}
|
|
1548
1608
|
// If there are stashed blobs in the pending state, we need to delay
|
|
1549
1609
|
// propagation of the "connected" event until we have uploaded them to
|
|
1550
1610
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1551
|
-
const connecting =
|
|
1611
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
1552
1612
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
1553
1613
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
|
|
1554
1614
|
(0, internal_2.assert)(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
1555
1615
|
this.delayConnectClientId = clientId;
|
|
1556
1616
|
return;
|
|
1557
1617
|
}
|
|
1558
|
-
this.setConnectionStateCore(
|
|
1618
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
1559
1619
|
}
|
|
1560
|
-
|
|
1620
|
+
/**
|
|
1621
|
+
* Raises and propagates connected events.
|
|
1622
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
1623
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
1624
|
+
*/
|
|
1625
|
+
setConnectionStateCore(canSendOps, clientId) {
|
|
1561
1626
|
(0, internal_2.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1562
1627
|
this.verifyNotClosed();
|
|
1563
1628
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1564
|
-
const
|
|
1565
|
-
const reconnection =
|
|
1629
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
1630
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
1566
1631
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
1567
1632
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
1568
1633
|
// We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
|
|
1569
|
-
if (
|
|
1634
|
+
if (canSendOpsChanged && canSendOps) {
|
|
1570
1635
|
this.flush();
|
|
1571
1636
|
}
|
|
1572
|
-
this.
|
|
1573
|
-
if (
|
|
1637
|
+
this.canSendOps = canSendOps;
|
|
1638
|
+
if (canSendOps) {
|
|
1574
1639
|
(0, internal_2.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
1575
|
-
if (
|
|
1640
|
+
if (canSendOpsChanged) {
|
|
1576
1641
|
this.signalTelemetryManager.resetTracking();
|
|
1577
1642
|
}
|
|
1578
1643
|
}
|
|
@@ -1588,12 +1653,12 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1588
1653
|
return;
|
|
1589
1654
|
}
|
|
1590
1655
|
}
|
|
1591
|
-
if (
|
|
1656
|
+
if (canSendOpsChanged) {
|
|
1592
1657
|
this.replayPendingStates();
|
|
1593
1658
|
}
|
|
1594
|
-
this.channelCollection.setConnectionState(
|
|
1595
|
-
this.garbageCollector.setConnectionState(
|
|
1596
|
-
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, connected
|
|
1659
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
1660
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
1661
|
+
(0, internal_8.raiseConnectedEvent)(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
1597
1662
|
}
|
|
1598
1663
|
async notifyOpReplay(message) {
|
|
1599
1664
|
await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
|
|
@@ -1721,6 +1786,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1721
1786
|
* @param groupedBatch - true if these messages are part of a grouped op batch.
|
|
1722
1787
|
*/
|
|
1723
1788
|
processInboundMessages(messagesWithMetadata, locationInBatch, local, savedOp, runtimeBatch, groupedBatch) {
|
|
1789
|
+
// This message could have been the last pending local (dirtyable) message, in which case we need to update dirty state to "saved"
|
|
1790
|
+
this.updateDocumentDirtyState();
|
|
1724
1791
|
if (locationInBatch.batchStart) {
|
|
1725
1792
|
const firstMessage = messagesWithMetadata[0]?.message;
|
|
1726
1793
|
(0, internal_2.assert)(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
@@ -1815,11 +1882,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1815
1882
|
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
1816
1883
|
}
|
|
1817
1884
|
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
1818
|
-
// If there are no more pending messages after processing a local message,
|
|
1819
|
-
// the document is no longer dirty.
|
|
1820
|
-
if (!this.hasPendingMessages()) {
|
|
1821
|
-
this.updateDocumentDirtyState(false);
|
|
1822
|
-
}
|
|
1823
1885
|
// The DeltaManager used to do this, but doesn't anymore as of Loader v2.4
|
|
1824
1886
|
// Anyone listening to our "op" event would expect the contents to be parsed per this same logic
|
|
1825
1887
|
if (typeof message.contents === "string" &&
|
|
@@ -1841,11 +1903,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1841
1903
|
*
|
|
1842
1904
|
*/
|
|
1843
1905
|
validateAndProcessRuntimeMessages(message, messagesContent, local, savedOp) {
|
|
1844
|
-
// If there are no more pending messages after processing a local message,
|
|
1845
|
-
// the document is no longer dirty.
|
|
1846
|
-
if (!this.hasPendingMessages()) {
|
|
1847
|
-
this.updateDocumentDirtyState(false);
|
|
1848
|
-
}
|
|
1849
1906
|
// Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
|
|
1850
1907
|
const contents = messagesContent.map((c) => c.contents);
|
|
1851
1908
|
switch (message.type) {
|
|
@@ -1921,20 +1978,44 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1921
1978
|
if (message.clientId === this.clientId) {
|
|
1922
1979
|
this.signalTelemetryManager.trackReceivedSignal(envelope, this.mc.logger, this.consecutiveReconnects);
|
|
1923
1980
|
}
|
|
1924
|
-
|
|
1981
|
+
const fullAddress = envelope.address;
|
|
1982
|
+
if (fullAddress === undefined) {
|
|
1925
1983
|
// No address indicates a container signal message.
|
|
1926
1984
|
this.emit("signal", transformed, local);
|
|
1927
1985
|
return;
|
|
1928
1986
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1987
|
+
this.routeNonContainerSignal(fullAddress, transformed, local);
|
|
1988
|
+
}
|
|
1989
|
+
routeNonContainerSignal(address, signalMessage, local) {
|
|
1990
|
+
// channelCollection signals are identified by no starting `/` in address.
|
|
1991
|
+
if (!address.startsWith("/")) {
|
|
1992
|
+
// Due to a mismatch between different layers in terms of
|
|
1993
|
+
// what is the interface of passing signals, we need to adjust
|
|
1994
|
+
// the signal envelope before sending it to the datastores to be processed
|
|
1995
|
+
const envelope = {
|
|
1996
|
+
address,
|
|
1997
|
+
contents: signalMessage.content,
|
|
1998
|
+
};
|
|
1999
|
+
signalMessage.content = envelope;
|
|
2000
|
+
this.channelCollection.processSignal(signalMessage, local);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const addresses = address.split("/");
|
|
2004
|
+
if (addresses.length > 2 && addresses[1] === "ext") {
|
|
2005
|
+
const id = addresses[2];
|
|
2006
|
+
const entry = this.extensions.get(id);
|
|
2007
|
+
if (entry !== undefined) {
|
|
2008
|
+
entry.extension.processSignal?.(addresses.slice(3), signalMessage, local);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
(0, internal_2.assert)(!local, 0xba0 /* No recipient found for local signal */);
|
|
2013
|
+
this.mc.logger.sendTelemetryEvent({
|
|
2014
|
+
eventName: "SignalAddressNotFound",
|
|
2015
|
+
...(0, internal_8.tagCodeArtifacts)({
|
|
2016
|
+
address,
|
|
2017
|
+
}),
|
|
2018
|
+
});
|
|
1938
2019
|
}
|
|
1939
2020
|
/**
|
|
1940
2021
|
* Flush the current batch of ops to the ordering service for sequencing
|
|
@@ -1944,6 +2025,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1944
2025
|
* @param resubmitInfo - If defined, indicates this is a resubmission of a batch with the given Batch info needed for resubmit.
|
|
1945
2026
|
*/
|
|
1946
2027
|
flush(resubmitInfo) {
|
|
2028
|
+
this.flushScheduled = false;
|
|
1947
2029
|
try {
|
|
1948
2030
|
(0, internal_2.assert)(!this.batchRunner.running, 0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */);
|
|
1949
2031
|
this.outbox.flush(resubmitInfo);
|
|
@@ -1964,7 +2046,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1964
2046
|
*/
|
|
1965
2047
|
orderSequentially(callback) {
|
|
1966
2048
|
let checkpoint;
|
|
1967
|
-
const checkpointDirtyState = this.dirtyContainer;
|
|
1968
2049
|
// eslint-disable-next-line import/no-deprecated
|
|
1969
2050
|
let stageControls;
|
|
1970
2051
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
@@ -1984,11 +2065,10 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1984
2065
|
if (checkpoint) {
|
|
1985
2066
|
// This will throw and close the container if rollback fails
|
|
1986
2067
|
try {
|
|
1987
|
-
checkpoint.rollback((message) =>
|
|
1988
|
-
//
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
}
|
|
2068
|
+
checkpoint.rollback((message) =>
|
|
2069
|
+
// These changes are staged since we entered staging mode above
|
|
2070
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata));
|
|
2071
|
+
this.updateDocumentDirtyState();
|
|
1992
2072
|
stageControls?.discardChanges();
|
|
1993
2073
|
stageControls = undefined;
|
|
1994
2074
|
}
|
|
@@ -2064,17 +2144,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2064
2144
|
const context = this.channelCollection.createDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], loadingGroupId);
|
|
2065
2145
|
return (0, dataStore_js_1.channelToDataStore)(await context.realize(), context.id, this.channelCollection, this.mc.logger);
|
|
2066
2146
|
}
|
|
2067
|
-
|
|
2147
|
+
shouldSendOps() {
|
|
2068
2148
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
2069
2149
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2070
2150
|
return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
|
|
2071
2151
|
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Typically ops are batched and later flushed together, but in some cases we want to flush immediately.
|
|
2074
|
-
*/
|
|
2075
|
-
currentlyBatching() {
|
|
2076
|
-
return this.flushMode !== internal_6.FlushMode.Immediate || this.batchRunner.running;
|
|
2077
|
-
}
|
|
2078
2152
|
getQuorum() {
|
|
2079
2153
|
return this._quorum;
|
|
2080
2154
|
}
|
|
@@ -2086,36 +2160,17 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2086
2160
|
* either were not sent out to delta stream or were not yet acknowledged.
|
|
2087
2161
|
*/
|
|
2088
2162
|
get isDirty() {
|
|
2089
|
-
|
|
2163
|
+
// Rather than recomputing the dirty state in this moment,
|
|
2164
|
+
// just regurgitate the last emitted dirty state.
|
|
2165
|
+
return this.lastEmittedDirty;
|
|
2090
2166
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
return false;
|
|
2099
|
-
}
|
|
2100
|
-
break;
|
|
2101
|
-
}
|
|
2102
|
-
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2103
|
-
const envelope = contents;
|
|
2104
|
-
if (envelope.address === exports.agentSchedulerId) {
|
|
2105
|
-
return false;
|
|
2106
|
-
}
|
|
2107
|
-
break;
|
|
2108
|
-
}
|
|
2109
|
-
case messageTypes_js_1.ContainerMessageType.IdAllocation:
|
|
2110
|
-
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange:
|
|
2111
|
-
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
2112
|
-
return false;
|
|
2113
|
-
}
|
|
2114
|
-
default: {
|
|
2115
|
-
break;
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return true;
|
|
2167
|
+
/**
|
|
2168
|
+
* Returns true if the container is dirty: not attached, or no pending user messages (could be some "non-dirtyable" ones though)
|
|
2169
|
+
*/
|
|
2170
|
+
computeCurrentDirtyState() {
|
|
2171
|
+
return (this.attachState !== container_definitions_1.AttachState.Attached ||
|
|
2172
|
+
this.pendingStateManager.hasPendingUserChanges() ||
|
|
2173
|
+
this.outbox.containsUserChanges());
|
|
2119
2174
|
}
|
|
2120
2175
|
/**
|
|
2121
2176
|
* Submits the signal to be sent to other clients.
|
|
@@ -2132,9 +2187,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2132
2187
|
submitSignal(type, content, targetClientId) {
|
|
2133
2188
|
this.verifyNotClosed();
|
|
2134
2189
|
const envelope = createNewSignalEnvelope(undefined /* address */, type, content);
|
|
2135
|
-
if (targetClientId === undefined) {
|
|
2136
|
-
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope);
|
|
2137
|
-
}
|
|
2138
2190
|
this.submitSignalFn(envelope, targetClientId);
|
|
2139
2191
|
}
|
|
2140
2192
|
setAttachState(attachState) {
|
|
@@ -2145,9 +2197,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2145
2197
|
(0, internal_2.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
2146
2198
|
this.emit("attached");
|
|
2147
2199
|
}
|
|
2148
|
-
|
|
2149
|
-
this.updateDocumentDirtyState(false);
|
|
2150
|
-
}
|
|
2200
|
+
this.updateDocumentDirtyState();
|
|
2151
2201
|
this.channelCollection.setAttachState(attachState);
|
|
2152
2202
|
}
|
|
2153
2203
|
/**
|
|
@@ -2715,18 +2765,20 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2715
2765
|
hasPendingMessages() {
|
|
2716
2766
|
return this.pendingMessagesCount !== 0;
|
|
2717
2767
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2768
|
+
/**
|
|
2769
|
+
* Emit "dirty" or "saved" event based on the current dirty state of the document.
|
|
2770
|
+
* This must be called every time the states underlying the dirty state change.
|
|
2771
|
+
*
|
|
2772
|
+
* @privateRemarks - It's helpful to think of this as an event handler registered
|
|
2773
|
+
* for hypothetical "changed" events for PendingStateManager, Outbox, and Container Attach machinery.
|
|
2774
|
+
* But those events don't exist so we manually call this wherever we know those changes happen.
|
|
2775
|
+
*/
|
|
2776
|
+
updateDocumentDirtyState() {
|
|
2777
|
+
const dirty = this.computeCurrentDirtyState();
|
|
2778
|
+
if (this.lastEmittedDirty === dirty) {
|
|
2727
2779
|
return;
|
|
2728
2780
|
}
|
|
2729
|
-
this.
|
|
2781
|
+
this.lastEmittedDirty = dirty;
|
|
2730
2782
|
if (this.emitDirtyDocumentEvent) {
|
|
2731
2783
|
this.emit(dirty ? "dirty" : "saved");
|
|
2732
2784
|
}
|
|
@@ -2781,6 +2833,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2781
2833
|
try {
|
|
2782
2834
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
2783
2835
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
2836
|
+
(0, internal_2.assert)(!staged || canStageMessageOfType(type), 0xbba /* Unexpected message type submitted in Staging Mode */);
|
|
2784
2837
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
2785
2838
|
if (!staged) {
|
|
2786
2839
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -2788,7 +2841,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2788
2841
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
2789
2842
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
2790
2843
|
// on this callback to do actual sending.
|
|
2791
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
2844
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
2792
2845
|
if (schemaChangeMessage) {
|
|
2793
2846
|
this.mc.logger.sendTelemetryEvent({
|
|
2794
2847
|
eventName: "SchemaChangeProposal",
|
|
@@ -2824,14 +2877,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2824
2877
|
else {
|
|
2825
2878
|
this.outbox.submit(message);
|
|
2826
2879
|
}
|
|
2827
|
-
|
|
2828
|
-
const flushImmediatelyOnSubmit = !this.currentlyBatching();
|
|
2829
|
-
if (flushImmediatelyOnSubmit) {
|
|
2830
|
-
this.flush();
|
|
2831
|
-
}
|
|
2832
|
-
else {
|
|
2833
|
-
this.scheduleFlush();
|
|
2834
|
-
}
|
|
2880
|
+
this.scheduleFlush();
|
|
2835
2881
|
}
|
|
2836
2882
|
catch (error) {
|
|
2837
2883
|
const dpe = internal_8.DataProcessingError.wrapIfUnrecognized(error, "ContainerRuntime.submit", {
|
|
@@ -2840,27 +2886,26 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2840
2886
|
this.closeFn(dpe);
|
|
2841
2887
|
throw dpe;
|
|
2842
2888
|
}
|
|
2843
|
-
|
|
2844
|
-
this.updateDocumentDirtyState(true);
|
|
2845
|
-
}
|
|
2889
|
+
this.updateDocumentDirtyState();
|
|
2846
2890
|
}
|
|
2847
2891
|
scheduleFlush() {
|
|
2848
|
-
if (this.
|
|
2892
|
+
if (this.flushScheduled) {
|
|
2849
2893
|
return;
|
|
2850
2894
|
}
|
|
2851
|
-
this.
|
|
2852
|
-
// TODO: hoist this out of the function scope to save unnecessary allocations
|
|
2853
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping -- Separate `flush` method already exists in outer scope
|
|
2854
|
-
const flush = () => {
|
|
2855
|
-
this.flushTaskExists = false;
|
|
2856
|
-
this.flush();
|
|
2857
|
-
};
|
|
2895
|
+
this.flushScheduled = true;
|
|
2858
2896
|
switch (this.flushMode) {
|
|
2897
|
+
case internal_6.FlushMode.Immediate: {
|
|
2898
|
+
// When in Immediate flush mode, flush immediately unless we are intentionally batching multiple ops (e.g. via orderSequentially)
|
|
2899
|
+
if (!this.batchRunner.running) {
|
|
2900
|
+
this.flush();
|
|
2901
|
+
}
|
|
2902
|
+
break;
|
|
2903
|
+
}
|
|
2859
2904
|
case internal_6.FlushMode.TurnBased: {
|
|
2860
2905
|
// When in TurnBased flush mode the runtime will buffer operations in the current turn and send them as a single
|
|
2861
2906
|
// batch at the end of the turn
|
|
2862
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2863
|
-
Promise.resolve().then(flush);
|
|
2907
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- Container will close if flush throws
|
|
2908
|
+
Promise.resolve().then(() => this.flush());
|
|
2864
2909
|
break;
|
|
2865
2910
|
}
|
|
2866
2911
|
// FlushModeExperimental is experimental and not exposed directly in the runtime APIs
|
|
@@ -2868,12 +2913,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2868
2913
|
// When in Async flush mode, the runtime will accumulate all operations across JS turns and send them as a single
|
|
2869
2914
|
// batch when all micro-tasks are complete.
|
|
2870
2915
|
// Compared to TurnBased, this flush mode will capture more ops into the same batch.
|
|
2871
|
-
setTimeout(flush, 0);
|
|
2916
|
+
setTimeout(() => this.flush(), 0);
|
|
2872
2917
|
break;
|
|
2873
2918
|
}
|
|
2874
2919
|
default: {
|
|
2875
|
-
(0, internal_2.
|
|
2876
|
-
break;
|
|
2920
|
+
(0, internal_2.fail)(0x587 /* Unreachable unless manually accumulating a batch */);
|
|
2877
2921
|
}
|
|
2878
2922
|
}
|
|
2879
2923
|
}
|
|
@@ -2895,43 +2939,72 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2895
2939
|
}
|
|
2896
2940
|
/**
|
|
2897
2941
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
2942
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
2943
|
+
*
|
|
2944
|
+
* @remarks
|
|
2945
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
2946
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
2898
2947
|
*
|
|
2899
|
-
*
|
|
2948
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
2949
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
2900
2950
|
* for correlation to detect container forking.
|
|
2901
2951
|
*/
|
|
2902
2952
|
reSubmitBatch(batch, { batchId, staged, squash }) {
|
|
2953
|
+
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2903
2954
|
const resubmitInfo = {
|
|
2904
2955
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
2905
2956
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
2906
2957
|
batchId: this.offlineEnabled ? batchId : undefined,
|
|
2907
2958
|
staged,
|
|
2908
2959
|
};
|
|
2960
|
+
const resubmitFn = squash
|
|
2961
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
2962
|
+
: this.reSubmit.bind(this);
|
|
2909
2963
|
this.batchRunner.run(() => {
|
|
2910
2964
|
for (const message of batch) {
|
|
2911
|
-
|
|
2965
|
+
resubmitFn(message);
|
|
2912
2966
|
}
|
|
2913
2967
|
}, resubmitInfo);
|
|
2914
2968
|
this.flush(resubmitInfo);
|
|
2915
2969
|
}
|
|
2916
|
-
|
|
2917
|
-
|
|
2970
|
+
/**
|
|
2971
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
2972
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
2973
|
+
*/
|
|
2974
|
+
reSubmitWithSquashing(resubmitData) {
|
|
2975
|
+
const message = resubmitData.runtimeOp;
|
|
2976
|
+
(0, internal_2.assert)(canStageMessageOfType(message.type), 0xbbb /* Expected message type to be compatible with staging */);
|
|
2977
|
+
switch (message.type) {
|
|
2978
|
+
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2979
|
+
this.channelCollection.reSubmit(message.type, message.contents, resubmitData.localOpMetadata,
|
|
2980
|
+
/* squash: */ true);
|
|
2981
|
+
break;
|
|
2982
|
+
}
|
|
2983
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
2984
|
+
case messageTypes_js_1.ContainerMessageType.GC:
|
|
2985
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
2986
|
+
this.reSubmit(resubmitData);
|
|
2987
|
+
break;
|
|
2988
|
+
}
|
|
2989
|
+
default: {
|
|
2990
|
+
(0, internal_2.unreachableCase)(message.type);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2918
2993
|
}
|
|
2919
2994
|
/**
|
|
2920
|
-
*
|
|
2921
|
-
*
|
|
2922
|
-
*
|
|
2923
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
2924
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
2995
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
2996
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
2997
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
2925
2998
|
*/
|
|
2926
|
-
|
|
2927
|
-
(0, internal_2.assert)(this._summarizer === undefined, 0x8f2 /* Summarizer never reconnects so should never resubmit */);
|
|
2999
|
+
reSubmit({ runtimeOp: message, localOpMetadata, opMetadata, }) {
|
|
2928
3000
|
switch (message.type) {
|
|
2929
3001
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp:
|
|
2930
3002
|
case messageTypes_js_1.ContainerMessageType.Attach:
|
|
2931
3003
|
case messageTypes_js_1.ContainerMessageType.Alias: {
|
|
2932
3004
|
// For Operations, call resubmitDataStoreOp which will find the right store
|
|
2933
3005
|
// and trigger resubmission on it.
|
|
2934
|
-
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3006
|
+
this.channelCollection.reSubmit(message.type, message.contents, localOpMetadata,
|
|
3007
|
+
/* squash: */ false);
|
|
2935
3008
|
break;
|
|
2936
3009
|
}
|
|
2937
3010
|
case messageTypes_js_1.ContainerMessageType.IdAllocation: {
|
|
@@ -2957,9 +3030,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2957
3030
|
break;
|
|
2958
3031
|
}
|
|
2959
3032
|
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
2960
|
-
//
|
|
2961
|
-
//
|
|
2962
|
-
|
|
3033
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
3034
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
3035
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
2963
3036
|
break;
|
|
2964
3037
|
}
|
|
2965
3038
|
default: {
|
|
@@ -2969,8 +3042,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2969
3042
|
}
|
|
2970
3043
|
}
|
|
2971
3044
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
3045
|
+
/**
|
|
3046
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
3047
|
+
*/
|
|
3048
|
+
rollbackStagedChanges({ type, contents }, localOpMetadata) {
|
|
3049
|
+
(0, internal_2.assert)(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
2974
3050
|
switch (type) {
|
|
2975
3051
|
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
2976
3052
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -2978,8 +3054,24 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2978
3054
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
2979
3055
|
break;
|
|
2980
3056
|
}
|
|
3057
|
+
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
3058
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
3059
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
3060
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
3061
|
+
this.mc.logger.sendErrorEvent({
|
|
3062
|
+
eventName: "GC_OpDiscarded",
|
|
3063
|
+
details: { subType: contents.type },
|
|
3064
|
+
});
|
|
3065
|
+
break;
|
|
3066
|
+
}
|
|
3067
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange: {
|
|
3068
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
3069
|
+
// This will allow it to propose the schema change again if needed.
|
|
3070
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
2981
3073
|
default: {
|
|
2982
|
-
|
|
3074
|
+
(0, internal_2.unreachableCase)(type);
|
|
2983
3075
|
}
|
|
2984
3076
|
}
|
|
2985
3077
|
}
|
|
@@ -3144,6 +3236,30 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3144
3236
|
return this.summaryManager.enqueueSummarize(options);
|
|
3145
3237
|
}
|
|
3146
3238
|
}
|
|
3239
|
+
acquireExtension(id, factory, ...useContext) {
|
|
3240
|
+
let entry = this.extensions.get(id);
|
|
3241
|
+
if (entry === undefined) {
|
|
3242
|
+
const runtime = {
|
|
3243
|
+
isConnected: () => this.connected,
|
|
3244
|
+
getClientId: () => this.clientId,
|
|
3245
|
+
events: this.lazyEventsForExtensions.value,
|
|
3246
|
+
logger: this.baseLogger,
|
|
3247
|
+
submitAddressedSignal: (addressChain, message) => {
|
|
3248
|
+
this.submitExtensionSignal(id, addressChain, message);
|
|
3249
|
+
},
|
|
3250
|
+
getQuorum: this.getQuorum.bind(this),
|
|
3251
|
+
getAudience: this.getAudience.bind(this),
|
|
3252
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
3253
|
+
};
|
|
3254
|
+
entry = new factory(runtime, ...useContext);
|
|
3255
|
+
this.extensions.set(id, entry);
|
|
3256
|
+
}
|
|
3257
|
+
else {
|
|
3258
|
+
(0, internal_2.assert)(entry instanceof factory, 0xba1 /* Extension entry is not of the expected type */);
|
|
3259
|
+
entry.extension.onNewUse(...useContext);
|
|
3260
|
+
}
|
|
3261
|
+
return entry.interface;
|
|
3262
|
+
}
|
|
3147
3263
|
get groupedBatchingEnabled() {
|
|
3148
3264
|
return this.sessionSchema.opGroupingEnabled === true;
|
|
3149
3265
|
}
|
|
@@ -3157,4 +3273,34 @@ function createNewSignalEnvelope(address, type, content) {
|
|
|
3157
3273
|
return newEnvelope;
|
|
3158
3274
|
}
|
|
3159
3275
|
exports.createNewSignalEnvelope = createNewSignalEnvelope;
|
|
3276
|
+
function isContainerMessageDirtyable({ type, contents, }) {
|
|
3277
|
+
// Certain container runtime messages should not mark the container dirty such as the old built-in
|
|
3278
|
+
// AgentScheduler and Garbage collector messages.
|
|
3279
|
+
switch (type) {
|
|
3280
|
+
case messageTypes_js_1.ContainerMessageType.Attach: {
|
|
3281
|
+
const attachMessage = contents;
|
|
3282
|
+
if (attachMessage.id === exports.agentSchedulerId) {
|
|
3283
|
+
return false;
|
|
3284
|
+
}
|
|
3285
|
+
break;
|
|
3286
|
+
}
|
|
3287
|
+
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
3288
|
+
const envelope = contents;
|
|
3289
|
+
if (envelope.address === exports.agentSchedulerId) {
|
|
3290
|
+
return false;
|
|
3291
|
+
}
|
|
3292
|
+
break;
|
|
3293
|
+
}
|
|
3294
|
+
case messageTypes_js_1.ContainerMessageType.IdAllocation:
|
|
3295
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange:
|
|
3296
|
+
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
3297
|
+
return false;
|
|
3298
|
+
}
|
|
3299
|
+
default: {
|
|
3300
|
+
break;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
return true;
|
|
3304
|
+
}
|
|
3305
|
+
exports.isContainerMessageDirtyable = isContainerMessageDirtyable;
|
|
3160
3306
|
//# sourceMappingURL=containerRuntime.js.map
|