@fluidframework/container-runtime 2.41.0-338186 → 2.41.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 +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/containerRuntime.d.ts +35 -17
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +174 -127
- package/dist/containerRuntime.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/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 +4 -0
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +16 -0
- 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/lib/containerRuntime.d.ts +35 -17
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +174 -128
- package/lib/containerRuntime.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/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 +4 -0
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +16 -0
- 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/package.json +18 -18
- package/src/containerRuntime.ts +263 -152
- package/src/opLifecycle/batchManager.ts +8 -0
- package/src/opLifecycle/outbox.ts +8 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +17 -0
- package/src/runCounter.ts +4 -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");
|
|
@@ -503,7 +503,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
503
503
|
}
|
|
504
504
|
/**
|
|
505
505
|
* Invokes the given callback and expects that no ops are submitted
|
|
506
|
-
* until execution finishes. If an op is submitted,
|
|
506
|
+
* until execution finishes. If an op is submitted, it will be marked as reentrant.
|
|
507
507
|
*
|
|
508
508
|
* @param callback - the callback to be invoked
|
|
509
509
|
*/
|
|
@@ -575,7 +575,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
575
575
|
// 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
576
|
this.pendingIdCompressorOps = [];
|
|
577
577
|
this.batchRunner = new runCounter_js_1.BatchRunCounter();
|
|
578
|
-
this.
|
|
578
|
+
this.flushScheduled = false;
|
|
579
579
|
this.consecutiveReconnects = 0;
|
|
580
580
|
this.dataModelChangeRunner = new runCounter_js_1.RunCounter();
|
|
581
581
|
this._disposed = false;
|
|
@@ -588,6 +588,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
588
588
|
this.snapshotCacheForLoadingGroupIds = new internal_2.PromiseCache({
|
|
589
589
|
expiry: { policy: "absolute", durationMs: 60000 },
|
|
590
590
|
});
|
|
591
|
+
this.extensions = new Map();
|
|
591
592
|
this.notifyReadOnlyState = (readonly) => this.channelCollection.notifyReadOnlyState(readonly);
|
|
592
593
|
/**
|
|
593
594
|
* Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
|
|
@@ -621,9 +622,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
621
622
|
(0, internal_2.assert)(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
|
|
622
623
|
this.rollback(runtimeOp, localOpMetadata);
|
|
623
624
|
});
|
|
624
|
-
|
|
625
|
-
this.updateDocumentDirtyState(this.pendingMessagesCount !== 0);
|
|
626
|
-
}
|
|
625
|
+
this.updateDocumentDirtyState();
|
|
627
626
|
}),
|
|
628
627
|
commitChanges: (optionsParam) => {
|
|
629
628
|
const options = { ...defaultStagingCommitOptions, ...optionsParam };
|
|
@@ -640,6 +639,15 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
640
639
|
return this.stageControls;
|
|
641
640
|
};
|
|
642
641
|
this.readAndParseBlob = async (id) => (0, internal_4.readAndParse)(this.storage, id);
|
|
642
|
+
// While internal, ContainerRuntime has not been converted to use the new events support.
|
|
643
|
+
// Recreate the required events (new pattern) with injected, wrapper new emitter.
|
|
644
|
+
// It is lazily create to avoid listeners (old events) that ultimately go nowhere.
|
|
645
|
+
this.lazyEventsForExtensions = new internal_2.Lazy(() => {
|
|
646
|
+
const eventEmitter = (0, client_utils_1.createEmitter)();
|
|
647
|
+
this.on("connected", (clientId) => eventEmitter.emit("connected", clientId));
|
|
648
|
+
this.on("disconnected", () => eventEmitter.emit("disconnected"));
|
|
649
|
+
return eventEmitter;
|
|
650
|
+
});
|
|
643
651
|
const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
|
|
644
652
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
645
653
|
this.disposeFn = disposeFn ?? closeFn;
|
|
@@ -668,7 +676,23 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
668
676
|
this.submitSummaryFn =
|
|
669
677
|
submitSummaryFn ??
|
|
670
678
|
((summaryOp, refseq) => submitFn(internal_3.MessageType.Summarize, summaryOp, false));
|
|
671
|
-
|
|
679
|
+
const sequenceAndSubmitSignal = (envelope, targetClientId) => {
|
|
680
|
+
if (targetClientId === undefined) {
|
|
681
|
+
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope);
|
|
682
|
+
}
|
|
683
|
+
submitSignalFn(envelope, targetClientId);
|
|
684
|
+
};
|
|
685
|
+
this.submitSignalFn = (envelope, targetClientId) => {
|
|
686
|
+
if (envelope.address?.startsWith("/")) {
|
|
687
|
+
throw new Error("General path based addressing is not implemented");
|
|
688
|
+
}
|
|
689
|
+
sequenceAndSubmitSignal(envelope, targetClientId);
|
|
690
|
+
};
|
|
691
|
+
this.submitExtensionSignal = (id, addressChain, message) => {
|
|
692
|
+
this.verifyNotClosed();
|
|
693
|
+
const envelope = createNewSignalEnvelope(`/ext/${id}/${addressChain.join("/")}`, message.type, message.content);
|
|
694
|
+
sequenceAndSubmitSignal(envelope, message.targetClientId);
|
|
695
|
+
};
|
|
672
696
|
// TODO: After IContainerContext.options is removed, we'll just create a new blank object {} here.
|
|
673
697
|
// Values are generally expected to be set from the runtime side.
|
|
674
698
|
this.options = options ?? {};
|
|
@@ -700,8 +724,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
700
724
|
this.mc.logger.sendTelemetryEvent({
|
|
701
725
|
eventName: "Attached",
|
|
702
726
|
details: {
|
|
703
|
-
|
|
704
|
-
|
|
727
|
+
lastEmittedDirty: this.lastEmittedDirty,
|
|
728
|
+
currentDirtyState: this.computeCurrentDirtyState(),
|
|
705
729
|
},
|
|
706
730
|
});
|
|
707
731
|
});
|
|
@@ -868,9 +892,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
868
892
|
// verifyNotClosed is called in FluidDataStoreContext, which is *the* expected caller.
|
|
869
893
|
const envelope1 = content;
|
|
870
894
|
const envelope2 = createNewSignalEnvelope(envelope1.address, type, envelope1.contents);
|
|
871
|
-
if (targetClientId === undefined) {
|
|
872
|
-
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope2);
|
|
873
|
-
}
|
|
874
895
|
this.submitSignalFn(envelope2, targetClientId);
|
|
875
896
|
};
|
|
876
897
|
let snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(baseSnapshot, metadata);
|
|
@@ -964,9 +985,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
964
985
|
const closeSummarizerDelayOverride = this.mc.config.getNumber("Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs");
|
|
965
986
|
this.closeSummarizerDelayMs =
|
|
966
987
|
closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
|
|
967
|
-
this
|
|
968
|
-
|
|
969
|
-
context.updateDirtyContainerState(this.
|
|
988
|
+
// We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
|
|
989
|
+
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
990
|
+
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
970
991
|
if (!this.skipSafetyFlushDuringProcessStack) {
|
|
971
992
|
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
972
993
|
// so we should flush now to clear the way for the next ops.
|
|
@@ -1434,32 +1455,29 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1434
1455
|
if (!this.canSendOps()) {
|
|
1435
1456
|
return;
|
|
1436
1457
|
}
|
|
1437
|
-
//
|
|
1438
|
-
// dirty state change events
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
const oldState = this.dirtyContainer;
|
|
1442
|
-
this.dirtyContainer = false;
|
|
1458
|
+
// Replaying is an internal operation and we don't want to generate noise while doing it.
|
|
1459
|
+
// So temporarily disable dirty state change events, and save the old state.
|
|
1460
|
+
// When we're done, we'll emit the event if the state changed.
|
|
1461
|
+
const oldState = this.lastEmittedDirty;
|
|
1443
1462
|
(0, internal_2.assert)(this.emitDirtyDocumentEvent, 0x127 /* "dirty document event not set on replay" */);
|
|
1444
1463
|
this.emitDirtyDocumentEvent = false;
|
|
1445
|
-
let newState;
|
|
1446
1464
|
try {
|
|
1447
1465
|
// Any ID Allocation ops that failed to submit after the pending state was queued need to have
|
|
1448
1466
|
// the corresponding ranges resubmitted (note this call replaces the typical resubmit flow).
|
|
1449
1467
|
// Since we don't submit ID Allocation ops when staged, any outstanding ranges would be from
|
|
1450
1468
|
// before staging mode so we can simply say staged: false.
|
|
1451
1469
|
this.submitIdAllocationOpIfNeeded({ resubmitOutstandingRanges: true, staged: false });
|
|
1470
|
+
this.scheduleFlush();
|
|
1452
1471
|
// replay the ops
|
|
1453
1472
|
this.pendingStateManager.replayPendingStates();
|
|
1454
1473
|
}
|
|
1455
1474
|
finally {
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
this.dirtyContainer = oldState;
|
|
1475
|
+
// Restore the old state, re-enable event emit
|
|
1476
|
+
this.lastEmittedDirty = oldState;
|
|
1459
1477
|
this.emitDirtyDocumentEvent = true;
|
|
1460
1478
|
}
|
|
1461
|
-
//
|
|
1462
|
-
this.updateDocumentDirtyState(
|
|
1479
|
+
// This will emit an event if the state changed relative to before replay
|
|
1480
|
+
this.updateDocumentDirtyState();
|
|
1463
1481
|
}
|
|
1464
1482
|
/**
|
|
1465
1483
|
* Parse an op's type and actual content from given serialized content
|
|
@@ -1721,6 +1739,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1721
1739
|
* @param groupedBatch - true if these messages are part of a grouped op batch.
|
|
1722
1740
|
*/
|
|
1723
1741
|
processInboundMessages(messagesWithMetadata, locationInBatch, local, savedOp, runtimeBatch, groupedBatch) {
|
|
1742
|
+
// This message could have been the last pending local (dirtyable) message, in which case we need to update dirty state to "saved"
|
|
1743
|
+
this.updateDocumentDirtyState();
|
|
1724
1744
|
if (locationInBatch.batchStart) {
|
|
1725
1745
|
const firstMessage = messagesWithMetadata[0]?.message;
|
|
1726
1746
|
(0, internal_2.assert)(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
@@ -1815,11 +1835,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1815
1835
|
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
1816
1836
|
}
|
|
1817
1837
|
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
1838
|
// The DeltaManager used to do this, but doesn't anymore as of Loader v2.4
|
|
1824
1839
|
// Anyone listening to our "op" event would expect the contents to be parsed per this same logic
|
|
1825
1840
|
if (typeof message.contents === "string" &&
|
|
@@ -1841,11 +1856,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1841
1856
|
*
|
|
1842
1857
|
*/
|
|
1843
1858
|
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
1859
|
// Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
|
|
1850
1860
|
const contents = messagesContent.map((c) => c.contents);
|
|
1851
1861
|
switch (message.type) {
|
|
@@ -1921,20 +1931,44 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1921
1931
|
if (message.clientId === this.clientId) {
|
|
1922
1932
|
this.signalTelemetryManager.trackReceivedSignal(envelope, this.mc.logger, this.consecutiveReconnects);
|
|
1923
1933
|
}
|
|
1924
|
-
|
|
1934
|
+
const fullAddress = envelope.address;
|
|
1935
|
+
if (fullAddress === undefined) {
|
|
1925
1936
|
// No address indicates a container signal message.
|
|
1926
1937
|
this.emit("signal", transformed, local);
|
|
1927
1938
|
return;
|
|
1928
1939
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1940
|
+
this.routeNonContainerSignal(fullAddress, transformed, local);
|
|
1941
|
+
}
|
|
1942
|
+
routeNonContainerSignal(address, signalMessage, local) {
|
|
1943
|
+
// channelCollection signals are identified by no starting `/` in address.
|
|
1944
|
+
if (!address.startsWith("/")) {
|
|
1945
|
+
// Due to a mismatch between different layers in terms of
|
|
1946
|
+
// what is the interface of passing signals, we need to adjust
|
|
1947
|
+
// the signal envelope before sending it to the datastores to be processed
|
|
1948
|
+
const envelope = {
|
|
1949
|
+
address,
|
|
1950
|
+
contents: signalMessage.content,
|
|
1951
|
+
};
|
|
1952
|
+
signalMessage.content = envelope;
|
|
1953
|
+
this.channelCollection.processSignal(signalMessage, local);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const addresses = address.split("/");
|
|
1957
|
+
if (addresses.length > 2 && addresses[1] === "ext") {
|
|
1958
|
+
const id = addresses[2];
|
|
1959
|
+
const entry = this.extensions.get(id);
|
|
1960
|
+
if (entry !== undefined) {
|
|
1961
|
+
entry.extension.processSignal?.(addresses.slice(3), signalMessage, local);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
(0, internal_2.assert)(!local, 0xba0 /* No recipient found for local signal */);
|
|
1966
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1967
|
+
eventName: "SignalAddressNotFound",
|
|
1968
|
+
...(0, internal_8.tagCodeArtifacts)({
|
|
1969
|
+
address,
|
|
1970
|
+
}),
|
|
1971
|
+
});
|
|
1938
1972
|
}
|
|
1939
1973
|
/**
|
|
1940
1974
|
* Flush the current batch of ops to the ordering service for sequencing
|
|
@@ -1944,6 +1978,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1944
1978
|
* @param resubmitInfo - If defined, indicates this is a resubmission of a batch with the given Batch info needed for resubmit.
|
|
1945
1979
|
*/
|
|
1946
1980
|
flush(resubmitInfo) {
|
|
1981
|
+
this.flushScheduled = false;
|
|
1947
1982
|
try {
|
|
1948
1983
|
(0, internal_2.assert)(!this.batchRunner.running, 0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */);
|
|
1949
1984
|
this.outbox.flush(resubmitInfo);
|
|
@@ -1964,7 +1999,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1964
1999
|
*/
|
|
1965
2000
|
orderSequentially(callback) {
|
|
1966
2001
|
let checkpoint;
|
|
1967
|
-
const checkpointDirtyState = this.dirtyContainer;
|
|
1968
2002
|
// eslint-disable-next-line import/no-deprecated
|
|
1969
2003
|
let stageControls;
|
|
1970
2004
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
@@ -1985,10 +2019,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
1985
2019
|
// This will throw and close the container if rollback fails
|
|
1986
2020
|
try {
|
|
1987
2021
|
checkpoint.rollback((message) => this.rollback(message.runtimeOp, message.localOpMetadata));
|
|
1988
|
-
|
|
1989
|
-
if (this.dirtyContainer !== checkpointDirtyState) {
|
|
1990
|
-
this.updateDocumentDirtyState(checkpointDirtyState);
|
|
1991
|
-
}
|
|
2022
|
+
this.updateDocumentDirtyState();
|
|
1992
2023
|
stageControls?.discardChanges();
|
|
1993
2024
|
stageControls = undefined;
|
|
1994
2025
|
}
|
|
@@ -2069,12 +2100,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2069
2100
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
2070
2101
|
return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
|
|
2071
2102
|
}
|
|
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
2103
|
getQuorum() {
|
|
2079
2104
|
return this._quorum;
|
|
2080
2105
|
}
|
|
@@ -2086,36 +2111,17 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2086
2111
|
* either were not sent out to delta stream or were not yet acknowledged.
|
|
2087
2112
|
*/
|
|
2088
2113
|
get isDirty() {
|
|
2089
|
-
|
|
2114
|
+
// Rather than recomputing the dirty state in this moment,
|
|
2115
|
+
// just regurgitate the last emitted dirty state.
|
|
2116
|
+
return this.lastEmittedDirty;
|
|
2090
2117
|
}
|
|
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;
|
|
2118
|
+
/**
|
|
2119
|
+
* Returns true if the container is dirty: not attached, or no pending user messages (could be some "non-dirtyable" ones though)
|
|
2120
|
+
*/
|
|
2121
|
+
computeCurrentDirtyState() {
|
|
2122
|
+
return (this.attachState !== container_definitions_1.AttachState.Attached ||
|
|
2123
|
+
this.pendingStateManager.hasPendingUserChanges() ||
|
|
2124
|
+
this.outbox.containsUserChanges());
|
|
2119
2125
|
}
|
|
2120
2126
|
/**
|
|
2121
2127
|
* Submits the signal to be sent to other clients.
|
|
@@ -2132,9 +2138,6 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2132
2138
|
submitSignal(type, content, targetClientId) {
|
|
2133
2139
|
this.verifyNotClosed();
|
|
2134
2140
|
const envelope = createNewSignalEnvelope(undefined /* address */, type, content);
|
|
2135
|
-
if (targetClientId === undefined) {
|
|
2136
|
-
this.signalTelemetryManager.applyTrackingToBroadcastSignalEnvelope(envelope);
|
|
2137
|
-
}
|
|
2138
2141
|
this.submitSignalFn(envelope, targetClientId);
|
|
2139
2142
|
}
|
|
2140
2143
|
setAttachState(attachState) {
|
|
@@ -2145,9 +2148,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2145
2148
|
(0, internal_2.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
2146
2149
|
this.emit("attached");
|
|
2147
2150
|
}
|
|
2148
|
-
|
|
2149
|
-
this.updateDocumentDirtyState(false);
|
|
2150
|
-
}
|
|
2151
|
+
this.updateDocumentDirtyState();
|
|
2151
2152
|
this.channelCollection.setAttachState(attachState);
|
|
2152
2153
|
}
|
|
2153
2154
|
/**
|
|
@@ -2715,18 +2716,20 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2715
2716
|
hasPendingMessages() {
|
|
2716
2717
|
return this.pendingMessagesCount !== 0;
|
|
2717
2718
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2719
|
+
/**
|
|
2720
|
+
* Emit "dirty" or "saved" event based on the current dirty state of the document.
|
|
2721
|
+
* This must be called every time the states underlying the dirty state change.
|
|
2722
|
+
*
|
|
2723
|
+
* @privateRemarks - It's helpful to think of this as an event handler registered
|
|
2724
|
+
* for hypothetical "changed" events for PendingStateManager, Outbox, and Container Attach machinery.
|
|
2725
|
+
* But those events don't exist so we manually call this wherever we know those changes happen.
|
|
2726
|
+
*/
|
|
2727
|
+
updateDocumentDirtyState() {
|
|
2728
|
+
const dirty = this.computeCurrentDirtyState();
|
|
2729
|
+
if (this.lastEmittedDirty === dirty) {
|
|
2727
2730
|
return;
|
|
2728
2731
|
}
|
|
2729
|
-
this.
|
|
2732
|
+
this.lastEmittedDirty = dirty;
|
|
2730
2733
|
if (this.emitDirtyDocumentEvent) {
|
|
2731
2734
|
this.emit(dirty ? "dirty" : "saved");
|
|
2732
2735
|
}
|
|
@@ -2824,14 +2827,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2824
2827
|
else {
|
|
2825
2828
|
this.outbox.submit(message);
|
|
2826
2829
|
}
|
|
2827
|
-
|
|
2828
|
-
const flushImmediatelyOnSubmit = !this.currentlyBatching();
|
|
2829
|
-
if (flushImmediatelyOnSubmit) {
|
|
2830
|
-
this.flush();
|
|
2831
|
-
}
|
|
2832
|
-
else {
|
|
2833
|
-
this.scheduleFlush();
|
|
2834
|
-
}
|
|
2830
|
+
this.scheduleFlush();
|
|
2835
2831
|
}
|
|
2836
2832
|
catch (error) {
|
|
2837
2833
|
const dpe = internal_8.DataProcessingError.wrapIfUnrecognized(error, "ContainerRuntime.submit", {
|
|
@@ -2840,27 +2836,26 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2840
2836
|
this.closeFn(dpe);
|
|
2841
2837
|
throw dpe;
|
|
2842
2838
|
}
|
|
2843
|
-
|
|
2844
|
-
this.updateDocumentDirtyState(true);
|
|
2845
|
-
}
|
|
2839
|
+
this.updateDocumentDirtyState();
|
|
2846
2840
|
}
|
|
2847
2841
|
scheduleFlush() {
|
|
2848
|
-
if (this.
|
|
2842
|
+
if (this.flushScheduled) {
|
|
2849
2843
|
return;
|
|
2850
2844
|
}
|
|
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
|
-
};
|
|
2845
|
+
this.flushScheduled = true;
|
|
2858
2846
|
switch (this.flushMode) {
|
|
2847
|
+
case internal_6.FlushMode.Immediate: {
|
|
2848
|
+
// When in Immediate flush mode, flush immediately unless we are intentionally batching multiple ops (e.g. via orderSequentially)
|
|
2849
|
+
if (!this.batchRunner.running) {
|
|
2850
|
+
this.flush();
|
|
2851
|
+
}
|
|
2852
|
+
break;
|
|
2853
|
+
}
|
|
2859
2854
|
case internal_6.FlushMode.TurnBased: {
|
|
2860
2855
|
// When in TurnBased flush mode the runtime will buffer operations in the current turn and send them as a single
|
|
2861
2856
|
// batch at the end of the turn
|
|
2862
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2863
|
-
Promise.resolve().then(flush);
|
|
2857
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- Container will close if flush throws
|
|
2858
|
+
Promise.resolve().then(() => this.flush());
|
|
2864
2859
|
break;
|
|
2865
2860
|
}
|
|
2866
2861
|
// FlushModeExperimental is experimental and not exposed directly in the runtime APIs
|
|
@@ -2868,12 +2863,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
2868
2863
|
// When in Async flush mode, the runtime will accumulate all operations across JS turns and send them as a single
|
|
2869
2864
|
// batch when all micro-tasks are complete.
|
|
2870
2865
|
// Compared to TurnBased, this flush mode will capture more ops into the same batch.
|
|
2871
|
-
setTimeout(flush, 0);
|
|
2866
|
+
setTimeout(() => this.flush(), 0);
|
|
2872
2867
|
break;
|
|
2873
2868
|
}
|
|
2874
2869
|
default: {
|
|
2875
|
-
(0, internal_2.
|
|
2876
|
-
break;
|
|
2870
|
+
(0, internal_2.fail)(0x587 /* Unreachable unless manually accumulating a batch */);
|
|
2877
2871
|
}
|
|
2878
2872
|
}
|
|
2879
2873
|
}
|
|
@@ -3144,6 +3138,29 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
|
|
|
3144
3138
|
return this.summaryManager.enqueueSummarize(options);
|
|
3145
3139
|
}
|
|
3146
3140
|
}
|
|
3141
|
+
acquireExtension(id, factory, ...useContext) {
|
|
3142
|
+
let entry = this.extensions.get(id);
|
|
3143
|
+
if (entry === undefined) {
|
|
3144
|
+
const runtime = {
|
|
3145
|
+
isConnected: () => this.connected,
|
|
3146
|
+
getClientId: () => this.clientId,
|
|
3147
|
+
events: this.lazyEventsForExtensions.value,
|
|
3148
|
+
logger: this.baseLogger,
|
|
3149
|
+
submitAddressedSignal: (addressChain, message) => {
|
|
3150
|
+
this.submitExtensionSignal(id, addressChain, message);
|
|
3151
|
+
},
|
|
3152
|
+
getQuorum: this.getQuorum.bind(this),
|
|
3153
|
+
getAudience: this.getAudience.bind(this),
|
|
3154
|
+
};
|
|
3155
|
+
entry = new factory(runtime, ...useContext);
|
|
3156
|
+
this.extensions.set(id, entry);
|
|
3157
|
+
}
|
|
3158
|
+
else {
|
|
3159
|
+
(0, internal_2.assert)(entry instanceof factory, 0xba1 /* Extension entry is not of the expected type */);
|
|
3160
|
+
entry.extension.onNewUse(...useContext);
|
|
3161
|
+
}
|
|
3162
|
+
return entry.interface;
|
|
3163
|
+
}
|
|
3147
3164
|
get groupedBatchingEnabled() {
|
|
3148
3165
|
return this.sessionSchema.opGroupingEnabled === true;
|
|
3149
3166
|
}
|
|
@@ -3157,4 +3174,34 @@ function createNewSignalEnvelope(address, type, content) {
|
|
|
3157
3174
|
return newEnvelope;
|
|
3158
3175
|
}
|
|
3159
3176
|
exports.createNewSignalEnvelope = createNewSignalEnvelope;
|
|
3177
|
+
function isContainerMessageDirtyable({ type, contents, }) {
|
|
3178
|
+
// Certain container runtime messages should not mark the container dirty such as the old built-in
|
|
3179
|
+
// AgentScheduler and Garbage collector messages.
|
|
3180
|
+
switch (type) {
|
|
3181
|
+
case messageTypes_js_1.ContainerMessageType.Attach: {
|
|
3182
|
+
const attachMessage = contents;
|
|
3183
|
+
if (attachMessage.id === exports.agentSchedulerId) {
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
break;
|
|
3187
|
+
}
|
|
3188
|
+
case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp: {
|
|
3189
|
+
const envelope = contents;
|
|
3190
|
+
if (envelope.address === exports.agentSchedulerId) {
|
|
3191
|
+
return false;
|
|
3192
|
+
}
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3195
|
+
case messageTypes_js_1.ContainerMessageType.IdAllocation:
|
|
3196
|
+
case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange:
|
|
3197
|
+
case messageTypes_js_1.ContainerMessageType.GC: {
|
|
3198
|
+
return false;
|
|
3199
|
+
}
|
|
3200
|
+
default: {
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return true;
|
|
3205
|
+
}
|
|
3206
|
+
exports.isContainerMessageDirtyable = isContainerMessageDirtyable;
|
|
3160
3207
|
//# sourceMappingURL=containerRuntime.js.map
|