@fluidframework/container-runtime 0.57.0 → 0.58.0-55983
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/dist/batchTracker.d.ts +26 -0
- package/dist/batchTracker.d.ts.map +1 -0
- package/dist/batchTracker.js +59 -0
- package/dist/batchTracker.js.map +1 -0
- package/dist/containerRuntime.d.ts +10 -5
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +113 -35
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +1 -36
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -27
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +11 -7
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.js +1 -1
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +1 -0
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +6 -4
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.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.map +1 -1
- package/dist/pendingStateManager.js +1 -6
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts +3 -4
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +8 -9
- package/dist/summarizer.js.map +1 -1
- package/dist/summaryGenerator.d.ts +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -1
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -6
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +4 -10
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +26 -0
- package/lib/batchTracker.d.ts.map +1 -0
- package/lib/batchTracker.js +54 -0
- package/lib/batchTracker.js.map +1 -0
- package/lib/containerRuntime.d.ts +10 -5
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +114 -36
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +1 -36
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +4 -26
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +12 -8
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.js +1 -1
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +1 -0
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +4 -2
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.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.map +1 -1
- package/lib/pendingStateManager.js +1 -6
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts +3 -4
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +8 -9
- package/lib/summarizer.js.map +1 -1
- package/lib/summaryGenerator.d.ts +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -1
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -6
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +5 -11
- package/lib/summaryManager.js.map +1 -1
- package/package.json +16 -16
- package/src/batchTracker.ts +80 -0
- package/src/containerRuntime.ts +151 -38
- package/src/dataStore.ts +6 -42
- package/src/dataStoreContext.ts +12 -15
- package/src/dataStores.ts +1 -1
- package/src/garbageCollection.ts +11 -10
- package/src/index.ts +0 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +4 -8
- package/src/runningSummarizer.ts +3 -3
- package/src/summarizer.ts +8 -8
- package/src/summaryGenerator.ts +2 -2
- package/src/summaryManager.ts +5 -20
package/src/containerRuntime.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
// See #9219
|
|
6
|
+
/* eslint-disable max-lines */
|
|
6
7
|
import { EventEmitter } from "events";
|
|
7
8
|
import { ITelemetryBaseLogger, ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
8
9
|
import {
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
IDeltaManager,
|
|
22
23
|
IDeltaSender,
|
|
23
24
|
IRuntime,
|
|
24
|
-
ContainerWarning,
|
|
25
25
|
ICriticalContainerError,
|
|
26
26
|
AttachState,
|
|
27
27
|
ILoaderOptions,
|
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
SummarizeInternalFn,
|
|
87
87
|
channelsTreeName,
|
|
88
88
|
IAttachMessage,
|
|
89
|
+
IDataStore,
|
|
89
90
|
} from "@fluidframework/runtime-definitions";
|
|
90
91
|
import {
|
|
91
92
|
addBlobToSummary,
|
|
@@ -146,12 +147,11 @@ import {
|
|
|
146
147
|
IGCStats,
|
|
147
148
|
} from "./garbageCollection";
|
|
148
149
|
import {
|
|
149
|
-
AliasResult,
|
|
150
150
|
channelToDataStore,
|
|
151
|
-
IDataStore,
|
|
152
151
|
IDataStoreAliasMessage,
|
|
153
152
|
isDataStoreAliasMessage,
|
|
154
153
|
} from "./dataStore";
|
|
154
|
+
import { BindBatchTracker } from "./batchTracker";
|
|
155
155
|
|
|
156
156
|
export enum ContainerMessageType {
|
|
157
157
|
// An op to be delivered to store
|
|
@@ -329,13 +329,23 @@ export enum RuntimeHeaders {
|
|
|
329
329
|
* have the untagged logger, so to accommodate that scenario the below interface is used. It can be removed once
|
|
330
330
|
* its usage is removed from TaggedLoggerAdapter fallback.
|
|
331
331
|
*/
|
|
332
|
-
interface OldContainerContextWithLogger extends IContainerContext {
|
|
332
|
+
interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedLogger"> {
|
|
333
333
|
logger: ITelemetryBaseLogger;
|
|
334
|
+
taggedLogger: undefined;
|
|
334
335
|
}
|
|
335
336
|
|
|
336
|
-
// Local storage key to set the default flush mode to TurnBased
|
|
337
|
-
const turnBasedFlushModeKey = "Fluid.ContainerRuntime.FlushModeTurnBased";
|
|
338
337
|
const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
|
|
338
|
+
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
339
|
+
|
|
340
|
+
// Feature gate for the max op size. If the value is negative, chunking is enabled
|
|
341
|
+
// and all ops over 16k would be chunked. If the value is positive, all ops with
|
|
342
|
+
// a size strictly larger will be rejected and the container closed with an error.
|
|
343
|
+
const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
|
|
344
|
+
|
|
345
|
+
// By default, we should reject any op larger than 768KB,
|
|
346
|
+
// in order to account for some extra overhead from serialization
|
|
347
|
+
// to not reach the 1MB limits in socket.io and Kafka.
|
|
348
|
+
const defaultMaxOpSizeInBytes = 768000;
|
|
339
349
|
|
|
340
350
|
export enum RuntimeMessage {
|
|
341
351
|
FluidDataStoreOp = "component",
|
|
@@ -686,8 +696,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
686
696
|
): Promise<ContainerRuntime> {
|
|
687
697
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
688
698
|
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
|
|
689
|
-
const
|
|
690
|
-
|
|
699
|
+
const backCompatContext: IContainerContext | OldContainerContextWithLogger = context;
|
|
700
|
+
const passLogger = backCompatContext.taggedLogger ??
|
|
701
|
+
new TaggedLoggerAdapter((backCompatContext as OldContainerContextWithLogger).logger);
|
|
691
702
|
const logger = ChildLogger.create(passLogger, undefined, {
|
|
692
703
|
all: {
|
|
693
704
|
runtimeVersion: pkgVersion,
|
|
@@ -769,7 +780,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
769
780
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
770
781
|
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
771
782
|
const error = new DataCorruptionError(
|
|
772
|
-
"
|
|
783
|
+
"Summary metadata mismatch",
|
|
773
784
|
{ runtimeSequenceNumber, protocolSequenceNumber },
|
|
774
785
|
);
|
|
775
786
|
|
|
@@ -891,9 +902,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
891
902
|
|
|
892
903
|
private readonly summarizerNode: IRootSummarizerNodeWithGC;
|
|
893
904
|
private readonly _aliasingEnabled: boolean;
|
|
905
|
+
private readonly _maxOpSizeInBytes: number;
|
|
906
|
+
|
|
907
|
+
private readonly maxConsecutiveReconnects: number;
|
|
908
|
+
private readonly defaultMaxConsecutiveReconnects = 15;
|
|
894
909
|
|
|
895
910
|
private _orderSequentiallyCalls: number = 0;
|
|
896
|
-
private _flushMode: FlushMode;
|
|
911
|
+
private _flushMode: FlushMode = FlushMode.TurnBased;
|
|
897
912
|
private needsFlush = false;
|
|
898
913
|
private flushTrigger = false;
|
|
899
914
|
|
|
@@ -901,6 +916,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
901
916
|
|
|
902
917
|
private paused: boolean = false;
|
|
903
918
|
|
|
919
|
+
private consecutiveReconnects = 0;
|
|
920
|
+
|
|
904
921
|
public get connected(): boolean {
|
|
905
922
|
return this._connected;
|
|
906
923
|
}
|
|
@@ -927,8 +944,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
927
944
|
private dirtyContainer: boolean;
|
|
928
945
|
private emitDirtyDocumentEvent = true;
|
|
929
946
|
|
|
930
|
-
private readonly summarizerWarning = (warning: ContainerWarning) =>
|
|
931
|
-
this.mc.logger.sendTelemetryEvent({ eventName: "summarizerWarning" }, warning);
|
|
932
947
|
/**
|
|
933
948
|
* Summarizer is responsible for coordinating when to send generate and send summaries.
|
|
934
949
|
* It is the main entry point for summary work.
|
|
@@ -1013,14 +1028,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1013
1028
|
this.mc = loggerToMonitoringContext(
|
|
1014
1029
|
ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
1015
1030
|
|
|
1016
|
-
this._flushMode =
|
|
1017
|
-
this.mc.config.getBoolean(turnBasedFlushModeKey) ?? false
|
|
1018
|
-
? FlushMode.TurnBased : FlushMode.Immediate;
|
|
1019
|
-
|
|
1020
1031
|
this._aliasingEnabled =
|
|
1021
1032
|
(this.mc.config.getBoolean(useDataStoreAliasingKey) ?? false) ||
|
|
1022
1033
|
(runtimeOptions.useDataStoreAliasing ?? false);
|
|
1023
1034
|
|
|
1035
|
+
this._maxOpSizeInBytes = (this.mc.config.getNumber(maxOpSizeInBytesKey) ?? defaultMaxOpSizeInBytes);
|
|
1036
|
+
this.maxConsecutiveReconnects =
|
|
1037
|
+
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
|
|
1038
|
+
|
|
1024
1039
|
this.garbageCollector = GarbageCollector.create(
|
|
1025
1040
|
this,
|
|
1026
1041
|
this.runtimeOptions.gcOptions,
|
|
@@ -1210,7 +1225,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1210
1225
|
},
|
|
1211
1226
|
this.runtimeOptions.summaryOptions.summarizerOptions,
|
|
1212
1227
|
);
|
|
1213
|
-
this.summaryManager.on("summarizerWarning", this.summarizerWarning);
|
|
1214
1228
|
this.summaryManager.start();
|
|
1215
1229
|
}
|
|
1216
1230
|
}
|
|
@@ -1260,6 +1274,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1260
1274
|
});
|
|
1261
1275
|
|
|
1262
1276
|
ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
|
|
1277
|
+
BindBatchTracker(this, this.logger);
|
|
1263
1278
|
}
|
|
1264
1279
|
|
|
1265
1280
|
public dispose(error?: Error): void {
|
|
@@ -1276,7 +1291,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1276
1291
|
}, error);
|
|
1277
1292
|
|
|
1278
1293
|
if (this.summaryManager !== undefined) {
|
|
1279
|
-
this.summaryManager.off("summarizerWarning", this.summarizerWarning);
|
|
1280
1294
|
this.summaryManager.dispose();
|
|
1281
1295
|
}
|
|
1282
1296
|
this.garbageCollector.dispose();
|
|
@@ -1447,6 +1461,42 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1447
1461
|
}
|
|
1448
1462
|
}
|
|
1449
1463
|
|
|
1464
|
+
// Track how many times the container tries to reconnect with pending messages.
|
|
1465
|
+
// This happens when the connection state is changed and we reset the counter
|
|
1466
|
+
// when we are able to process a local op or when there are no pending messages.
|
|
1467
|
+
// If this counter reaches a max, it's a good indicator that the container
|
|
1468
|
+
// is not making progress and it is stuck in a retry loop.
|
|
1469
|
+
private shouldContinueReconnecting(): boolean {
|
|
1470
|
+
if (this.maxConsecutiveReconnects <= 0) {
|
|
1471
|
+
// Feature disabled, we never stop reconnecting
|
|
1472
|
+
return true;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
if (!this.pendingStateManager.hasPendingMessages()) {
|
|
1476
|
+
// If there are no pending messages, we can always reconnect
|
|
1477
|
+
this.resetReconnectCount();
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
this.consecutiveReconnects++;
|
|
1482
|
+
if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
|
|
1483
|
+
// If we're halfway through the max reconnects, send an event in order
|
|
1484
|
+
// to better identify false positives, if any. If the rate of this event
|
|
1485
|
+
// matches Container Close count below, we can safely cut down
|
|
1486
|
+
// maxConsecutiveReconnects to half.
|
|
1487
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1488
|
+
eventName: "ReconnectsWithNoProgress",
|
|
1489
|
+
attempts: this.consecutiveReconnects,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
return this.consecutiveReconnects < this.maxConsecutiveReconnects;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
private resetReconnectCount() {
|
|
1497
|
+
this.consecutiveReconnects = 0;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1450
1500
|
private replayPendingStates() {
|
|
1451
1501
|
// We need to be able to send ops to replay states
|
|
1452
1502
|
if (!this.canSendOps()) { return; }
|
|
@@ -1527,6 +1577,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1527
1577
|
if (changeOfState) {
|
|
1528
1578
|
this.deltaManager.off("op", this.onOp);
|
|
1529
1579
|
this.context.pendingLocalState = undefined;
|
|
1580
|
+
if (!this.shouldContinueReconnecting()) {
|
|
1581
|
+
this.closeFn(new GenericError(
|
|
1582
|
+
"Runtime detected too many reconnects with no progress syncing local ops",
|
|
1583
|
+
undefined, // error
|
|
1584
|
+
{ attempts: this.consecutiveReconnects }));
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1530
1588
|
this.replayPendingStates();
|
|
1531
1589
|
}
|
|
1532
1590
|
|
|
@@ -1590,6 +1648,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1590
1648
|
|
|
1591
1649
|
this.emit("op", message);
|
|
1592
1650
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1651
|
+
|
|
1652
|
+
if (local) {
|
|
1653
|
+
// If we have processed a local op, this means that the container is
|
|
1654
|
+
// making progress and we can reset the counter for how many times
|
|
1655
|
+
// we have consecutively replayed the pending states
|
|
1656
|
+
this.resetReconnectCount();
|
|
1657
|
+
}
|
|
1593
1658
|
} catch (e) {
|
|
1594
1659
|
this.scheduleManager.afterOpProcessing(e, message);
|
|
1595
1660
|
throw e;
|
|
@@ -1697,7 +1762,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1697
1762
|
this._orderSequentiallyCalls++;
|
|
1698
1763
|
callback();
|
|
1699
1764
|
} catch (error) {
|
|
1700
|
-
this.closeFn(new GenericError("
|
|
1765
|
+
this.closeFn(new GenericError("orderSequentially callback exception", error));
|
|
1701
1766
|
throw error; // throw the original error for the consumer of the runtime
|
|
1702
1767
|
} finally {
|
|
1703
1768
|
this._orderSequentiallyCalls--;
|
|
@@ -1743,12 +1808,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1743
1808
|
* @param props - Properties for the data store
|
|
1744
1809
|
* @returns - An aliased data store which can can be found / loaded by alias.
|
|
1745
1810
|
*/
|
|
1746
|
-
private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<
|
|
1811
|
+
private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<IDataStore> {
|
|
1747
1812
|
const internalId = uuid();
|
|
1748
1813
|
const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
|
|
1749
|
-
const aliasedDataStore = channelToDataStore(dataStore, internalId, this,this.dataStores, this.mc.logger);
|
|
1814
|
+
const aliasedDataStore = channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
|
|
1750
1815
|
const result = await aliasedDataStore.trySetAlias(alias);
|
|
1751
|
-
if (result !==
|
|
1816
|
+
if (result !== "Success") {
|
|
1752
1817
|
throw new GenericError(
|
|
1753
1818
|
"dataStoreAliasFailure",
|
|
1754
1819
|
undefined /* error */,
|
|
@@ -1790,13 +1855,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1790
1855
|
props?: any,
|
|
1791
1856
|
id = uuid(),
|
|
1792
1857
|
isRoot = false,
|
|
1793
|
-
): Promise<
|
|
1858
|
+
): Promise<IDataStore> {
|
|
1794
1859
|
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
|
|
1795
1860
|
Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
1796
1861
|
if (isRoot) {
|
|
1797
1862
|
fluidDataStore.bindToContext();
|
|
1798
1863
|
}
|
|
1799
|
-
return fluidDataStore;
|
|
1864
|
+
return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
|
|
1800
1865
|
}
|
|
1801
1866
|
|
|
1802
1867
|
public async _createDataStoreWithProps(
|
|
@@ -1804,7 +1869,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1804
1869
|
props?: any,
|
|
1805
1870
|
id = uuid(),
|
|
1806
1871
|
isRoot = false,
|
|
1807
|
-
): Promise<
|
|
1872
|
+
): Promise<IDataStore> {
|
|
1808
1873
|
return this._aliasingEnabled === true && isRoot ?
|
|
1809
1874
|
this.createAndAliasDataStore(pkg, id, props) :
|
|
1810
1875
|
this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
|
|
@@ -2077,6 +2142,15 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2077
2142
|
const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
|
|
2078
2143
|
const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
|
|
2079
2144
|
|
|
2145
|
+
// We should be here is we haven't processed be here. If we are of if the last message's sequence number
|
|
2146
|
+
// doesn't match the last processed sequence number, log an error.
|
|
2147
|
+
if (summaryRefSeqNum !== this.deltaManager.lastMessage?.sequenceNumber) {
|
|
2148
|
+
summaryLogger.sendErrorEvent({
|
|
2149
|
+
eventName: "LastSequenceMismatch",
|
|
2150
|
+
message,
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2080
2154
|
this.summarizerNode.startSummary(summaryRefSeqNum, summaryLogger);
|
|
2081
2155
|
|
|
2082
2156
|
// Helper function to check whether we should still continue between each async step.
|
|
@@ -2346,18 +2420,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2346
2420
|
}
|
|
2347
2421
|
}
|
|
2348
2422
|
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
/* batch: */ this._flushMode === FlushMode.TurnBased,
|
|
2357
|
-
opMetadataInternal);
|
|
2358
|
-
} else {
|
|
2359
|
-
clientSequenceNumber = this.submitChunkedMessage(type, serializedContent, maxOpSize);
|
|
2360
|
-
}
|
|
2423
|
+
clientSequenceNumber = this.submitMaybeChunkedMessages(
|
|
2424
|
+
type,
|
|
2425
|
+
content,
|
|
2426
|
+
serializedContent,
|
|
2427
|
+
maxOpSize,
|
|
2428
|
+
this._flushMode === FlushMode.TurnBased,
|
|
2429
|
+
opMetadataInternal);
|
|
2361
2430
|
}
|
|
2362
2431
|
|
|
2363
2432
|
// Let the PendingStateManager know that a message was submitted.
|
|
@@ -2374,6 +2443,48 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2374
2443
|
}
|
|
2375
2444
|
}
|
|
2376
2445
|
|
|
2446
|
+
private submitMaybeChunkedMessages(
|
|
2447
|
+
type: ContainerMessageType,
|
|
2448
|
+
content: any,
|
|
2449
|
+
serializedContent: string,
|
|
2450
|
+
serverMaxOpSize: number,
|
|
2451
|
+
batch: boolean,
|
|
2452
|
+
opMetadataInternal: unknown = undefined,
|
|
2453
|
+
): number {
|
|
2454
|
+
if (this._maxOpSizeInBytes >= 0) {
|
|
2455
|
+
// Chunking disabled
|
|
2456
|
+
if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
|
|
2457
|
+
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// When chunking is disabled, we ignore the server max message size
|
|
2461
|
+
// and if the content length is larger than the client configured message size
|
|
2462
|
+
// instead of splitting the content, we will fail by explicitly close the container
|
|
2463
|
+
this.closeFn(new GenericError(
|
|
2464
|
+
"OpTooLarge",
|
|
2465
|
+
/* error */ undefined,
|
|
2466
|
+
{
|
|
2467
|
+
length: {
|
|
2468
|
+
value: serializedContent.length,
|
|
2469
|
+
tag: TelemetryDataTag.PackageData,
|
|
2470
|
+
},
|
|
2471
|
+
limit: {
|
|
2472
|
+
value: this._maxOpSizeInBytes,
|
|
2473
|
+
tag: TelemetryDataTag.PackageData,
|
|
2474
|
+
},
|
|
2475
|
+
}));
|
|
2476
|
+
return -1;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// Chunking enabled, fallback on the server's max message size
|
|
2480
|
+
// and split the content accordingly
|
|
2481
|
+
if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
|
|
2482
|
+
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2377
2488
|
private submitChunkedMessage(type: ContainerMessageType, content: string, maxOpSize: number): number {
|
|
2378
2489
|
const contentLength = content.length;
|
|
2379
2490
|
const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
|
|
@@ -2489,6 +2600,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2489
2600
|
summaryRefSeq,
|
|
2490
2601
|
async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
2491
2602
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2603
|
+
ackHandle,
|
|
2604
|
+
summaryRefSeq,
|
|
2492
2605
|
fetchLatest: false,
|
|
2493
2606
|
}),
|
|
2494
2607
|
readAndParseBlob,
|
package/src/dataStore.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { unreachableCase } from "@fluidframework/common-utils";
|
|
8
8
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
9
|
-
import {
|
|
10
|
-
import { IFluidDataStoreChannel } from "@fluidframework/runtime-definitions";
|
|
9
|
+
import { IRequest, IResponse } from "@fluidframework/core-interfaces";
|
|
10
|
+
import { AliasResult, IDataStore, IFluidDataStoreChannel } from "@fluidframework/runtime-definitions";
|
|
11
11
|
import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
12
12
|
import { ContainerRuntime } from "./containerRuntime";
|
|
13
13
|
import { DataStores } from "./dataStores";
|
|
@@ -36,42 +36,6 @@ export interface IDataStoreAliasMessage {
|
|
|
36
36
|
&& typeof maybeDataStoreAliasMessage?.alias === "string";
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Encapsulates the return codes of the aliasing API
|
|
41
|
-
*/
|
|
42
|
-
export enum AliasResult {
|
|
43
|
-
/**
|
|
44
|
-
* The datastore has been successfully aliased
|
|
45
|
-
*/
|
|
46
|
-
Success = "Success",
|
|
47
|
-
/**
|
|
48
|
-
* There is already a datastore bound to the provided alias
|
|
49
|
-
*/
|
|
50
|
-
Conflict = "Conflict",
|
|
51
|
-
/**
|
|
52
|
-
* The datastore is currently in the process of being aliased
|
|
53
|
-
*/
|
|
54
|
-
Aliasing = "Aliasing",
|
|
55
|
-
/**
|
|
56
|
-
* The datastore has been attempted to be aliased before
|
|
57
|
-
*/
|
|
58
|
-
AlreadyAliased = "AlreadyAliased",
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* A fluid router with the capability of being assigned an alias
|
|
63
|
-
*/
|
|
64
|
-
export interface IDataStore extends IFluidRouter {
|
|
65
|
-
/**
|
|
66
|
-
* Attempt to assign an alias to the datastore.
|
|
67
|
-
* If the operation succeeds, the datastore can be referenced
|
|
68
|
-
* by the supplied alias.
|
|
69
|
-
*
|
|
70
|
-
* @param alias - Given alias for this datastore.
|
|
71
|
-
*/
|
|
72
|
-
trySetAlias(alias: string): Promise<AliasResult>;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
39
|
export const channelToDataStore = (
|
|
76
40
|
fluidDataStoreChannel: IFluidDataStoreChannel,
|
|
77
41
|
internalId: string,
|
|
@@ -94,11 +58,11 @@ class DataStore implements IDataStore {
|
|
|
94
58
|
switch (this.aliasState) {
|
|
95
59
|
// If we're already aliasing, throw an exception
|
|
96
60
|
case AliasState.Aliasing:
|
|
97
|
-
return
|
|
61
|
+
return "Aliasing";
|
|
98
62
|
// If this datastore is already aliased, return true only if this
|
|
99
63
|
// is a repeated call for the same alias
|
|
100
64
|
case AliasState.Aliased:
|
|
101
|
-
return this.alias === alias ?
|
|
65
|
+
return this.alias === alias ? "Success" : "AlreadyAliased";
|
|
102
66
|
// There is no current or past alias operation for this datastore,
|
|
103
67
|
// it is safe to continue execution
|
|
104
68
|
case AliasState.None: break;
|
|
@@ -118,7 +82,7 @@ class DataStore implements IDataStore {
|
|
|
118
82
|
// Explicitly Lock-out future attempts of aliasing,
|
|
119
83
|
// regardless of result
|
|
120
84
|
this.aliasState = AliasState.Aliased;
|
|
121
|
-
return localResult ?
|
|
85
|
+
return localResult ? "Success" : "Conflict";
|
|
122
86
|
}
|
|
123
87
|
|
|
124
88
|
const aliased = await this.ackBasedPromise<boolean>((resolve) => {
|
|
@@ -148,7 +112,7 @@ class DataStore implements IDataStore {
|
|
|
148
112
|
return false;
|
|
149
113
|
});
|
|
150
114
|
|
|
151
|
-
return aliased ?
|
|
115
|
+
return aliased ? "Success" : "Conflict";
|
|
152
116
|
}
|
|
153
117
|
|
|
154
118
|
async request(request: IRequest): Promise<IResponse> {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
TelemetryDataTag,
|
|
67
67
|
ThresholdCounter,
|
|
68
68
|
} from "@fluidframework/telemetry-utils";
|
|
69
|
-
import {
|
|
69
|
+
import { DataProcessingError } from "@fluidframework/container-utils";
|
|
70
70
|
|
|
71
71
|
import { ContainerRuntime } from "./containerRuntime";
|
|
72
72
|
import {
|
|
@@ -106,11 +106,6 @@ export function createAttributesBlob(
|
|
|
106
106
|
|
|
107
107
|
interface ISnapshotDetails {
|
|
108
108
|
pkg: readonly string[];
|
|
109
|
-
/**
|
|
110
|
-
* This tells whether a data store is root. Root data stores are never collected.
|
|
111
|
-
* Non-root data stores may be collected if they are not used.
|
|
112
|
-
*/
|
|
113
|
-
isRootDataStore: boolean;
|
|
114
109
|
snapshot?: ISnapshotTree;
|
|
115
110
|
}
|
|
116
111
|
|
|
@@ -212,7 +207,10 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
212
207
|
}
|
|
213
208
|
|
|
214
209
|
public async isRoot(): Promise<boolean> {
|
|
215
|
-
|
|
210
|
+
// This call updates this.isRootDataStore if it has not yet been updated
|
|
211
|
+
// The initial value is stored in the initial snapshot of the data store
|
|
212
|
+
await this.getInitialSnapshotDetails();
|
|
213
|
+
return this.isRootDataStore;
|
|
216
214
|
}
|
|
217
215
|
|
|
218
216
|
protected registry: IFluidDataStoreRegistry | undefined;
|
|
@@ -225,6 +223,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
225
223
|
protected channelDeferred: Deferred<IFluidDataStoreChannel> | undefined;
|
|
226
224
|
private _baseSnapshot: ISnapshotTree | undefined;
|
|
227
225
|
protected _attachState: AttachState;
|
|
226
|
+
protected isRootDataStore: boolean = false;
|
|
228
227
|
protected readonly summarizerNode: ISummarizerNodeWithGC;
|
|
229
228
|
private readonly subLogger: ITelemetryLogger;
|
|
230
229
|
private readonly thresholdOpsCounter: ThresholdCounter;
|
|
@@ -311,7 +310,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
311
310
|
if (!this.channelDeferred) {
|
|
312
311
|
this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
|
|
313
312
|
this.realizeCore(this.existing).catch((error) => {
|
|
314
|
-
const errorWrapped =
|
|
313
|
+
const errorWrapped = DataProcessingError.wrapIfUnrecognized(error, "realizeFluidDataStoreContext");
|
|
315
314
|
errorWrapped.addTelemetryProperties({ fluidDataStoreId: { value: this.id, tag: "PackageData"} });
|
|
316
315
|
this.channelDeferred?.reject(errorWrapped);
|
|
317
316
|
this.logger.sendErrorEvent({ eventName: "RealizeError"}, errorWrapped);
|
|
@@ -450,8 +449,8 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
450
449
|
}
|
|
451
450
|
|
|
452
451
|
// Add data store's attributes to the summary.
|
|
453
|
-
const { pkg
|
|
454
|
-
const attributes = createAttributes(pkg, isRootDataStore, this.disableIsolatedChannels);
|
|
452
|
+
const { pkg } = await this.getInitialSnapshotDetails();
|
|
453
|
+
const attributes = createAttributes(pkg, this.isRootDataStore, this.disableIsolatedChannels);
|
|
455
454
|
addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
|
|
456
455
|
|
|
457
456
|
// Add GC data to the summary if it's not written at the root.
|
|
@@ -729,7 +728,6 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
729
728
|
}
|
|
730
729
|
|
|
731
730
|
export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
732
|
-
private isRootDataStore: boolean | undefined;
|
|
733
731
|
private readonly initSnapshotValue: ISnapshotTree | string | undefined;
|
|
734
732
|
private readonly baseGCDetailsP: Promise<IGarbageCollectionDetailsBase>;
|
|
735
733
|
|
|
@@ -804,11 +802,12 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
804
802
|
}
|
|
805
803
|
}
|
|
806
804
|
|
|
805
|
+
this.isRootDataStore = isRootDataStore;
|
|
806
|
+
|
|
807
807
|
return {
|
|
808
808
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
809
809
|
pkg: this.pkg!,
|
|
810
810
|
snapshot: tree,
|
|
811
|
-
isRootDataStore,
|
|
812
811
|
};
|
|
813
812
|
});
|
|
814
813
|
|
|
@@ -846,7 +845,6 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
846
845
|
*/
|
|
847
846
|
export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
848
847
|
private readonly snapshotTree: ISnapshotTree | undefined;
|
|
849
|
-
protected isRootDataStore: boolean | undefined;
|
|
850
848
|
/**
|
|
851
849
|
* @deprecated 0.16 Issue #1635, #3631
|
|
852
850
|
*/
|
|
@@ -862,7 +860,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
862
860
|
);
|
|
863
861
|
|
|
864
862
|
this.snapshotTree = props.snapshotTree;
|
|
865
|
-
this.isRootDataStore = props.isRootDataStore;
|
|
863
|
+
this.isRootDataStore = props.isRootDataStore ?? false;
|
|
866
864
|
this.createProps = props.createProps;
|
|
867
865
|
this.attachListeners();
|
|
868
866
|
}
|
|
@@ -938,7 +936,6 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
938
936
|
return {
|
|
939
937
|
pkg: this.pkg,
|
|
940
938
|
snapshot,
|
|
941
|
-
isRootDataStore: this.isRootDataStore,
|
|
942
939
|
};
|
|
943
940
|
}
|
|
944
941
|
|
package/src/dataStores.ts
CHANGED
|
@@ -194,7 +194,7 @@ export class DataStores implements IDisposable {
|
|
|
194
194
|
if (this.alreadyProcessed(attachMessage.id)) {
|
|
195
195
|
// TODO: dataStoreId may require a different tag from PackageData #7488
|
|
196
196
|
const error = new DataCorruptionError(
|
|
197
|
-
"
|
|
197
|
+
"Duplicate DataStore created with existing id",
|
|
198
198
|
{
|
|
199
199
|
...extractSafePropertiesFromMessage(message),
|
|
200
200
|
dataStoreId: {
|
package/src/garbageCollection.ts
CHANGED
|
@@ -69,7 +69,7 @@ const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
|
69
69
|
const runSessionExpiry = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
70
70
|
|
|
71
71
|
const defaultDeleteTimeoutMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
72
|
-
const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
72
|
+
export const defaultSessionExpiryDurationMs = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
73
73
|
|
|
74
74
|
/** The statistics of the system state after a garbage collection run. */
|
|
75
75
|
export interface IGCStats {
|
|
@@ -545,17 +545,18 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
545
545
|
// used in the container.
|
|
546
546
|
if (this.shouldRunGC) {
|
|
547
547
|
this.initializeBaseStateP.catch((error) => {
|
|
548
|
-
|
|
549
|
-
error
|
|
548
|
+
const dpe = DataProcessingError.wrapIfUnrecognized(
|
|
549
|
+
error,
|
|
550
550
|
"FailedToInitializeGC",
|
|
551
|
-
{
|
|
552
|
-
gcEnabled: this.gcEnabled,
|
|
553
|
-
runSweep: this.shouldRunSweep,
|
|
554
|
-
writeAtRoot: this._writeDataAtRoot,
|
|
555
|
-
testMode: this.testMode,
|
|
556
|
-
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
557
|
-
},
|
|
558
551
|
);
|
|
552
|
+
dpe.addTelemetryProperties({
|
|
553
|
+
gcEnabled: this.gcEnabled,
|
|
554
|
+
runSweep: this.shouldRunSweep,
|
|
555
|
+
writeAtRoot: this._writeDataAtRoot,
|
|
556
|
+
testMode: this.testMode,
|
|
557
|
+
sessionExpiry: this.sessionExpiryTimeoutMs,
|
|
558
|
+
});
|
|
559
|
+
throw dpe;
|
|
559
560
|
});
|
|
560
561
|
}
|
|
561
562
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,6 @@ export {
|
|
|
19
19
|
ContainerRuntime,
|
|
20
20
|
RuntimeHeaders,
|
|
21
21
|
} from "./containerRuntime";
|
|
22
|
-
export { IDataStore, AliasResult } from "./dataStore";
|
|
23
22
|
export { DeltaScheduler } from "./deltaScheduler";
|
|
24
23
|
export { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
25
24
|
export {
|
package/src/packageVersion.ts
CHANGED
|
@@ -339,15 +339,11 @@ export class PendingStateManager implements IDisposable {
|
|
|
339
339
|
// The clientSequenceNumber of the incoming message must match that of the pending message.
|
|
340
340
|
if (pendingState.clientSequenceNumber !== message.clientSequenceNumber) {
|
|
341
341
|
// Close the container because this could indicate data corruption.
|
|
342
|
-
const error =
|
|
342
|
+
const error = DataProcessingError.create(
|
|
343
|
+
"pending local message clientSequenceNumber mismatch",
|
|
343
344
|
"unexpectedAckReceived",
|
|
344
|
-
|
|
345
|
-
{
|
|
346
|
-
clientId: message.clientId,
|
|
347
|
-
sequenceNumber: message.sequenceNumber,
|
|
348
|
-
clientSequenceNumber: message.clientSequenceNumber,
|
|
349
|
-
expectedClientSequenceNumber: pendingState.clientSequenceNumber,
|
|
350
|
-
},
|
|
345
|
+
message,
|
|
346
|
+
{ expectedClientSequenceNumber: pendingState.clientSequenceNumber },
|
|
351
347
|
);
|
|
352
348
|
|
|
353
349
|
this.containerRuntime.closeFn(error);
|