@fluidframework/container-runtime 2.5.0-302463 → 2.5.0

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 (99) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +3 -1
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +3 -3
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +1 -1
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +20 -5
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +185 -129
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/containerRuntime.d.ts +14 -4
  13. package/dist/containerRuntime.d.ts.map +1 -1
  14. package/dist/containerRuntime.js +138 -55
  15. package/dist/containerRuntime.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +15 -3
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js +48 -19
  19. package/dist/dataStoreContext.js.map +1 -1
  20. package/dist/dataStoreContexts.d.ts.map +1 -1
  21. package/dist/dataStoreContexts.js +6 -14
  22. package/dist/dataStoreContexts.js.map +1 -1
  23. package/dist/gc/garbageCollection.d.ts +5 -6
  24. package/dist/gc/garbageCollection.d.ts.map +1 -1
  25. package/dist/gc/garbageCollection.js +23 -22
  26. package/dist/gc/garbageCollection.js.map +1 -1
  27. package/dist/gc/gcDefinitions.d.ts +2 -2
  28. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  29. package/dist/gc/gcDefinitions.js.map +1 -1
  30. package/dist/opLifecycle/outbox.d.ts +3 -0
  31. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  32. package/dist/opLifecycle/outbox.js +9 -0
  33. package/dist/opLifecycle/outbox.js.map +1 -1
  34. package/dist/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  35. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  36. package/dist/opLifecycle/remoteMessageProcessor.js +2 -0
  37. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  38. package/dist/packageVersion.d.ts +1 -1
  39. package/dist/packageVersion.d.ts.map +1 -1
  40. package/dist/packageVersion.js +1 -1
  41. package/dist/packageVersion.js.map +1 -1
  42. package/dist/summary/documentSchema.d.ts +11 -0
  43. package/dist/summary/documentSchema.d.ts.map +1 -1
  44. package/dist/summary/documentSchema.js +43 -28
  45. package/dist/summary/documentSchema.js.map +1 -1
  46. package/lib/blobManager/blobManager.d.ts +3 -3
  47. package/lib/blobManager/blobManager.d.ts.map +1 -1
  48. package/lib/blobManager/blobManager.js +1 -1
  49. package/lib/blobManager/blobManager.js.map +1 -1
  50. package/lib/channelCollection.d.ts +20 -5
  51. package/lib/channelCollection.d.ts.map +1 -1
  52. package/lib/channelCollection.js +186 -130
  53. package/lib/channelCollection.js.map +1 -1
  54. package/lib/containerRuntime.d.ts +14 -4
  55. package/lib/containerRuntime.d.ts.map +1 -1
  56. package/lib/containerRuntime.js +137 -54
  57. package/lib/containerRuntime.js.map +1 -1
  58. package/lib/dataStoreContext.d.ts +15 -3
  59. package/lib/dataStoreContext.d.ts.map +1 -1
  60. package/lib/dataStoreContext.js +48 -19
  61. package/lib/dataStoreContext.js.map +1 -1
  62. package/lib/dataStoreContexts.d.ts.map +1 -1
  63. package/lib/dataStoreContexts.js +7 -15
  64. package/lib/dataStoreContexts.js.map +1 -1
  65. package/lib/gc/garbageCollection.d.ts +5 -6
  66. package/lib/gc/garbageCollection.d.ts.map +1 -1
  67. package/lib/gc/garbageCollection.js +23 -22
  68. package/lib/gc/garbageCollection.js.map +1 -1
  69. package/lib/gc/gcDefinitions.d.ts +2 -2
  70. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  71. package/lib/gc/gcDefinitions.js.map +1 -1
  72. package/lib/opLifecycle/outbox.d.ts +3 -0
  73. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  74. package/lib/opLifecycle/outbox.js +9 -0
  75. package/lib/opLifecycle/outbox.js.map +1 -1
  76. package/lib/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  77. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  78. package/lib/opLifecycle/remoteMessageProcessor.js +2 -0
  79. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  80. package/lib/packageVersion.d.ts +1 -1
  81. package/lib/packageVersion.d.ts.map +1 -1
  82. package/lib/packageVersion.js +1 -1
  83. package/lib/packageVersion.js.map +1 -1
  84. package/lib/summary/documentSchema.d.ts +11 -0
  85. package/lib/summary/documentSchema.d.ts.map +1 -1
  86. package/lib/summary/documentSchema.js +43 -28
  87. package/lib/summary/documentSchema.js.map +1 -1
  88. package/package.json +23 -19
  89. package/src/blobManager/blobManager.ts +2 -2
  90. package/src/channelCollection.ts +234 -176
  91. package/src/containerRuntime.ts +179 -68
  92. package/src/dataStoreContext.ts +66 -23
  93. package/src/dataStoreContexts.ts +7 -20
  94. package/src/gc/garbageCollection.ts +32 -32
  95. package/src/gc/gcDefinitions.ts +3 -3
  96. package/src/opLifecycle/outbox.ts +12 -0
  97. package/src/opLifecycle/remoteMessageProcessor.ts +3 -0
  98. package/src/packageVersion.ts +1 -1
  99. package/src/summary/documentSchema.ts +56 -37
@@ -89,6 +89,12 @@ export const TombstoneResponseHeaderKey = "isTombstoned";
89
89
  * to this was experimental and is no longer supported.
90
90
  */
91
91
  export const InactiveResponseHeaderKey = "isInactive";
92
+ /** Default values for Runtime Headers */
93
+ export const defaultRuntimeHeaderData = {
94
+ wait: true,
95
+ viaHandle: false,
96
+ allowTombstone: false,
97
+ };
92
98
  /**
93
99
  * Available compression algorithms for op compression.
94
100
  * @legacy
@@ -1581,7 +1587,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1581
1587
  : inboundResult.type === "batchStartingMessage"
1582
1588
  ? { batchStart: true, batchEnd: false }
1583
1589
  : { batchStart: false, batchEnd: inboundResult.batchEnd === true };
1584
- this.processInboundMessages(messagesWithPendingState, locationInBatch, local, savedOp, runtimeBatch);
1590
+ this.processInboundMessages(messagesWithPendingState, locationInBatch, local, savedOp, runtimeBatch, inboundResult.type === "fullBatch"
1591
+ ? inboundResult.groupedBatch
1592
+ : false /* groupedBatch */);
1585
1593
  }
1586
1594
  else {
1587
1595
  if (!runtimeBatch) {
@@ -1594,7 +1602,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1594
1602
  }
1595
1603
  }
1596
1604
  this.processInboundMessages([{ message: messageCopy, localOpMetadata: undefined }], { batchStart: true, batchEnd: true }, // Single message
1597
- local, savedOp, runtimeBatch);
1605
+ local, savedOp, runtimeBatch, false /* groupedBatch */);
1598
1606
  }
1599
1607
  if (local) {
1600
1608
  // If we have processed a local op, this means that the container is
@@ -1605,35 +1613,93 @@ export class ContainerRuntime extends TypedEventEmitter {
1605
1613
  }
1606
1614
  /**
1607
1615
  * Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
1608
- * @param messages - messages to process.
1616
+ * @param messagesWithMetadata - messages to process along with their metadata.
1609
1617
  * @param locationInBatch - Are we processing the start and/or end of a batch?
1610
1618
  * @param local - true if the messages were originally generated by the client receiving it.
1611
1619
  * @param savedOp - true if the message is a replayed saved op.
1612
1620
  * @param runtimeBatch - true if these are runtime messages.
1621
+ * @param groupedBatch - true if these messages are part of a grouped op batch.
1613
1622
  */
1614
- processInboundMessages(messages, locationInBatch, local, savedOp, runtimeBatch) {
1623
+ processInboundMessages(messagesWithMetadata, locationInBatch, local, savedOp, runtimeBatch, groupedBatch) {
1615
1624
  if (locationInBatch.batchStart) {
1616
- const firstMessage = messages[0]?.message;
1625
+ const firstMessage = messagesWithMetadata[0]?.message;
1617
1626
  assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
1618
1627
  this.scheduleManager.batchBegin(firstMessage);
1619
1628
  }
1620
1629
  let error;
1621
1630
  try {
1622
- messages.forEach(({ message, localOpMetadata }) => {
1623
- this.ensureNoDataModelChanges(() => {
1624
- if (runtimeBatch) {
1625
- this.validateAndProcessRuntimeMessage({
1626
- message: message,
1627
- local,
1628
- savedOp,
1629
- localOpMetadata,
1630
- });
1631
- }
1632
- else {
1631
+ if (!runtimeBatch) {
1632
+ messagesWithMetadata.forEach(({ message }) => {
1633
+ this.ensureNoDataModelChanges(() => {
1633
1634
  this.observeNonRuntimeMessage(message);
1634
- }
1635
+ });
1635
1636
  });
1636
- });
1637
+ return;
1638
+ }
1639
+ // Helper that updates a message's minimum sequence number to the minimum sequence number that container
1640
+ // runtime is tracking and sets _processedClientSequenceNumber. It returns the updated message.
1641
+ const updateSequenceNumbers = (message) => {
1642
+ // Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
1643
+ message.minimumSequenceNumber =
1644
+ this.useDeltaManagerOpsProxy &&
1645
+ this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
1646
+ ? this.deltaManager.minimumSequenceNumber
1647
+ : message.minimumSequenceNumber;
1648
+ this._processedClientSequenceNumber = message.clientSequenceNumber;
1649
+ return message;
1650
+ };
1651
+ // Non-grouped batch messages are processed one at a time.
1652
+ if (!groupedBatch) {
1653
+ for (const { message, localOpMetadata } of messagesWithMetadata) {
1654
+ updateSequenceNumbers(message);
1655
+ this.ensureNoDataModelChanges(() => {
1656
+ this.validateAndProcessRuntimeMessages(message, [
1657
+ {
1658
+ contents: message.contents,
1659
+ localOpMetadata,
1660
+ clientSequenceNumber: message.clientSequenceNumber,
1661
+ },
1662
+ ], local, savedOp);
1663
+ this.emit("op", message, true /* runtimeMessage */);
1664
+ });
1665
+ }
1666
+ return;
1667
+ }
1668
+ let bunchedMessagesContent = [];
1669
+ let previousMessage;
1670
+ // Helper that processes the previous bunch of messages.
1671
+ const sendBunchedMessages = () => {
1672
+ assert(previousMessage !== undefined, 0xa67 /* previous message must exist */);
1673
+ this.ensureNoDataModelChanges(() => {
1674
+ this.validateAndProcessRuntimeMessages(
1675
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1676
+ previousMessage, bunchedMessagesContent, local, savedOp);
1677
+ });
1678
+ bunchedMessagesContent = [];
1679
+ };
1680
+ /**
1681
+ * For grouped batch messages, bunch contiguous messages of the same type and process them together.
1682
+ * This is an optimization mainly for DDSes, where it can process a bunch of ops together. DDSes
1683
+ * like merge tree or shared tree can process ops more efficiently when they are bunched together.
1684
+ */
1685
+ for (const { message, localOpMetadata } of messagesWithMetadata) {
1686
+ const currentMessage = updateSequenceNumbers(message);
1687
+ if (previousMessage && previousMessage.type !== currentMessage.type) {
1688
+ sendBunchedMessages();
1689
+ }
1690
+ previousMessage = currentMessage;
1691
+ bunchedMessagesContent.push({
1692
+ contents: message.contents,
1693
+ localOpMetadata,
1694
+ clientSequenceNumber: message.clientSequenceNumber,
1695
+ });
1696
+ }
1697
+ // Process the last bunch of messages.
1698
+ sendBunchedMessages();
1699
+ // Send the "op" events for the messages now that the ops have been processed.
1700
+ for (const { message } of messagesWithMetadata) {
1701
+ this.emit("op", message, true /* runtimeMessage */);
1702
+ }
1637
1703
  }
1638
1704
  catch (e) {
1639
1705
  error = e;
@@ -1641,7 +1707,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1641
1707
  }
1642
1708
  finally {
1643
1709
  if (locationInBatch.batchEnd) {
1644
- const lastMessage = messages[messages.length - 1]?.message;
1710
+ const lastMessage = messagesWithMetadata[messagesWithMetadata.length - 1]?.message;
1645
1711
  assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
1646
1712
  this.scheduleManager.batchEnd(error, lastMessage);
1647
1713
  }
@@ -1665,62 +1731,50 @@ export class ContainerRuntime extends TypedEventEmitter {
1665
1731
  this.emit("op", message, false /* runtimeMessage */);
1666
1732
  }
1667
1733
  /**
1668
- * Assuming the given message is also a TypedContainerRuntimeMessage,
1669
- * checks its type and dispatches the message to the appropriate handler in the runtime.
1734
+ * Process runtime messages. The messages here are contiguous messages in a batch.
1735
+ * Assuming the messages in the given bunch are also a TypedContainerRuntimeMessage, checks its type and dispatch
1736
+ * the messages to the appropriate handler in the runtime.
1670
1737
  * Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
1738
+ * @param message - The core message with common properties for all the messages.
1739
+ * @param messageContents - The contents, local metadata and clientSequenceNumbers of the messages.
1740
+ * @param local - true if the messages were originally generated by the client receiving it.
1741
+ * @param savedOp - true if the message is a replayed saved op.
1742
+ *
1671
1743
  */
1672
- validateAndProcessRuntimeMessage(messageWithContext) {
1673
- const { local, message, savedOp, localOpMetadata } = messageWithContext;
1674
- // Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
1675
- if (this.useDeltaManagerOpsProxy &&
1676
- this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber) {
1677
- message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
1678
- }
1679
- this._processedClientSequenceNumber = message.clientSequenceNumber;
1744
+ validateAndProcessRuntimeMessages(message, messagesContent, local, savedOp) {
1680
1745
  // If there are no more pending messages after processing a local message,
1681
1746
  // the document is no longer dirty.
1682
1747
  if (!this.hasPendingMessages()) {
1683
1748
  this.updateDocumentDirtyState(false);
1684
1749
  }
1750
+ // Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
1751
+ const contents = messagesContent.map((c) => c.contents);
1685
1752
  switch (message.type) {
1753
+ case ContainerMessageType.FluidDataStoreOp:
1686
1754
  case ContainerMessageType.Attach:
1687
1755
  case ContainerMessageType.Alias:
1688
- case ContainerMessageType.FluidDataStoreOp:
1689
- this.channelCollection.process(message, local, localOpMetadata);
1756
+ // Remove the metadata from the message before sending it to the channel collection. The metadata
1757
+ // is added by the container runtime and is not part of the message that the channel collection and
1758
+ // layers below it expect.
1759
+ this.channelCollection.processMessages({ envelope: message, messagesContent, local });
1690
1760
  break;
1691
1761
  case ContainerMessageType.BlobAttach:
1692
- this.blobManager.processBlobAttachOp(message, local);
1762
+ this.blobManager.processBlobAttachMessage(message, local);
1693
1763
  break;
1694
1764
  case ContainerMessageType.IdAllocation:
1695
- // Don't re-finalize the range if we're processing a "savedOp" in
1696
- // stashed ops flow. The compressor is stashed with these ops already processed.
1697
- // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
1698
- // thus we need to process all the ops.
1699
- if (!(this.skipSavedCompressorOps && savedOp === true)) {
1700
- const range = message.contents;
1701
- // Some other client turned on the id compressor. If we have not turned it on,
1702
- // put it in a pending queue and delay finalization.
1703
- if (this._idCompressor === undefined) {
1704
- assert(this.idCompressorMode !== undefined, 0x93c /* id compressor should be enabled */);
1705
- this.pendingIdCompressorOps.push(range);
1706
- }
1707
- else {
1708
- assert(this.pendingIdCompressorOps.length === 0, 0x979 /* there should be no pending ops! */);
1709
- this._idCompressor.finalizeCreationRange(range);
1710
- }
1711
- }
1765
+ this.processIdCompressorMessages(contents, savedOp);
1712
1766
  break;
1713
1767
  case ContainerMessageType.GC:
1714
- this.garbageCollector.processMessage(message, message.timestamp, local);
1768
+ this.garbageCollector.processMessages(contents, message.timestamp, local);
1715
1769
  break;
1716
1770
  case ContainerMessageType.ChunkedOp:
1717
- // From observability POV, we should not exppse the rest of the system (including "op" events on object) to these messages.
1771
+ // From observability POV, we should not expose the rest of the system (including "op" events on object) to these messages.
1718
1772
  // Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
1719
1773
  assert(false, 0x93d /* should not even get here */);
1720
1774
  case ContainerMessageType.Rejoin:
1721
1775
  break;
1722
1776
  case ContainerMessageType.DocumentSchemaChange:
1723
- this.documentsSchemaController.processDocumentSchemaOp(message.contents, local, message.sequenceNumber);
1777
+ this.documentsSchemaController.processDocumentSchemaMessages(contents, local, message.sequenceNumber);
1724
1778
  break;
1725
1779
  default: {
1726
1780
  const error = getUnknownMessageTypeError(message.type, "validateAndProcessRuntimeMessage" /* codePath */, message);
@@ -1728,7 +1782,26 @@ export class ContainerRuntime extends TypedEventEmitter {
1728
1782
  throw error;
1729
1783
  }
1730
1784
  }
1731
- this.emit("op", message, true /* runtimeMessage */);
1785
+ }
1786
+ processIdCompressorMessages(messageContents, savedOp) {
1787
+ for (const range of messageContents) {
1788
+ // Don't re-finalize the range if we're processing a "savedOp" in
1789
+ // stashed ops flow. The compressor is stashed with these ops already processed.
1790
+ // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
1791
+ // thus we need to process all the ops.
1792
+ if (!(this.skipSavedCompressorOps && savedOp === true)) {
1793
+ // Some other client turned on the id compressor. If we have not turned it on,
1794
+ // put it in a pending queue and delay finalization.
1795
+ if (this._idCompressor === undefined) {
1796
+ assert(this.idCompressorMode !== undefined, 0x93c /* id compressor should be enabled */);
1797
+ this.pendingIdCompressorOps.push(range);
1798
+ }
1799
+ else {
1800
+ assert(this.pendingIdCompressorOps.length === 0, 0x979 /* there should be no pending ops! */);
1801
+ this._idCompressor.finalizeCreationRange(range);
1802
+ }
1803
+ }
1804
+ }
1732
1805
  }
1733
1806
  /**
1734
1807
  * Emits the Signal event and update the perf signal data.
@@ -2304,7 +2377,17 @@ export class ContainerRuntime extends TypedEventEmitter {
2304
2377
  },
2305
2378
  },
2306
2379
  });
2307
- assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2380
+ // legacy: assert 0x3d1
2381
+ if (!this.outbox.isEmpty) {
2382
+ throw DataProcessingError.create("Can't trigger summary in the middle of a batch", "submitSummary", undefined, {
2383
+ summaryNumber,
2384
+ pendingMessages: this.pendingMessagesCount,
2385
+ outboxLength: this.outbox.messageCount,
2386
+ mainBatchLength: this.outbox.mainBatchMessageCount,
2387
+ blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
2388
+ idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
2389
+ });
2390
+ }
2308
2391
  // If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
2309
2392
  // and it may even be incorrect. So, wait for the container to be saved with a timeout. If the container is not
2310
2393
  // saved within the timeout, check if it should be failed or can continue.