@fluidframework/container-runtime 2.30.0 → 2.31.1
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 +588 -584
- package/dist/channelCollection.js +1 -1
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +19 -4
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +100 -74
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +6 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +12 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/gcTelemetry.js +2 -2
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +16 -5
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +12 -3
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +41 -21
- package/dist/opLifecycle/outbox.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 +1 -0
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +12 -2
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runCounter.d.ts +11 -0
- package/dist/runCounter.d.ts.map +1 -0
- package/dist/runCounter.js +43 -0
- package/dist/runCounter.js.map +1 -0
- package/dist/runtimeLayerCompatState.d.ts +51 -0
- package/dist/runtimeLayerCompatState.d.ts.map +1 -0
- package/dist/runtimeLayerCompatState.js +123 -0
- package/dist/runtimeLayerCompatState.js.map +1 -0
- package/dist/summary/summarizerNode/summarizerNode.d.ts +2 -2
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +4 -4
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +1 -18
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +0 -27
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +2 -2
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +1 -2
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/channelCollection.js +1 -1
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +19 -4
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +100 -74
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +6 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +12 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/gcTelemetry.js +2 -2
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +16 -5
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +12 -3
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +43 -23
- package/lib/opLifecycle/outbox.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 +1 -0
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +12 -2
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runCounter.d.ts +11 -0
- package/lib/runCounter.d.ts.map +1 -0
- package/lib/runCounter.js +39 -0
- package/lib/runCounter.js.map +1 -0
- package/lib/runtimeLayerCompatState.d.ts +51 -0
- package/lib/runtimeLayerCompatState.d.ts.map +1 -0
- package/lib/runtimeLayerCompatState.js +118 -0
- package/lib/runtimeLayerCompatState.js.map +1 -0
- package/lib/summary/summarizerNode/summarizerNode.d.ts +2 -2
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +5 -5
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +1 -18
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +1 -25
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +2 -2
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +1 -2
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +20 -30
- package/src/channelCollection.ts +1 -1
- package/src/containerRuntime.ts +148 -100
- package/src/dataStoreContext.ts +22 -1
- package/src/gc/{garbageCollection.md → README.md} +17 -19
- package/src/gc/gcTelemetry.ts +2 -2
- package/src/opLifecycle/batchManager.ts +20 -6
- package/src/opLifecycle/outbox.ts +64 -24
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +18 -2
- package/src/runCounter.ts +25 -0
- package/src/runtimeLayerCompatState.ts +143 -0
- package/src/summary/summarizerNode/summarizerNode.ts +6 -5
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +1 -27
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +2 -3
- package/tsconfig.json +1 -0
- package/dist/layerCompatState.d.ts +0 -19
- package/dist/layerCompatState.d.ts.map +0 -1
- package/dist/layerCompatState.js +0 -64
- package/dist/layerCompatState.js.map +0 -1
- package/lib/layerCompatState.d.ts +0 -19
- package/lib/layerCompatState.d.ts.map +0 -1
- package/lib/layerCompatState.js +0 -60
- package/lib/layerCompatState.js.map +0 -1
- package/prettier.config.cjs +0 -8
- package/src/layerCompatState.ts +0 -75
package/src/channelCollection.ts
CHANGED
|
@@ -625,7 +625,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
|
|
|
625
625
|
* in the snapshot.
|
|
626
626
|
* So, return short ids only if explicitly enabled via feature flags. Else, return uuid();
|
|
627
627
|
*/
|
|
628
|
-
if (this.mc.config.getBoolean("Fluid.Runtime.
|
|
628
|
+
if (this.mc.config.getBoolean("Fluid.Runtime.IsShortIdEnabled") === true) {
|
|
629
629
|
// We use three non-overlapping namespaces:
|
|
630
630
|
// - detached state: even numbers
|
|
631
631
|
// - attached state: odd numbers
|
package/src/containerRuntime.ts
CHANGED
|
@@ -168,7 +168,6 @@ import {
|
|
|
168
168
|
type GarbageCollectionMessage,
|
|
169
169
|
} from "./gc/index.js";
|
|
170
170
|
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
|
|
171
|
-
import { RuntimeCompatDetails, validateLoaderCompatibility } from "./layerCompatState.js";
|
|
172
171
|
import {
|
|
173
172
|
ContainerMessageType,
|
|
174
173
|
type ContainerRuntimeDocumentSchemaMessage,
|
|
@@ -202,6 +201,11 @@ import {
|
|
|
202
201
|
IPendingLocalState,
|
|
203
202
|
PendingStateManager,
|
|
204
203
|
} from "./pendingStateManager.js";
|
|
204
|
+
import { RunCounter } from "./runCounter.js";
|
|
205
|
+
import {
|
|
206
|
+
runtimeCompatDetailsForLoader,
|
|
207
|
+
validateLoaderCompatibility,
|
|
208
|
+
} from "./runtimeLayerCompatState.js";
|
|
205
209
|
import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
|
|
206
210
|
import {
|
|
207
211
|
DocumentsSchemaController,
|
|
@@ -1175,7 +1179,7 @@ export class ContainerRuntime
|
|
|
1175
1179
|
|
|
1176
1180
|
private readonly maxConsecutiveReconnects: number;
|
|
1177
1181
|
|
|
1178
|
-
private
|
|
1182
|
+
private readonly batchRunner = new RunCounter();
|
|
1179
1183
|
private readonly _flushMode: FlushMode;
|
|
1180
1184
|
private readonly offlineEnabled: boolean;
|
|
1181
1185
|
private flushTaskExists = false;
|
|
@@ -1190,23 +1194,16 @@ export class ContainerRuntime
|
|
|
1190
1194
|
*/
|
|
1191
1195
|
private delayConnectClientId?: string;
|
|
1192
1196
|
|
|
1193
|
-
private
|
|
1197
|
+
private readonly dataModelChangeRunner = new RunCounter();
|
|
1194
1198
|
|
|
1195
1199
|
/**
|
|
1196
1200
|
* Invokes the given callback and expects that no ops are submitted
|
|
1197
1201
|
* until execution finishes. If an op is submitted, an error will be raised.
|
|
1198
1202
|
*
|
|
1199
|
-
* Can be disabled by feature gate `Fluid.ContainerRuntime.DisableOpReentryCheck`
|
|
1200
|
-
*
|
|
1201
1203
|
* @param callback - the callback to be invoked
|
|
1202
1204
|
*/
|
|
1203
1205
|
public ensureNoDataModelChanges<T>(callback: () => T): T {
|
|
1204
|
-
this.
|
|
1205
|
-
try {
|
|
1206
|
-
return callback();
|
|
1207
|
-
} finally {
|
|
1208
|
-
this.ensureNoDataModelChangesCalls--;
|
|
1209
|
-
}
|
|
1206
|
+
return this.dataModelChangeRunner.run(callback);
|
|
1210
1207
|
}
|
|
1211
1208
|
|
|
1212
1209
|
public get connected(): boolean {
|
|
@@ -1309,10 +1306,21 @@ export class ContainerRuntime
|
|
|
1309
1306
|
expiry: { policy: "absolute", durationMs: 60000 },
|
|
1310
1307
|
});
|
|
1311
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* The compatibility details of the Runtime layer that is exposed to the Loader layer
|
|
1311
|
+
* for validating Loader-Runtime compatibility.
|
|
1312
|
+
*/
|
|
1312
1313
|
public get ILayerCompatDetails(): ILayerCompatDetails {
|
|
1313
|
-
return
|
|
1314
|
+
return runtimeCompatDetailsForLoader;
|
|
1314
1315
|
}
|
|
1315
1316
|
|
|
1317
|
+
/**
|
|
1318
|
+
* If true, will skip Outbox flushing before processing an incoming message,
|
|
1319
|
+
* and instead the Outbox will check for a split batch on every submit.
|
|
1320
|
+
* This is a kill-bit switch for this simplification of logic, in case it causes unexpected issues.
|
|
1321
|
+
*/
|
|
1322
|
+
private readonly disableFlushBeforeProcess: boolean;
|
|
1323
|
+
|
|
1316
1324
|
/***/
|
|
1317
1325
|
protected constructor(
|
|
1318
1326
|
context: IContainerContext,
|
|
@@ -1374,8 +1382,12 @@ export class ContainerRuntime
|
|
|
1374
1382
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
1375
1383
|
this.disposeFn = disposeFn ?? closeFn;
|
|
1376
1384
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1385
|
+
// Validate that the Loader is compatible with this Runtime.
|
|
1386
|
+
const maybeloaderCompatDetailsForRuntime = context as FluidObject<ILayerCompatDetails>;
|
|
1387
|
+
validateLoaderCompatibility(
|
|
1388
|
+
maybeloaderCompatDetailsForRuntime.ILayerCompatDetails,
|
|
1389
|
+
this.disposeFn,
|
|
1390
|
+
);
|
|
1379
1391
|
|
|
1380
1392
|
this.mc = createChildMonitoringContext({
|
|
1381
1393
|
logger: this.baseLogger,
|
|
@@ -1429,6 +1441,25 @@ export class ContainerRuntime
|
|
|
1429
1441
|
this.on("dirty", () => context.updateDirtyContainerState(true));
|
|
1430
1442
|
this.on("saved", () => context.updateDirtyContainerState(false));
|
|
1431
1443
|
|
|
1444
|
+
// Telemetry for when the container is attached and subsequently saved for the first time.
|
|
1445
|
+
// These events are useful for investigating the validity of container "saved" eventing upon attach.
|
|
1446
|
+
// See this.setAttachState() and this.updateDocumentDirtyState() for more details on "attached" and "saved" events.
|
|
1447
|
+
this.once("attached", () => {
|
|
1448
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1449
|
+
eventName: "Attached",
|
|
1450
|
+
details: {
|
|
1451
|
+
dirtyContainer: this.dirtyContainer,
|
|
1452
|
+
hasPendingMessages: this.hasPendingMessages(),
|
|
1453
|
+
},
|
|
1454
|
+
});
|
|
1455
|
+
});
|
|
1456
|
+
this.once("saved", () =>
|
|
1457
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1458
|
+
eventName: "Saved",
|
|
1459
|
+
details: { attachState: this.attachState },
|
|
1460
|
+
}),
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1432
1463
|
// In cases of summarizer, we want to dispose instead since consumer doesn't interact with this container
|
|
1433
1464
|
this.closeFn = isSummarizerClient ? this.disposeFn : closeFn;
|
|
1434
1465
|
|
|
@@ -1551,7 +1582,7 @@ export class ContainerRuntime
|
|
|
1551
1582
|
// If the context has ILayerCompatDetails, it supports referenceSequenceNumbers since that features
|
|
1552
1583
|
// predates ILayerCompatDetails.
|
|
1553
1584
|
const referenceSequenceNumbersSupported =
|
|
1554
|
-
|
|
1585
|
+
maybeloaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
|
|
1555
1586
|
? supportedFeatures?.get("referenceSequenceNumbers") === true
|
|
1556
1587
|
: true;
|
|
1557
1588
|
if (
|
|
@@ -1723,12 +1754,11 @@ export class ContainerRuntime
|
|
|
1723
1754
|
createChildLogger({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }),
|
|
1724
1755
|
);
|
|
1725
1756
|
|
|
1726
|
-
const disablePartialFlush = this.mc.config.getBoolean(
|
|
1727
|
-
"Fluid.ContainerRuntime.DisablePartialFlush",
|
|
1728
|
-
);
|
|
1729
|
-
|
|
1730
1757
|
const legacySendBatchFn = makeLegacySendBatchFn(submitFn, this.innerDeltaManager);
|
|
1731
1758
|
|
|
1759
|
+
this.disableFlushBeforeProcess =
|
|
1760
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableFlushBeforeProcess") === true;
|
|
1761
|
+
|
|
1732
1762
|
this.outbox = new Outbox({
|
|
1733
1763
|
shouldSend: () => this.canSendOps(),
|
|
1734
1764
|
pendingStateManager: this.pendingStateManager,
|
|
@@ -1739,16 +1769,18 @@ export class ContainerRuntime
|
|
|
1739
1769
|
config: {
|
|
1740
1770
|
compressionOptions,
|
|
1741
1771
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1742
|
-
|
|
1772
|
+
// If we disable flush before process, we must be ready to flush partial batches
|
|
1773
|
+
flushPartialBatches: this.disableFlushBeforeProcess,
|
|
1743
1774
|
},
|
|
1744
1775
|
logger: this.mc.logger,
|
|
1745
1776
|
groupingManager: opGroupingManager,
|
|
1746
1777
|
getCurrentSequenceNumbers: () => ({
|
|
1778
|
+
// Note: These sequence numbers only change when DeltaManager processes an incoming op
|
|
1747
1779
|
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1748
1780
|
clientSequenceNumber: this._processedClientSequenceNumber,
|
|
1749
1781
|
}),
|
|
1750
1782
|
reSubmit: this.reSubmit.bind(this),
|
|
1751
|
-
opReentrancy: () => this.
|
|
1783
|
+
opReentrancy: () => this.dataModelChangeRunner.running,
|
|
1752
1784
|
closeContainer: this.closeFn,
|
|
1753
1785
|
});
|
|
1754
1786
|
|
|
@@ -1918,8 +1950,8 @@ export class ContainerRuntime
|
|
|
1918
1950
|
sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
|
|
1919
1951
|
featureGates: JSON.stringify({
|
|
1920
1952
|
...featureGatesForTelemetry,
|
|
1921
|
-
disablePartialFlush,
|
|
1922
1953
|
closeSummarizerDelayOverride,
|
|
1954
|
+
disableFlushBeforeProcess: this.disableFlushBeforeProcess,
|
|
1923
1955
|
}),
|
|
1924
1956
|
telemetryDocumentId: this.telemetryDocumentId,
|
|
1925
1957
|
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
@@ -2607,6 +2639,26 @@ export class ContainerRuntime
|
|
|
2607
2639
|
|
|
2608
2640
|
this.verifyNotClosed();
|
|
2609
2641
|
|
|
2642
|
+
if (!this.disableFlushBeforeProcess) {
|
|
2643
|
+
// Reference Sequence Number may be about to change, and it must be consistent across a batch, so flush now
|
|
2644
|
+
this.outbox.flush();
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
this.ensureNoDataModelChanges(() => {
|
|
2648
|
+
this.processInboundMessageOrBatch(messageCopy, local);
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
/**
|
|
2653
|
+
* Implementation of core logic for {@link ContainerRuntime.process}, once preconditions are established
|
|
2654
|
+
*
|
|
2655
|
+
* @param messageCopy - Shallow copy of the sequenced message. If it's a virtualized batch, we'll process
|
|
2656
|
+
* all messages in the batch here.
|
|
2657
|
+
*/
|
|
2658
|
+
private processInboundMessageOrBatch(
|
|
2659
|
+
messageCopy: ISequencedDocumentMessage,
|
|
2660
|
+
local: boolean,
|
|
2661
|
+
): void {
|
|
2610
2662
|
// Whether or not the message appears to be a runtime message from an up-to-date client.
|
|
2611
2663
|
// It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
|
|
2612
2664
|
// or something different, like a system message.
|
|
@@ -2768,9 +2820,7 @@ export class ContainerRuntime
|
|
|
2768
2820
|
try {
|
|
2769
2821
|
if (!runtimeBatch) {
|
|
2770
2822
|
for (const { message } of messagesWithMetadata) {
|
|
2771
|
-
this.
|
|
2772
|
-
this.observeNonRuntimeMessage(message);
|
|
2773
|
-
});
|
|
2823
|
+
this.observeNonRuntimeMessage(message);
|
|
2774
2824
|
}
|
|
2775
2825
|
return;
|
|
2776
2826
|
}
|
|
@@ -2794,21 +2844,19 @@ export class ContainerRuntime
|
|
|
2794
2844
|
if (!groupedBatch) {
|
|
2795
2845
|
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2796
2846
|
updateSequenceNumbers(message);
|
|
2797
|
-
this.
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
this.emit("op", message, true /* runtimeMessage */);
|
|
2811
|
-
});
|
|
2847
|
+
this.validateAndProcessRuntimeMessages(
|
|
2848
|
+
message as InboundSequencedContainerRuntimeMessage,
|
|
2849
|
+
[
|
|
2850
|
+
{
|
|
2851
|
+
contents: message.contents,
|
|
2852
|
+
localOpMetadata,
|
|
2853
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
2854
|
+
},
|
|
2855
|
+
],
|
|
2856
|
+
local,
|
|
2857
|
+
savedOp,
|
|
2858
|
+
);
|
|
2859
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2812
2860
|
}
|
|
2813
2861
|
return;
|
|
2814
2862
|
}
|
|
@@ -2817,17 +2865,14 @@ export class ContainerRuntime
|
|
|
2817
2865
|
let previousMessage: InboundSequencedContainerRuntimeMessage | undefined;
|
|
2818
2866
|
|
|
2819
2867
|
// Process the previous bunch of messages.
|
|
2820
|
-
const
|
|
2868
|
+
const processBunchedMessages = (): void => {
|
|
2821
2869
|
assert(previousMessage !== undefined, 0xa67 /* previous message must exist */);
|
|
2822
|
-
this.
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
savedOp,
|
|
2829
|
-
);
|
|
2830
|
-
});
|
|
2870
|
+
this.validateAndProcessRuntimeMessages(
|
|
2871
|
+
previousMessage,
|
|
2872
|
+
bunchedMessagesContent,
|
|
2873
|
+
local,
|
|
2874
|
+
savedOp,
|
|
2875
|
+
);
|
|
2831
2876
|
bunchedMessagesContent = [];
|
|
2832
2877
|
};
|
|
2833
2878
|
|
|
@@ -2839,7 +2884,7 @@ export class ContainerRuntime
|
|
|
2839
2884
|
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2840
2885
|
const currentMessage = updateSequenceNumbers(message);
|
|
2841
2886
|
if (previousMessage && previousMessage.type !== currentMessage.type) {
|
|
2842
|
-
|
|
2887
|
+
processBunchedMessages();
|
|
2843
2888
|
}
|
|
2844
2889
|
previousMessage = currentMessage;
|
|
2845
2890
|
bunchedMessagesContent.push({
|
|
@@ -2850,7 +2895,7 @@ export class ContainerRuntime
|
|
|
2850
2895
|
}
|
|
2851
2896
|
|
|
2852
2897
|
// Process the last bunch of messages.
|
|
2853
|
-
|
|
2898
|
+
processBunchedMessages();
|
|
2854
2899
|
|
|
2855
2900
|
// Send the "op" events for the messages now that the ops have been processed.
|
|
2856
2901
|
for (const { message } of messagesWithMetadata) {
|
|
@@ -3052,8 +3097,8 @@ export class ContainerRuntime
|
|
|
3052
3097
|
*/
|
|
3053
3098
|
private flush(resubmittingBatchId?: BatchId): void {
|
|
3054
3099
|
assert(
|
|
3055
|
-
this.
|
|
3056
|
-
0x24c /* "Cannot call `flush()`
|
|
3100
|
+
!this.batchRunner.running,
|
|
3101
|
+
0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */,
|
|
3057
3102
|
);
|
|
3058
3103
|
|
|
3059
3104
|
this.outbox.flush(resubmittingBatchId);
|
|
@@ -3065,57 +3110,60 @@ export class ContainerRuntime
|
|
|
3065
3110
|
*/
|
|
3066
3111
|
public orderSequentially<T>(callback: () => T): T {
|
|
3067
3112
|
let checkpoint: IBatchCheckpoint | undefined;
|
|
3068
|
-
|
|
3113
|
+
const checkpointDirtyState = this.dirtyContainer;
|
|
3069
3114
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
3070
3115
|
// Note: we are not touching any batches other than mainBatch here, for two reasons:
|
|
3071
3116
|
// 1. It would not help, as other batches are flushed independently from main batch.
|
|
3072
3117
|
// 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
|
|
3073
3118
|
checkpoint = this.outbox.getBatchCheckpoints().mainBatch;
|
|
3074
3119
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3120
|
+
const result = this.batchRunner.run(() => {
|
|
3121
|
+
try {
|
|
3122
|
+
return callback();
|
|
3123
|
+
} catch (error) {
|
|
3124
|
+
if (checkpoint) {
|
|
3125
|
+
// This will throw and close the container if rollback fails
|
|
3126
|
+
try {
|
|
3127
|
+
checkpoint.rollback((message: BatchMessage) =>
|
|
3128
|
+
this.rollback(message.contents, message.localOpMetadata),
|
|
3129
|
+
);
|
|
3130
|
+
// reset the dirty state after rollback to what it was before to keep it consistent
|
|
3131
|
+
if (this.dirtyContainer !== checkpointDirtyState) {
|
|
3132
|
+
this.updateDocumentDirtyState(checkpointDirtyState);
|
|
3133
|
+
}
|
|
3134
|
+
} catch (error_) {
|
|
3135
|
+
const error2 = wrapError(error_, (message) => {
|
|
3136
|
+
return DataProcessingError.create(
|
|
3137
|
+
`RollbackError: ${message}`,
|
|
3138
|
+
"checkpointRollback",
|
|
3139
|
+
undefined,
|
|
3140
|
+
) as DataProcessingError;
|
|
3141
|
+
});
|
|
3142
|
+
this.closeFn(error2);
|
|
3143
|
+
throw error2;
|
|
3144
|
+
}
|
|
3145
|
+
} else {
|
|
3146
|
+
this.closeFn(
|
|
3147
|
+
wrapError(
|
|
3148
|
+
error,
|
|
3149
|
+
(errorMessage) =>
|
|
3150
|
+
new GenericError(
|
|
3151
|
+
`orderSequentially callback exception: ${errorMessage}`,
|
|
3152
|
+
error,
|
|
3153
|
+
{
|
|
3154
|
+
orderSequentiallyCalls: this.batchRunner.runs,
|
|
3155
|
+
},
|
|
3156
|
+
),
|
|
3157
|
+
),
|
|
3084
3158
|
);
|
|
3085
|
-
} catch (error_) {
|
|
3086
|
-
const error2 = wrapError(error_, (message) => {
|
|
3087
|
-
return DataProcessingError.create(
|
|
3088
|
-
`RollbackError: ${message}`,
|
|
3089
|
-
"checkpointRollback",
|
|
3090
|
-
undefined,
|
|
3091
|
-
) as DataProcessingError;
|
|
3092
|
-
});
|
|
3093
|
-
this.closeFn(error2);
|
|
3094
|
-
throw error2;
|
|
3095
3159
|
}
|
|
3096
|
-
} else {
|
|
3097
|
-
this.closeFn(
|
|
3098
|
-
wrapError(
|
|
3099
|
-
error,
|
|
3100
|
-
(errorMessage) =>
|
|
3101
|
-
new GenericError(
|
|
3102
|
-
`orderSequentially callback exception: ${errorMessage}`,
|
|
3103
|
-
error,
|
|
3104
|
-
{
|
|
3105
|
-
orderSequentiallyCalls: this._orderSequentiallyCalls,
|
|
3106
|
-
},
|
|
3107
|
-
),
|
|
3108
|
-
),
|
|
3109
|
-
);
|
|
3110
|
-
}
|
|
3111
3160
|
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
}
|
|
3161
|
+
throw error; // throw the original error for the consumer of the runtime
|
|
3162
|
+
}
|
|
3163
|
+
});
|
|
3116
3164
|
|
|
3117
3165
|
// We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
|
|
3118
|
-
if (this.flushMode !== FlushMode.TurnBased && this.
|
|
3166
|
+
if (this.flushMode !== FlushMode.TurnBased && !this.batchRunner.running) {
|
|
3119
3167
|
this.flush();
|
|
3120
3168
|
}
|
|
3121
3169
|
return result;
|
|
@@ -3196,7 +3244,7 @@ export class ContainerRuntime
|
|
|
3196
3244
|
* Typically ops are batched and later flushed together, but in some cases we want to flush immediately.
|
|
3197
3245
|
*/
|
|
3198
3246
|
private currentlyBatching(): boolean {
|
|
3199
|
-
return this.flushMode !== FlushMode.Immediate || this.
|
|
3247
|
+
return this.flushMode !== FlushMode.Immediate || this.batchRunner.running;
|
|
3200
3248
|
}
|
|
3201
3249
|
|
|
3202
3250
|
private readonly _quorum: IQuorumClients;
|
|
@@ -4265,8 +4313,8 @@ export class ContainerRuntime
|
|
|
4265
4313
|
|
|
4266
4314
|
default: {
|
|
4267
4315
|
assert(
|
|
4268
|
-
this.
|
|
4269
|
-
0x587 /* Unreachable unless
|
|
4316
|
+
this.batchRunner.running,
|
|
4317
|
+
0x587 /* Unreachable unless manually accumulating a batch */,
|
|
4270
4318
|
);
|
|
4271
4319
|
break;
|
|
4272
4320
|
}
|
|
@@ -4306,7 +4354,7 @@ export class ContainerRuntime
|
|
|
4306
4354
|
* for correlation to detect container forking.
|
|
4307
4355
|
*/
|
|
4308
4356
|
private reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void {
|
|
4309
|
-
this.
|
|
4357
|
+
this.batchRunner.run(() => {
|
|
4310
4358
|
for (const message of batch) {
|
|
4311
4359
|
this.reSubmit(message);
|
|
4312
4360
|
}
|
|
@@ -4554,8 +4602,8 @@ export class ContainerRuntime
|
|
|
4554
4602
|
public getPendingLocalState(props?: IGetPendingLocalStateProps): unknown {
|
|
4555
4603
|
this.verifyNotClosed();
|
|
4556
4604
|
|
|
4557
|
-
if (this.
|
|
4558
|
-
throw new UsageError("can't get state
|
|
4605
|
+
if (this.batchRunner.running) {
|
|
4606
|
+
throw new UsageError("can't get state while manually accumulating a batch");
|
|
4559
4607
|
}
|
|
4560
4608
|
this.imminentClosure ||= props?.notifyImminentClosure ?? false;
|
|
4561
4609
|
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
6
|
+
import { TypedEventEmitter, type ILayerCompatDetails } from "@fluid-internal/client-utils";
|
|
7
7
|
import { AttachState, IAudience } from "@fluidframework/container-definitions";
|
|
8
8
|
import { IDeltaManager } from "@fluidframework/container-definitions/internal";
|
|
9
9
|
import {
|
|
@@ -77,6 +77,10 @@ import {
|
|
|
77
77
|
tagCodeArtifacts,
|
|
78
78
|
} from "@fluidframework/telemetry-utils/internal";
|
|
79
79
|
|
|
80
|
+
import {
|
|
81
|
+
runtimeCompatDetailsForDataStore,
|
|
82
|
+
validateDatastoreCompatibility,
|
|
83
|
+
} from "./runtimeLayerCompatState.js";
|
|
80
84
|
import {
|
|
81
85
|
// eslint-disable-next-line import/no-deprecated
|
|
82
86
|
ReadFluidDataStoreAttributes,
|
|
@@ -278,6 +282,14 @@ export abstract class FluidDataStoreContext
|
|
|
278
282
|
return this.registry;
|
|
279
283
|
}
|
|
280
284
|
|
|
285
|
+
/**
|
|
286
|
+
* The compatibility details of the Runtime layer that is exposed to the DataStore layer
|
|
287
|
+
* for validating DataStore-Runtime compatibility.
|
|
288
|
+
*/
|
|
289
|
+
public get ILayerCompatDetails(): ILayerCompatDetails {
|
|
290
|
+
return runtimeCompatDetailsForDataStore;
|
|
291
|
+
}
|
|
292
|
+
|
|
281
293
|
private baseSnapshotSequenceNumber: number | undefined;
|
|
282
294
|
|
|
283
295
|
/**
|
|
@@ -573,6 +585,7 @@ export abstract class FluidDataStoreContext
|
|
|
573
585
|
|
|
574
586
|
const channel = await factory.instantiateDataStore(this, existing);
|
|
575
587
|
assert(channel !== undefined, 0x140 /* "undefined channel on datastore context" */);
|
|
588
|
+
|
|
576
589
|
await this.bindRuntime(channel, existing);
|
|
577
590
|
// This data store may have been disposed before the channel is created during realization. If so,
|
|
578
591
|
// dispose the channel now.
|
|
@@ -860,6 +873,13 @@ export abstract class FluidDataStoreContext
|
|
|
860
873
|
}
|
|
861
874
|
|
|
862
875
|
protected completeBindingRuntime(channel: IFluidDataStoreChannel): void {
|
|
876
|
+
// Validate that the DataStore is compatible with this Runtime.
|
|
877
|
+
const maybeDataStoreCompatDetails = channel as FluidObject<ILayerCompatDetails>;
|
|
878
|
+
validateDatastoreCompatibility(
|
|
879
|
+
maybeDataStoreCompatDetails.ILayerCompatDetails,
|
|
880
|
+
this.dispose.bind(this),
|
|
881
|
+
);
|
|
882
|
+
|
|
863
883
|
// And now mark the runtime active
|
|
864
884
|
this.loaded = true;
|
|
865
885
|
this.channel = channel;
|
|
@@ -1005,6 +1025,7 @@ export abstract class FluidDataStoreContext
|
|
|
1005
1025
|
callSite,
|
|
1006
1026
|
undefined /* sequencedMessage */,
|
|
1007
1027
|
safeTelemetryProps,
|
|
1028
|
+
30 /* stackTraceLimit */,
|
|
1008
1029
|
);
|
|
1009
1030
|
|
|
1010
1031
|
this.mc.logger.sendTelemetryEvent(
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
# Garbage Collection
|
|
2
2
|
|
|
3
|
-
Garbage collection (GC) is the process by which Fluid Framework safely
|
|
3
|
+
Garbage collection (GC) is the process by which Fluid Framework safely deletes objects that are not used.
|
|
4
|
+
GC reduces the size of the Fluid file at rest, the in-memory content and the summary that is uploaded to / downloaded from the server.
|
|
5
|
+
It saves COGS on the server and it makes containers load faster as there is less data to download and process.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
All Fluid objects that are in use must be properly referenced so that they are not deleted by GC. Similarly, references to all unused Fluid objects should be removed so that they can be deleted by GC.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
It is the responsibility of the users of Fluid Framework to correctly add and remove references to Fluid objects.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Referencing / unreferencing Fluid objects
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Currently, the only Fluid objects that are eligible for GC are data stores and attachment blobs. The following sections describe how you can mark them as referenced or unreferenced.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Currently, the only Fluid objects that are eligible for GC are data stores and attachment blobs. The following sections describe how you can mark them as referenced or unreferenced. These sections speak of a "referenced DDS" which refers to a DDS that is created by a referenced data store.
|
|
15
|
+
These sections speak of a "referenced DDS" which refers to a DDS that is created by a referenced data store.
|
|
16
16
|
|
|
17
17
|
### Data stores
|
|
18
18
|
|
|
19
19
|
There are 2 ways to reference a data store:
|
|
20
20
|
|
|
21
|
-
- Store the data
|
|
21
|
+
- Store the data store's handle (see [IFluidHandle](../../../../../packages/common/core-interfaces/src/handles.ts)) in a referenced DDS that supports handle in its data.
|
|
22
|
+
For example, a data store's handle can be stored in a referenced `SharedMap` DDS.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Storing a handle of any of a data store's DDS will also mark the data store as referenced.
|
|
24
25
|
|
|
25
|
-
- Alias the data store. Aliased data stores are rooted in the container, i.e., they are always referenced and cannot be unreferenced later.
|
|
26
|
+
- Alias the data store by calling [trySetAlias](../../../runtime-definitions/src/dataStoreContext.ts) on a data store during creation. Aliased data stores are rooted in the container, i.e., they are always referenced and cannot be unreferenced later.
|
|
27
|
+
Aliased data stores can never be deleted so only do so if you want them to live forever.
|
|
26
28
|
|
|
27
29
|
Once there are no more referenced DDSes in the container containing a handle to a particular data store, that data store is unreferenced and is eligible for GC.
|
|
28
30
|
|
|
@@ -30,7 +32,7 @@ Once there are no more referenced DDSes in the container containing a handle to
|
|
|
30
32
|
|
|
31
33
|
### Attachment blobs
|
|
32
34
|
|
|
33
|
-
The only way to reference an attachment blob is to store its IFluidHandle in a referenced DDS similar to data stores.
|
|
35
|
+
The only way to reference an attachment blob is to store its [IFluidHandle](../../../../../packages/common/core-interfaces/src/handles.ts) in a referenced DDS similar to data stores.
|
|
34
36
|
|
|
35
37
|
Once there are no more referenced DDSes in the container containing a handle to a particular attachment blob, that attachment blob is unreferenced and is eligible for GC.
|
|
36
38
|
|
|
@@ -59,8 +61,9 @@ GC sweep phase runs in two stages:
|
|
|
59
61
|
|
|
60
62
|
- The first stage is the "Tombstone" stage, where objects are marked as Tombstones, meaning GC believes they will
|
|
61
63
|
never be referenced again and are safe to delete. They are not yet deleted at this point, but any attempt to
|
|
62
|
-
load them will fail.
|
|
63
|
-
|
|
64
|
+
load them will fail. Loading them will trigger "auto recovery" where the timestamp of when this object is unreferenced will be reset to now, thereby extending its lifetime.
|
|
65
|
+
This way, there's a chance to recover a Tombstoned object in case we detect it's still being used.
|
|
66
|
+
- The second stage is the "Sweep" stage, where the objects are fully deleted.
|
|
64
67
|
This occurs after a configurable delay called the "Sweep Grace Period", to give time for application teams
|
|
65
68
|
to monitor for Tombstone-related errors and react before delete occurs.
|
|
66
69
|
|
|
@@ -91,8 +94,3 @@ and then later update the passed-in GC Options to finalize the configuration in
|
|
|
91
94
|
### Enabling Sweep Phase
|
|
92
95
|
|
|
93
96
|
To enable the Sweep Phase for new documents, you must set the `enableGCSweep` GC Option to true.
|
|
94
|
-
|
|
95
|
-
### More Advanced Configuration
|
|
96
|
-
|
|
97
|
-
For additional behaviors that can be configured (e.g. for testing), please see these
|
|
98
|
-
[Advanced Configuration](./gcEarlyAdoption.md#more-advanced-configurations) docs.
|
package/src/gc/gcTelemetry.ts
CHANGED
|
@@ -279,7 +279,7 @@ export class GCTelemetryTracker {
|
|
|
279
279
|
const event = {
|
|
280
280
|
eventName: `${state}Object_${usageType}`,
|
|
281
281
|
...tagCodeArtifacts({ pkg: packagePath?.join("/") }),
|
|
282
|
-
stack: generateStack(),
|
|
282
|
+
stack: generateStack(30),
|
|
283
283
|
id,
|
|
284
284
|
fromId,
|
|
285
285
|
headers: { ...headers },
|
|
@@ -314,7 +314,7 @@ export class GCTelemetryTracker {
|
|
|
314
314
|
const event = {
|
|
315
315
|
eventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,
|
|
316
316
|
...tagCodeArtifacts({ pkg: packagePath?.join("/") }),
|
|
317
|
-
stack: generateStack(),
|
|
317
|
+
stack: generateStack(30),
|
|
318
318
|
id,
|
|
319
319
|
fromId,
|
|
320
320
|
headers: { ...headers },
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
7
|
+
import {
|
|
8
|
+
LoggingError,
|
|
9
|
+
tagData,
|
|
10
|
+
TelemetryDataTag,
|
|
11
|
+
} from "@fluidframework/telemetry-utils/internal";
|
|
7
12
|
|
|
8
13
|
import { ICompressionRuntimeOptions } from "../containerRuntime.js";
|
|
9
14
|
import { asBatchMetadata, type IBatchMetadata } from "../metadata.js";
|
|
@@ -99,7 +104,8 @@ export class BatchManager {
|
|
|
99
104
|
private get referenceSequenceNumber(): number | undefined {
|
|
100
105
|
return this.pendingBatch.length === 0
|
|
101
106
|
? undefined
|
|
102
|
-
:
|
|
107
|
+
: // NOTE: In case of reentrant ops, there could be multiple reference sequence numbers, but we will rebase before submitting.
|
|
108
|
+
this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
/**
|
|
@@ -166,17 +172,25 @@ export class BatchManager {
|
|
|
166
172
|
* Capture the pending state at this point
|
|
167
173
|
*/
|
|
168
174
|
public checkpoint(): IBatchCheckpoint {
|
|
175
|
+
const startSequenceNumber = this.clientSequenceNumber;
|
|
169
176
|
const startPoint = this.pendingBatch.length;
|
|
170
177
|
return {
|
|
171
178
|
rollback: (process: (message: BatchMessage) => void) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
this.clientSequenceNumber = startSequenceNumber;
|
|
180
|
+
const rollbackOpsLifo = this.pendingBatch.splice(startPoint).reverse();
|
|
181
|
+
for (const message of rollbackOpsLifo) {
|
|
175
182
|
this.batchContentSize -= message.contents?.length ?? 0;
|
|
176
183
|
process(message);
|
|
177
184
|
}
|
|
178
|
-
|
|
179
|
-
|
|
185
|
+
const count = this.pendingBatch.length - startPoint;
|
|
186
|
+
if (count !== 0) {
|
|
187
|
+
throw new LoggingError("Ops generated durning rollback", {
|
|
188
|
+
count,
|
|
189
|
+
...tagData(TelemetryDataTag.UserData, {
|
|
190
|
+
ops: JSON.stringify(this.pendingBatch.slice(startPoint).map((b) => b.contents)),
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
180
194
|
},
|
|
181
195
|
};
|
|
182
196
|
}
|