@fluidframework/container-runtime 2.41.0 → 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 +4 -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 +34 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +158 -59
- 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/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/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +18 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +20 -13
- package/dist/pendingStateManager.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 +34 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +160 -61
- 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/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/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +18 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +20 -13
- package/lib/pendingStateManager.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 +18 -18
- package/src/channelCollection.ts +4 -4
- package/src/compatUtils.ts +145 -10
- package/src/containerRuntime.ts +209 -73
- 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/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/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +49 -22
- package/src/summary/documentSchema.ts +111 -86
- package/src/summary/index.ts +2 -1
package/src/containerRuntime.ts
CHANGED
|
@@ -33,6 +33,8 @@ import type {
|
|
|
33
33
|
IContainerRuntime,
|
|
34
34
|
IContainerRuntimeEvents,
|
|
35
35
|
IContainerRuntimeInternal,
|
|
36
|
+
// eslint-disable-next-line import/no-deprecated
|
|
37
|
+
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
36
38
|
OutboundExtensionMessage,
|
|
37
39
|
} from "@fluidframework/container-runtime-definitions/internal";
|
|
38
40
|
import type {
|
|
@@ -60,6 +62,7 @@ import {
|
|
|
60
62
|
PromiseCache,
|
|
61
63
|
delay,
|
|
62
64
|
fail,
|
|
65
|
+
unreachableCase,
|
|
63
66
|
} from "@fluidframework/core-utils/internal";
|
|
64
67
|
import type {
|
|
65
68
|
IClientDetails,
|
|
@@ -180,6 +183,7 @@ import {
|
|
|
180
183
|
isValidMinVersionForCollab,
|
|
181
184
|
type RuntimeOptionsAffectingDocSchema,
|
|
182
185
|
type MinimumVersionForCollab,
|
|
186
|
+
validateRuntimeOptions,
|
|
183
187
|
} from "./compatUtils.js";
|
|
184
188
|
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
185
189
|
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
@@ -206,7 +210,7 @@ import {
|
|
|
206
210
|
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
|
|
207
211
|
import {
|
|
208
212
|
ContainerMessageType,
|
|
209
|
-
type
|
|
213
|
+
type OutboundContainerRuntimeDocumentSchemaMessage,
|
|
210
214
|
ContainerRuntimeGCMessage,
|
|
211
215
|
type ContainerRuntimeIdAllocationMessage,
|
|
212
216
|
type InboundSequencedContainerRuntimeMessage,
|
|
@@ -244,7 +248,7 @@ import {
|
|
|
244
248
|
import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
|
|
245
249
|
// These types are imported as types here because they are present in summaryDelayLoadedModule, which is loaded dynamically when required.
|
|
246
250
|
import type {
|
|
247
|
-
|
|
251
|
+
IDocumentSchemaChangeMessageIncoming,
|
|
248
252
|
IDocumentSchemaCurrent,
|
|
249
253
|
Summarizer,
|
|
250
254
|
IDocumentSchemaFeatures,
|
|
@@ -772,6 +776,27 @@ export async function loadContainerRuntime(
|
|
|
772
776
|
|
|
773
777
|
const defaultMaxConsecutiveReconnects = 7;
|
|
774
778
|
|
|
779
|
+
/**
|
|
780
|
+
* These are the ONLY message types that are allowed to be submitted while in staging mode
|
|
781
|
+
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
|
|
782
|
+
*/
|
|
783
|
+
function canStageMessageOfType(
|
|
784
|
+
type: LocalContainerRuntimeMessage["type"],
|
|
785
|
+
): type is
|
|
786
|
+
| ContainerMessageType.FluidDataStoreOp
|
|
787
|
+
| ContainerMessageType.GC
|
|
788
|
+
| ContainerMessageType.DocumentSchemaChange {
|
|
789
|
+
return (
|
|
790
|
+
// These are user changes coming up from the runtime's DataStores
|
|
791
|
+
type === ContainerMessageType.FluidDataStoreOp ||
|
|
792
|
+
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
|
|
793
|
+
// These can be submitted at any time, including while in Staging Mode.
|
|
794
|
+
type === ContainerMessageType.GC ||
|
|
795
|
+
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
|
|
796
|
+
type === ContainerMessageType.DocumentSchemaChange
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
775
800
|
/**
|
|
776
801
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
777
802
|
* It will define the store level mappings.
|
|
@@ -784,6 +809,8 @@ export class ContainerRuntime
|
|
|
784
809
|
IContainerRuntimeInternal,
|
|
785
810
|
// eslint-disable-next-line import/no-deprecated
|
|
786
811
|
IContainerRuntimeBaseExperimental,
|
|
812
|
+
// eslint-disable-next-line import/no-deprecated
|
|
813
|
+
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
787
814
|
IRuntime,
|
|
788
815
|
IGarbageCollectionRuntime,
|
|
789
816
|
ISummarizerRuntime,
|
|
@@ -862,6 +889,10 @@ export class ContainerRuntime
|
|
|
862
889
|
`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`,
|
|
863
890
|
);
|
|
864
891
|
}
|
|
892
|
+
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
|
|
893
|
+
// were manually set.
|
|
894
|
+
validateRuntimeOptions(minVersionForCollab, runtimeOptions);
|
|
895
|
+
|
|
865
896
|
const defaultsAffectingDocSchema = getMinVersionForCollabDefaults(minVersionForCollab);
|
|
866
897
|
|
|
867
898
|
// The following are the default values for the options that do not affect the DocumentSchema.
|
|
@@ -1160,6 +1191,8 @@ export class ContainerRuntime
|
|
|
1160
1191
|
|
|
1161
1192
|
public readonly clientDetails: IClientDetails;
|
|
1162
1193
|
|
|
1194
|
+
private readonly isSummarizerClient: boolean;
|
|
1195
|
+
|
|
1163
1196
|
public get storage(): IDocumentStorageService {
|
|
1164
1197
|
return this._storage;
|
|
1165
1198
|
}
|
|
@@ -1292,7 +1325,7 @@ export class ContainerRuntime
|
|
|
1292
1325
|
private readonly offlineEnabled: boolean;
|
|
1293
1326
|
private flushScheduled = false;
|
|
1294
1327
|
|
|
1295
|
-
private
|
|
1328
|
+
private canSendOps: boolean;
|
|
1296
1329
|
|
|
1297
1330
|
private consecutiveReconnects = 0;
|
|
1298
1331
|
|
|
@@ -1314,8 +1347,12 @@ export class ContainerRuntime
|
|
|
1314
1347
|
return this.dataModelChangeRunner.run(callback);
|
|
1315
1348
|
}
|
|
1316
1349
|
|
|
1350
|
+
/**
|
|
1351
|
+
* Indicates whether the container is in a state where it is able to send
|
|
1352
|
+
* ops (connected to op stream and not in readonly mode).
|
|
1353
|
+
*/
|
|
1317
1354
|
public get connected(): boolean {
|
|
1318
|
-
return this.
|
|
1355
|
+
return this.canSendOps;
|
|
1319
1356
|
}
|
|
1320
1357
|
|
|
1321
1358
|
/**
|
|
@@ -1502,6 +1539,11 @@ export class ContainerRuntime
|
|
|
1502
1539
|
this.mc = createChildMonitoringContext({
|
|
1503
1540
|
logger: this.baseLogger,
|
|
1504
1541
|
namespace: "ContainerRuntime",
|
|
1542
|
+
properties: {
|
|
1543
|
+
all: {
|
|
1544
|
+
inStagingMode: this.inStagingMode,
|
|
1545
|
+
},
|
|
1546
|
+
},
|
|
1505
1547
|
});
|
|
1506
1548
|
|
|
1507
1549
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
@@ -1558,7 +1600,7 @@ export class ContainerRuntime
|
|
|
1558
1600
|
// Values are generally expected to be set from the runtime side.
|
|
1559
1601
|
this.options = options ?? {};
|
|
1560
1602
|
this.clientDetails = clientDetails;
|
|
1561
|
-
|
|
1603
|
+
this.isSummarizerClient = this.clientDetails.type === summarizerClientType;
|
|
1562
1604
|
this.loadedFromVersionId = context.getLoadedFromVersion()?.id;
|
|
1563
1605
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
1564
1606
|
this._getClientId = () => context.clientId;
|
|
@@ -1599,7 +1641,7 @@ export class ContainerRuntime
|
|
|
1599
1641
|
);
|
|
1600
1642
|
|
|
1601
1643
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
1602
|
-
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
1644
|
+
this.closeFn = this.isSummarizerClient ? this.disposeFn : closeFn;
|
|
1603
1645
|
|
|
1604
1646
|
let loadSummaryNumber: number;
|
|
1605
1647
|
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
@@ -1625,7 +1667,7 @@ export class ContainerRuntime
|
|
|
1625
1667
|
|
|
1626
1668
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
1627
1669
|
// Later updates come through calls to setConnectionState.
|
|
1628
|
-
this.
|
|
1670
|
+
this.canSendOps = connected;
|
|
1629
1671
|
|
|
1630
1672
|
this.mc.logger.sendTelemetryEvent({
|
|
1631
1673
|
eventName: "GCFeatureMatrix",
|
|
@@ -1760,7 +1802,7 @@ export class ContainerRuntime
|
|
|
1760
1802
|
existing,
|
|
1761
1803
|
metadata,
|
|
1762
1804
|
createContainerMetadata: this.createContainerMetadata,
|
|
1763
|
-
isSummarizerClient,
|
|
1805
|
+
isSummarizerClient: this.isSummarizerClient,
|
|
1764
1806
|
getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1765
1807
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
1766
1808
|
readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
@@ -1887,7 +1929,7 @@ export class ContainerRuntime
|
|
|
1887
1929
|
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
1888
1930
|
|
|
1889
1931
|
this.outbox = new Outbox({
|
|
1890
|
-
shouldSend: () => this.
|
|
1932
|
+
shouldSend: () => this.shouldSendOps(),
|
|
1891
1933
|
pendingStateManager: this.pendingStateManager,
|
|
1892
1934
|
submitBatchFn,
|
|
1893
1935
|
legacySendBatchFn,
|
|
@@ -2068,7 +2110,18 @@ export class ContainerRuntime
|
|
|
2068
2110
|
this.sessionSchema.idCompressorMode === "on" ||
|
|
2069
2111
|
(this.sessionSchema.idCompressorMode === "delayed" && this.connected)
|
|
2070
2112
|
) {
|
|
2071
|
-
|
|
2113
|
+
PerformanceEvent.timedExec(
|
|
2114
|
+
this.mc.logger,
|
|
2115
|
+
{ eventName: "CreateIdCompressorOnBoot" },
|
|
2116
|
+
(event) => {
|
|
2117
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
2118
|
+
event.end({
|
|
2119
|
+
details: {
|
|
2120
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
2121
|
+
},
|
|
2122
|
+
});
|
|
2123
|
+
},
|
|
2124
|
+
);
|
|
2072
2125
|
// This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
|
|
2073
2126
|
assert(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
|
|
2074
2127
|
}
|
|
@@ -2125,8 +2178,7 @@ export class ContainerRuntime
|
|
|
2125
2178
|
maxOpsSinceLastSummary,
|
|
2126
2179
|
);
|
|
2127
2180
|
|
|
2128
|
-
|
|
2129
|
-
if (isSummarizerClient) {
|
|
2181
|
+
if (this.isSummarizerClient) {
|
|
2130
2182
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
2131
2183
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
2132
2184
|
const module = await import(
|
|
@@ -2575,7 +2627,7 @@ export class ContainerRuntime
|
|
|
2575
2627
|
|
|
2576
2628
|
private replayPendingStates(): void {
|
|
2577
2629
|
// We need to be able to send ops to replay states
|
|
2578
|
-
if (!this.
|
|
2630
|
+
if (!this.shouldSendOps()) {
|
|
2579
2631
|
return;
|
|
2580
2632
|
}
|
|
2581
2633
|
|
|
@@ -2671,13 +2723,27 @@ export class ContainerRuntime
|
|
|
2671
2723
|
this._idCompressor === undefined &&
|
|
2672
2724
|
this.sessionSchema.idCompressorMode !== undefined
|
|
2673
2725
|
) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2726
|
+
PerformanceEvent.timedExec(
|
|
2727
|
+
this.mc.logger,
|
|
2728
|
+
{ eventName: "CreateIdCompressorOnDelayedLoad" },
|
|
2729
|
+
(event) => {
|
|
2730
|
+
this._idCompressor = this.createIdCompressorFn();
|
|
2731
|
+
// Finalize any ranges we received while the compressor was turned off.
|
|
2732
|
+
const ops = this.pendingIdCompressorOps;
|
|
2733
|
+
this.pendingIdCompressorOps = [];
|
|
2734
|
+
const trace = Trace.start();
|
|
2735
|
+
for (const range of ops) {
|
|
2736
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
2737
|
+
}
|
|
2738
|
+
event.end({
|
|
2739
|
+
details: {
|
|
2740
|
+
finalizeCreationRangeDuration: trace.trace().duration,
|
|
2741
|
+
idCompressorMode: this.sessionSchema.idCompressorMode,
|
|
2742
|
+
pendingIdCompressorOps: ops.length,
|
|
2743
|
+
},
|
|
2744
|
+
});
|
|
2745
|
+
},
|
|
2746
|
+
);
|
|
2681
2747
|
assert(this.pendingIdCompressorOps.length === 0, 0x976 /* No new ops added */);
|
|
2682
2748
|
}
|
|
2683
2749
|
}
|
|
@@ -2685,7 +2751,7 @@ export class ContainerRuntime
|
|
|
2685
2751
|
private readonly notifyReadOnlyState = (readonly: boolean): void =>
|
|
2686
2752
|
this.channelCollection.notifyReadOnlyState(readonly);
|
|
2687
2753
|
|
|
2688
|
-
public setConnectionState(
|
|
2754
|
+
public setConnectionState(canSendOps: boolean, clientId?: string): void {
|
|
2689
2755
|
// Validate we have consistent state
|
|
2690
2756
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
2691
2757
|
assert(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
@@ -2694,10 +2760,10 @@ export class ContainerRuntime
|
|
|
2694
2760
|
0x978 /* this.clientId does not match Audience */,
|
|
2695
2761
|
);
|
|
2696
2762
|
|
|
2697
|
-
if (
|
|
2763
|
+
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
2698
2764
|
this.loadIdCompressor();
|
|
2699
2765
|
}
|
|
2700
|
-
if (
|
|
2766
|
+
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
2701
2767
|
this.delayConnectClientId = undefined;
|
|
2702
2768
|
this.mc.logger.sendTelemetryEvent({
|
|
2703
2769
|
eventName: "UnsuccessfulConnectedTransition",
|
|
@@ -2706,14 +2772,10 @@ export class ContainerRuntime
|
|
|
2706
2772
|
return;
|
|
2707
2773
|
}
|
|
2708
2774
|
|
|
2709
|
-
if (!connected) {
|
|
2710
|
-
this.documentsSchemaController.onDisconnect();
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
2775
|
// If there are stashed blobs in the pending state, we need to delay
|
|
2714
2776
|
// propagation of the "connected" event until we have uploaded them to
|
|
2715
2777
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
2716
|
-
const connecting =
|
|
2778
|
+
const connecting = canSendOps && !this.canSendOps;
|
|
2717
2779
|
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
2718
2780
|
assert(
|
|
2719
2781
|
!this.delayConnectClientId,
|
|
@@ -2724,10 +2786,15 @@ export class ContainerRuntime
|
|
|
2724
2786
|
return;
|
|
2725
2787
|
}
|
|
2726
2788
|
|
|
2727
|
-
this.setConnectionStateCore(
|
|
2789
|
+
this.setConnectionStateCore(canSendOps, clientId);
|
|
2728
2790
|
}
|
|
2729
2791
|
|
|
2730
|
-
|
|
2792
|
+
/**
|
|
2793
|
+
* Raises and propagates connected events.
|
|
2794
|
+
* @param canSendOps - Indicates whether the container can send ops or not (connected and not readonly).
|
|
2795
|
+
* @remarks The connection state from container context used here when raising connected events.
|
|
2796
|
+
*/
|
|
2797
|
+
private setConnectionStateCore(canSendOps: boolean, clientId?: string): void {
|
|
2731
2798
|
assert(
|
|
2732
2799
|
!this.delayConnectClientId,
|
|
2733
2800
|
0x394 /* connect event delay must be cleared before propagating connect event */,
|
|
@@ -2735,24 +2802,24 @@ export class ContainerRuntime
|
|
|
2735
2802
|
this.verifyNotClosed();
|
|
2736
2803
|
|
|
2737
2804
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
2738
|
-
const
|
|
2739
|
-
const reconnection =
|
|
2805
|
+
const canSendOpsChanged = this.canSendOps !== canSendOps;
|
|
2806
|
+
const reconnection = canSendOpsChanged && !canSendOps;
|
|
2740
2807
|
|
|
2741
2808
|
// We need to flush the ops currently collected by Outbox to preserve original order.
|
|
2742
2809
|
// This flush NEEDS to happen before we set the ContainerRuntime to "connected".
|
|
2743
2810
|
// We want these ops to get to the PendingStateManager without sending to service and have them return to the Outbox upon calling "replayPendingStates".
|
|
2744
|
-
if (
|
|
2811
|
+
if (canSendOpsChanged && canSendOps) {
|
|
2745
2812
|
this.flush();
|
|
2746
2813
|
}
|
|
2747
2814
|
|
|
2748
|
-
this.
|
|
2815
|
+
this.canSendOps = canSendOps;
|
|
2749
2816
|
|
|
2750
|
-
if (
|
|
2817
|
+
if (canSendOps) {
|
|
2751
2818
|
assert(
|
|
2752
2819
|
this.attachState === AttachState.Attached,
|
|
2753
2820
|
0x3cd /* Connection is possible only if container exists in storage */,
|
|
2754
2821
|
);
|
|
2755
|
-
if (
|
|
2822
|
+
if (canSendOpsChanged) {
|
|
2756
2823
|
this.signalTelemetryManager.resetTracking();
|
|
2757
2824
|
}
|
|
2758
2825
|
}
|
|
@@ -2778,14 +2845,14 @@ export class ContainerRuntime
|
|
|
2778
2845
|
}
|
|
2779
2846
|
}
|
|
2780
2847
|
|
|
2781
|
-
if (
|
|
2848
|
+
if (canSendOpsChanged) {
|
|
2782
2849
|
this.replayPendingStates();
|
|
2783
2850
|
}
|
|
2784
2851
|
|
|
2785
|
-
this.channelCollection.setConnectionState(
|
|
2786
|
-
this.garbageCollector.setConnectionState(
|
|
2852
|
+
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
2853
|
+
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
2787
2854
|
|
|
2788
|
-
raiseConnectedEvent(this.mc.logger, this, connected
|
|
2855
|
+
raiseConnectedEvent(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
2789
2856
|
}
|
|
2790
2857
|
|
|
2791
2858
|
public async notifyOpReplay(message: ISequencedDocumentMessage): Promise<void> {
|
|
@@ -3161,7 +3228,7 @@ export class ContainerRuntime
|
|
|
3161
3228
|
}
|
|
3162
3229
|
case ContainerMessageType.DocumentSchemaChange: {
|
|
3163
3230
|
this.documentsSchemaController.processDocumentSchemaMessages(
|
|
3164
|
-
contents as
|
|
3231
|
+
contents as IDocumentSchemaChangeMessageIncoming[],
|
|
3165
3232
|
local,
|
|
3166
3233
|
message.sequenceNumber,
|
|
3167
3234
|
);
|
|
@@ -3334,7 +3401,8 @@ export class ContainerRuntime
|
|
|
3334
3401
|
// This will throw and close the container if rollback fails
|
|
3335
3402
|
try {
|
|
3336
3403
|
checkpoint.rollback((message: LocalBatchMessage) =>
|
|
3337
|
-
|
|
3404
|
+
// These changes are staged since we entered staging mode above
|
|
3405
|
+
this.rollbackStagedChanges(message.runtimeOp, message.localOpMetadata),
|
|
3338
3406
|
);
|
|
3339
3407
|
this.updateDocumentDirtyState();
|
|
3340
3408
|
stageControls?.discardChanges();
|
|
@@ -3400,7 +3468,10 @@ export class ContainerRuntime
|
|
|
3400
3468
|
// eslint-disable-next-line import/no-deprecated
|
|
3401
3469
|
public enterStagingMode = (): StageControlsExperimental => {
|
|
3402
3470
|
if (this.stageControls !== undefined) {
|
|
3403
|
-
throw new
|
|
3471
|
+
throw new UsageError("already in staging mode");
|
|
3472
|
+
}
|
|
3473
|
+
if (this.attachState === AttachState.Detached) {
|
|
3474
|
+
throw new UsageError("cannot enter staging mode while detached");
|
|
3404
3475
|
}
|
|
3405
3476
|
|
|
3406
3477
|
// Make sure all BatchManagers are empty before entering staging mode,
|
|
@@ -3430,7 +3501,7 @@ export class ContainerRuntime
|
|
|
3430
3501
|
runtimeOp !== undefined,
|
|
3431
3502
|
0xb82 /* Staged batches expected to have runtimeOp defined */,
|
|
3432
3503
|
);
|
|
3433
|
-
this.
|
|
3504
|
+
this.rollbackStagedChanges(runtimeOp, localOpMetadata);
|
|
3434
3505
|
});
|
|
3435
3506
|
this.updateDocumentDirtyState();
|
|
3436
3507
|
}),
|
|
@@ -3514,7 +3585,7 @@ export class ContainerRuntime
|
|
|
3514
3585
|
);
|
|
3515
3586
|
}
|
|
3516
3587
|
|
|
3517
|
-
private
|
|
3588
|
+
private shouldSendOps(): boolean {
|
|
3518
3589
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
3519
3590
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
3520
3591
|
return (
|
|
@@ -4479,6 +4550,11 @@ export class ContainerRuntime
|
|
|
4479
4550
|
// If we're resubmitting a batch, keep the same "staged" value as before. Otherwise, use the current "global" state.
|
|
4480
4551
|
const staged = this.batchRunner.resubmitInfo?.staged ?? this.inStagingMode;
|
|
4481
4552
|
|
|
4553
|
+
assert(
|
|
4554
|
+
!staged || canStageMessageOfType(type),
|
|
4555
|
+
0xbba /* Unexpected message type submitted in Staging Mode */,
|
|
4556
|
+
);
|
|
4557
|
+
|
|
4482
4558
|
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
4483
4559
|
if (!staged) {
|
|
4484
4560
|
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
@@ -4487,7 +4563,7 @@ export class ContainerRuntime
|
|
|
4487
4563
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
4488
4564
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
4489
4565
|
// on this callback to do actual sending.
|
|
4490
|
-
const schemaChangeMessage = this.documentsSchemaController.
|
|
4566
|
+
const schemaChangeMessage = this.documentsSchemaController.maybeGenerateSchemaMessage();
|
|
4491
4567
|
if (schemaChangeMessage) {
|
|
4492
4568
|
this.mc.logger.sendTelemetryEvent({
|
|
4493
4569
|
eventName: "SchemaChangeProposal",
|
|
@@ -4497,7 +4573,7 @@ export class ContainerRuntime
|
|
|
4497
4573
|
sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
|
|
4498
4574
|
oldRuntimeSchema: JSON.stringify(this.metadata?.documentSchema?.runtime),
|
|
4499
4575
|
});
|
|
4500
|
-
const msg:
|
|
4576
|
+
const msg: OutboundContainerRuntimeDocumentSchemaMessage = {
|
|
4501
4577
|
type: ContainerMessageType.DocumentSchemaChange,
|
|
4502
4578
|
contents: schemaChangeMessage,
|
|
4503
4579
|
};
|
|
@@ -4601,14 +4677,25 @@ export class ContainerRuntime
|
|
|
4601
4677
|
|
|
4602
4678
|
/**
|
|
4603
4679
|
* Resubmits each message in the batch, and then flushes the outbox.
|
|
4680
|
+
* This typically happens when we reconnect and there are pending messages.
|
|
4604
4681
|
*
|
|
4605
|
-
* @remarks
|
|
4682
|
+
* @remarks
|
|
4683
|
+
* Attempting to resubmit a batch that has been successfully sequenced will not happen due to
|
|
4684
|
+
* checks in the ConnectionStateHandler (Loader layer)
|
|
4685
|
+
*
|
|
4686
|
+
* The only exception to this would be if the Container "forks" due to misuse of the "Offline Load" feature.
|
|
4687
|
+
* If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
|
|
4606
4688
|
* for correlation to detect container forking.
|
|
4607
4689
|
*/
|
|
4608
4690
|
private reSubmitBatch(
|
|
4609
4691
|
batch: PendingMessageResubmitData[],
|
|
4610
4692
|
{ batchId, staged, squash }: PendingBatchResubmitMetadata,
|
|
4611
4693
|
): void {
|
|
4694
|
+
assert(
|
|
4695
|
+
this._summarizer === undefined,
|
|
4696
|
+
0x8f2 /* Summarizer never reconnects so should never resubmit */,
|
|
4697
|
+
);
|
|
4698
|
+
|
|
4612
4699
|
const resubmitInfo = {
|
|
4613
4700
|
// Only include Batch ID if "Offline Load" feature is enabled
|
|
4614
4701
|
// It's only needed to identify batches across container forks arising from misuse of offline load.
|
|
@@ -4616,36 +4703,61 @@ export class ContainerRuntime
|
|
|
4616
4703
|
staged,
|
|
4617
4704
|
};
|
|
4618
4705
|
|
|
4706
|
+
const resubmitFn = squash
|
|
4707
|
+
? this.reSubmitWithSquashing.bind(this)
|
|
4708
|
+
: this.reSubmit.bind(this);
|
|
4709
|
+
|
|
4619
4710
|
this.batchRunner.run(() => {
|
|
4620
4711
|
for (const message of batch) {
|
|
4621
|
-
|
|
4712
|
+
resubmitFn(message);
|
|
4622
4713
|
}
|
|
4623
4714
|
}, resubmitInfo);
|
|
4624
4715
|
|
|
4625
4716
|
this.flush(resubmitInfo);
|
|
4626
4717
|
}
|
|
4627
4718
|
|
|
4628
|
-
private reSubmit(message: PendingMessageResubmitData, squash: boolean): void {
|
|
4629
|
-
this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata, squash);
|
|
4630
|
-
}
|
|
4631
|
-
|
|
4632
4719
|
/**
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4635
|
-
* ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
|
|
4636
|
-
* @param message - The original LocalContainerRuntimeMessage.
|
|
4637
|
-
* @param localOpMetadata - The local metadata associated with the original message.
|
|
4720
|
+
* Resubmit the given message as part of a squash rebase upon exiting Staging Mode.
|
|
4721
|
+
* How exactly to resubmit the message is up to the subsystem that submitted the op to begin with.
|
|
4638
4722
|
*/
|
|
4639
|
-
private
|
|
4640
|
-
message
|
|
4641
|
-
localOpMetadata: unknown,
|
|
4642
|
-
opMetadata: Record<string, unknown> | undefined,
|
|
4643
|
-
squash: boolean,
|
|
4644
|
-
): void {
|
|
4723
|
+
private reSubmitWithSquashing(resubmitData: PendingMessageResubmitData): void {
|
|
4724
|
+
const message = resubmitData.runtimeOp;
|
|
4645
4725
|
assert(
|
|
4646
|
-
|
|
4647
|
-
|
|
4726
|
+
canStageMessageOfType(message.type),
|
|
4727
|
+
0xbbb /* Expected message type to be compatible with staging */,
|
|
4648
4728
|
);
|
|
4729
|
+
switch (message.type) {
|
|
4730
|
+
case ContainerMessageType.FluidDataStoreOp: {
|
|
4731
|
+
this.channelCollection.reSubmit(
|
|
4732
|
+
message.type,
|
|
4733
|
+
message.contents,
|
|
4734
|
+
resubmitData.localOpMetadata,
|
|
4735
|
+
/* squash: */ true,
|
|
4736
|
+
);
|
|
4737
|
+
break;
|
|
4738
|
+
}
|
|
4739
|
+
// NOTE: Squash doesn't apply to GC or DocumentSchemaChange ops, fallback to typical resubmit logic.
|
|
4740
|
+
case ContainerMessageType.GC:
|
|
4741
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
4742
|
+
this.reSubmit(resubmitData);
|
|
4743
|
+
break;
|
|
4744
|
+
}
|
|
4745
|
+
default: {
|
|
4746
|
+
unreachableCase(message.type);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
/**
|
|
4752
|
+
* Resubmit the given message which was previously submitted to the ContainerRuntime but not successfully
|
|
4753
|
+
* transmitted to the ordering service (e.g. due to a disconnect, or being in Staging Mode)
|
|
4754
|
+
* How to resubmit is up to the subsystem that submitted the op to begin with
|
|
4755
|
+
*/
|
|
4756
|
+
private reSubmit({
|
|
4757
|
+
runtimeOp: message,
|
|
4758
|
+
localOpMetadata,
|
|
4759
|
+
opMetadata,
|
|
4760
|
+
}: PendingMessageResubmitData): void {
|
|
4649
4761
|
switch (message.type) {
|
|
4650
4762
|
case ContainerMessageType.FluidDataStoreOp:
|
|
4651
4763
|
case ContainerMessageType.Attach:
|
|
@@ -4656,7 +4768,7 @@ export class ContainerRuntime
|
|
|
4656
4768
|
message.type,
|
|
4657
4769
|
message.contents,
|
|
4658
4770
|
localOpMetadata,
|
|
4659
|
-
squash,
|
|
4771
|
+
/* squash: */ false,
|
|
4660
4772
|
);
|
|
4661
4773
|
break;
|
|
4662
4774
|
}
|
|
@@ -4683,9 +4795,9 @@ export class ContainerRuntime
|
|
|
4683
4795
|
break;
|
|
4684
4796
|
}
|
|
4685
4797
|
case ContainerMessageType.DocumentSchemaChange: {
|
|
4686
|
-
//
|
|
4687
|
-
//
|
|
4688
|
-
|
|
4798
|
+
// We shouldn't directly resubmit due to Compare-And-Swap semantics.
|
|
4799
|
+
// If needed it will be generated from scratch before other ops are submitted.
|
|
4800
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
4689
4801
|
break;
|
|
4690
4802
|
}
|
|
4691
4803
|
default: {
|
|
@@ -4696,8 +4808,15 @@ export class ContainerRuntime
|
|
|
4696
4808
|
}
|
|
4697
4809
|
}
|
|
4698
4810
|
|
|
4699
|
-
|
|
4700
|
-
|
|
4811
|
+
/**
|
|
4812
|
+
* Rollback the given op which was only staged but not yet submitted.
|
|
4813
|
+
*/
|
|
4814
|
+
private rollbackStagedChanges(
|
|
4815
|
+
{ type, contents }: LocalContainerRuntimeMessage,
|
|
4816
|
+
localOpMetadata: unknown,
|
|
4817
|
+
): void {
|
|
4818
|
+
assert(canStageMessageOfType(type), 0xbbc /* Unexpected message type to be rolled back */);
|
|
4819
|
+
|
|
4701
4820
|
switch (type) {
|
|
4702
4821
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
4703
4822
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
@@ -4705,8 +4824,24 @@ export class ContainerRuntime
|
|
|
4705
4824
|
this.channelCollection.rollback(type, contents, localOpMetadata);
|
|
4706
4825
|
break;
|
|
4707
4826
|
}
|
|
4827
|
+
case ContainerMessageType.GC: {
|
|
4828
|
+
// Just drop it, but log an error, this is not expected and not ideal, but not critical failure either.
|
|
4829
|
+
// Currently the only expected type here is TombstoneLoaded, which will have been preceded by one of these events as well:
|
|
4830
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
4831
|
+
this.mc.logger.sendErrorEvent({
|
|
4832
|
+
eventName: "GC_OpDiscarded",
|
|
4833
|
+
details: { subType: contents.type },
|
|
4834
|
+
});
|
|
4835
|
+
break;
|
|
4836
|
+
}
|
|
4837
|
+
case ContainerMessageType.DocumentSchemaChange: {
|
|
4838
|
+
// Notify the document schema controller that the pending op was not acked.
|
|
4839
|
+
// This will allow it to propose the schema change again if needed.
|
|
4840
|
+
this.documentsSchemaController.pendingOpNotAcked();
|
|
4841
|
+
break;
|
|
4842
|
+
}
|
|
4708
4843
|
default: {
|
|
4709
|
-
|
|
4844
|
+
unreachableCase(type);
|
|
4710
4845
|
}
|
|
4711
4846
|
}
|
|
4712
4847
|
}
|
|
@@ -4986,6 +5121,7 @@ export class ContainerRuntime
|
|
|
4986
5121
|
},
|
|
4987
5122
|
getQuorum: this.getQuorum.bind(this),
|
|
4988
5123
|
getAudience: this.getAudience.bind(this),
|
|
5124
|
+
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
4989
5125
|
} satisfies ExtensionHost<TRuntimeProperties>;
|
|
4990
5126
|
entry = new factory(runtime, ...useContext);
|
|
4991
5127
|
this.extensions.set(id, entry);
|
package/src/dataStore.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
AliasResult,
|
|
12
12
|
IDataStore,
|
|
13
13
|
IFluidDataStoreChannel,
|
|
14
|
+
// eslint-disable-next-line import/no-deprecated
|
|
15
|
+
type IContainerRuntimeBaseExperimental,
|
|
14
16
|
} from "@fluidframework/runtime-definitions/internal";
|
|
15
17
|
import {
|
|
16
18
|
ITelemetryLoggerExt,
|
|
@@ -78,6 +80,11 @@ class DataStore implements IDataStore {
|
|
|
78
80
|
if (alias.includes("/")) {
|
|
79
81
|
throw new UsageError(`The alias cannot contain slashes: '${alias}'`);
|
|
80
82
|
}
|
|
83
|
+
// eslint-disable-next-line import/no-deprecated
|
|
84
|
+
const runtime = this.parentContext.containerRuntime as IContainerRuntimeBaseExperimental;
|
|
85
|
+
if (runtime.inStagingMode === true) {
|
|
86
|
+
throw new UsageError("Cannot set aliases while in staging mode");
|
|
87
|
+
}
|
|
81
88
|
|
|
82
89
|
switch (this.aliasState) {
|
|
83
90
|
// If we're already aliasing, check if it's for the same value and return
|
|
@@ -1045,6 +1045,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1045
1045
|
|
|
1046
1046
|
// Any time we log a Tombstone Loaded error (via Telemetry Tracker),
|
|
1047
1047
|
// we want to also trigger autorecovery to avoid the object being deleted
|
|
1048
|
+
// i.e. this will be preceded by one of these telemetry events;
|
|
1049
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
1048
1050
|
// Note: We don't need to trigger on "Changed" because any change will cause the object
|
|
1049
1051
|
// to be loaded by the Summarizer, and auto-recovery will be triggered then.
|
|
1050
1052
|
if (isTombstoned && reason === "Loaded") {
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -448,7 +448,7 @@ export interface IGarbageCollector {
|
|
|
448
448
|
* Returns true if this node has been deleted by GC during sweep phase.
|
|
449
449
|
*/
|
|
450
450
|
isNodeDeleted(nodePath: string): boolean;
|
|
451
|
-
setConnectionState(
|
|
451
|
+
setConnectionState(canSendOps: boolean, clientId?: string): void;
|
|
452
452
|
dispose(): void;
|
|
453
453
|
}
|
|
454
454
|
|
package/src/index.ts
CHANGED
|
@@ -95,7 +95,8 @@ export {
|
|
|
95
95
|
IDocumentSchemaCurrent,
|
|
96
96
|
currentDocumentVersionSchema,
|
|
97
97
|
DocumentsSchemaController,
|
|
98
|
-
|
|
98
|
+
IDocumentSchemaChangeMessageIncoming,
|
|
99
|
+
IDocumentSchemaChangeMessageOutgoing,
|
|
99
100
|
IDocumentSchemaFeatures,
|
|
100
101
|
ReadFluidDataStoreAttributes,
|
|
101
102
|
IFluidDataStoreAttributes0,
|