@fluidframework/container-runtime 2.4.0 → 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 (95) 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 +183 -119
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/containerRuntime.d.ts +12 -4
  13. package/dist/containerRuntime.d.ts.map +1 -1
  14. package/dist/containerRuntime.js +155 -66
  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/gc/garbageCollection.d.ts +5 -6
  21. package/dist/gc/garbageCollection.d.ts.map +1 -1
  22. package/dist/gc/garbageCollection.js +23 -22
  23. package/dist/gc/garbageCollection.js.map +1 -1
  24. package/dist/gc/gcDefinitions.d.ts +2 -2
  25. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  26. package/dist/gc/gcDefinitions.js.map +1 -1
  27. package/dist/opLifecycle/outbox.d.ts +3 -0
  28. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  29. package/dist/opLifecycle/outbox.js +9 -0
  30. package/dist/opLifecycle/outbox.js.map +1 -1
  31. package/dist/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  32. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  33. package/dist/opLifecycle/remoteMessageProcessor.js +2 -0
  34. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  35. package/dist/opProperties.js +1 -1
  36. package/dist/opProperties.js.map +1 -1
  37. package/dist/packageVersion.d.ts +1 -1
  38. package/dist/packageVersion.js +1 -1
  39. package/dist/packageVersion.js.map +1 -1
  40. package/dist/summary/documentSchema.d.ts +11 -0
  41. package/dist/summary/documentSchema.d.ts.map +1 -1
  42. package/dist/summary/documentSchema.js +45 -30
  43. package/dist/summary/documentSchema.js.map +1 -1
  44. package/lib/blobManager/blobManager.d.ts +3 -3
  45. package/lib/blobManager/blobManager.d.ts.map +1 -1
  46. package/lib/blobManager/blobManager.js +1 -1
  47. package/lib/blobManager/blobManager.js.map +1 -1
  48. package/lib/channelCollection.d.ts +20 -5
  49. package/lib/channelCollection.d.ts.map +1 -1
  50. package/lib/channelCollection.js +183 -119
  51. package/lib/channelCollection.js.map +1 -1
  52. package/lib/containerRuntime.d.ts +12 -4
  53. package/lib/containerRuntime.d.ts.map +1 -1
  54. package/lib/containerRuntime.js +155 -66
  55. package/lib/containerRuntime.js.map +1 -1
  56. package/lib/dataStoreContext.d.ts +15 -3
  57. package/lib/dataStoreContext.d.ts.map +1 -1
  58. package/lib/dataStoreContext.js +48 -19
  59. package/lib/dataStoreContext.js.map +1 -1
  60. package/lib/gc/garbageCollection.d.ts +5 -6
  61. package/lib/gc/garbageCollection.d.ts.map +1 -1
  62. package/lib/gc/garbageCollection.js +23 -22
  63. package/lib/gc/garbageCollection.js.map +1 -1
  64. package/lib/gc/gcDefinitions.d.ts +2 -2
  65. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  66. package/lib/gc/gcDefinitions.js.map +1 -1
  67. package/lib/opLifecycle/outbox.d.ts +3 -0
  68. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  69. package/lib/opLifecycle/outbox.js +9 -0
  70. package/lib/opLifecycle/outbox.js.map +1 -1
  71. package/lib/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  72. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  73. package/lib/opLifecycle/remoteMessageProcessor.js +2 -0
  74. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  75. package/lib/opProperties.js +1 -1
  76. package/lib/opProperties.js.map +1 -1
  77. package/lib/packageVersion.d.ts +1 -1
  78. package/lib/packageVersion.js +1 -1
  79. package/lib/packageVersion.js.map +1 -1
  80. package/lib/summary/documentSchema.d.ts +11 -0
  81. package/lib/summary/documentSchema.d.ts.map +1 -1
  82. package/lib/summary/documentSchema.js +45 -30
  83. package/lib/summary/documentSchema.js.map +1 -1
  84. package/package.json +24 -24
  85. package/src/blobManager/blobManager.ts +2 -2
  86. package/src/channelCollection.ts +227 -160
  87. package/src/containerRuntime.ts +197 -80
  88. package/src/dataStoreContext.ts +66 -23
  89. package/src/gc/garbageCollection.ts +32 -32
  90. package/src/gc/gcDefinitions.ts +3 -3
  91. package/src/opLifecycle/outbox.ts +12 -0
  92. package/src/opLifecycle/remoteMessageProcessor.ts +3 -0
  93. package/src/opProperties.ts +1 -1
  94. package/src/packageVersion.ts +1 -1
  95. package/src/summary/documentSchema.ts +58 -39
@@ -1593,7 +1593,9 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1593
1593
  : inboundResult.type === "batchStartingMessage"
1594
1594
  ? { batchStart: true, batchEnd: false }
1595
1595
  : { batchStart: false, batchEnd: inboundResult.batchEnd === true };
1596
- this.processInboundMessages(messagesWithPendingState, locationInBatch, local, savedOp, runtimeBatch);
1596
+ this.processInboundMessages(messagesWithPendingState, locationInBatch, local, savedOp, runtimeBatch, inboundResult.type === "fullBatch"
1597
+ ? inboundResult.groupedBatch
1598
+ : false /* groupedBatch */);
1597
1599
  }
1598
1600
  else {
1599
1601
  if (!runtimeBatch) {
@@ -1606,7 +1608,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1606
1608
  }
1607
1609
  }
1608
1610
  this.processInboundMessages([{ message: messageCopy, localOpMetadata: undefined }], { batchStart: true, batchEnd: true }, // Single message
1609
- local, savedOp, runtimeBatch);
1611
+ local, savedOp, runtimeBatch, false /* groupedBatch */);
1610
1612
  }
1611
1613
  if (local) {
1612
1614
  // If we have processed a local op, this means that the container is
@@ -1617,35 +1619,93 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1617
1619
  }
1618
1620
  /**
1619
1621
  * Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
1620
- * @param messages - messages to process.
1622
+ * @param messagesWithMetadata - messages to process along with their metadata.
1621
1623
  * @param locationInBatch - Are we processing the start and/or end of a batch?
1622
1624
  * @param local - true if the messages were originally generated by the client receiving it.
1623
1625
  * @param savedOp - true if the message is a replayed saved op.
1624
1626
  * @param runtimeBatch - true if these are runtime messages.
1627
+ * @param groupedBatch - true if these messages are part of a grouped op batch.
1625
1628
  */
1626
- processInboundMessages(messages, locationInBatch, local, savedOp, runtimeBatch) {
1629
+ processInboundMessages(messagesWithMetadata, locationInBatch, local, savedOp, runtimeBatch, groupedBatch) {
1627
1630
  if (locationInBatch.batchStart) {
1628
- const firstMessage = messages[0]?.message;
1631
+ const firstMessage = messagesWithMetadata[0]?.message;
1629
1632
  (0, internal_2.assert)(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
1630
1633
  this.scheduleManager.batchBegin(firstMessage);
1631
1634
  }
1632
1635
  let error;
1633
1636
  try {
1634
- messages.forEach(({ message, localOpMetadata }) => {
1635
- this.ensureNoDataModelChanges(() => {
1636
- if (runtimeBatch) {
1637
- this.validateAndProcessRuntimeMessage({
1638
- message: message,
1639
- local,
1640
- savedOp,
1641
- localOpMetadata,
1642
- });
1643
- }
1644
- else {
1637
+ if (!runtimeBatch) {
1638
+ messagesWithMetadata.forEach(({ message }) => {
1639
+ this.ensureNoDataModelChanges(() => {
1645
1640
  this.observeNonRuntimeMessage(message);
1646
- }
1641
+ });
1647
1642
  });
1648
- });
1643
+ return;
1644
+ }
1645
+ // Helper that updates a message's minimum sequence number to the minimum sequence number that container
1646
+ // runtime is tracking and sets _processedClientSequenceNumber. It returns the updated message.
1647
+ const updateSequenceNumbers = (message) => {
1648
+ // Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
1649
+ message.minimumSequenceNumber =
1650
+ this.useDeltaManagerOpsProxy &&
1651
+ this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
1652
+ ? this.deltaManager.minimumSequenceNumber
1653
+ : message.minimumSequenceNumber;
1654
+ this._processedClientSequenceNumber = message.clientSequenceNumber;
1655
+ return message;
1656
+ };
1657
+ // Non-grouped batch messages are processed one at a time.
1658
+ if (!groupedBatch) {
1659
+ for (const { message, localOpMetadata } of messagesWithMetadata) {
1660
+ updateSequenceNumbers(message);
1661
+ this.ensureNoDataModelChanges(() => {
1662
+ this.validateAndProcessRuntimeMessages(message, [
1663
+ {
1664
+ contents: message.contents,
1665
+ localOpMetadata,
1666
+ clientSequenceNumber: message.clientSequenceNumber,
1667
+ },
1668
+ ], local, savedOp);
1669
+ this.emit("op", message, true /* runtimeMessage */);
1670
+ });
1671
+ }
1672
+ return;
1673
+ }
1674
+ let bunchedMessagesContent = [];
1675
+ let previousMessage;
1676
+ // Helper that processes the previous bunch of messages.
1677
+ const sendBunchedMessages = () => {
1678
+ (0, internal_2.assert)(previousMessage !== undefined, 0xa67 /* previous message must exist */);
1679
+ this.ensureNoDataModelChanges(() => {
1680
+ this.validateAndProcessRuntimeMessages(
1681
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1682
+ previousMessage, bunchedMessagesContent, local, savedOp);
1683
+ });
1684
+ bunchedMessagesContent = [];
1685
+ };
1686
+ /**
1687
+ * For grouped batch messages, bunch contiguous messages of the same type and process them together.
1688
+ * This is an optimization mainly for DDSes, where it can process a bunch of ops together. DDSes
1689
+ * like merge tree or shared tree can process ops more efficiently when they are bunched together.
1690
+ */
1691
+ for (const { message, localOpMetadata } of messagesWithMetadata) {
1692
+ const currentMessage = updateSequenceNumbers(message);
1693
+ if (previousMessage && previousMessage.type !== currentMessage.type) {
1694
+ sendBunchedMessages();
1695
+ }
1696
+ previousMessage = currentMessage;
1697
+ bunchedMessagesContent.push({
1698
+ contents: message.contents,
1699
+ localOpMetadata,
1700
+ clientSequenceNumber: message.clientSequenceNumber,
1701
+ });
1702
+ }
1703
+ // Process the last bunch of messages.
1704
+ sendBunchedMessages();
1705
+ // Send the "op" events for the messages now that the ops have been processed.
1706
+ for (const { message } of messagesWithMetadata) {
1707
+ this.emit("op", message, true /* runtimeMessage */);
1708
+ }
1649
1709
  }
1650
1710
  catch (e) {
1651
1711
  error = e;
@@ -1653,7 +1713,7 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1653
1713
  }
1654
1714
  finally {
1655
1715
  if (locationInBatch.batchEnd) {
1656
- const lastMessage = messages[messages.length - 1]?.message;
1716
+ const lastMessage = messagesWithMetadata[messagesWithMetadata.length - 1]?.message;
1657
1717
  (0, internal_2.assert)(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
1658
1718
  this.scheduleManager.batchEnd(error, lastMessage);
1659
1719
  }
@@ -1677,62 +1737,50 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1677
1737
  this.emit("op", message, false /* runtimeMessage */);
1678
1738
  }
1679
1739
  /**
1680
- * Assuming the given message is also a TypedContainerRuntimeMessage,
1681
- * checks its type and dispatches the message to the appropriate handler in the runtime.
1740
+ * Process runtime messages. The messages here are contiguous messages in a batch.
1741
+ * Assuming the messages in the given bunch are also a TypedContainerRuntimeMessage, checks its type and dispatch
1742
+ * the messages to the appropriate handler in the runtime.
1682
1743
  * Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
1744
+ * @param message - The core message with common properties for all the messages.
1745
+ * @param messageContents - The contents, local metadata and clientSequenceNumbers of the messages.
1746
+ * @param local - true if the messages were originally generated by the client receiving it.
1747
+ * @param savedOp - true if the message is a replayed saved op.
1748
+ *
1683
1749
  */
1684
- validateAndProcessRuntimeMessage(messageWithContext) {
1685
- const { local, message, savedOp, localOpMetadata } = messageWithContext;
1686
- // Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
1687
- if (this.useDeltaManagerOpsProxy &&
1688
- this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber) {
1689
- message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
1690
- }
1691
- this._processedClientSequenceNumber = message.clientSequenceNumber;
1750
+ validateAndProcessRuntimeMessages(message, messagesContent, local, savedOp) {
1692
1751
  // If there are no more pending messages after processing a local message,
1693
1752
  // the document is no longer dirty.
1694
1753
  if (!this.hasPendingMessages()) {
1695
1754
  this.updateDocumentDirtyState(false);
1696
1755
  }
1756
+ // Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
1757
+ const contents = messagesContent.map((c) => c.contents);
1697
1758
  switch (message.type) {
1759
+ case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp:
1698
1760
  case messageTypes_js_1.ContainerMessageType.Attach:
1699
1761
  case messageTypes_js_1.ContainerMessageType.Alias:
1700
- case messageTypes_js_1.ContainerMessageType.FluidDataStoreOp:
1701
- this.channelCollection.process(message, local, localOpMetadata);
1762
+ // Remove the metadata from the message before sending it to the channel collection. The metadata
1763
+ // is added by the container runtime and is not part of the message that the channel collection and
1764
+ // layers below it expect.
1765
+ this.channelCollection.processMessages({ envelope: message, messagesContent, local });
1702
1766
  break;
1703
1767
  case messageTypes_js_1.ContainerMessageType.BlobAttach:
1704
- this.blobManager.processBlobAttachOp(message, local);
1768
+ this.blobManager.processBlobAttachMessage(message, local);
1705
1769
  break;
1706
1770
  case messageTypes_js_1.ContainerMessageType.IdAllocation:
1707
- // Don't re-finalize the range if we're processing a "savedOp" in
1708
- // stashed ops flow. The compressor is stashed with these ops already processed.
1709
- // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
1710
- // thus we need to process all the ops.
1711
- if (!(this.skipSavedCompressorOps && savedOp === true)) {
1712
- const range = message.contents;
1713
- // Some other client turned on the id compressor. If we have not turned it on,
1714
- // put it in a pending queue and delay finalization.
1715
- if (this._idCompressor === undefined) {
1716
- (0, internal_2.assert)(this.idCompressorMode !== undefined, 0x93c /* id compressor should be enabled */);
1717
- this.pendingIdCompressorOps.push(range);
1718
- }
1719
- else {
1720
- (0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x979 /* there should be no pending ops! */);
1721
- this._idCompressor.finalizeCreationRange(range);
1722
- }
1723
- }
1771
+ this.processIdCompressorMessages(contents, savedOp);
1724
1772
  break;
1725
1773
  case messageTypes_js_1.ContainerMessageType.GC:
1726
- this.garbageCollector.processMessage(message, message.timestamp, local);
1774
+ this.garbageCollector.processMessages(contents, message.timestamp, local);
1727
1775
  break;
1728
1776
  case messageTypes_js_1.ContainerMessageType.ChunkedOp:
1729
- // From observability POV, we should not exppse the rest of the system (including "op" events on object) to these messages.
1777
+ // From observability POV, we should not expose the rest of the system (including "op" events on object) to these messages.
1730
1778
  // Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
1731
1779
  (0, internal_2.assert)(false, 0x93d /* should not even get here */);
1732
1780
  case messageTypes_js_1.ContainerMessageType.Rejoin:
1733
1781
  break;
1734
1782
  case messageTypes_js_1.ContainerMessageType.DocumentSchemaChange:
1735
- this.documentsSchemaController.processDocumentSchemaOp(message.contents, local, message.sequenceNumber);
1783
+ this.documentsSchemaController.processDocumentSchemaMessages(contents, local, message.sequenceNumber);
1736
1784
  break;
1737
1785
  default: {
1738
1786
  const error = getUnknownMessageTypeError(message.type, "validateAndProcessRuntimeMessage" /* codePath */, message);
@@ -1740,7 +1788,26 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1740
1788
  throw error;
1741
1789
  }
1742
1790
  }
1743
- this.emit("op", message, true /* runtimeMessage */);
1791
+ }
1792
+ processIdCompressorMessages(messageContents, savedOp) {
1793
+ for (const range of messageContents) {
1794
+ // Don't re-finalize the range if we're processing a "savedOp" in
1795
+ // stashed ops flow. The compressor is stashed with these ops already processed.
1796
+ // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
1797
+ // thus we need to process all the ops.
1798
+ if (!(this.skipSavedCompressorOps && savedOp === true)) {
1799
+ // Some other client turned on the id compressor. If we have not turned it on,
1800
+ // put it in a pending queue and delay finalization.
1801
+ if (this._idCompressor === undefined) {
1802
+ (0, internal_2.assert)(this.idCompressorMode !== undefined, 0x93c /* id compressor should be enabled */);
1803
+ this.pendingIdCompressorOps.push(range);
1804
+ }
1805
+ else {
1806
+ (0, internal_2.assert)(this.pendingIdCompressorOps.length === 0, 0x979 /* there should be no pending ops! */);
1807
+ this._idCompressor.finalizeCreationRange(range);
1808
+ }
1809
+ }
1810
+ }
1744
1811
  }
1745
1812
  /**
1746
1813
  * Emits the Signal event and update the perf signal data.
@@ -1749,11 +1816,13 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1749
1816
  const duration = Date.now() - this._signalTracking.signalTimestamp;
1750
1817
  this.mc.logger.sendPerformanceEvent({
1751
1818
  eventName: "SignalLatency",
1752
- duration, // Roundtrip duration of the tracked signal in milliseconds.
1753
- signalsSent: this._signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
1754
- signalsLost: this._signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
1755
- outOfOrderSignals: this._signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
1756
- reconnectCount: this.consecutiveReconnects, // Container reconnect count.
1819
+ details: {
1820
+ duration, // Roundtrip duration of the tracked signal in milliseconds.
1821
+ sent: this._signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
1822
+ lost: this._signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
1823
+ outOfOrder: this._signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
1824
+ reconnectCount: this.consecutiveReconnects, // Container reconnect count.
1825
+ },
1757
1826
  });
1758
1827
  this._signalTracking.signalsLost = 0;
1759
1828
  this._signalTracking.signalsOutOfOrder = 0;
@@ -1780,9 +1849,11 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1780
1849
  this._signalTracking.signalsLost += signalsLost;
1781
1850
  this.mc.logger.sendErrorEvent({
1782
1851
  eventName: "SignalLost",
1783
- signalsLost, // Number of lost signals detected.
1784
- trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
1785
- clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
1852
+ details: {
1853
+ signalsLost, // Number of lost signals detected.
1854
+ expectedSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
1855
+ clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
1856
+ },
1786
1857
  });
1787
1858
  }
1788
1859
  // Update the tracking signal sequence number to the next expected signal in the sequence.
@@ -1794,11 +1865,18 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1794
1865
  clientBroadcastSignalSequenceNumber >=
1795
1866
  this._signalTracking.minimumTrackingSignalSequenceNumber) {
1796
1867
  this._signalTracking.signalsOutOfOrder++;
1868
+ const details = {
1869
+ expectedSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
1870
+ clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
1871
+ };
1872
+ // Only log `contents.type` when address is for container to avoid
1873
+ // chance that contents type is customer data.
1874
+ if (envelope.address === undefined) {
1875
+ details.contentsType = envelope.contents.type; // Type of signal that was received out of order.
1876
+ }
1797
1877
  this.mc.logger.sendTelemetryEvent({
1798
1878
  eventName: "SignalOutOfOrder",
1799
- type: envelope.contents.type, // Type of signal that was received out of order.
1800
- trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
1801
- clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
1879
+ details,
1802
1880
  });
1803
1881
  }
1804
1882
  if (this._signalTracking.roundTripSignalSequenceNumber !== undefined &&
@@ -1807,7 +1885,8 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
1807
1885
  this._signalTracking.roundTripSignalSequenceNumber) {
1808
1886
  // Latency tracked signal has been received.
1809
1887
  // We now log the roundtrip duration of the tracked signal.
1810
- // This telemetry event also logs metrics for signals sent, signals lost, and out of order signals received.
1888
+ // This telemetry event also logs metrics for broadcast signals
1889
+ // sent, lost, and out of order.
1811
1890
  // These metrics are reset after logging the telemetry event.
1812
1891
  this.sendSignalTelemetryEvent();
1813
1892
  }
@@ -2304,7 +2383,17 @@ class ContainerRuntime extends client_utils_1.TypedEventEmitter {
2304
2383
  },
2305
2384
  },
2306
2385
  });
2307
- (0, internal_2.assert)(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
2386
+ // legacy: assert 0x3d1
2387
+ if (!this.outbox.isEmpty) {
2388
+ throw internal_7.DataProcessingError.create("Can't trigger summary in the middle of a batch", "submitSummary", undefined, {
2389
+ summaryNumber,
2390
+ pendingMessages: this.pendingMessagesCount,
2391
+ outboxLength: this.outbox.messageCount,
2392
+ mainBatchLength: this.outbox.mainBatchMessageCount,
2393
+ blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
2394
+ idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
2395
+ });
2396
+ }
2308
2397
  // If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
2309
2398
  // and it may even be incorrect. So, wait for the container to be saved with a timeout. If the container is not
2310
2399
  // saved within the timeout, check if it should be failed or can continue.