@fluidframework/container-runtime 0.57.2 → 0.58.1000

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 (90) 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 +2 -1
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +63 -27
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.js +1 -1
  10. package/dist/dataStoreContext.js.map +1 -1
  11. package/dist/dataStores.js +1 -1
  12. package/dist/dataStores.js.map +1 -1
  13. package/dist/garbageCollection.d.ts +1 -0
  14. package/dist/garbageCollection.d.ts.map +1 -1
  15. package/dist/garbageCollection.js +6 -4
  16. package/dist/garbageCollection.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.d.ts.map +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/dist/pendingStateManager.d.ts.map +1 -1
  22. package/dist/pendingStateManager.js +1 -6
  23. package/dist/pendingStateManager.js.map +1 -1
  24. package/dist/runningSummarizer.d.ts +1 -1
  25. package/dist/runningSummarizer.d.ts.map +1 -1
  26. package/dist/runningSummarizer.js +1 -1
  27. package/dist/runningSummarizer.js.map +1 -1
  28. package/dist/summarizer.d.ts +3 -4
  29. package/dist/summarizer.d.ts.map +1 -1
  30. package/dist/summarizer.js +8 -9
  31. package/dist/summarizer.js.map +1 -1
  32. package/dist/summaryGenerator.d.ts +1 -1
  33. package/dist/summaryGenerator.d.ts.map +1 -1
  34. package/dist/summaryGenerator.js +1 -1
  35. package/dist/summaryGenerator.js.map +1 -1
  36. package/dist/summaryManager.d.ts +2 -6
  37. package/dist/summaryManager.d.ts.map +1 -1
  38. package/dist/summaryManager.js +4 -10
  39. package/dist/summaryManager.js.map +1 -1
  40. package/lib/batchTracker.d.ts +26 -0
  41. package/lib/batchTracker.d.ts.map +1 -0
  42. package/lib/batchTracker.js +54 -0
  43. package/lib/batchTracker.js.map +1 -0
  44. package/lib/containerRuntime.d.ts +2 -1
  45. package/lib/containerRuntime.d.ts.map +1 -1
  46. package/lib/containerRuntime.js +63 -27
  47. package/lib/containerRuntime.js.map +1 -1
  48. package/lib/dataStoreContext.js +2 -2
  49. package/lib/dataStoreContext.js.map +1 -1
  50. package/lib/dataStores.js +1 -1
  51. package/lib/dataStores.js.map +1 -1
  52. package/lib/garbageCollection.d.ts +1 -0
  53. package/lib/garbageCollection.d.ts.map +1 -1
  54. package/lib/garbageCollection.js +4 -2
  55. package/lib/garbageCollection.js.map +1 -1
  56. package/lib/packageVersion.d.ts +1 -1
  57. package/lib/packageVersion.d.ts.map +1 -1
  58. package/lib/packageVersion.js +1 -1
  59. package/lib/packageVersion.js.map +1 -1
  60. package/lib/pendingStateManager.d.ts.map +1 -1
  61. package/lib/pendingStateManager.js +1 -6
  62. package/lib/pendingStateManager.js.map +1 -1
  63. package/lib/runningSummarizer.d.ts +1 -1
  64. package/lib/runningSummarizer.d.ts.map +1 -1
  65. package/lib/runningSummarizer.js +1 -1
  66. package/lib/runningSummarizer.js.map +1 -1
  67. package/lib/summarizer.d.ts +3 -4
  68. package/lib/summarizer.d.ts.map +1 -1
  69. package/lib/summarizer.js +8 -9
  70. package/lib/summarizer.js.map +1 -1
  71. package/lib/summaryGenerator.d.ts +1 -1
  72. package/lib/summaryGenerator.d.ts.map +1 -1
  73. package/lib/summaryGenerator.js +1 -1
  74. package/lib/summaryGenerator.js.map +1 -1
  75. package/lib/summaryManager.d.ts +2 -6
  76. package/lib/summaryManager.d.ts.map +1 -1
  77. package/lib/summaryManager.js +5 -11
  78. package/lib/summaryManager.js.map +1 -1
  79. package/package.json +15 -15
  80. package/src/batchTracker.ts +80 -0
  81. package/src/containerRuntime.ts +84 -31
  82. package/src/dataStoreContext.ts +2 -2
  83. package/src/dataStores.ts +1 -1
  84. package/src/garbageCollection.ts +11 -10
  85. package/src/packageVersion.ts +1 -1
  86. package/src/pendingStateManager.ts +4 -8
  87. package/src/runningSummarizer.ts +3 -3
  88. package/src/summarizer.ts +8 -8
  89. package/src/summaryGenerator.ts +2 -2
  90. package/src/summaryManager.ts +5 -20
@@ -26,6 +26,7 @@ import { formExponentialFn, Throttler } from "./throttler";
26
26
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
27
27
  import { GarbageCollector, gcTreeKey, } from "./garbageCollection";
28
28
  import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
29
+ import { BindBatchTracker } from "./batchTracker";
29
30
  export var ContainerMessageType;
30
31
  (function (ContainerMessageType) {
31
32
  // An op to be delivered to store
@@ -68,10 +69,16 @@ export var RuntimeHeaders;
68
69
  /** True if the request is coming from an IFluidHandle. */
69
70
  RuntimeHeaders["viaHandle"] = "viaHandle";
70
71
  })(RuntimeHeaders || (RuntimeHeaders = {}));
71
- // Local storage key to set the default flush mode to TurnBased
72
- const turnBasedFlushModeKey = "Fluid.ContainerRuntime.FlushModeTurnBased";
73
72
  const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
74
73
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
74
+ // Feature gate for the max op size. If the value is negative, chunking is enabled
75
+ // and all ops over 16k would be chunked. If the value is positive, all ops with
76
+ // a size strictly larger will be rejected and the container closed with an error.
77
+ const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
78
+ // By default, we should reject any op larger than 768KB,
79
+ // in order to account for some extra overhead from serialization
80
+ // to not reach the 1MB limits in socket.io and Kafka.
81
+ const defaultMaxOpSizeInBytes = 768000;
75
82
  export var RuntimeMessage;
76
83
  (function (RuntimeMessage) {
77
84
  RuntimeMessage["FluidDataStoreOp"] = "component";
@@ -356,13 +363,13 @@ export class ContainerRuntime extends TypedEventEmitter {
356
363
  this._storage = _storage;
357
364
  this.defaultMaxConsecutiveReconnects = 15;
358
365
  this._orderSequentiallyCalls = 0;
366
+ this._flushMode = FlushMode.TurnBased;
359
367
  this.needsFlush = false;
360
368
  this.flushTrigger = false;
361
369
  this.paused = false;
362
370
  this.consecutiveReconnects = 0;
363
371
  this._disposed = false;
364
372
  this.emitDirtyDocumentEvent = true;
365
- this.summarizerWarning = (warning) => this.mc.logger.sendTelemetryEvent({ eventName: "summarizerWarning" }, warning);
366
373
  /**
367
374
  * Used to apply stashed ops at their reference sequence number.
368
375
  * Normal op processing is synchronous, but applying stashed ops is async since the
@@ -434,11 +441,10 @@ export class ContainerRuntime extends TypedEventEmitter {
434
441
  this.chunkMap = new Map(chunks);
435
442
  this.handleContext = new ContainerFluidHandleContext("", this);
436
443
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
437
- this._flushMode =
438
- ((_b = this.mc.config.getBoolean(turnBasedFlushModeKey)) !== null && _b !== void 0 ? _b : false) ? FlushMode.TurnBased : FlushMode.Immediate;
439
444
  this._aliasingEnabled =
440
- ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
441
- ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
445
+ ((_b = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _b !== void 0 ? _b : false) ||
446
+ ((_c = runtimeOptions.useDataStoreAliasing) !== null && _c !== void 0 ? _c : false);
447
+ this._maxOpSizeInBytes = ((_d = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _d !== void 0 ? _d : defaultMaxOpSizeInBytes);
442
448
  this.maxConsecutiveReconnects = (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
443
449
  this.garbageCollector = GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), (nodePath) => this.dataStores.getNodePackagePath(nodePath),
444
450
  /**
@@ -523,7 +529,6 @@ export class ContainerRuntime extends TypedEventEmitter {
523
529
  formExponentialFn({ coefficient: 20, initialDelay: 0 })), {
524
530
  initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
525
531
  }, this.runtimeOptions.summaryOptions.summarizerOptions);
526
- this.summaryManager.on("summarizerWarning", this.summarizerWarning);
527
532
  this.summaryManager.start();
528
533
  }
529
534
  }
@@ -554,6 +559,7 @@ export class ContainerRuntime extends TypedEventEmitter {
554
559
  // logging container load stats
555
560
  this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryCount: this.summaryCount, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
556
561
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
562
+ BindBatchTracker(this, this.logger);
557
563
  }
558
564
  get IContainerRuntime() { return this; }
559
565
  get IFluidRouter() { return this; }
@@ -569,7 +575,8 @@ export class ContainerRuntime extends TypedEventEmitter {
569
575
  var _a, _b, _c;
570
576
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
571
577
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
572
- const passLogger = (_a = context.taggedLogger) !== null && _a !== void 0 ? _a : new TaggedLoggerAdapter(context.logger);
578
+ const backCompatContext = context;
579
+ const passLogger = (_a = backCompatContext.taggedLogger) !== null && _a !== void 0 ? _a : new TaggedLoggerAdapter(backCompatContext.logger);
573
580
  const logger = ChildLogger.create(passLogger, undefined, {
574
581
  all: {
575
582
  runtimeVersion: pkgVersion,
@@ -630,7 +637,7 @@ export class ContainerRuntime extends TypedEventEmitter {
630
637
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
631
638
  if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
632
639
  // "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
633
- const error = new DataCorruptionError("SummaryMetadataMismatch", { runtimeSequenceNumber, protocolSequenceNumber });
640
+ const error = new DataCorruptionError("Summary metadata mismatch", { runtimeSequenceNumber, protocolSequenceNumber });
634
641
  if (loadSequenceNumberVerification === "log") {
635
642
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
636
643
  }
@@ -732,7 +739,6 @@ export class ContainerRuntime extends TypedEventEmitter {
732
739
  attachState: this.attachState,
733
740
  }, error);
734
741
  if (this.summaryManager !== undefined) {
735
- this.summaryManager.off("summarizerWarning", this.summarizerWarning);
736
742
  this.summaryManager.dispose();
737
743
  }
738
744
  this.garbageCollector.dispose();
@@ -896,7 +902,7 @@ export class ContainerRuntime extends TypedEventEmitter {
896
902
  if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
897
903
  // If we're halfway through the max reconnects, send an event in order
898
904
  // to better identify false positives, if any. If the rate of this event
899
- // matches `MaxReconnectsWithNoProgress`, we can safely cut down
905
+ // matches Container Close count below, we can safely cut down
900
906
  // maxConsecutiveReconnects to half.
901
907
  this.mc.logger.sendTelemetryEvent({
902
908
  eventName: "ReconnectsWithNoProgress",
@@ -961,7 +967,7 @@ export class ContainerRuntime extends TypedEventEmitter {
961
967
  this.deltaManager.off("op", this.onOp);
962
968
  this.context.pendingLocalState = undefined;
963
969
  if (!this.shouldContinueReconnecting()) {
964
- this.closeFn(new GenericError("MaxReconnectsWithNoProgress", undefined, // error
970
+ this.closeFn(new GenericError("Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
965
971
  { attempts: this.consecutiveReconnects }));
966
972
  return;
967
973
  }
@@ -1107,7 +1113,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1107
1113
  callback();
1108
1114
  }
1109
1115
  catch (error) {
1110
- this.closeFn(new GenericError("orderSequentiallyCallbackException", error));
1116
+ this.closeFn(new GenericError("orderSequentially callback exception", error));
1111
1117
  throw error; // throw the original error for the consumer of the runtime
1112
1118
  }
1113
1119
  finally {
@@ -1364,7 +1370,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1364
1370
  * @param options - options controlling how the summary is generated or submitted
1365
1371
  */
1366
1372
  async submitSummary(options) {
1367
- var _a, _b;
1373
+ var _a, _b, _c;
1368
1374
  const { fullTree, refreshLatestAck, summaryLogger } = options;
1369
1375
  if (refreshLatestAck) {
1370
1376
  const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryLogger, undefined, { all: { safeSummary: true } }));
@@ -1382,6 +1388,14 @@ export class ContainerRuntime extends TypedEventEmitter {
1382
1388
  await this.deltaManager.inbound.pause();
1383
1389
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
1384
1390
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
1391
+ // We should be here is we haven't processed be here. If we are of if the last message's sequence number
1392
+ // doesn't match the last processed sequence number, log an error.
1393
+ if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
1394
+ summaryLogger.sendErrorEvent({
1395
+ eventName: "LastSequenceMismatch",
1396
+ message,
1397
+ });
1398
+ }
1385
1399
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryLogger);
1386
1400
  // Helper function to check whether we should still continue between each async step.
1387
1401
  const checkContinue = () => {
@@ -1446,7 +1460,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1446
1460
  const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[channelsTreeName];
1447
1461
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1448
1462
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
1449
- const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount }, partialStats);
1463
+ const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_b = summarizeResult.gcStats) === null || _b === void 0 ? void 0 : _b.updatedDataStoreCount }, partialStats);
1450
1464
  const generateSummaryData = {
1451
1465
  referenceSequenceNumber: summaryRefSeqNum,
1452
1466
  summaryTree,
@@ -1462,7 +1476,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1462
1476
  const summaryContext = lastAck === undefined
1463
1477
  ? {
1464
1478
  proposalHandle: undefined,
1465
- ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
1479
+ ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
1466
1480
  referenceSequenceNumber: summaryRefSeqNum,
1467
1481
  }
1468
1482
  : {
@@ -1593,16 +1607,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1593
1607
  });
1594
1608
  }
1595
1609
  }
1596
- // Note: Chunking will increase content beyond maxOpSize because we JSON'ing JSON payload -
1597
- // there will be a lot of escape characters that can make it up to 2x bigger!
1598
- // This is Ok, because DeltaManager.shouldSplit() will have 2 * maxMessageSize limit
1599
- if (!serializedContent || serializedContent.length <= maxOpSize) {
1600
- clientSequenceNumber = this.submitRuntimeMessage(type, content,
1601
- /* batch: */ this._flushMode === FlushMode.TurnBased, opMetadataInternal);
1602
- }
1603
- else {
1604
- clientSequenceNumber = this.submitChunkedMessage(type, serializedContent, maxOpSize);
1605
- }
1610
+ clientSequenceNumber = this.submitMaybeChunkedMessages(type, content, serializedContent, maxOpSize, this._flushMode === FlushMode.TurnBased, opMetadataInternal);
1606
1611
  }
1607
1612
  // Let the PendingStateManager know that a message was submitted.
1608
1613
  this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
@@ -1610,6 +1615,35 @@ export class ContainerRuntime extends TypedEventEmitter {
1610
1615
  this.updateDocumentDirtyState(true);
1611
1616
  }
1612
1617
  }
1618
+ submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
1619
+ if (this._maxOpSizeInBytes >= 0) {
1620
+ // Chunking disabled
1621
+ if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
1622
+ return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1623
+ }
1624
+ // When chunking is disabled, we ignore the server max message size
1625
+ // and if the content length is larger than the client configured message size
1626
+ // instead of splitting the content, we will fail by explicitly close the container
1627
+ this.closeFn(new GenericError("OpTooLarge",
1628
+ /* error */ undefined, {
1629
+ length: {
1630
+ value: serializedContent.length,
1631
+ tag: TelemetryDataTag.PackageData,
1632
+ },
1633
+ limit: {
1634
+ value: this._maxOpSizeInBytes,
1635
+ tag: TelemetryDataTag.PackageData,
1636
+ },
1637
+ }));
1638
+ return -1;
1639
+ }
1640
+ // Chunking enabled, fallback on the server's max message size
1641
+ // and split the content accordingly
1642
+ if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
1643
+ return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1644
+ }
1645
+ return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
1646
+ }
1613
1647
  submitChunkedMessage(type, content, maxOpSize) {
1614
1648
  const contentLength = content.length;
1615
1649
  const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
@@ -1688,6 +1722,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1688
1722
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
1689
1723
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1690
1724
  eventName: "RefreshLatestSummaryGetSnapshot",
1725
+ ackHandle,
1726
+ summaryRefSeq,
1691
1727
  fetchLatest: false,
1692
1728
  }), readAndParseBlob, summaryLogger);
1693
1729
  // Notify the garbage collector so it can update its latest summary state.