@fluidframework/container-runtime 2.1.0-281041 → 2.2.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 (207) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +6 -6
  3. package/api-report/container-runtime.legacy.alpha.api.md +4 -3
  4. package/container-runtime.test-files.tar +0 -0
  5. package/dist/batchTracker.d.ts.map +1 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager/blobManager.d.ts.map +1 -1
  8. package/dist/blobManager/blobManager.js +9 -0
  9. package/dist/blobManager/blobManager.js.map +1 -1
  10. package/dist/channelCollection.d.ts +0 -14
  11. package/dist/channelCollection.d.ts.map +1 -1
  12. package/dist/channelCollection.js +2 -12
  13. package/dist/channelCollection.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +34 -6
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +177 -74
  17. package/dist/containerRuntime.js.map +1 -1
  18. package/dist/dataStoreContext.d.ts +9 -18
  19. package/dist/dataStoreContext.d.ts.map +1 -1
  20. package/dist/dataStoreContext.js +40 -78
  21. package/dist/dataStoreContext.js.map +1 -1
  22. package/dist/gc/garbageCollection.d.ts +0 -6
  23. package/dist/gc/garbageCollection.d.ts.map +1 -1
  24. package/dist/gc/garbageCollection.js +23 -66
  25. package/dist/gc/garbageCollection.js.map +1 -1
  26. package/dist/gc/gcConfigs.d.ts.map +1 -1
  27. package/dist/gc/gcConfigs.js +11 -34
  28. package/dist/gc/gcConfigs.js.map +1 -1
  29. package/dist/gc/gcDefinitions.d.ts +9 -52
  30. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  31. package/dist/gc/gcDefinitions.js +3 -23
  32. package/dist/gc/gcDefinitions.js.map +1 -1
  33. package/dist/gc/gcHelpers.d.ts.map +1 -1
  34. package/dist/gc/gcHelpers.js +2 -6
  35. package/dist/gc/gcHelpers.js.map +1 -1
  36. package/dist/gc/gcSummaryStateTracker.d.ts +1 -1
  37. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  38. package/dist/gc/gcSummaryStateTracker.js +4 -8
  39. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  40. package/dist/gc/gcTelemetry.d.ts +1 -9
  41. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  42. package/dist/gc/gcTelemetry.js +3 -25
  43. package/dist/gc/gcTelemetry.js.map +1 -1
  44. package/dist/gc/index.d.ts +2 -2
  45. package/dist/gc/index.d.ts.map +1 -1
  46. package/dist/gc/index.js +2 -7
  47. package/dist/gc/index.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/messageTypes.d.ts +6 -5
  53. package/dist/messageTypes.d.ts.map +1 -1
  54. package/dist/messageTypes.js.map +1 -1
  55. package/dist/metadata.d.ts +9 -1
  56. package/dist/metadata.d.ts.map +1 -1
  57. package/dist/metadata.js +6 -1
  58. package/dist/metadata.js.map +1 -1
  59. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  60. package/dist/opLifecycle/batchManager.js +1 -1
  61. package/dist/opLifecycle/batchManager.js.map +1 -1
  62. package/dist/opLifecycle/index.d.ts +1 -1
  63. package/dist/opLifecycle/index.d.ts.map +1 -1
  64. package/dist/opLifecycle/index.js +2 -1
  65. package/dist/opLifecycle/index.js.map +1 -1
  66. package/dist/opLifecycle/opGroupingManager.d.ts +8 -0
  67. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  68. package/dist/opLifecycle/opGroupingManager.js +34 -2
  69. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  70. package/dist/opLifecycle/outbox.d.ts +1 -0
  71. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  72. package/dist/opLifecycle/outbox.js +21 -1
  73. package/dist/opLifecycle/outbox.js.map +1 -1
  74. package/dist/opLifecycle/remoteMessageProcessor.d.ts +35 -14
  75. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  76. package/dist/opLifecycle/remoteMessageProcessor.js +71 -46
  77. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  78. package/dist/packageVersion.d.ts +1 -1
  79. package/dist/packageVersion.d.ts.map +1 -1
  80. package/dist/packageVersion.js +1 -1
  81. package/dist/packageVersion.js.map +1 -1
  82. package/dist/pendingStateManager.d.ts +28 -27
  83. package/dist/pendingStateManager.d.ts.map +1 -1
  84. package/dist/pendingStateManager.js +143 -112
  85. package/dist/pendingStateManager.js.map +1 -1
  86. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  87. package/dist/summary/summarizerNode/summarizerNode.js +5 -1
  88. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  89. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
  90. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  91. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
  92. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  93. package/lib/batchTracker.d.ts.map +1 -1
  94. package/lib/batchTracker.js.map +1 -1
  95. package/lib/blobManager/blobManager.d.ts.map +1 -1
  96. package/lib/blobManager/blobManager.js +9 -0
  97. package/lib/blobManager/blobManager.js.map +1 -1
  98. package/lib/channelCollection.d.ts +0 -14
  99. package/lib/channelCollection.d.ts.map +1 -1
  100. package/lib/channelCollection.js +2 -11
  101. package/lib/channelCollection.js.map +1 -1
  102. package/lib/containerRuntime.d.ts +34 -6
  103. package/lib/containerRuntime.d.ts.map +1 -1
  104. package/lib/containerRuntime.js +178 -75
  105. package/lib/containerRuntime.js.map +1 -1
  106. package/lib/dataStoreContext.d.ts +9 -18
  107. package/lib/dataStoreContext.d.ts.map +1 -1
  108. package/lib/dataStoreContext.js +27 -65
  109. package/lib/dataStoreContext.js.map +1 -1
  110. package/lib/gc/garbageCollection.d.ts +0 -6
  111. package/lib/gc/garbageCollection.d.ts.map +1 -1
  112. package/lib/gc/garbageCollection.js +25 -68
  113. package/lib/gc/garbageCollection.js.map +1 -1
  114. package/lib/gc/gcConfigs.d.ts.map +1 -1
  115. package/lib/gc/gcConfigs.js +12 -35
  116. package/lib/gc/gcConfigs.js.map +1 -1
  117. package/lib/gc/gcDefinitions.d.ts +9 -52
  118. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  119. package/lib/gc/gcDefinitions.js +2 -22
  120. package/lib/gc/gcDefinitions.js.map +1 -1
  121. package/lib/gc/gcHelpers.d.ts.map +1 -1
  122. package/lib/gc/gcHelpers.js +2 -6
  123. package/lib/gc/gcHelpers.js.map +1 -1
  124. package/lib/gc/gcSummaryStateTracker.d.ts +1 -1
  125. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  126. package/lib/gc/gcSummaryStateTracker.js +4 -8
  127. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  128. package/lib/gc/gcTelemetry.d.ts +1 -9
  129. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  130. package/lib/gc/gcTelemetry.js +3 -24
  131. package/lib/gc/gcTelemetry.js.map +1 -1
  132. package/lib/gc/index.d.ts +2 -2
  133. package/lib/gc/index.d.ts.map +1 -1
  134. package/lib/gc/index.js +2 -2
  135. package/lib/gc/index.js.map +1 -1
  136. package/lib/index.d.ts +1 -1
  137. package/lib/index.d.ts.map +1 -1
  138. package/lib/index.js +1 -1
  139. package/lib/index.js.map +1 -1
  140. package/lib/messageTypes.d.ts +6 -5
  141. package/lib/messageTypes.d.ts.map +1 -1
  142. package/lib/messageTypes.js.map +1 -1
  143. package/lib/metadata.d.ts +9 -1
  144. package/lib/metadata.d.ts.map +1 -1
  145. package/lib/metadata.js +4 -0
  146. package/lib/metadata.js.map +1 -1
  147. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  148. package/lib/opLifecycle/batchManager.js +1 -1
  149. package/lib/opLifecycle/batchManager.js.map +1 -1
  150. package/lib/opLifecycle/index.d.ts +1 -1
  151. package/lib/opLifecycle/index.d.ts.map +1 -1
  152. package/lib/opLifecycle/index.js +1 -1
  153. package/lib/opLifecycle/index.js.map +1 -1
  154. package/lib/opLifecycle/opGroupingManager.d.ts +8 -0
  155. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  156. package/lib/opLifecycle/opGroupingManager.js +34 -2
  157. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  158. package/lib/opLifecycle/outbox.d.ts +1 -0
  159. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  160. package/lib/opLifecycle/outbox.js +21 -1
  161. package/lib/opLifecycle/outbox.js.map +1 -1
  162. package/lib/opLifecycle/remoteMessageProcessor.d.ts +35 -14
  163. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  164. package/lib/opLifecycle/remoteMessageProcessor.js +69 -45
  165. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  166. package/lib/packageVersion.d.ts +1 -1
  167. package/lib/packageVersion.d.ts.map +1 -1
  168. package/lib/packageVersion.js +1 -1
  169. package/lib/packageVersion.js.map +1 -1
  170. package/lib/pendingStateManager.d.ts +28 -27
  171. package/lib/pendingStateManager.d.ts.map +1 -1
  172. package/lib/pendingStateManager.js +144 -113
  173. package/lib/pendingStateManager.js.map +1 -1
  174. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  175. package/lib/summary/summarizerNode/summarizerNode.js +5 -1
  176. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  177. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
  178. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  179. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
  180. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  181. package/package.json +23 -32
  182. package/src/batchTracker.ts +4 -2
  183. package/src/blobManager/blobManager.ts +9 -0
  184. package/src/channelCollection.ts +2 -11
  185. package/src/containerRuntime.ts +214 -100
  186. package/src/dataStoreContext.ts +29 -93
  187. package/src/gc/garbageCollection.ts +26 -79
  188. package/src/gc/gcConfigs.ts +12 -45
  189. package/src/gc/gcDefinitions.ts +10 -55
  190. package/src/gc/gcHelpers.ts +10 -8
  191. package/src/gc/gcSummaryStateTracker.ts +6 -9
  192. package/src/gc/gcTelemetry.ts +3 -38
  193. package/src/gc/index.ts +2 -6
  194. package/src/index.ts +0 -1
  195. package/src/messageTypes.ts +12 -11
  196. package/src/metadata.ts +16 -2
  197. package/src/opLifecycle/batchManager.ts +4 -1
  198. package/src/opLifecycle/index.ts +6 -1
  199. package/src/opLifecycle/opGroupingManager.ts +42 -3
  200. package/src/opLifecycle/outbox.ts +31 -1
  201. package/src/opLifecycle/remoteMessageProcessor.ts +116 -57
  202. package/src/packageVersion.ts +1 -1
  203. package/src/pendingStateManager.ts +199 -177
  204. package/src/summary/README.md +31 -28
  205. package/src/summary/summarizerNode/summarizerNode.ts +6 -1
  206. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +20 -43
  207. package/src/summary/summaryFormats.md +25 -22
@@ -240,10 +240,11 @@ export function unpackChildNodesGCDetails(gcDetails: IGarbageCollectionDetailsBa
240
240
  continue;
241
241
  }
242
242
 
243
- assert(id.startsWith("/"), 0x5d9 /* node id should always be an absolute route */);
244
- // TODO Why are we non null asserting here
245
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
246
- const childId = id.split("/")[1]!;
243
+ const childId = id.split("/")[1];
244
+ assert(
245
+ childId !== undefined,
246
+ 0x9fe /* node id should be an absolute route with child id part */,
247
+ );
247
248
  let childGCNodeId = id.slice(childId.length + 1);
248
249
  // GC node id always begins with "/". Handle the special case where a child's id in the parent's GC nodes is
249
250
  // of format `/root`. In this case, the childId is root and childGCNodeId is "". Make childGCNodeId = "/".
@@ -271,10 +272,11 @@ export function unpackChildNodesGCDetails(gcDetails: IGarbageCollectionDetailsBa
271
272
  // Remove the node's self used route, if any, and generate the children used routes.
272
273
  const usedRoutes = gcDetails.usedRoutes.filter((route) => route !== "" && route !== "/");
273
274
  for (const route of usedRoutes) {
274
- assert(route.startsWith("/"), 0x5db /* Used route should always be an absolute route */);
275
- // TODO Why are we non null asserting here
276
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
277
- const childId = route.split("/")[1]!;
275
+ const childId = route.split("/")[1];
276
+ assert(
277
+ childId !== undefined,
278
+ 0x9ff /* used route should be an absolute route with child id part */,
279
+ );
278
280
  const childUsedRoute = route.slice(childId.length + 1);
279
281
 
280
282
  const childGCDetails = childGCDetailsMap.get(childId);
@@ -66,7 +66,7 @@ export class GCSummaryStateTracker {
66
66
  // Tells whether GC should run or not.
67
67
  private readonly configs: Pick<
68
68
  IGarbageCollectorConfigs,
69
- "gcEnabled" | "tombstoneMode" | "gcVersionInBaseSnapshot" | "gcVersionInEffect"
69
+ "gcAllowed" | "gcVersionInBaseSnapshot" | "gcVersionInEffect"
70
70
  >,
71
71
  ) {}
72
72
 
@@ -100,7 +100,7 @@ export class GCSummaryStateTracker {
100
100
  deletedNodes: Set<string>,
101
101
  tombstones: string[],
102
102
  ): ISummarizeResult | undefined {
103
- if (!this.configs.gcEnabled) {
103
+ if (!this.configs.gcAllowed) {
104
104
  return;
105
105
  }
106
106
 
@@ -109,12 +109,9 @@ export class GCSummaryStateTracker {
109
109
  // to identify deleted nodes' usage.
110
110
  const serializedDeletedNodes =
111
111
  deletedNodes.size > 0 ? JSON.stringify(Array.from(deletedNodes).sort()) : undefined;
112
- // If running in tombstone mode, serialize and write tombstones, if any.
113
- const serializedTombstones = this.configs.tombstoneMode
114
- ? tombstones.length > 0
115
- ? JSON.stringify(tombstones.sort())
116
- : undefined
117
- : undefined;
112
+ // Serialize and write tombstones, if any.
113
+ const serializedTombstones =
114
+ tombstones.length > 0 ? JSON.stringify(tombstones.sort()) : undefined;
118
115
 
119
116
  /**
120
117
  * Incremental summary of GC data - If none of GC state, deleted nodes or tombstones changed since last summary,
@@ -231,7 +228,7 @@ export class GCSummaryStateTracker {
231
228
  * Called to refresh the latest summary state. This happens when a pending summary is acked.
232
229
  */
233
230
  public async refreshLatestSummary(result: IRefreshSummaryResult): Promise<void> {
234
- if (!this.configs.gcEnabled || !result.isSummaryTracked) {
231
+ if (!this.configs.gcAllowed || !result.isSummaryTracked) {
235
232
  return;
236
233
  }
237
234
 
@@ -10,7 +10,6 @@ import {
10
10
  MonitoringContext,
11
11
  generateStack,
12
12
  tagCodeArtifacts,
13
- type ITelemetryGenericEventExt,
14
13
  type ITelemetryPropertiesExt,
15
14
  } from "@fluidframework/telemetry-utils/internal";
16
15
 
@@ -22,11 +21,7 @@ import {
22
21
  GCNodeType,
23
22
  IGarbageCollectorConfigs,
24
23
  UnreferencedState,
25
- disableTombstoneKey,
26
- throwOnTombstoneLoadOverrideKey,
27
- throwOnTombstoneUsageKey,
28
24
  } from "./gcDefinitions.js";
29
- import { getGCVersionInEffect } from "./gcHelpers.js";
30
25
  import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker.js";
31
26
 
32
27
  type NodeUsageType = "Changed" | "Loaded" | "Revived" | "Realized";
@@ -287,18 +282,12 @@ export class GCTelemetryTracker {
287
282
  headers: { ...headers },
288
283
  details: { ...detailedProps, ...additionalProps }, // Also includes some properties from INodeUsageProps type
289
284
  gcConfigs,
290
- tombstoneFlags: {
291
- DisableTombstone: this.mc.config.getBoolean(disableTombstoneKey),
292
- ThrowOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey),
293
- ThrowOnTombstoneLoad: this.mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
294
- },
295
285
  };
296
286
 
297
287
  if (
298
- (usageType === "Loaded" &&
299
- this.configs.throwOnTombstoneLoad &&
300
- !headers?.allowTombstone) ||
301
- (usageType === "Changed" && this.configs.throwOnTombstoneUsage)
288
+ usageType === "Loaded" &&
289
+ this.configs.throwOnTombstoneLoad &&
290
+ !headers?.allowTombstone
302
291
  ) {
303
292
  this.mc.logger.sendErrorEvent(event);
304
293
  } else {
@@ -419,27 +408,3 @@ export class GCTelemetryTracker {
419
408
  this.pendingEventsQueue = [];
420
409
  }
421
410
  }
422
-
423
- /**
424
- * Consolidates info / logic for logging when we encounter unexpected usage of GC'd objects. For example, when a
425
- * tombstoned or deleted object is loaded.
426
- */
427
- export function sendGCUnexpectedUsageEvent(
428
- mc: MonitoringContext,
429
- event: ITelemetryGenericEventExt & {
430
- category: "error" | "generic";
431
- gcTombstoneEnforcementAllowed: boolean | undefined;
432
- },
433
- packagePath: readonly string[] | undefined,
434
- error?: unknown,
435
- ) {
436
- event.pkg = tagCodeArtifacts({ pkg: packagePath?.join("/") })?.pkg;
437
- event.tombstoneFlags = JSON.stringify({
438
- DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
439
- ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
440
- ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
441
- });
442
- event.gcVersion = getGCVersionInEffect(mc.config);
443
-
444
- mc.logger.sendTelemetryEvent(event, error);
445
- }
package/src/gc/index.ts CHANGED
@@ -11,8 +11,6 @@ export {
11
11
  defaultSessionExpiryDurationMs,
12
12
  GCNodeType,
13
13
  gcTestModeKey,
14
- gcDisableDataStoreSweepOptionName,
15
- gcDisableThrowOnTombstoneLoadOptionName,
16
14
  gcGenerationOptionName,
17
15
  GCFeatureMatrix,
18
16
  GCVersion,
@@ -32,10 +30,8 @@ export {
32
30
  oneDayMs,
33
31
  runSessionExpiryKey,
34
32
  stableGCVersion,
35
- disableAutoRecoveryKey,
36
- disableDatastoreSweepKey,
37
33
  UnreferencedState,
38
- throwOnTombstoneLoadOverrideKey,
34
+ disableThrowOnTombstoneLoadKey,
39
35
  GarbageCollectionMessage,
40
36
  GarbageCollectionMessageType,
41
37
  ISweepMessage,
@@ -59,5 +55,5 @@ export {
59
55
  GCSummaryStateTracker,
60
56
  IGCSummaryTrackingData,
61
57
  } from "./gcSummaryStateTracker.js";
62
- export { GCTelemetryTracker, sendGCUnexpectedUsageEvent } from "./gcTelemetry.js";
58
+ export { GCTelemetryTracker } from "./gcTelemetry.js";
63
59
  export { UnreferencedStateTracker } from "./gcUnreferencedStateTracker.js";
package/src/index.ts CHANGED
@@ -37,7 +37,6 @@ export {
37
37
  RuntimeHeaders,
38
38
  ChannelCollectionFactory,
39
39
  AllowTombstoneRequestHeaderKey,
40
- AllowInactiveRequestHeaderKey,
41
40
  } from "./channelCollection.js";
42
41
  export {
43
42
  GCNodeType,
@@ -88,12 +88,18 @@ export interface IContainerRuntimeMessageCompatDetails {
88
88
  * IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
89
89
  * This way stringified values can be compared.
90
90
  */
91
- interface TypedContainerRuntimeMessage<TType extends ContainerMessageType, TContents> {
91
+ type TypedContainerRuntimeMessage<
92
+ TType extends ContainerMessageType,
93
+ TContents,
94
+ TUSedCompatDetails extends boolean = false,
95
+ > = {
92
96
  /** Type of the op, within the ContainerRuntime's domain */
93
97
  type: TType;
94
98
  /** Domain-specific contents, interpreted according to the type */
95
99
  contents: TContents;
96
- }
100
+ } & (TUSedCompatDetails extends true
101
+ ? Partial<RecentlyAddedContainerRuntimeMessageDetails>
102
+ : { compatDetails?: undefined });
97
103
 
98
104
  /**
99
105
  * Additional details expected for any recently added message.
@@ -139,10 +145,9 @@ export type ContainerRuntimeIdAllocationMessage = TypedContainerRuntimeMessage<
139
145
  >;
140
146
  export type ContainerRuntimeGCMessage = TypedContainerRuntimeMessage<
141
147
  ContainerMessageType.GC,
142
- GarbageCollectionMessage
143
- > &
144
- // While deprecating: GC messages may still contain compat details for now
145
- Partial<RecentlyAddedContainerRuntimeMessageDetails>;
148
+ GarbageCollectionMessage,
149
+ true // TUsedCompatDetails
150
+ >;
146
151
  export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
147
152
  ContainerMessageType.DocumentSchemaChange,
148
153
  IDocumentSchemaChangeMessage
@@ -223,15 +228,11 @@ export type InboundSequencedContainerRuntimeMessage = Omit<
223
228
  * There should never be a runtime value of "__not_a_...".
224
229
  * Currently additionally replaces `contents` type until protocol-definitions update is taken with `unknown` instead of `any`.
225
230
  */
226
- type InboundSequencedNonContainerRuntimeMessage = Omit<
231
+ export type InboundSequencedNonContainerRuntimeMessage = Omit<
227
232
  ISequencedDocumentMessage,
228
233
  "type" | "contents"
229
234
  > & { type: "__not_a_container_runtime_message_type__"; contents: unknown };
230
235
 
231
- export type InboundSequencedContainerRuntimeMessageOrSystemMessage =
232
- | InboundSequencedContainerRuntimeMessage
233
- | InboundSequencedNonContainerRuntimeMessage;
234
-
235
236
  /** A [loose] InboundSequencedContainerRuntimeMessage that is recent and may contain compat details.
236
237
  * It exists solely to to provide access to those details.
237
238
  */
package/src/metadata.ts CHANGED
@@ -6,10 +6,24 @@
6
6
  import type { BatchId } from "./opLifecycle/index.js";
7
7
 
8
8
  /** Syntactic sugar for casting */
9
- export function asBatchMetadata(metadata: unknown): IBatchMetadata | undefined {
10
- return metadata as IBatchMetadata | undefined;
9
+ export function asBatchMetadata(metadata: unknown): Partial<IBatchMetadata> | undefined {
10
+ return metadata as Partial<IBatchMetadata> | undefined;
11
11
  }
12
12
 
13
+ /** Syntactic sugar for casting */
14
+ export function asEmptyBatchLocalOpMetadata(
15
+ localOpMetadata: unknown,
16
+ ): IEmptyBatchMetadata | undefined {
17
+ return localOpMetadata as IEmptyBatchMetadata | undefined;
18
+ }
19
+
20
+ /**
21
+ * Properties put on the localOpMetadata object for empty batches
22
+ */
23
+ export interface IEmptyBatchMetadata {
24
+ // Set to true on localOpMetadata for empty batches
25
+ emptyBatch?: boolean;
26
+ }
13
27
  /**
14
28
  * Properties put on the op metadata object for batch tracking
15
29
  */
@@ -156,7 +156,10 @@ const addBatchMetadata = (batch: IBatch, batchId?: BatchId): IBatch => {
156
156
 
157
157
  const firstMsg = batch.messages[0];
158
158
  const lastMsg = batch.messages[batchEnd];
159
- assert(firstMsg !== undefined && lastMsg !== undefined, "expected non-empty batch");
159
+ assert(
160
+ firstMsg !== undefined && lastMsg !== undefined,
161
+ 0x9d1 /* expected non-empty batch */,
162
+ );
160
163
 
161
164
  const firstMetadata: Partial<IBatchMetadata> = firstMsg.metadata ?? {};
162
165
  const lastMetadata: Partial<IBatchMetadata> = lastMsg.metadata ?? {};
@@ -16,7 +16,12 @@ export { Outbox, getLongStack } from "./outbox.js";
16
16
  export { OpCompressor } from "./opCompressor.js";
17
17
  export { OpDecompressor } from "./opDecompressor.js";
18
18
  export { OpSplitter, splitOp, isChunkedMessage } from "./opSplitter.js";
19
- export { RemoteMessageProcessor, unpackRuntimeMessage } from "./remoteMessageProcessor.js";
19
+ export {
20
+ ensureContentsDeserialized,
21
+ InboundBatch,
22
+ RemoteMessageProcessor,
23
+ unpackRuntimeMessage,
24
+ } from "./remoteMessageProcessor.js";
20
25
  export {
21
26
  OpGroupingManager,
22
27
  OpGroupingManagerConfig,
@@ -49,6 +49,40 @@ export class OpGroupingManager {
49
49
  this.logger = createChildLogger({ logger, namespace: "OpGroupingManager" });
50
50
  }
51
51
 
52
+ /**
53
+ * Creates a new batch with a single message of type "groupedBatch" and empty contents.
54
+ * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.
55
+ * @param resubmittingBatchId - batch ID of the resubmitting batch
56
+ * @param referenceSequenceNumber - reference sequence number
57
+ * @returns - IBatch containing a single empty Grouped Batch op
58
+ */
59
+ public createEmptyGroupedBatch(
60
+ resubmittingBatchId: string,
61
+ referenceSequenceNumber: number,
62
+ ): IBatch<[BatchMessage]> {
63
+ assert(
64
+ this.config.groupedBatchingEnabled,
65
+ 0xa00 /* cannot create empty grouped batch when grouped batching is disabled */,
66
+ );
67
+ const serializedContent = JSON.stringify({
68
+ type: OpGroupingManager.groupedBatchOp,
69
+ contents: [],
70
+ });
71
+
72
+ return {
73
+ contentSizeInBytes: 0,
74
+ messages: [
75
+ {
76
+ metadata: { batchId: resubmittingBatchId },
77
+ localOpMetadata: { emptyBatch: true },
78
+ referenceSequenceNumber,
79
+ contents: serializedContent,
80
+ },
81
+ ],
82
+ referenceSequenceNumber,
83
+ };
84
+ }
85
+
52
86
  /**
53
87
  * Converts the given batch into a "grouped batch" - a batch with a single message of type "groupedBatch",
54
88
  * with contents being an array of the original batch's messages.
@@ -70,10 +104,14 @@ export class OpGroupingManager {
70
104
  referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
71
105
  });
72
106
  }
73
-
107
+ // We expect this will be on the first message, if present at all.
108
+ let groupedBatchId;
74
109
  for (const message of batch.messages) {
75
110
  if (message.metadata) {
76
111
  const { batch: _batch, batchId, ...rest } = message.metadata;
112
+ if (batchId) {
113
+ groupedBatchId = batchId;
114
+ }
77
115
  assert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);
78
116
  }
79
117
  }
@@ -91,7 +129,7 @@ export class OpGroupingManager {
91
129
  ...batch,
92
130
  messages: [
93
131
  {
94
- metadata: undefined,
132
+ metadata: { batchId: groupedBatchId },
95
133
  // TODO why are we non null asserting here?
96
134
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97
135
  referenceSequenceNumber: batch.messages[0]!.referenceSequenceNumber,
@@ -121,7 +159,8 @@ export class OpGroupingManager {
121
159
  // Grouped batching must be enabled
122
160
  this.config.groupedBatchingEnabled &&
123
161
  // The number of ops in the batch must surpass the configured threshold
124
- batch.messages.length >= this.config.opCountThreshold &&
162
+ // or be empty (to allow for empty batches to be grouped)
163
+ (batch.messages.length === 0 || batch.messages.length >= this.config.opCountThreshold) &&
125
164
  // Support for reentrant batches must be explicitly enabled
126
165
  (this.config.reentrantBatchGroupingEnabled || batch.hasReentrantOps !== true)
127
166
  );
@@ -254,6 +254,14 @@ export class Outbox {
254
254
  // Don't use resubmittingBatchId for idAllocationBatch.
255
255
  // ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
256
256
  this.flushInternal(this.idAllocationBatch);
257
+ // We need to flush an empty batch if the main batch *becomes* empty on resubmission.
258
+ // When resubmitting the main batch, the blobAttach batch will always be empty since we don't resubmit them simultaneously.
259
+ // And conversely, the blobAttach will never *become* empty on resubmit.
260
+ // So if both blobAttachBatch and mainBatch are empty, we must submit an empty main batch.
261
+ if (resubmittingBatchId && this.blobAttachBatch.empty && this.mainBatch.empty) {
262
+ this.flushEmptyBatch(resubmittingBatchId);
263
+ return;
264
+ }
257
265
  this.flushInternal(
258
266
  this.blobAttachBatch,
259
267
  true /* disableGroupedBatching */,
@@ -266,6 +274,28 @@ export class Outbox {
266
274
  );
267
275
  }
268
276
 
277
+ private flushEmptyBatch(resubmittingBatchId: BatchId) {
278
+ const referenceSequenceNumber =
279
+ this.params.getCurrentSequenceNumbers().referenceSequenceNumber;
280
+ assert(
281
+ referenceSequenceNumber !== undefined,
282
+ 0xa01 /* reference sequence number should be defined */,
283
+ );
284
+ const emptyGroupedBatch = this.params.groupingManager.createEmptyGroupedBatch(
285
+ resubmittingBatchId,
286
+ referenceSequenceNumber,
287
+ );
288
+ let clientSequenceNumber: number | undefined;
289
+ if (this.params.shouldSend()) {
290
+ clientSequenceNumber = this.sendBatch(emptyGroupedBatch);
291
+ }
292
+ this.params.pendingStateManager.onFlushBatch(
293
+ emptyGroupedBatch.messages, // This is the single empty Grouped Batch message
294
+ clientSequenceNumber,
295
+ );
296
+ return;
297
+ }
298
+
269
299
  private flushInternal(
270
300
  batchManager: BatchManager,
271
301
  disableGroupedBatching: boolean = false,
@@ -298,7 +328,7 @@ export class Outbox {
298
328
  clientSequenceNumber = this.sendBatch(processedBatch);
299
329
  assert(
300
330
  clientSequenceNumber === undefined || clientSequenceNumber >= 0,
301
- "unexpected negative clientSequenceNumber (empty batch should yield undefined)",
331
+ 0x9d2 /* unexpected negative clientSequenceNumber (empty batch should yield undefined) */,
302
332
  );
303
333
  }
304
334