@fluidframework/container-runtime 2.0.0-dev-rc.3.0.0.254674 → 2.0.0-dev-rc.4.0.0.261659

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 (293) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/api-report/container-runtime.api.md +33 -19
  3. package/dist/batchTracker.d.ts +1 -1
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js.map +1 -1
  6. package/dist/channelCollection.d.ts +5 -3
  7. package/dist/channelCollection.d.ts.map +1 -1
  8. package/dist/channelCollection.js +67 -15
  9. package/dist/channelCollection.js.map +1 -1
  10. package/dist/connectionTelemetry.d.ts +1 -1
  11. package/dist/connectionTelemetry.d.ts.map +1 -1
  12. package/dist/connectionTelemetry.js +54 -5
  13. package/dist/connectionTelemetry.js.map +1 -1
  14. package/dist/containerRuntime.d.ts +17 -27
  15. package/dist/containerRuntime.d.ts.map +1 -1
  16. package/dist/containerRuntime.js +174 -143
  17. package/dist/containerRuntime.js.map +1 -1
  18. package/dist/dataStore.d.ts +1 -1
  19. package/dist/dataStore.d.ts.map +1 -1
  20. package/dist/dataStore.js.map +1 -1
  21. package/dist/dataStoreContext.d.ts.map +1 -1
  22. package/dist/dataStoreContext.js +1 -0
  23. package/dist/dataStoreContext.js.map +1 -1
  24. package/dist/dataStoreContexts.d.ts +2 -0
  25. package/dist/dataStoreContexts.d.ts.map +1 -1
  26. package/dist/dataStoreContexts.js +7 -0
  27. package/dist/dataStoreContexts.js.map +1 -1
  28. package/dist/deltaManagerSummarizerProxy.d.ts +18 -6
  29. package/dist/deltaManagerSummarizerProxy.d.ts.map +1 -1
  30. package/dist/deltaManagerSummarizerProxy.js +38 -19
  31. package/dist/deltaManagerSummarizerProxy.js.map +1 -1
  32. package/dist/deltaScheduler.d.ts +1 -1
  33. package/dist/deltaScheduler.d.ts.map +1 -1
  34. package/dist/deltaScheduler.js.map +1 -1
  35. package/dist/gc/garbageCollection.d.ts +5 -12
  36. package/dist/gc/garbageCollection.d.ts.map +1 -1
  37. package/dist/gc/garbageCollection.js +45 -29
  38. package/dist/gc/garbageCollection.js.map +1 -1
  39. package/dist/gc/gcDefinitions.d.ts +27 -6
  40. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  41. package/dist/gc/gcDefinitions.js.map +1 -1
  42. package/dist/gc/gcHelpers.d.ts +5 -4
  43. package/dist/gc/gcHelpers.d.ts.map +1 -1
  44. package/dist/gc/gcHelpers.js +14 -2
  45. package/dist/gc/gcHelpers.js.map +1 -1
  46. package/dist/gc/gcTelemetry.d.ts +14 -4
  47. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  48. package/dist/gc/gcTelemetry.js +24 -21
  49. package/dist/gc/gcTelemetry.js.map +1 -1
  50. package/dist/gc/index.d.ts +2 -2
  51. package/dist/gc/index.d.ts.map +1 -1
  52. package/dist/gc/index.js +2 -2
  53. package/dist/gc/index.js.map +1 -1
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/{alpha.d.ts → legacy.d.ts} +3 -1
  59. package/dist/metadata.d.ts +2 -2
  60. package/dist/metadata.d.ts.map +1 -1
  61. package/dist/metadata.js.map +1 -1
  62. package/dist/opLifecycle/batchManager.d.ts +0 -1
  63. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  64. package/dist/opLifecycle/batchManager.js +0 -10
  65. package/dist/opLifecycle/batchManager.js.map +1 -1
  66. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  67. package/dist/opLifecycle/opDecompressor.js +6 -6
  68. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  69. package/dist/opLifecycle/opGroupingManager.js +2 -2
  70. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  71. package/dist/opLifecycle/opSplitter.js +1 -1
  72. package/dist/opLifecycle/opSplitter.js.map +1 -1
  73. package/dist/opLifecycle/outbox.d.ts +0 -4
  74. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  75. package/dist/opLifecycle/outbox.js +2 -34
  76. package/dist/opLifecycle/outbox.js.map +1 -1
  77. package/dist/packageVersion.d.ts +1 -1
  78. package/dist/packageVersion.js +1 -1
  79. package/dist/packageVersion.js.map +1 -1
  80. package/dist/pendingStateManager.d.ts +3 -2
  81. package/dist/pendingStateManager.d.ts.map +1 -1
  82. package/dist/pendingStateManager.js +17 -10
  83. package/dist/pendingStateManager.js.map +1 -1
  84. package/dist/public.d.ts +3 -0
  85. package/dist/scheduleManager.d.ts +1 -1
  86. package/dist/scheduleManager.d.ts.map +1 -1
  87. package/dist/scheduleManager.js.map +1 -1
  88. package/dist/summary/documentSchema.d.ts +3 -1
  89. package/dist/summary/documentSchema.d.ts.map +1 -1
  90. package/dist/summary/documentSchema.js +34 -16
  91. package/dist/summary/documentSchema.js.map +1 -1
  92. package/dist/summary/orderedClientElection.d.ts +1 -1
  93. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  94. package/dist/summary/orderedClientElection.js.map +1 -1
  95. package/dist/summary/summarizer.d.ts +1 -2
  96. package/dist/summary/summarizer.d.ts.map +1 -1
  97. package/dist/summary/summarizer.js.map +1 -1
  98. package/dist/summary/summarizerClientElection.d.ts +1 -1
  99. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  100. package/dist/summary/summarizerClientElection.js.map +1 -1
  101. package/dist/summary/summarizerHeuristics.d.ts +1 -1
  102. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  103. package/dist/summary/summarizerHeuristics.js.map +1 -1
  104. package/dist/summary/summarizerNode/summarizerNode.d.ts +4 -3
  105. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  106. package/dist/summary/summarizerNode/summarizerNode.js +4 -10
  107. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  108. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -3
  109. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  110. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  111. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +1 -2
  112. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  113. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +2 -9
  114. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  115. package/dist/summary/summarizerTypes.d.ts +2 -3
  116. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  117. package/dist/summary/summarizerTypes.js.map +1 -1
  118. package/dist/summary/summaryCollection.d.ts +1 -1
  119. package/dist/summary/summaryCollection.d.ts.map +1 -1
  120. package/dist/summary/summaryCollection.js.map +1 -1
  121. package/dist/summary/summaryGenerator.d.ts +1 -2
  122. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  123. package/dist/summary/summaryGenerator.js +3 -2
  124. package/dist/summary/summaryGenerator.js.map +1 -1
  125. package/dist/summary/summaryManager.d.ts.map +1 -1
  126. package/dist/summary/summaryManager.js.map +1 -1
  127. package/{dist/beta.d.ts → internal.d.ts} +2 -0
  128. package/{lib/beta.d.ts → legacy.d.ts} +2 -0
  129. package/lib/batchTracker.d.ts +1 -1
  130. package/lib/batchTracker.d.ts.map +1 -1
  131. package/lib/batchTracker.js.map +1 -1
  132. package/lib/channelCollection.d.ts +5 -3
  133. package/lib/channelCollection.d.ts.map +1 -1
  134. package/lib/channelCollection.js +69 -17
  135. package/lib/channelCollection.js.map +1 -1
  136. package/lib/connectionTelemetry.d.ts +1 -1
  137. package/lib/connectionTelemetry.d.ts.map +1 -1
  138. package/lib/connectionTelemetry.js +49 -0
  139. package/lib/connectionTelemetry.js.map +1 -1
  140. package/lib/containerRuntime.d.ts +17 -27
  141. package/lib/containerRuntime.d.ts.map +1 -1
  142. package/lib/containerRuntime.js +174 -143
  143. package/lib/containerRuntime.js.map +1 -1
  144. package/lib/dataStore.d.ts +1 -1
  145. package/lib/dataStore.d.ts.map +1 -1
  146. package/lib/dataStore.js +1 -1
  147. package/lib/dataStore.js.map +1 -1
  148. package/lib/dataStoreContext.d.ts.map +1 -1
  149. package/lib/dataStoreContext.js +1 -0
  150. package/lib/dataStoreContext.js.map +1 -1
  151. package/lib/dataStoreContexts.d.ts +2 -0
  152. package/lib/dataStoreContexts.d.ts.map +1 -1
  153. package/lib/dataStoreContexts.js +7 -0
  154. package/lib/dataStoreContexts.js.map +1 -1
  155. package/lib/deltaManagerSummarizerProxy.d.ts +18 -6
  156. package/lib/deltaManagerSummarizerProxy.d.ts.map +1 -1
  157. package/lib/deltaManagerSummarizerProxy.js +36 -18
  158. package/lib/deltaManagerSummarizerProxy.js.map +1 -1
  159. package/lib/deltaScheduler.d.ts +1 -1
  160. package/lib/deltaScheduler.d.ts.map +1 -1
  161. package/lib/deltaScheduler.js.map +1 -1
  162. package/lib/gc/garbageCollection.d.ts +5 -12
  163. package/lib/gc/garbageCollection.d.ts.map +1 -1
  164. package/lib/gc/garbageCollection.js +47 -31
  165. package/lib/gc/garbageCollection.js.map +1 -1
  166. package/lib/gc/gcDefinitions.d.ts +27 -6
  167. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  168. package/lib/gc/gcDefinitions.js.map +1 -1
  169. package/lib/gc/gcHelpers.d.ts +5 -4
  170. package/lib/gc/gcHelpers.d.ts.map +1 -1
  171. package/lib/gc/gcHelpers.js +12 -1
  172. package/lib/gc/gcHelpers.js.map +1 -1
  173. package/lib/gc/gcTelemetry.d.ts +14 -4
  174. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  175. package/lib/gc/gcTelemetry.js +24 -21
  176. package/lib/gc/gcTelemetry.js.map +1 -1
  177. package/lib/gc/index.d.ts +2 -2
  178. package/lib/gc/index.d.ts.map +1 -1
  179. package/lib/gc/index.js +1 -1
  180. package/lib/gc/index.js.map +1 -1
  181. package/lib/index.d.ts +2 -2
  182. package/lib/index.d.ts.map +1 -1
  183. package/lib/index.js +1 -1
  184. package/lib/index.js.map +1 -1
  185. package/lib/{alpha.d.ts → legacy.d.ts} +3 -1
  186. package/lib/metadata.d.ts +2 -2
  187. package/lib/metadata.d.ts.map +1 -1
  188. package/lib/metadata.js.map +1 -1
  189. package/lib/opLifecycle/batchManager.d.ts +0 -1
  190. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  191. package/lib/opLifecycle/batchManager.js +0 -10
  192. package/lib/opLifecycle/batchManager.js.map +1 -1
  193. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  194. package/lib/opLifecycle/opDecompressor.js +6 -6
  195. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  196. package/lib/opLifecycle/opGroupingManager.js +2 -2
  197. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  198. package/lib/opLifecycle/opSplitter.js +1 -1
  199. package/lib/opLifecycle/opSplitter.js.map +1 -1
  200. package/lib/opLifecycle/outbox.d.ts +0 -4
  201. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  202. package/lib/opLifecycle/outbox.js +2 -34
  203. package/lib/opLifecycle/outbox.js.map +1 -1
  204. package/lib/packageVersion.d.ts +1 -1
  205. package/lib/packageVersion.js +1 -1
  206. package/lib/packageVersion.js.map +1 -1
  207. package/lib/pendingStateManager.d.ts +3 -2
  208. package/lib/pendingStateManager.d.ts.map +1 -1
  209. package/lib/pendingStateManager.js +18 -11
  210. package/lib/pendingStateManager.js.map +1 -1
  211. package/lib/public.d.ts +3 -0
  212. package/lib/scheduleManager.d.ts +1 -1
  213. package/lib/scheduleManager.d.ts.map +1 -1
  214. package/lib/scheduleManager.js.map +1 -1
  215. package/lib/summary/documentSchema.d.ts +3 -1
  216. package/lib/summary/documentSchema.d.ts.map +1 -1
  217. package/lib/summary/documentSchema.js +34 -16
  218. package/lib/summary/documentSchema.js.map +1 -1
  219. package/lib/summary/orderedClientElection.d.ts +1 -1
  220. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  221. package/lib/summary/orderedClientElection.js +1 -1
  222. package/lib/summary/orderedClientElection.js.map +1 -1
  223. package/lib/summary/summarizer.d.ts +1 -2
  224. package/lib/summary/summarizer.d.ts.map +1 -1
  225. package/lib/summary/summarizer.js.map +1 -1
  226. package/lib/summary/summarizerClientElection.d.ts +1 -1
  227. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  228. package/lib/summary/summarizerClientElection.js.map +1 -1
  229. package/lib/summary/summarizerHeuristics.d.ts +1 -1
  230. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  231. package/lib/summary/summarizerHeuristics.js.map +1 -1
  232. package/lib/summary/summarizerNode/summarizerNode.d.ts +4 -3
  233. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  234. package/lib/summary/summarizerNode/summarizerNode.js +4 -10
  235. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  236. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +2 -3
  237. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  238. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  239. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +1 -2
  240. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  241. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +2 -9
  242. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  243. package/lib/summary/summarizerTypes.d.ts +2 -3
  244. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  245. package/lib/summary/summarizerTypes.js.map +1 -1
  246. package/lib/summary/summaryCollection.d.ts +1 -1
  247. package/lib/summary/summaryCollection.d.ts.map +1 -1
  248. package/lib/summary/summaryCollection.js.map +1 -1
  249. package/lib/summary/summaryGenerator.d.ts +1 -2
  250. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  251. package/lib/summary/summaryGenerator.js +4 -3
  252. package/lib/summary/summaryGenerator.js.map +1 -1
  253. package/lib/summary/summaryManager.d.ts.map +1 -1
  254. package/lib/summary/summaryManager.js +1 -1
  255. package/lib/summary/summaryManager.js.map +1 -1
  256. package/package.json +33 -57
  257. package/src/batchTracker.ts +1 -2
  258. package/src/channelCollection.ts +87 -35
  259. package/src/connectionTelemetry.ts +58 -3
  260. package/src/containerRuntime.ts +214 -222
  261. package/src/dataStore.ts +5 -2
  262. package/src/dataStoreContext.ts +1 -0
  263. package/src/dataStoreContexts.ts +13 -2
  264. package/src/deltaManagerSummarizerProxy.ts +43 -21
  265. package/src/deltaScheduler.ts +1 -2
  266. package/src/gc/garbageCollection.ts +64 -42
  267. package/src/gc/gcDefinitions.ts +22 -10
  268. package/src/gc/gcHelpers.ts +14 -1
  269. package/src/gc/gcTelemetry.ts +57 -50
  270. package/src/gc/index.ts +2 -1
  271. package/src/index.ts +2 -0
  272. package/src/metadata.ts +2 -2
  273. package/src/opLifecycle/README.md +4 -4
  274. package/src/opLifecycle/batchManager.ts +0 -14
  275. package/src/opLifecycle/opDecompressor.ts +12 -6
  276. package/src/opLifecycle/opGroupingManager.ts +2 -2
  277. package/src/opLifecycle/opSplitter.ts +1 -1
  278. package/src/opLifecycle/outbox.ts +2 -49
  279. package/src/packageVersion.ts +1 -1
  280. package/src/pendingStateManager.ts +28 -15
  281. package/src/scheduleManager.ts +1 -1
  282. package/src/summary/documentSchema.ts +52 -18
  283. package/src/summary/orderedClientElection.ts +5 -2
  284. package/src/summary/summarizer.ts +1 -1
  285. package/src/summary/summarizerClientElection.ts +1 -1
  286. package/src/summary/summarizerHeuristics.ts +1 -1
  287. package/src/summary/summarizerNode/summarizerNode.ts +3 -12
  288. package/src/summary/summarizerNode/summarizerNodeUtils.ts +2 -3
  289. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +1 -10
  290. package/src/summary/summarizerTypes.ts +5 -3
  291. package/src/summary/summaryCollection.ts +1 -1
  292. package/src/summary/summaryGenerator.ts +19 -8
  293. package/src/summary/summaryManager.ts +5 -2
@@ -7,6 +7,7 @@ import { Trace, TypedEventEmitter } from "@fluid-internal/client-utils";
7
7
  import {
8
8
  AttachState,
9
9
  IAudience,
10
+ ISelf,
10
11
  ICriticalContainerError,
11
12
  IDeltaManager,
12
13
  } from "@fluidframework/container-definitions";
@@ -17,6 +18,7 @@ import {
17
18
  ILoader,
18
19
  IRuntime,
19
20
  LoaderHeader,
21
+ type IAudienceEvents,
20
22
  } from "@fluidframework/container-definitions/internal";
21
23
  import {
22
24
  IContainerRuntime,
@@ -99,11 +101,9 @@ import {
99
101
  responseToException,
100
102
  seqFromTree,
101
103
  } from "@fluidframework/runtime-utils/internal";
104
+ import type { ITelemetryGenericEventExt } from "@fluidframework/telemetry-utils/internal";
102
105
  import {
103
- type ITelemetryGenericEventExt,
104
106
  ITelemetryLoggerExt,
105
- } from "@fluidframework/telemetry-utils";
106
- import {
107
107
  DataCorruptionError,
108
108
  DataProcessingError,
109
109
  GenericError,
@@ -150,7 +150,7 @@ import {
150
150
  type OutboundContainerRuntimeMessage,
151
151
  type UnknownContainerRuntimeMessage,
152
152
  } from "./messageTypes.js";
153
- import { IBatchMetadata, IIdAllocationMetadata } from "./metadata.js";
153
+ import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js";
154
154
  import {
155
155
  BatchMessage,
156
156
  IBatch,
@@ -161,7 +161,6 @@ import {
161
161
  OpSplitter,
162
162
  Outbox,
163
163
  RemoteMessageProcessor,
164
- getLongStack,
165
164
  } from "./opLifecycle/index.js";
166
165
  import { pkgVersion } from "./packageVersion.js";
167
166
  import {
@@ -461,16 +460,6 @@ export interface IContainerRuntimeOptions {
461
460
  */
462
461
  readonly enableRuntimeIdCompressor?: IdCompressorMode;
463
462
 
464
- /**
465
- * If enabled, the runtime will block all attempts to send an op inside the
466
- * {@link ContainerRuntime#ensureNoDataModelChanges} callback. The callback is used by
467
- * {@link @fluidframework/shared-object-base#SharedObjectCore} for event handlers so enabling this
468
- * will disallow modifying DDSes while handling DDS events.
469
- *
470
- * By default, the feature is disabled. If enabled from options, the `Fluid.ContainerRuntime.DisableOpReentryCheck`
471
- * can be used to disable it at runtime.
472
- */
473
- readonly enableOpReentryCheck?: boolean;
474
463
  /**
475
464
  * If enabled, the runtime will group messages within a batch into a single
476
465
  * message to be sent to the service.
@@ -493,6 +482,11 @@ export interface IContainerRuntimeOptions {
493
482
  readonly explicitSchemaControl?: boolean;
494
483
  }
495
484
 
485
+ /**
486
+ * Error responses when requesting a deleted object will have this header set to true
487
+ * @alpha
488
+ */
489
+ export const DeletedResponseHeaderKey = "wasDeleted";
496
490
  /**
497
491
  * Tombstone error responses will have this header set to true
498
492
  * @alpha
@@ -666,11 +660,13 @@ type MessageWithContext =
666
660
  message: InboundSequencedContainerRuntimeMessage;
667
661
  modernRuntimeMessage: true;
668
662
  local: boolean;
663
+ savedOp?: boolean;
669
664
  }
670
665
  | {
671
666
  message: InboundSequencedContainerRuntimeMessageOrSystemMessage;
672
667
  modernRuntimeMessage: false;
673
668
  local: boolean;
669
+ savedOp?: boolean;
674
670
  };
675
671
 
676
672
  const summarizerRequestUrl = "_summarizer";
@@ -805,7 +801,6 @@ export class ContainerRuntime
805
801
  maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
806
802
  enableRuntimeIdCompressor,
807
803
  chunkSizeInBytes = defaultChunkSizeInBytes,
808
- enableOpReentryCheck = false,
809
804
  enableGroupedBatching = false,
810
805
  explicitSchemaControl = false,
811
806
  } = runtimeOptions;
@@ -852,9 +847,9 @@ export class ContainerRuntime
852
847
 
853
848
  // Verify summary runtime sequence number matches protocol sequence number.
854
849
  const runtimeSequenceNumber = messageAtLastSummary?.sequenceNumber;
850
+ const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
855
851
  // When we load with pending state, we reuse an old snapshot so we don't expect these numbers to match
856
852
  if (!context.pendingLocalState && runtimeSequenceNumber !== undefined) {
857
- const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
858
853
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
859
854
  if (
860
855
  loadSequenceNumberVerification !== "bypass" &&
@@ -914,7 +909,16 @@ export class ContainerRuntime
914
909
 
915
910
  // This is the only exception to the rule above - we have proper plumbing to load ID compressor on schema change
916
911
  // event. It is loaded async (relative to op processing), so this conversion is only safe for off -> delayed conversion!
917
- if (idCompressorMode === undefined && desiredIdCompressorMode === "delayed") {
912
+ // Clients do not expect ID compressor ops unless ID compressor is On for them, and that could be achieved only through
913
+ // explicit schema change, i.e. only if explicitSchemaControl is on.
914
+ // Note: it would be better if we throw on combination of options (explicitSchemaControl = off, desiredIdCompressorMode === "delayed")
915
+ // that is not supported. But our service tests are oblivious to these problems and throwing here will cause a ton of failures
916
+ // We ignored incompatible ID compressor changes from the start (they were sticky), so that's not a new problem being introduced...
917
+ if (
918
+ idCompressorMode === undefined &&
919
+ desiredIdCompressorMode === "delayed" &&
920
+ explicitSchemaControl
921
+ ) {
918
922
  idCompressorMode = desiredIdCompressorMode;
919
923
  }
920
924
  } else {
@@ -973,6 +977,7 @@ export class ContainerRuntime
973
977
 
974
978
  const documentSchemaController = new DocumentsSchemaController(
975
979
  existing,
980
+ protocolSequenceNumber,
976
981
  metadata?.documentSchema,
977
982
  {
978
983
  explicitSchemaControl,
@@ -1008,7 +1013,6 @@ export class ContainerRuntime
1008
1013
  chunkSizeInBytes,
1009
1014
  // Requires<> drops undefined from IdCompressorType
1010
1015
  enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
1011
- enableOpReentryCheck,
1012
1016
  enableGroupedBatching,
1013
1017
  explicitSchemaControl,
1014
1018
  },
@@ -1101,7 +1105,17 @@ export class ContainerRuntime
1101
1105
  return this._getAttachState();
1102
1106
  }
1103
1107
 
1104
- public get documentSchema() {
1108
+ /**
1109
+ * Current session schema - defines what options are on & off.
1110
+ * It's overlap of document schema (controlled by summary & ops) and options controlling this session.
1111
+ * For example, document schema might have compression ON, but feature gates / runtime options turn it Off.
1112
+ * In such case it will be off in session schema (i.e. this session should not use compression), but this client
1113
+ * has to deal with compressed ops as other clients might send them.
1114
+ * And in reverse, session schema can have compression Off, but feature gates / runtime options want it On.
1115
+ * In such case it will be off in session schema, however this client will propose change to schema, and once / if
1116
+ * this op rountrips, compression will be On. Client can't send compressed ops until it's change in schema.
1117
+ */
1118
+ public get sessionSchema() {
1105
1119
  return this.documentsSchemaController.sessionSchema.runtime;
1106
1120
  }
1107
1121
 
@@ -1114,11 +1128,11 @@ export class ContainerRuntime
1114
1128
  // Id Compressor serializes final state (see getPendingLocalState()). As result, it needs to skip all ops that preceeded that state
1115
1129
  // (such ops will be marked by Loader layer as savedOp === true)
1116
1130
  // That said, in "delayed" mode it's possible that Id Compressor was never initialized before getPendingLocalState() is called.
1117
- // In such case we have to process all ops, including those marked with saveOp === true.
1131
+ // In such case we have to process all ops, including those marked with savedOp === true.
1118
1132
  private readonly skipSavedCompressorOps: boolean;
1119
1133
 
1120
1134
  public get idCompressorMode() {
1121
- return this.documentSchema.idCompressorMode;
1135
+ return this.sessionSchema.idCompressorMode;
1122
1136
  }
1123
1137
  /**
1124
1138
  * See IContainerRuntimeBase.idCompressor() for details.
@@ -1201,14 +1215,6 @@ export class ContainerRuntime
1201
1215
 
1202
1216
  private ensureNoDataModelChangesCalls = 0;
1203
1217
 
1204
- /**
1205
- * Tracks the number of detected reentrant ops to report,
1206
- * in order to self-throttle the telemetry events.
1207
- *
1208
- * This should be removed as part of ADO:2322
1209
- */
1210
- private opReentryCallsToReport = 5;
1211
-
1212
1218
  /**
1213
1219
  * Invokes the given callback and expects that no ops are submitted
1214
1220
  * until execution finishes. If an op is submitted, an error will be raised.
@@ -1242,16 +1248,8 @@ export class ContainerRuntime
1242
1248
 
1243
1249
  private dirtyContainer: boolean;
1244
1250
  private emitDirtyDocumentEvent = true;
1245
- private readonly enableOpReentryCheck: boolean;
1246
1251
  private readonly disableAttachReorder: boolean | undefined;
1247
1252
  private readonly closeSummarizerDelayMs: number;
1248
- /**
1249
- * If true, summary generated is validate before uploading it to the server. With single commit summaries,
1250
- * summaries will be accepted once uploaded, so they should be validated before upload. However, this can
1251
- * currently be controlled via a feature flag as its a new functionality.
1252
- */
1253
- private readonly validateSummaryBeforeUpload: boolean;
1254
-
1255
1253
  private readonly defaultTelemetrySignalSampleCount = 100;
1256
1254
  private readonly _perfSignalData: IPerfSignalReport = {
1257
1255
  signalsLost: 0,
@@ -1408,7 +1406,7 @@ export class ContainerRuntime
1408
1406
  // If it's not in the list, then we will need to either use no compression, or fallback to some other (supported by format)
1409
1407
  // compression.
1410
1408
  const compressionOptions: ICompressionRuntimeOptions = {
1411
- minimumBatchSizeInBytes: this.documentSchema.compressionLz4
1409
+ minimumBatchSizeInBytes: this.sessionSchema.compressionLz4
1412
1410
  ? runtimeOptions.compressionOptions.minimumBatchSizeInBytes
1413
1411
  : Number.POSITIVE_INFINITY,
1414
1412
  compressionAlgorithm: CompressionAlgorithms.lz4,
@@ -1526,14 +1524,6 @@ export class ContainerRuntime
1526
1524
  this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
1527
1525
  }
1528
1526
 
1529
- const disableOpReentryCheck = this.mc.config.getBoolean(
1530
- "Fluid.ContainerRuntime.DisableOpReentryCheck",
1531
- );
1532
- this.enableOpReentryCheck =
1533
- runtimeOptions.enableOpReentryCheck === true &&
1534
- // Allow for a break-glass config to override the options
1535
- disableOpReentryCheck !== true;
1536
-
1537
1527
  this.summariesDisabled = this.isSummariesDisabled();
1538
1528
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
1539
1529
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
@@ -1630,22 +1620,7 @@ export class ContainerRuntime
1630
1620
  getSummaryForDatastores(baseSnapshot, metadata),
1631
1621
  parentContext,
1632
1622
  this.mc.logger,
1633
- (
1634
- path: string,
1635
- reason: "Loaded" | "Changed",
1636
- timestampMs?: number,
1637
- packagePath?: readonly string[],
1638
- request?: IRequest,
1639
- headerData?: RuntimeHeaderData,
1640
- ) =>
1641
- this.garbageCollector.nodeUpdated(
1642
- path,
1643
- reason,
1644
- timestampMs,
1645
- packagePath,
1646
- request,
1647
- headerData,
1648
- ),
1623
+ (props) => this.garbageCollector.nodeUpdated(props),
1649
1624
  (path: string) => this.garbageCollector.isNodeDeleted(path),
1650
1625
  new Map<string, string>(dataStoreAliasMap),
1651
1626
  async (runtime: ChannelCollection) => provideEntryPoint,
@@ -1668,7 +1643,10 @@ export class ContainerRuntime
1668
1643
  }
1669
1644
  },
1670
1645
  blobRequested: (blobPath: string) =>
1671
- this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
1646
+ this.garbageCollector.nodeUpdated({
1647
+ node: { type: "Blob", path: blobPath },
1648
+ reason: "Loaded",
1649
+ }),
1672
1650
  isBlobDeleted: (blobPath: string) => this.garbageCollector.isNodeDeleted(blobPath),
1673
1651
  runtime: this,
1674
1652
  stashedBlobs: pendingRuntimeState?.pendingAttachmentBlobs,
@@ -1734,16 +1712,37 @@ export class ContainerRuntime
1734
1712
  this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
1735
1713
  });
1736
1714
 
1737
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1738
- this._audience = audience!;
1715
+ this._audience = audience;
1716
+ if (audience.getSelf === undefined) {
1717
+ // back-compat, added in 2.0 RC3.
1718
+ // Purpose: deal with cases when we run against old loader that does not have newly added capabilities
1719
+ audience.getSelf = () => {
1720
+ const clientId = this._getClientId();
1721
+ return clientId === undefined
1722
+ ? undefined
1723
+ : ({
1724
+ clientId,
1725
+ client: audience.getMember(clientId),
1726
+ } satisfies ISelf);
1727
+ };
1728
+
1729
+ let oldClientId = this.clientId;
1730
+ this.on("connected", () => {
1731
+ const clientId = this.clientId;
1732
+ assert(clientId !== undefined, "can't be undefined");
1733
+ (audience as unknown as TypedEventEmitter<IAudienceEvents>).emit(
1734
+ "selfChanged",
1735
+ { clientId: oldClientId },
1736
+ { clientId, client: audience.getMember(clientId) },
1737
+ );
1738
+ oldClientId = clientId;
1739
+ });
1740
+ }
1739
1741
 
1740
1742
  const closeSummarizerDelayOverride = this.mc.config.getNumber(
1741
1743
  "Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
1742
1744
  );
1743
1745
  this.closeSummarizerDelayMs = closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
1744
- this.validateSummaryBeforeUpload =
1745
- this.mc.config.getBoolean("Fluid.Summarizer.ValidateSummaryBeforeUpload") ?? false;
1746
-
1747
1746
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
1748
1747
 
1749
1748
  this.dirtyContainer =
@@ -1855,9 +1854,9 @@ export class ContainerRuntime
1855
1854
  options: JSON.stringify(runtimeOptions),
1856
1855
  idCompressorModeMetadata: metadata?.documentSchema?.runtime?.idCompressorMode,
1857
1856
  idCompressorMode: this.idCompressorMode,
1857
+ sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
1858
1858
  featureGates: JSON.stringify({
1859
1859
  ...featureGatesForTelemetry,
1860
- disableOpReentryCheck,
1861
1860
  disableChunking,
1862
1861
  disableAttachReorder: this.disableAttachReorder,
1863
1862
  disablePartialFlush,
@@ -1865,6 +1864,7 @@ export class ContainerRuntime
1865
1864
  }),
1866
1865
  telemetryDocumentId: this.telemetryDocumentId,
1867
1866
  groupedBatchingEnabled: this.groupedBatchingEnabled,
1867
+ initialSequenceNumber: this.deltaManager.initialSequenceNumber,
1868
1868
  });
1869
1869
 
1870
1870
  ReportOpPerfTelemetry(this.clientId, this.deltaManager, this, this.logger);
@@ -1887,6 +1887,11 @@ export class ContainerRuntime
1887
1887
  }
1888
1888
 
1889
1889
  public onSchemaChange(schema: IDocumentSchemaCurrent) {
1890
+ this.logger.sendTelemetryEvent({
1891
+ eventName: "SchemaChangeAccept",
1892
+ sessionRuntimeSchema: JSON.stringify(schema),
1893
+ });
1894
+
1890
1895
  // Most of the settings will be picked up only by new sessions (i.e. after reload).
1891
1896
  // We can make it better in the future (i.e. start to use op compression right away), but for simplicity
1892
1897
  // this is not done.
@@ -1936,9 +1941,9 @@ export class ContainerRuntime
1936
1941
  this.idCompressorMode === "on" ||
1937
1942
  (this.idCompressorMode === "delayed" && this.connected)
1938
1943
  ) {
1944
+ this._idCompressor = await this.createIdCompressor();
1939
1945
  // This is called from loadRuntime(), long before we process any ops, so there should be no ops accumulated yet.
1940
1946
  assert(this.pendingIdCompressorOps.length === 0, 0x8ec /* no pending ops */);
1941
- this._idCompressor = await this.createIdCompressor();
1942
1947
  }
1943
1948
 
1944
1949
  await this.garbageCollector.initializeBaseState();
@@ -2409,12 +2414,14 @@ export class ContainerRuntime
2409
2414
  ) {
2410
2415
  this._loadIdCompressor = this.createIdCompressor()
2411
2416
  .then((compressor) => {
2412
- this._idCompressor = compressor;
2413
2417
  // Finalize any ranges we received while the compressor was turned off.
2414
- for (const range of this.pendingIdCompressorOps) {
2415
- this._idCompressor.finalizeCreationRange(range);
2416
- }
2418
+ const ops = this.pendingIdCompressorOps;
2417
2419
  this.pendingIdCompressorOps = [];
2420
+ for (const range of ops) {
2421
+ compressor.finalizeCreationRange(range);
2422
+ }
2423
+ assert(this.pendingIdCompressorOps.length === 0, "No new ops added");
2424
+ this._idCompressor = compressor;
2418
2425
  })
2419
2426
  .catch((error) => {
2420
2427
  this.logger.sendErrorEvent({ eventName: "IdCompressorDelayedLoad" }, error);
@@ -2425,6 +2432,11 @@ export class ContainerRuntime
2425
2432
  }
2426
2433
 
2427
2434
  public setConnectionState(connected: boolean, clientId?: string) {
2435
+ // Validate we have consistent state
2436
+ const currentClientId = this._audience.getSelf()?.clientId;
2437
+ assert(clientId === currentClientId, "input clientId does not match Audience");
2438
+ assert(this.clientId === currentClientId, "this.clientId does not match Audience");
2439
+
2428
2440
  if (connected && this.idCompressorMode === "delayed") {
2429
2441
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
2430
2442
  this.loadIdCompressor();
@@ -2538,21 +2550,28 @@ export class ContainerRuntime
2538
2550
  // We do not need to make a deep copy. Each layer will just replace message.contents itself,
2539
2551
  // but will not modify the contents object (likely it will replace it on the message).
2540
2552
  const messageCopy = { ...messageArg };
2553
+ const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2541
2554
  for (const message of this.remoteMessageProcessor.process(messageCopy)) {
2542
- if (modernRuntimeMessage) {
2543
- this.processCore({
2544
- // Cast it since we expect it to be this based on modernRuntimeMessage computation above.
2545
- // There is nothing really ensuring that anytime original message.type is Operation that
2546
- // the result messages will be so. In the end modern bool being true only directs to
2547
- // throw error if ultimately unrecognized without compat details saying otherwise.
2548
- message: message as InboundSequencedContainerRuntimeMessage,
2549
- local,
2550
- modernRuntimeMessage,
2551
- });
2552
- } else {
2553
- // Unrecognized message will be ignored.
2554
- this.processCore({ message, local, modernRuntimeMessage });
2555
- }
2555
+ const msg: MessageWithContext = modernRuntimeMessage
2556
+ ? {
2557
+ // Cast it since we expect it to be this based on modernRuntimeMessage computation above.
2558
+ // There is nothing really ensuring that anytime original message.type is Operation that
2559
+ // the result messages will be so. In the end modern bool being true only directs to
2560
+ // throw error if ultimately unrecognized without compat details saying otherwise.
2561
+ message: message as InboundSequencedContainerRuntimeMessage,
2562
+ local,
2563
+ modernRuntimeMessage,
2564
+ }
2565
+ : // Unrecognized message will be ignored.
2566
+ {
2567
+ message,
2568
+ local,
2569
+ modernRuntimeMessage,
2570
+ };
2571
+ msg.savedOp = savedOp;
2572
+
2573
+ // ensure that we observe any re-entrancy, and if needed, rebase ops
2574
+ this.ensureNoDataModelChanges(() => this.processCore(msg));
2556
2575
  }
2557
2576
  }
2558
2577
 
@@ -2577,7 +2596,7 @@ export class ContainerRuntime
2577
2596
  // 2) this.resetReconnectCount() below
2578
2597
  assert(
2579
2598
  message.type !== ContainerMessageType.ChunkedOp,
2580
- "we should never get here with chunked ops",
2599
+ 0x93b /* we should never get here with chunked ops */,
2581
2600
  );
2582
2601
 
2583
2602
  let localOpMetadata: unknown;
@@ -2640,23 +2659,21 @@ export class ContainerRuntime
2640
2659
  // stashed ops flow. The compressor is stashed with these ops already processed.
2641
2660
  // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
2642
2661
  // thus we need to process all the ops.
2643
- if (
2644
- !(
2645
- this.skipSavedCompressorOps &&
2646
- (messageWithContext.message.metadata as IIdAllocationMetadata)?.savedOp ===
2647
- true
2648
- )
2649
- ) {
2662
+ if (!(this.skipSavedCompressorOps && messageWithContext.savedOp === true)) {
2650
2663
  const range = messageWithContext.message.contents;
2651
2664
  // Some other client turned on the id compressor. If we have not turned it on,
2652
2665
  // put it in a pending queue and delay finalization.
2653
2666
  if (this._idCompressor === undefined) {
2654
2667
  assert(
2655
2668
  this.idCompressorMode !== undefined,
2656
- "id compressor should be enabled",
2669
+ 0x93c /* id compressor should be enabled */,
2657
2670
  );
2658
2671
  this.pendingIdCompressorOps.push(range);
2659
2672
  } else {
2673
+ assert(
2674
+ this.pendingIdCompressorOps.length === 0,
2675
+ "there should be no pending ops!",
2676
+ );
2660
2677
  this._idCompressor.finalizeCreationRange(range);
2661
2678
  }
2662
2679
  }
@@ -2667,7 +2684,7 @@ export class ContainerRuntime
2667
2684
  case ContainerMessageType.ChunkedOp:
2668
2685
  // From observability POV, we should not exppse the rest of the system (including "op" events on object) to these messages.
2669
2686
  // Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
2670
- assert(false, "should not even get here");
2687
+ assert(false, 0x93d /* should not even get here */);
2671
2688
  case ContainerMessageType.Rejoin:
2672
2689
  break;
2673
2690
  case ContainerMessageType.DocumentSchemaChange:
@@ -2807,9 +2824,9 @@ export class ContainerRuntime
2807
2824
  let checkpoint: IBatchCheckpoint | undefined;
2808
2825
  let result: T;
2809
2826
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
2810
- // Note: we are not touching this.pendingAttachBatch here, for two reasons:
2811
- // 1. It would not help, as we flush attach ops as they become available.
2812
- // 2. There is no way to undo process of data store creation.
2827
+ // Note: we are not touching any batches other than mainBatch here, for two reasons:
2828
+ // 1. It would not help, as other batches are flushed independently from main batch.
2829
+ // 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
2813
2830
  checkpoint = this.outbox.checkpoint().mainBatch;
2814
2831
  }
2815
2832
  try {
@@ -2892,12 +2909,11 @@ export class ContainerRuntime
2892
2909
  "entryPoint must be defined on data store runtime for using getAliasedDataStoreEntryPoint",
2893
2910
  );
2894
2911
  }
2895
- this.garbageCollector.nodeUpdated(
2896
- `/${internalId}`,
2897
- "Loaded",
2898
- undefined /* timestampMs */,
2899
- context.packagePath,
2900
- );
2912
+ this.garbageCollector.nodeUpdated({
2913
+ node: { type: "DataStore", path: `/${internalId}` },
2914
+ reason: "Loaded",
2915
+ packagePath: context.packagePath,
2916
+ });
2901
2917
  return channel.entryPoint;
2902
2918
  }
2903
2919
 
@@ -3083,7 +3099,7 @@ export class ContainerRuntime
3083
3099
  if (idRange !== undefined) {
3084
3100
  assert(
3085
3101
  idRange.ids === undefined || idRange.ids.firstGenCount === 1,
3086
- "No other ranges should be taken while container is detached.",
3102
+ 0x93e /* No other ranges should be taken while container is detached. */,
3087
3103
  );
3088
3104
  this._idCompressor?.finalizeCreationRange(idRange);
3089
3105
  }
@@ -3392,10 +3408,14 @@ export class ContainerRuntime
3392
3408
  // The summary number for this summary. This will be updated during the summary process, so get it now and
3393
3409
  // use it for all events logged during this summary.
3394
3410
  const summaryNumber = this.nextSummaryNumber;
3411
+ let summaryRefSeqNum: number | undefined;
3395
3412
  const summaryNumberLogger = createChildLogger({
3396
3413
  logger: summaryLogger,
3397
3414
  properties: {
3398
- all: { summaryNumber },
3415
+ all: {
3416
+ summaryNumber,
3417
+ referenceSequenceNumber: () => summaryRefSeqNum,
3418
+ },
3399
3419
  },
3400
3420
  });
3401
3421
 
@@ -3414,7 +3434,7 @@ export class ContainerRuntime
3414
3434
  // If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
3415
3435
  // and it may even be incorrect. So, wait for the container to be saved with a timeout. If the container is not
3416
3436
  // saved within the timeout, check if it should be failed or can continue.
3417
- if (this.validateSummaryBeforeUpload && this.isDirty) {
3437
+ if (this.isDirty) {
3418
3438
  const countBefore = this.pendingMessagesCount;
3419
3439
  // The timeout for waiting for pending ops can be overridden via configurations.
3420
3440
  const pendingOpsTimeout =
@@ -3464,8 +3484,6 @@ export class ContainerRuntime
3464
3484
  "Fluid.ContainerRuntime.SubmitSummary.shouldValidatePreSummaryState",
3465
3485
  ) === true;
3466
3486
 
3467
- let summaryRefSeqNum: number | undefined;
3468
-
3469
3487
  try {
3470
3488
  await this.deltaManager.inbound.pause();
3471
3489
  if (shouldPauseInboundSignal) {
@@ -3483,11 +3501,19 @@ export class ContainerRuntime
3483
3501
  latestSummaryRefSeqNum,
3484
3502
  );
3485
3503
 
3504
+ /**
3505
+ * This was added to validate that the summarizer node tree has the same reference sequence number from the
3506
+ * top running summarizer down to the lowest summarizer node.
3507
+ *
3508
+ * The order of mismatch numbers goes (validate sequence number)-(node sequence number).
3509
+ * Generally the validate sequence number comes from the running summarizer and the node sequence number comes from the
3510
+ * summarizer nodes.
3511
+ */
3486
3512
  if (
3487
3513
  startSummaryResult.invalidNodes > 0 ||
3488
3514
  startSummaryResult.mismatchNumbers.size > 0
3489
3515
  ) {
3490
- summaryLogger.sendErrorEvent({
3516
+ summaryLogger.sendTelemetryEvent({
3491
3517
  eventName: "LatestSummaryRefSeqNumMismatch",
3492
3518
  details: {
3493
3519
  ...startSummaryResult,
@@ -3500,7 +3526,9 @@ export class ContainerRuntime
3500
3526
  stage: "base",
3501
3527
  referenceSequenceNumber: summaryRefSeqNum,
3502
3528
  minimumSequenceNumber,
3503
- error: `Summarizer node state inconsistent with summarizer state.`,
3529
+ error: new LoggingError(
3530
+ `Summarizer node state inconsistent with summarizer state.`,
3531
+ ),
3504
3532
  };
3505
3533
  }
3506
3534
  }
@@ -3550,7 +3578,7 @@ export class ContainerRuntime
3550
3578
  stage: "base",
3551
3579
  referenceSequenceNumber: summaryRefSeqNum,
3552
3580
  minimumSequenceNumber,
3553
- error: continueResult.error,
3581
+ error: new LoggingError(continueResult.error),
3554
3582
  };
3555
3583
  }
3556
3584
 
@@ -3571,39 +3599,38 @@ export class ContainerRuntime
3571
3599
  stage: "base",
3572
3600
  referenceSequenceNumber: summaryRefSeqNum,
3573
3601
  minimumSequenceNumber,
3574
- error,
3602
+ error: wrapError(error, (msg) => new LoggingError(msg)),
3575
3603
  };
3576
3604
  }
3577
3605
 
3578
- // If validateSummaryBeforeUpload is true, validate that the summary generated is correct before uploading.
3579
- if (this.validateSummaryBeforeUpload) {
3580
- // Validate that the summaries generated by summarize nodes is correct.
3581
- const validateResult = this.summarizerNode.validateSummary();
3582
- if (!validateResult.success) {
3583
- const { success, ...loggingProps } = validateResult;
3584
- const error = new RetriableSummaryError(
3585
- validateResult.reason,
3586
- validateResult.retryAfterSeconds,
3587
- { ...loggingProps },
3588
- );
3589
- return {
3590
- stage: "base",
3591
- referenceSequenceNumber: summaryRefSeqNum,
3592
- minimumSequenceNumber,
3593
- error,
3594
- };
3595
- }
3596
-
3597
- const pendingMessagesFailResult = await this.shouldFailSummaryOnPendingOps(
3598
- summaryNumberLogger,
3599
- summaryRefSeqNum,
3600
- minimumSequenceNumber,
3601
- finalAttempt,
3602
- false /* beforeSummaryGeneration */,
3606
+ // Validate that the summary generated by summarizer nodes is correct before uploading.
3607
+ const validateResult = this.summarizerNode.validateSummary();
3608
+ if (!validateResult.success) {
3609
+ const { success, ...loggingProps } = validateResult;
3610
+ const error = new RetriableSummaryError(
3611
+ validateResult.reason,
3612
+ validateResult.retryAfterSeconds,
3613
+ { ...loggingProps },
3603
3614
  );
3604
- if (pendingMessagesFailResult !== undefined) {
3605
- return pendingMessagesFailResult;
3606
- }
3615
+ return {
3616
+ stage: "base",
3617
+ referenceSequenceNumber: summaryRefSeqNum,
3618
+ minimumSequenceNumber,
3619
+ error,
3620
+ };
3621
+ }
3622
+
3623
+ // If there are pending unacked ops, this summary attempt may fail as the uploaded
3624
+ // summary would be eventually inconsistent.
3625
+ const pendingMessagesFailResult = await this.shouldFailSummaryOnPendingOps(
3626
+ summaryNumberLogger,
3627
+ summaryRefSeqNum,
3628
+ minimumSequenceNumber,
3629
+ finalAttempt,
3630
+ false /* beforeSummaryGeneration */,
3631
+ );
3632
+ if (pendingMessagesFailResult !== undefined) {
3633
+ return pendingMessagesFailResult;
3607
3634
  }
3608
3635
 
3609
3636
  const { summary: summaryTree, stats: partialStats } = summarizeResult;
@@ -3644,7 +3671,11 @@ export class ContainerRuntime
3644
3671
 
3645
3672
  continueResult = checkContinue();
3646
3673
  if (!continueResult.continue) {
3647
- return { stage: "generate", ...generateSummaryData, error: continueResult.error };
3674
+ return {
3675
+ stage: "generate",
3676
+ ...generateSummaryData,
3677
+ error: new LoggingError(continueResult.error),
3678
+ };
3648
3679
  }
3649
3680
 
3650
3681
  const summaryContext =
@@ -3667,7 +3698,11 @@ export class ContainerRuntime
3667
3698
  summaryContext,
3668
3699
  );
3669
3700
  } catch (error) {
3670
- return { stage: "generate", ...generateSummaryData, error };
3701
+ return {
3702
+ stage: "generate",
3703
+ ...generateSummaryData,
3704
+ error: wrapError(error, (msg) => new LoggingError(msg)),
3705
+ };
3671
3706
  }
3672
3707
 
3673
3708
  const parent = summaryContext.ackHandle;
@@ -3686,14 +3721,22 @@ export class ContainerRuntime
3686
3721
 
3687
3722
  continueResult = checkContinue();
3688
3723
  if (!continueResult.continue) {
3689
- return { stage: "upload", ...uploadData, error: continueResult.error };
3724
+ return {
3725
+ stage: "upload",
3726
+ ...uploadData,
3727
+ error: new LoggingError(continueResult.error),
3728
+ };
3690
3729
  }
3691
3730
 
3692
3731
  let clientSequenceNumber: number;
3693
3732
  try {
3694
3733
  clientSequenceNumber = this.submitSummaryMessage(summaryMessage, summaryRefSeqNum);
3695
3734
  } catch (error) {
3696
- return { stage: "upload", ...uploadData, error };
3735
+ return {
3736
+ stage: "upload",
3737
+ ...uploadData,
3738
+ error: wrapError(error, (msg) => new LoggingError(msg)),
3739
+ };
3697
3740
  }
3698
3741
 
3699
3742
  const submitData = {
@@ -3704,13 +3747,13 @@ export class ContainerRuntime
3704
3747
  } as const;
3705
3748
 
3706
3749
  try {
3707
- // If validateSummaryBeforeUpload is false, the summary should be validated in this step.
3708
- this.summarizerNode.completeSummary(
3709
- handle,
3710
- !this.validateSummaryBeforeUpload /* validate */,
3711
- );
3750
+ this.summarizerNode.completeSummary(handle);
3712
3751
  } catch (error) {
3713
- return { stage: "upload", ...uploadData, error };
3752
+ return {
3753
+ stage: "upload",
3754
+ ...uploadData,
3755
+ error: wrapError(error, (msg) => new LoggingError(msg)),
3756
+ };
3714
3757
  }
3715
3758
  return submitData;
3716
3759
  } finally {
@@ -3866,7 +3909,6 @@ export class ContainerRuntime
3866
3909
  metadata?: { localId: string; blobId?: string },
3867
3910
  ): void {
3868
3911
  this.verifyNotClosed();
3869
- this.verifyCanSubmitOps();
3870
3912
 
3871
3913
  // There should be no ops in detached container state!
3872
3914
  assert(
@@ -3877,7 +3919,7 @@ export class ContainerRuntime
3877
3919
  assert(
3878
3920
  metadata === undefined ||
3879
3921
  containerRuntimeMessage.type === ContainerMessageType.BlobAttach,
3880
- "metadata",
3922
+ 0x93f /* metadata */,
3881
3923
  );
3882
3924
 
3883
3925
  const serializedContent = JSON.stringify(containerRuntimeMessage);
@@ -3914,6 +3956,14 @@ export class ContainerRuntime
3914
3956
  // on this callback to do actual sending.
3915
3957
  const contents = this.documentsSchemaController.maybeSendSchemaMessage();
3916
3958
  if (contents) {
3959
+ this.logger.sendTelemetryEvent({
3960
+ eventName: "SchemaChangeProposal",
3961
+ refSeq: contents.refSeq,
3962
+ version: contents.version,
3963
+ newRuntimeSchema: JSON.stringify(contents.runtime),
3964
+ sessionRuntimeSchema: JSON.stringify(this.sessionSchema),
3965
+ oldRuntimeSchema: JSON.stringify(this.metadata?.documentSchema?.runtime),
3966
+ });
3917
3967
  const msg: ContainerRuntimeDocumentSchemaMessage = {
3918
3968
  type: ContainerMessageType.DocumentSchemaChange,
3919
3969
  contents,
@@ -3924,33 +3974,7 @@ export class ContainerRuntime
3924
3974
  });
3925
3975
  }
3926
3976
 
3927
- // If this is attach message for new data store, and we are in a batch, send this op out of order
3928
- // Is it safe:
3929
- // Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
3930
- // They become visible only when aliased, or handle to some sub-element of newly created datastore
3931
- // is stored in some DDS, i.e. only after some other op.
3932
- // Why:
3933
- // Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
3934
- // stores are created, causing issues like relay service throttling (too many ops) and catastrophic
3935
- // failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
3936
- // these issues.
3937
- // Cons:
3938
- // 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
3939
- // This change creates new possibility of a lot of newly created data stores never being referenced
3940
- // because client died before it had a change to submit the rest of the ops. This will create more
3941
- // garbage that needs to be collected leveraging GC (Garbage Collection) feature.
3942
- // 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
3943
- // today as rollback can't undo creation of data store. To some extent not sending them is a bigger
3944
- // issue than sending.
3945
- // Please note that this does not change file format, so it can be disabled in the future if this
3946
- // optimization no longer makes sense (for example, batch compression may make it less appealing).
3947
- if (
3948
- this.currentlyBatching() &&
3949
- type === ContainerMessageType.Attach &&
3950
- this.disableAttachReorder !== true
3951
- ) {
3952
- this.outbox.submitAttach(message);
3953
- } else if (type === ContainerMessageType.BlobAttach) {
3977
+ if (type === ContainerMessageType.BlobAttach) {
3954
3978
  // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
3955
3979
  this.outbox.submitBlobAttach(message);
3956
3980
  } else {
@@ -4039,37 +4063,6 @@ export class ContainerRuntime
4039
4063
  }
4040
4064
  }
4041
4065
 
4042
- private verifyCanSubmitOps() {
4043
- if (this.ensureNoDataModelChangesCalls > 0) {
4044
- const errorMessage =
4045
- "Op was submitted from within a `ensureNoDataModelChanges` callback";
4046
- if (this.opReentryCallsToReport > 0) {
4047
- this.mc.logger.sendTelemetryEvent(
4048
- { eventName: "OpReentry" },
4049
- // We need to capture the call stack in order to inspect the source of this usage pattern
4050
- getLongStack(() => new UsageError(errorMessage)),
4051
- );
4052
- this.opReentryCallsToReport--;
4053
- }
4054
-
4055
- // Creating ops while processing ops can lead
4056
- // to undefined behavior and events observed in the wrong order.
4057
- // For example, we have two callbacks registered for a DDS, A and B.
4058
- // Then if on change #1 callback A creates change #2, the invocation flow will be:
4059
- //
4060
- // A because of #1
4061
- // A because of #2
4062
- // B because of #2
4063
- // B because of #1
4064
- //
4065
- // The runtime must enforce op coherence by not allowing ops to be submitted
4066
- // while ops are being processed.
4067
- if (this.enableOpReentryCheck) {
4068
- throw new UsageError(errorMessage);
4069
- }
4070
- }
4071
- }
4072
-
4073
4066
  private reSubmitBatch(batch: IPendingBatchMessage[]) {
4074
4067
  this.orderSequentially(() => {
4075
4068
  for (const message of batch) {
@@ -4234,7 +4227,7 @@ export class ContainerRuntime
4234
4227
 
4235
4228
  return {
4236
4229
  stage: "base",
4237
- error: "summary state stale - Unsupported option 'refreshLatestAck'",
4230
+ error: new LoggingError("summary state stale - Unsupported option 'refreshLatestAck'"),
4238
4231
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4239
4232
  minimumSequenceNumber: this.deltaManager.minimumSequenceNumber,
4240
4233
  };
@@ -4316,10 +4309,9 @@ export class ContainerRuntime
4316
4309
  const getSyncState = (
4317
4310
  pendingAttachmentBlobs?: IPendingBlobs,
4318
4311
  ): IPendingRuntimeState | undefined => {
4319
- const pending = this.pendingStateManager.getLocalState();
4320
- if (pendingAttachmentBlobs === undefined && !this.hasPendingMessages()) {
4321
- return; // no pending state to save
4322
- }
4312
+ const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
4313
+ const sessionExpiryTimerStarted =
4314
+ props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
4323
4315
 
4324
4316
  const pendingIdCompressorState = this._idCompressor?.serialize(true);
4325
4317
 
@@ -4327,7 +4319,7 @@ export class ContainerRuntime
4327
4319
  pending,
4328
4320
  pendingIdCompressorState,
4329
4321
  pendingAttachmentBlobs,
4330
- sessionExpiryTimerStarted: this.garbageCollector.sessionExpiryTimerStarted,
4322
+ sessionExpiryTimerStarted,
4331
4323
  };
4332
4324
  };
4333
4325
  const perfEvent = {
@@ -4418,6 +4410,6 @@ export class ContainerRuntime
4418
4410
  }
4419
4411
 
4420
4412
  private get groupedBatchingEnabled(): boolean {
4421
- return this.documentSchema.opGroupingEnabled === true;
4413
+ return this.sessionSchema.opGroupingEnabled === true;
4422
4414
  }
4423
4415
  }