@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.
Files changed (112) 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 +10 -5
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +113 -35
  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 +1 -7
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +11 -7
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.js +1 -1
  18. package/dist/dataStores.js.map +1 -1
  19. package/dist/garbageCollection.d.ts +1 -0
  20. package/dist/garbageCollection.d.ts.map +1 -1
  21. package/dist/garbageCollection.js +6 -4
  22. package/dist/garbageCollection.js.map +1 -1
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/packageVersion.d.ts +1 -1
  28. package/dist/packageVersion.d.ts.map +1 -1
  29. package/dist/packageVersion.js +1 -1
  30. package/dist/packageVersion.js.map +1 -1
  31. package/dist/pendingStateManager.d.ts.map +1 -1
  32. package/dist/pendingStateManager.js +1 -6
  33. package/dist/pendingStateManager.js.map +1 -1
  34. package/dist/runningSummarizer.d.ts +1 -1
  35. package/dist/runningSummarizer.d.ts.map +1 -1
  36. package/dist/runningSummarizer.js +1 -1
  37. package/dist/runningSummarizer.js.map +1 -1
  38. package/dist/summarizer.d.ts +3 -4
  39. package/dist/summarizer.d.ts.map +1 -1
  40. package/dist/summarizer.js +8 -9
  41. package/dist/summarizer.js.map +1 -1
  42. package/dist/summaryGenerator.d.ts +1 -1
  43. package/dist/summaryGenerator.d.ts.map +1 -1
  44. package/dist/summaryGenerator.js +1 -1
  45. package/dist/summaryGenerator.js.map +1 -1
  46. package/dist/summaryManager.d.ts +2 -6
  47. package/dist/summaryManager.d.ts.map +1 -1
  48. package/dist/summaryManager.js +4 -10
  49. package/dist/summaryManager.js.map +1 -1
  50. package/lib/batchTracker.d.ts +26 -0
  51. package/lib/batchTracker.d.ts.map +1 -0
  52. package/lib/batchTracker.js +54 -0
  53. package/lib/batchTracker.js.map +1 -0
  54. package/lib/containerRuntime.d.ts +10 -5
  55. package/lib/containerRuntime.d.ts.map +1 -1
  56. package/lib/containerRuntime.js +114 -36
  57. package/lib/containerRuntime.js.map +1 -1
  58. package/lib/dataStore.d.ts +1 -36
  59. package/lib/dataStore.d.ts.map +1 -1
  60. package/lib/dataStore.js +4 -26
  61. package/lib/dataStore.js.map +1 -1
  62. package/lib/dataStoreContext.d.ts +1 -7
  63. package/lib/dataStoreContext.d.ts.map +1 -1
  64. package/lib/dataStoreContext.js +12 -8
  65. package/lib/dataStoreContext.js.map +1 -1
  66. package/lib/dataStores.js +1 -1
  67. package/lib/dataStores.js.map +1 -1
  68. package/lib/garbageCollection.d.ts +1 -0
  69. package/lib/garbageCollection.d.ts.map +1 -1
  70. package/lib/garbageCollection.js +4 -2
  71. package/lib/garbageCollection.js.map +1 -1
  72. package/lib/index.d.ts +0 -1
  73. package/lib/index.d.ts.map +1 -1
  74. package/lib/index.js +0 -1
  75. package/lib/index.js.map +1 -1
  76. package/lib/packageVersion.d.ts +1 -1
  77. package/lib/packageVersion.d.ts.map +1 -1
  78. package/lib/packageVersion.js +1 -1
  79. package/lib/packageVersion.js.map +1 -1
  80. package/lib/pendingStateManager.d.ts.map +1 -1
  81. package/lib/pendingStateManager.js +1 -6
  82. package/lib/pendingStateManager.js.map +1 -1
  83. package/lib/runningSummarizer.d.ts +1 -1
  84. package/lib/runningSummarizer.d.ts.map +1 -1
  85. package/lib/runningSummarizer.js +1 -1
  86. package/lib/runningSummarizer.js.map +1 -1
  87. package/lib/summarizer.d.ts +3 -4
  88. package/lib/summarizer.d.ts.map +1 -1
  89. package/lib/summarizer.js +8 -9
  90. package/lib/summarizer.js.map +1 -1
  91. package/lib/summaryGenerator.d.ts +1 -1
  92. package/lib/summaryGenerator.d.ts.map +1 -1
  93. package/lib/summaryGenerator.js +1 -1
  94. package/lib/summaryGenerator.js.map +1 -1
  95. package/lib/summaryManager.d.ts +2 -6
  96. package/lib/summaryManager.d.ts.map +1 -1
  97. package/lib/summaryManager.js +5 -11
  98. package/lib/summaryManager.js.map +1 -1
  99. package/package.json +16 -16
  100. package/src/batchTracker.ts +80 -0
  101. package/src/containerRuntime.ts +151 -38
  102. package/src/dataStore.ts +6 -42
  103. package/src/dataStoreContext.ts +12 -15
  104. package/src/dataStores.ts +1 -1
  105. package/src/garbageCollection.ts +11 -10
  106. package/src/index.ts +0 -1
  107. package/src/packageVersion.ts +1 -1
  108. package/src/pendingStateManager.ts +4 -8
  109. package/src/runningSummarizer.ts +3 -3
  110. package/src/summarizer.ts +8 -8
  111. package/src/summaryGenerator.ts +2 -2
  112. 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.
@@ -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("orderSequentiallyCallbackException", error));
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<IFluidRouter> {
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 !== AliasResult.Success) {
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<IFluidRouter> {
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<IFluidRouter> {
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
- // Note: Chunking will increase content beyond maxOpSize because we JSON'ing JSON payload -
2350
- // there will be a lot of escape characters that can make it up to 2x bigger!
2351
- // This is Ok, because DeltaManager.shouldSplit() will have 2 * maxMessageSize limit
2352
- if (!serializedContent || serializedContent.length <= maxOpSize) {
2353
- clientSequenceNumber = this.submitRuntimeMessage(
2354
- type,
2355
- content,
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 { 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,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
- "duplicateDataStoreCreatedWithExistingId",
197
+ "Duplicate DataStore created with existing id",
198
198
  {
199
199
  ...extractSafePropertiesFromMessage(message),
200
200
  dataStoreId: {
@@ -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
- throw new DataProcessingError(
549
- error?.message,
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 {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.57.0";
9
+ export const pkgVersion = "0.58.0-55983";
@@ -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 = new DataProcessingError(
342
+ const error = DataProcessingError.create(
343
+ "pending local message clientSequenceNumber mismatch",
343
344
  "unexpectedAckReceived",
344
- "unexpectedAckReceived",
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);