@fluidframework/container-runtime 0.57.0-51086 → 0.58.0-55561

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.
Files changed (114) hide show
  1. package/dist/batchTracker.d.ts +26 -0
  2. package/dist/batchTracker.d.ts.map +1 -0
  3. package/dist/batchTracker.js +59 -0
  4. package/dist/batchTracker.js.map +1 -0
  5. package/dist/containerRuntime.d.ts +12 -7
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +125 -55
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStore.d.ts +1 -36
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +5 -27
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +5 -7
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +12 -7
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +1 -1
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +3 -3
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +25 -11
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +100 -57
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/index.d.ts +0 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -3
  28. package/dist/index.js.map +1 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/dist/pendingStateManager.d.ts.map +1 -1
  33. package/dist/pendingStateManager.js +1 -6
  34. package/dist/pendingStateManager.js.map +1 -1
  35. package/dist/runningSummarizer.d.ts +1 -1
  36. package/dist/runningSummarizer.d.ts.map +1 -1
  37. package/dist/runningSummarizer.js +1 -1
  38. package/dist/runningSummarizer.js.map +1 -1
  39. package/dist/summarizer.d.ts +3 -4
  40. package/dist/summarizer.d.ts.map +1 -1
  41. package/dist/summarizer.js +8 -9
  42. package/dist/summarizer.js.map +1 -1
  43. package/dist/summaryGenerator.d.ts +1 -1
  44. package/dist/summaryGenerator.d.ts.map +1 -1
  45. package/dist/summaryGenerator.js +1 -1
  46. package/dist/summaryGenerator.js.map +1 -1
  47. package/dist/summaryManager.d.ts +2 -6
  48. package/dist/summaryManager.d.ts.map +1 -1
  49. package/dist/summaryManager.js +4 -10
  50. package/dist/summaryManager.js.map +1 -1
  51. package/lib/batchTracker.d.ts +26 -0
  52. package/lib/batchTracker.d.ts.map +1 -0
  53. package/lib/batchTracker.js +54 -0
  54. package/lib/batchTracker.js.map +1 -0
  55. package/lib/containerRuntime.d.ts +12 -7
  56. package/lib/containerRuntime.d.ts.map +1 -1
  57. package/lib/containerRuntime.js +126 -56
  58. package/lib/containerRuntime.js.map +1 -1
  59. package/lib/dataStore.d.ts +1 -36
  60. package/lib/dataStore.d.ts.map +1 -1
  61. package/lib/dataStore.js +4 -26
  62. package/lib/dataStore.js.map +1 -1
  63. package/lib/dataStoreContext.d.ts +5 -7
  64. package/lib/dataStoreContext.d.ts.map +1 -1
  65. package/lib/dataStoreContext.js +13 -8
  66. package/lib/dataStoreContext.js.map +1 -1
  67. package/lib/dataStores.d.ts +1 -1
  68. package/lib/dataStores.d.ts.map +1 -1
  69. package/lib/dataStores.js +3 -3
  70. package/lib/dataStores.js.map +1 -1
  71. package/lib/garbageCollection.d.ts +25 -11
  72. package/lib/garbageCollection.d.ts.map +1 -1
  73. package/lib/garbageCollection.js +98 -55
  74. package/lib/garbageCollection.js.map +1 -1
  75. package/lib/index.d.ts +0 -1
  76. package/lib/index.d.ts.map +1 -1
  77. package/lib/index.js +0 -1
  78. package/lib/index.js.map +1 -1
  79. package/lib/packageVersion.d.ts +1 -1
  80. package/lib/packageVersion.js +1 -1
  81. package/lib/packageVersion.js.map +1 -1
  82. package/lib/pendingStateManager.d.ts.map +1 -1
  83. package/lib/pendingStateManager.js +1 -6
  84. package/lib/pendingStateManager.js.map +1 -1
  85. package/lib/runningSummarizer.d.ts +1 -1
  86. package/lib/runningSummarizer.d.ts.map +1 -1
  87. package/lib/runningSummarizer.js +1 -1
  88. package/lib/runningSummarizer.js.map +1 -1
  89. package/lib/summarizer.d.ts +3 -4
  90. package/lib/summarizer.d.ts.map +1 -1
  91. package/lib/summarizer.js +8 -9
  92. package/lib/summarizer.js.map +1 -1
  93. package/lib/summaryGenerator.d.ts +1 -1
  94. package/lib/summaryGenerator.d.ts.map +1 -1
  95. package/lib/summaryGenerator.js +1 -1
  96. package/lib/summaryGenerator.js.map +1 -1
  97. package/lib/summaryManager.d.ts +2 -6
  98. package/lib/summaryManager.d.ts.map +1 -1
  99. package/lib/summaryManager.js +5 -11
  100. package/lib/summaryManager.js.map +1 -1
  101. package/package.json +12 -12
  102. package/src/batchTracker.ts +80 -0
  103. package/src/containerRuntime.ts +180 -63
  104. package/src/dataStore.ts +6 -42
  105. package/src/dataStoreContext.ts +17 -15
  106. package/src/dataStores.ts +9 -4
  107. package/src/garbageCollection.ts +125 -67
  108. package/src/index.ts +0 -1
  109. package/src/packageVersion.ts +1 -1
  110. package/src/pendingStateManager.ts +4 -8
  111. package/src/runningSummarizer.ts +3 -3
  112. package/src/summarizer.ts +8 -8
  113. package/src/summaryGenerator.ts +2 -2
  114. package/src/summaryManager.ts +5 -20
@@ -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 passLogger = context.taggedLogger ?? new TaggedLoggerAdapter((context as
690
- OldContainerContextWithLogger).logger);
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
- "SummaryMetadataMismatch",
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.
@@ -952,8 +967,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
952
967
  * and is the single source of truth for this container.
953
968
  */
954
969
  public readonly disableIsolatedChannels: boolean;
955
- /** The message in the metadata of the base summary this container is loaded from. */
956
- private readonly baseSummaryMessage: ISummaryMetadataMessage | undefined;
970
+ /** The last message processed at the time of the last summary. */
971
+ private messageAtLastSummary: ISummaryMetadataMessage | undefined;
957
972
 
958
973
  private get summarizer(): Summarizer {
959
974
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
@@ -984,7 +999,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
984
999
  private _storage?: IDocumentStorageService,
985
1000
  ) {
986
1001
  super();
987
- this.baseSummaryMessage = metadata?.message;
1002
+
1003
+ this.messageAtLastSummary = metadata?.message;
988
1004
 
989
1005
  // If this is an existing container, we get values from metadata.
990
1006
  // otherwise, we initialize them.
@@ -1012,33 +1028,25 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1012
1028
  this.mc = loggerToMonitoringContext(
1013
1029
  ChildLogger.create(this.logger, "ContainerRuntime"));
1014
1030
 
1015
- this._flushMode =
1016
- this.mc.config.getBoolean(turnBasedFlushModeKey) ?? false
1017
- ? FlushMode.TurnBased : FlushMode.Immediate;
1018
-
1019
1031
  this._aliasingEnabled =
1020
1032
  (this.mc.config.getBoolean(useDataStoreAliasingKey) ?? false) ||
1021
1033
  (runtimeOptions.useDataStoreAliasing ?? false);
1022
1034
 
1023
- /**
1024
- * Function that return the current server timestamp. This is used by the garbage collector to set the
1025
- * time when a node becomes unreferenced.
1026
- * We use the timestamp of the last op for current timestamp. However, there can be cases where
1027
- * we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
1028
- * of this client's connection.
1029
- */
1030
- const getCurrentTimestamp = () => {
1031
- const client = this.clientId !== undefined ? this.getAudience().getMember(this.clientId) : undefined;
1032
- const timestamp = client?.timestamp;
1033
- return this.deltaManager.lastMessage?.timestamp ?? timestamp ?? Date.now();
1034
- };
1035
+ this._maxOpSizeInBytes = (this.mc.config.getNumber(maxOpSizeInBytesKey) ?? defaultMaxOpSizeInBytes);
1036
+ this.maxConsecutiveReconnects =
1037
+ this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
1038
+
1035
1039
  this.garbageCollector = GarbageCollector.create(
1036
1040
  this,
1037
1041
  this.runtimeOptions.gcOptions,
1038
1042
  (unusedRoutes: string[]) => this.dataStores.deleteUnusedRoutes(unusedRoutes),
1039
1043
  (nodePath: string) => this.dataStores.getNodePackagePath(nodePath),
1040
- getCurrentTimestamp,
1041
- this.closeFn,
1044
+ /**
1045
+ * Returns the timestamp of the last message seen by this client. This is used by garbage collector as
1046
+ * the current reference timestamp for tracking unreferenced objects.
1047
+ */
1048
+ () => this.deltaManager.lastMessage?.timestamp ?? this.messageAtLastSummary?.timestamp,
1049
+ () => this.messageAtLastSummary?.timestamp,
1042
1050
  context.baseSnapshot,
1043
1051
  async <T>(id: string) => readAndParse<T>(this.storage, id),
1044
1052
  this.mc.logger,
@@ -1090,8 +1098,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1090
1098
  (id: string) => this.summarizerNode.deleteChild(id),
1091
1099
  this.mc.logger,
1092
1100
  async () => this.garbageCollector.getDataStoreBaseGCDetails(),
1093
- (dataStorePath: string, packagePath?: readonly string[]) =>
1094
- this.garbageCollector.nodeUpdated(dataStorePath, "Changed", packagePath),
1101
+ (path: string, timestampMs: number, packagePath?: readonly string[]) => this.garbageCollector.nodeUpdated(
1102
+ path,
1103
+ "Changed",
1104
+ timestampMs,
1105
+ packagePath,
1106
+ ),
1095
1107
  new Map<string, string>(dataStoreAliasMap),
1096
1108
  this.garbageCollector.writeDataAtRoot,
1097
1109
  );
@@ -1213,7 +1225,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1213
1225
  },
1214
1226
  this.runtimeOptions.summaryOptions.summarizerOptions,
1215
1227
  );
1216
- this.summaryManager.on("summarizerWarning", this.summarizerWarning);
1217
1228
  this.summaryManager.start();
1218
1229
  }
1219
1230
  }
@@ -1263,6 +1274,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1263
1274
  });
1264
1275
 
1265
1276
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
1277
+ BindBatchTracker(this, this.logger);
1266
1278
  }
1267
1279
 
1268
1280
  public dispose(error?: Error): void {
@@ -1279,7 +1291,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1279
1291
  }, error);
1280
1292
 
1281
1293
  if (this.summaryManager !== undefined) {
1282
- this.summaryManager.off("summarizerWarning", this.summarizerWarning);
1283
1294
  this.summaryManager.dispose();
1284
1295
  }
1285
1296
  this.garbageCollector.dispose();
@@ -1393,9 +1404,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1393
1404
  }
1394
1405
 
1395
1406
  const dataStoreChannel = await dataStoreContext.realize();
1396
- // Let the garbage collector know that a data store was requested / loaded. Realize the data store first so
1397
- // that the package path is available.
1398
- this.garbageCollector.nodeUpdated(`/${id}`, "Loaded", dataStoreContext.packagePath, request?.headers);
1407
+ this.garbageCollector.nodeUpdated(
1408
+ `/${id}`,
1409
+ "Loaded",
1410
+ undefined /* timestampMs */,
1411
+ dataStoreContext.packagePath,
1412
+ request?.headers,
1413
+ );
1399
1414
  return dataStoreChannel;
1400
1415
  }
1401
1416
 
@@ -1406,9 +1421,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1406
1421
  summaryFormatVersion: 1,
1407
1422
  disableIsolatedChannels: this.disableIsolatedChannels || undefined,
1408
1423
  gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
1409
- // The last message processed at the time of summary. If there are no messages, nothing has changed from
1410
- // the base summary we loaded from. So, use the message from its metadata blob.
1411
- message: extractSummaryMetadataMessage(this.deltaManager.lastMessage) ?? this.baseSummaryMessage,
1424
+ // The last message processed at the time of summary. If there are no new messages, use the message from the
1425
+ // last summary.
1426
+ message: extractSummaryMetadataMessage(this.deltaManager.lastMessage) ?? this.messageAtLastSummary,
1412
1427
  sessionExpiryTimeoutMs: this.garbageCollector.sessionExpiryTimeoutMs,
1413
1428
  };
1414
1429
  }
@@ -1446,6 +1461,42 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1446
1461
  }
1447
1462
  }
1448
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
+
1449
1500
  private replayPendingStates() {
1450
1501
  // We need to be able to send ops to replay states
1451
1502
  if (!this.canSendOps()) { return; }
@@ -1526,6 +1577,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1526
1577
  if (changeOfState) {
1527
1578
  this.deltaManager.off("op", this.onOp);
1528
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
+
1529
1588
  this.replayPendingStates();
1530
1589
  }
1531
1590
 
@@ -1589,6 +1648,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1589
1648
 
1590
1649
  this.emit("op", message);
1591
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
+ }
1592
1658
  } catch (e) {
1593
1659
  this.scheduleManager.afterOpProcessing(e, message);
1594
1660
  throw e;
@@ -1696,7 +1762,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1696
1762
  this._orderSequentiallyCalls++;
1697
1763
  callback();
1698
1764
  } catch (error) {
1699
- this.closeFn(new GenericError("orderSequentiallyCallbackException", error));
1765
+ this.closeFn(new GenericError("orderSequentially callback exception", error));
1700
1766
  throw error; // throw the original error for the consumer of the runtime
1701
1767
  } finally {
1702
1768
  this._orderSequentiallyCalls--;
@@ -1742,12 +1808,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1742
1808
  * @param props - Properties for the data store
1743
1809
  * @returns - An aliased data store which can can be found / loaded by alias.
1744
1810
  */
1745
- private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<IFluidRouter> {
1811
+ private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<IDataStore> {
1746
1812
  const internalId = uuid();
1747
1813
  const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
1748
- const aliasedDataStore = channelToDataStore(dataStore, internalId, this,this.dataStores, this.mc.logger);
1814
+ const aliasedDataStore = channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
1749
1815
  const result = await aliasedDataStore.trySetAlias(alias);
1750
- if (result !== AliasResult.Success) {
1816
+ if (result !== "Success") {
1751
1817
  throw new GenericError(
1752
1818
  "dataStoreAliasFailure",
1753
1819
  undefined /* error */,
@@ -1789,13 +1855,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1789
1855
  props?: any,
1790
1856
  id = uuid(),
1791
1857
  isRoot = false,
1792
- ): Promise<IFluidRouter> {
1858
+ ): Promise<IDataStore> {
1793
1859
  const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
1794
1860
  Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
1795
1861
  if (isRoot) {
1796
1862
  fluidDataStore.bindToContext();
1797
1863
  }
1798
- return fluidDataStore;
1864
+ return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
1799
1865
  }
1800
1866
 
1801
1867
  public async _createDataStoreWithProps(
@@ -1803,7 +1869,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1803
1869
  props?: any,
1804
1870
  id = uuid(),
1805
1871
  isRoot = false,
1806
- ): Promise<IFluidRouter> {
1872
+ ): Promise<IDataStore> {
1807
1873
  return this._aliasingEnabled === true && isRoot ?
1808
1874
  this.createAndAliasDataStore(pkg, id, props) :
1809
1875
  this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
@@ -2076,6 +2142,15 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2076
2142
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
2077
2143
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
2078
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
+
2079
2154
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryLogger);
2080
2155
 
2081
2156
  // Helper function to check whether we should still continue between each async step.
@@ -2136,6 +2211,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2136
2211
  }
2137
2212
  const { summary: summaryTree, stats: partialStats } = summarizeResult;
2138
2213
 
2214
+ // Now that we have generated the summary, update the message at last summary to the last message processed.
2215
+ this.messageAtLastSummary = this.deltaManager.lastMessage;
2216
+
2139
2217
  // Counting dataStores and handles
2140
2218
  // Because handles are unchanged dataStores in the current logic,
2141
2219
  // summarized dataStore count is total dataStore count minus handle count
@@ -2342,18 +2420,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2342
2420
  }
2343
2421
  }
2344
2422
 
2345
- // Note: Chunking will increase content beyond maxOpSize because we JSON'ing JSON payload -
2346
- // there will be a lot of escape characters that can make it up to 2x bigger!
2347
- // This is Ok, because DeltaManager.shouldSplit() will have 2 * maxMessageSize limit
2348
- if (!serializedContent || serializedContent.length <= maxOpSize) {
2349
- clientSequenceNumber = this.submitRuntimeMessage(
2350
- type,
2351
- content,
2352
- /* batch: */ this._flushMode === FlushMode.TurnBased,
2353
- opMetadataInternal);
2354
- } else {
2355
- clientSequenceNumber = this.submitChunkedMessage(type, serializedContent, maxOpSize);
2356
- }
2423
+ clientSequenceNumber = this.submitMaybeChunkedMessages(
2424
+ type,
2425
+ content,
2426
+ serializedContent,
2427
+ maxOpSize,
2428
+ this._flushMode === FlushMode.TurnBased,
2429
+ opMetadataInternal);
2357
2430
  }
2358
2431
 
2359
2432
  // Let the PendingStateManager know that a message was submitted.
@@ -2370,6 +2443,48 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2370
2443
  }
2371
2444
  }
2372
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
+
2373
2488
  private submitChunkedMessage(type: ContainerMessageType, content: string, maxOpSize: number): number {
2374
2489
  const contentLength = content.length;
2375
2490
  const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
@@ -2485,6 +2600,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2485
2600
  summaryRefSeq,
2486
2601
  async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
2487
2602
  eventName: "RefreshLatestSummaryGetSnapshot",
2603
+ ackHandle,
2604
+ summaryRefSeq,
2488
2605
  fetchLatest: false,
2489
2606
  }),
2490
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 { IFluidRouter, IRequest, IResponse } from "@fluidframework/core-interfaces";
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 AliasResult.Aliasing;
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 ? AliasResult.Success : AliasResult.AlreadyAliased;
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 ? AliasResult.Success : AliasResult.Conflict;
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 ? AliasResult.Success : AliasResult.Conflict;
115
+ return aliased ? "Success" : "Conflict";
152
116
  }
153
117
 
154
118
  async request(request: IRequest): Promise<IResponse> {
@@ -66,7 +66,7 @@ import {
66
66
  TelemetryDataTag,
67
67
  ThresholdCounter,
68
68
  } from "@fluidframework/telemetry-utils";
69
- import { CreateProcessingError } from "@fluidframework/container-utils";
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
- return (await this.getInitialSnapshotDetails()).isRootDataStore;
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 = CreateProcessingError(error, "realizeFluidDataStoreContext");
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, isRootDataStore } = await this.getInitialSnapshotDetails();
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,10 @@ 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;
848
+ /**
849
+ * @deprecated 0.16 Issue #1635, #3631
850
+ */
851
+ public readonly createProps?: any;
850
852
 
851
853
  constructor(props: ILocalFluidDataStoreContextProps) {
852
854
  super(
@@ -858,7 +860,8 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
858
860
  );
859
861
 
860
862
  this.snapshotTree = props.snapshotTree;
861
- this.isRootDataStore = props.isRootDataStore;
863
+ this.isRootDataStore = props.isRootDataStore ?? false;
864
+ this.createProps = props.createProps;
862
865
  this.attachListeners();
863
866
  }
864
867
 
@@ -933,7 +936,6 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
933
936
  return {
934
937
  pkg: this.pkg,
935
938
  snapshot,
936
- isRootDataStore: this.isRootDataStore,
937
939
  };
938
940
  }
939
941