@fluidframework/container-runtime 2.33.0-333010 → 2.33.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 (180) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +71 -67
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +7 -4
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +38 -12
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +4 -0
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +24 -0
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/compatUtils.d.ts +74 -0
  13. package/dist/compatUtils.d.ts.map +1 -0
  14. package/dist/compatUtils.js +151 -0
  15. package/dist/compatUtils.js.map +1 -0
  16. package/dist/compressionDefinitions.d.ts +39 -0
  17. package/dist/compressionDefinitions.d.ts.map +1 -0
  18. package/dist/compressionDefinitions.js +30 -0
  19. package/dist/compressionDefinitions.js.map +1 -0
  20. package/dist/containerRuntime.d.ts +78 -52
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +141 -54
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +3 -0
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +122 -66
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/deltaManagerProxies.d.ts +55 -12
  29. package/dist/deltaManagerProxies.d.ts.map +1 -1
  30. package/dist/deltaManagerProxies.js +63 -55
  31. package/dist/deltaManagerProxies.js.map +1 -1
  32. package/dist/gc/gcDefinitions.d.ts +2 -0
  33. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  34. package/dist/gc/gcDefinitions.js.map +1 -1
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/legacy.d.ts +1 -0
  40. package/dist/opLifecycle/batchManager.d.ts +1 -1
  41. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  42. package/dist/opLifecycle/batchManager.js +4 -1
  43. package/dist/opLifecycle/batchManager.js.map +1 -1
  44. package/dist/opLifecycle/definitions.d.ts +35 -4
  45. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  46. package/dist/opLifecycle/definitions.js.map +1 -1
  47. package/dist/opLifecycle/index.d.ts +1 -1
  48. package/dist/opLifecycle/index.d.ts.map +1 -1
  49. package/dist/opLifecycle/index.js.map +1 -1
  50. package/dist/opLifecycle/opCompressor.js +2 -2
  51. package/dist/opLifecycle/opCompressor.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.js +3 -3
  53. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  54. package/dist/opLifecycle/opGroupingManager.d.ts +2 -2
  55. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  56. package/dist/opLifecycle/opGroupingManager.js +1 -2
  57. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  58. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  59. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  60. package/dist/opLifecycle/opSerialization.js +4 -2
  61. package/dist/opLifecycle/opSerialization.js.map +1 -1
  62. package/dist/opLifecycle/outbox.d.ts +6 -3
  63. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  64. package/dist/opLifecycle/outbox.js +46 -20
  65. package/dist/opLifecycle/outbox.js.map +1 -1
  66. package/dist/packageVersion.d.ts +1 -1
  67. package/dist/packageVersion.d.ts.map +1 -1
  68. package/dist/packageVersion.js +1 -1
  69. package/dist/packageVersion.js.map +1 -1
  70. package/dist/pendingStateManager.d.ts +36 -7
  71. package/dist/pendingStateManager.d.ts.map +1 -1
  72. package/dist/pendingStateManager.js +83 -16
  73. package/dist/pendingStateManager.js.map +1 -1
  74. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  75. package/dist/runtimeLayerCompatState.js +1 -1
  76. package/dist/runtimeLayerCompatState.js.map +1 -1
  77. package/dist/summary/documentSchema.d.ts +1 -0
  78. package/dist/summary/documentSchema.d.ts.map +1 -1
  79. package/dist/summary/documentSchema.js +2 -0
  80. package/dist/summary/documentSchema.js.map +1 -1
  81. package/lib/blobManager/blobManager.d.ts +7 -4
  82. package/lib/blobManager/blobManager.d.ts.map +1 -1
  83. package/lib/blobManager/blobManager.js +38 -12
  84. package/lib/blobManager/blobManager.js.map +1 -1
  85. package/lib/channelCollection.d.ts +4 -0
  86. package/lib/channelCollection.d.ts.map +1 -1
  87. package/lib/channelCollection.js +24 -0
  88. package/lib/channelCollection.js.map +1 -1
  89. package/lib/compatUtils.d.ts +74 -0
  90. package/lib/compatUtils.d.ts.map +1 -0
  91. package/lib/compatUtils.js +142 -0
  92. package/lib/compatUtils.js.map +1 -0
  93. package/lib/compressionDefinitions.d.ts +39 -0
  94. package/lib/compressionDefinitions.d.ts.map +1 -0
  95. package/lib/compressionDefinitions.js +27 -0
  96. package/lib/compressionDefinitions.js.map +1 -0
  97. package/lib/containerRuntime.d.ts +78 -52
  98. package/lib/containerRuntime.d.ts.map +1 -1
  99. package/lib/containerRuntime.js +143 -56
  100. package/lib/containerRuntime.js.map +1 -1
  101. package/lib/dataStoreContext.d.ts +3 -0
  102. package/lib/dataStoreContext.d.ts.map +1 -1
  103. package/lib/dataStoreContext.js +57 -1
  104. package/lib/dataStoreContext.js.map +1 -1
  105. package/lib/deltaManagerProxies.d.ts +55 -12
  106. package/lib/deltaManagerProxies.d.ts.map +1 -1
  107. package/lib/deltaManagerProxies.js +63 -55
  108. package/lib/deltaManagerProxies.js.map +1 -1
  109. package/lib/gc/gcDefinitions.d.ts +2 -0
  110. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  111. package/lib/gc/gcDefinitions.js.map +1 -1
  112. package/lib/index.d.ts +4 -2
  113. package/lib/index.d.ts.map +1 -1
  114. package/lib/index.js +2 -1
  115. package/lib/index.js.map +1 -1
  116. package/lib/legacy.d.ts +1 -0
  117. package/lib/opLifecycle/batchManager.d.ts +1 -1
  118. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  119. package/lib/opLifecycle/batchManager.js +4 -1
  120. package/lib/opLifecycle/batchManager.js.map +1 -1
  121. package/lib/opLifecycle/definitions.d.ts +35 -4
  122. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  123. package/lib/opLifecycle/definitions.js.map +1 -1
  124. package/lib/opLifecycle/index.d.ts +1 -1
  125. package/lib/opLifecycle/index.d.ts.map +1 -1
  126. package/lib/opLifecycle/index.js.map +1 -1
  127. package/lib/opLifecycle/opCompressor.js +1 -1
  128. package/lib/opLifecycle/opCompressor.js.map +1 -1
  129. package/lib/opLifecycle/opDecompressor.js +1 -1
  130. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  131. package/lib/opLifecycle/opGroupingManager.d.ts +2 -2
  132. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  133. package/lib/opLifecycle/opGroupingManager.js +1 -2
  134. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  135. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  136. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  137. package/lib/opLifecycle/opSerialization.js +4 -2
  138. package/lib/opLifecycle/opSerialization.js.map +1 -1
  139. package/lib/opLifecycle/outbox.d.ts +6 -3
  140. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  141. package/lib/opLifecycle/outbox.js +46 -20
  142. package/lib/opLifecycle/outbox.js.map +1 -1
  143. package/lib/packageVersion.d.ts +1 -1
  144. package/lib/packageVersion.d.ts.map +1 -1
  145. package/lib/packageVersion.js +1 -1
  146. package/lib/packageVersion.js.map +1 -1
  147. package/lib/pendingStateManager.d.ts +36 -7
  148. package/lib/pendingStateManager.d.ts.map +1 -1
  149. package/lib/pendingStateManager.js +84 -17
  150. package/lib/pendingStateManager.js.map +1 -1
  151. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  152. package/lib/runtimeLayerCompatState.js +2 -2
  153. package/lib/runtimeLayerCompatState.js.map +1 -1
  154. package/lib/summary/documentSchema.d.ts +1 -0
  155. package/lib/summary/documentSchema.d.ts.map +1 -1
  156. package/lib/summary/documentSchema.js +2 -0
  157. package/lib/summary/documentSchema.js.map +1 -1
  158. package/lib/tsdoc-metadata.json +1 -1
  159. package/package.json +21 -20
  160. package/src/blobManager/blobManager.ts +48 -15
  161. package/src/channelCollection.ts +27 -0
  162. package/src/compatUtils.ts +211 -0
  163. package/src/compressionDefinitions.ts +47 -0
  164. package/src/containerRuntime.ts +259 -108
  165. package/src/dataStoreContext.ts +82 -2
  166. package/src/deltaManagerProxies.ts +132 -70
  167. package/src/gc/gcDefinitions.ts +2 -0
  168. package/src/index.ts +5 -3
  169. package/src/opLifecycle/batchManager.ts +5 -4
  170. package/src/opLifecycle/definitions.ts +34 -4
  171. package/src/opLifecycle/index.ts +1 -0
  172. package/src/opLifecycle/opCompressor.ts +1 -1
  173. package/src/opLifecycle/opDecompressor.ts +1 -1
  174. package/src/opLifecycle/opGroupingManager.ts +7 -5
  175. package/src/opLifecycle/opSerialization.ts +6 -2
  176. package/src/opLifecycle/outbox.ts +65 -30
  177. package/src/packageVersion.ts +1 -1
  178. package/src/pendingStateManager.ts +135 -21
  179. package/src/runtimeLayerCompatState.ts +5 -2
  180. package/src/summary/documentSchema.ts +3 -0
@@ -10,7 +10,7 @@ import { SummaryType } from "@fluidframework/driver-definitions";
10
10
  import { FetchSource, MessageType } from "@fluidframework/driver-definitions/internal";
11
11
  import { readAndParse } from "@fluidframework/driver-utils/internal";
12
12
  import { FlushMode, FlushModeExperimental, channelsTreeName, gcTreeKey, } from "@fluidframework/runtime-definitions/internal";
13
- import { GCDataBuilder, RequestParser, TelemetryContext, addBlobToSummary, addSummarizeResultToSummary, calculateStats, create404Response, exceptionToResponse, seqFromTree, } from "@fluidframework/runtime-utils/internal";
13
+ import { GCDataBuilder, RequestParser, RuntimeHeaders, TelemetryContext, addBlobToSummary, addSummarizeResultToSummary, calculateStats, create404Response, exceptionToResponse, seqFromTree, } from "@fluidframework/runtime-utils/internal";
14
14
  import { DataCorruptionError, DataProcessingError, extractSafePropertiesFromMessage, GenericError, LoggingError, PerformanceEvent,
15
15
  // eslint-disable-next-line import/no-deprecated
16
16
  TaggedLoggerAdapter, UsageError, createChildLogger, createChildMonitoringContext, createSampledLogger, loggerToMonitoringContext, raiseConnectedEvent, wrapError, tagCodeArtifacts, normalizeError, } from "@fluidframework/telemetry-utils/internal";
@@ -18,22 +18,24 @@ import { v4 as uuid } from "uuid";
18
18
  import { BindBatchTracker } from "./batchTracker.js";
19
19
  import { BlobManager, blobManagerBasePath, blobsTreeName, isBlobPath, loadBlobManagerLoadInfo, } from "./blobManager/index.js";
20
20
  import { ChannelCollection, getSummaryForDatastores, wrapContext, } from "./channelCollection.js";
21
+ import { defaultCompatibilityVersion, getCompatibilityVersionDefaults, isValidCompatVersion, } from "./compatUtils.js";
22
+ import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
21
23
  import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
22
24
  import { ContainerFluidHandleContext } from "./containerHandleContext.js";
23
25
  import { channelToDataStore } from "./dataStore.js";
24
26
  import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
25
- import { DeltaManagerPendingOpsProxy, DeltaManagerSummarizerProxy, } from "./deltaManagerProxies.js";
27
+ import { BaseDeltaManagerProxy, DeltaManagerPendingOpsProxy, DeltaManagerSummarizerProxy, } from "./deltaManagerProxies.js";
26
28
  import { DeltaScheduler } from "./deltaScheduler.js";
27
29
  import { GCNodeType, GarbageCollector, gcGenerationOptionName, } from "./gc/index.js";
28
30
  import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
29
31
  import { ContainerMessageType, } from "./messageTypes.js";
30
- import { DuplicateBatchDetector, ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, serializeOp, } from "./opLifecycle/index.js";
32
+ import { DuplicateBatchDetector, ensureContentsDeserialized, OpCompressor, OpDecompressor, OpGroupingManager, OpSplitter, Outbox, RemoteMessageProcessor, } from "./opLifecycle/index.js";
31
33
  import { pkgVersion } from "./packageVersion.js";
32
34
  import { PendingStateManager, } from "./pendingStateManager.js";
33
35
  import { RunCounter } from "./runCounter.js";
34
36
  import { runtimeCompatDetailsForLoader, validateLoaderCompatibility, } from "./runtimeLayerCompatState.js";
35
37
  import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
36
- import { DocumentsSchemaController, OrderedClientElection, RetriableSummaryError, aliasBlobName, chunksBlobName, recentBatchInfoBlobName, createRootSummarizerNodeWithGC, electedSummarizerBlobName, extractSummaryMetadataMessage, idCompressorBlobName, metadataBlobName, rootHasIsolatedChannels, summarizerClientType, wrapSummaryInChannelsTree, formCreateSummarizerFn, summarizerRequestUrl, SummaryManager, SummarizerClientElection, SummaryCollection, OrderedClientCollection, validateSummaryHeuristicConfiguration, DefaultSummaryConfiguration, isSummariesDisabled, } from "./summary/index.js";
38
+ import { DocumentsSchemaController, OrderedClientElection, RetriableSummaryError, aliasBlobName, chunksBlobName, recentBatchInfoBlobName, createRootSummarizerNodeWithGC, electedSummarizerBlobName, extractSummaryMetadataMessage, idCompressorBlobName, metadataBlobName, rootHasIsolatedChannels, wrapSummaryInChannelsTree, formCreateSummarizerFn, summarizerRequestUrl, SummaryManager, SummarizerClientElection, SummaryCollection, OrderedClientCollection, validateSummaryHeuristicConfiguration, DefaultSummaryConfiguration, isSummariesDisabled, summarizerClientType, } from "./summary/index.js";
37
39
  import { Throttler, formExponentialFn } from "./throttler.js";
38
40
  /**
39
41
  * Creates an error object to be thrown / passed to Container's close fn in case of an unknown message type.
@@ -82,35 +84,12 @@ export const defaultRuntimeHeaderData = {
82
84
  viaHandle: false,
83
85
  allowTombstone: false,
84
86
  };
85
- /**
86
- * Available compression algorithms for op compression.
87
- * @legacy
88
- * @alpha
89
- */
90
- export var CompressionAlgorithms;
91
- (function (CompressionAlgorithms) {
92
- CompressionAlgorithms["lz4"] = "lz4";
93
- })(CompressionAlgorithms || (CompressionAlgorithms = {}));
94
- /**
95
- * @legacy
96
- * @alpha
97
- */
98
- export const disabledCompressionConfig = {
99
- minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
100
- compressionAlgorithm: CompressionAlgorithms.lz4,
101
- };
102
87
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
103
- const defaultFlushMode = FlushMode.TurnBased;
104
88
  // The actual limit is 1Mb (socket.io and Kafka limits)
105
89
  // We can't estimate it fully, as we
106
90
  // - do not know what properties relay service will add
107
91
  // - we do not stringify final op, thus we do not know how much escaping will be added.
108
92
  const defaultMaxBatchSizeInBytes = 700 * 1024;
109
- const defaultCompressionConfig = {
110
- // Batches with content size exceeding this value will be compressed
111
- minimumBatchSizeInBytes: 614400,
112
- compressionAlgorithm: CompressionAlgorithms.lz4,
113
- };
114
93
  const defaultChunkSizeInBytes = 204800;
115
94
  /**
116
95
  * The default time to wait for pending ops to be processed during summarization
@@ -249,9 +228,42 @@ export class ContainerRuntime extends TypedEventEmitter {
249
228
  },
250
229
  });
251
230
  const mc = loggerToMonitoringContext(logger);
252
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, compressionOptions = runtimeOptions.enableGroupedBatching === false
253
- ? disabledCompressionConfig // Compression must be disabled if Grouping is disabled
254
- : defaultCompressionConfig, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, enableRuntimeIdCompressor, chunkSizeInBytes = defaultChunkSizeInBytes, enableGroupedBatching = true, explicitSchemaControl = false, } = runtimeOptions;
231
+ // Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
232
+ // based on the compatibility mode.
233
+ // For example, if compatibility mode is set to "1.0.0", the default configs will ensure compatibility with FF runtime
234
+ // 1.0.0 or later. If the compatibility mode is set to "2.10.0", the default values will be generated to ensure compatibility
235
+ // with FF runtime 2.10.0 or later.
236
+ // TODO: We will add in a way for users to pass in compatibilityVersion in a follow up PR.
237
+ const compatibilityVersion = defaultCompatibilityVersion;
238
+ if (!isValidCompatVersion(compatibilityVersion)) {
239
+ throw new UsageError(`Invalid compatibility version: ${compatibilityVersion}. It must be an existing FF version (i.e. 2.22.1).`);
240
+ }
241
+ const defaultVersionDependentConfigs = getCompatibilityVersionDefaults(compatibilityVersion);
242
+ // The following are the default values for the options that do not affect the DocumentSchema.
243
+ const defaultConfigsNonVersionDependent = {
244
+ summaryOptions: {},
245
+ loadSequenceNumberVerification: "close",
246
+ maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
247
+ chunkSizeInBytes: defaultChunkSizeInBytes,
248
+ };
249
+ const defaultConfigs = {
250
+ ...defaultVersionDependentConfigs,
251
+ ...defaultConfigsNonVersionDependent,
252
+ };
253
+ // Here we set each option to its corresponding default config value if it's not provided in runtimeOptions.
254
+ // Note: We cannot do a simple object merge of defaultConfigs/runtimeOptions because in most cases we don't want
255
+ // a option that is undefined in runtimeOptions to override the default value (except for idCompressor, see below).
256
+ const { summaryOptions = defaultConfigs.summaryOptions, gcOptions = defaultConfigs.gcOptions, loadSequenceNumberVerification = defaultConfigs.loadSequenceNumberVerification, maxBatchSizeInBytes = defaultConfigs.maxBatchSizeInBytes, chunkSizeInBytes = defaultConfigs.chunkSizeInBytes, explicitSchemaControl = defaultConfigs.explicitSchemaControl, enableGroupedBatching = defaultConfigs.enableGroupedBatching, flushMode = defaultConfigs.flushMode,
257
+ // If batching is disabled then we should disable compression as well. If batching is disabled and compression
258
+ // is enabled via runtimeOptions, we will throw an error later.
259
+ compressionOptions = enableGroupedBatching === false
260
+ ? disabledCompressionConfig
261
+ : defaultConfigs.compressionOptions, createBlobPayloadPending = defaultConfigs.createBlobPayloadPending, } = runtimeOptions;
262
+ // The logic for enableRuntimeIdCompressor is a bit different. Since `undefined` represents a logical state (off)
263
+ // we need to check it's explicitly set in runtimeOptions. If so, we should use that value even if it's undefined.
264
+ const enableRuntimeIdCompressor = "enableRuntimeIdCompressor" in runtimeOptions
265
+ ? runtimeOptions.enableRuntimeIdCompressor
266
+ : defaultConfigs.enableRuntimeIdCompressor;
255
267
  const registry = new FluidDataStoreRegistry(registryEntries);
256
268
  const tryFetchBlob = async (blobName) => {
257
269
  const blobId = context.baseSnapshot?.blobs[blobName];
@@ -380,6 +392,7 @@ export class ContainerRuntime extends TypedEventEmitter {
380
392
  compressionLz4,
381
393
  idCompressorMode,
382
394
  opGroupingEnabled: enableGroupedBatching,
395
+ createBlobPayloadPending,
383
396
  disallowedVersions: [],
384
397
  }, (schema) => {
385
398
  runtime.onSchemaChange(schema);
@@ -397,10 +410,10 @@ export class ContainerRuntime extends TypedEventEmitter {
397
410
  compressionOptions,
398
411
  maxBatchSizeInBytes,
399
412
  chunkSizeInBytes,
400
- // Requires<> drops undefined from IdCompressorType
401
- enableRuntimeIdCompressor: enableRuntimeIdCompressor,
413
+ enableRuntimeIdCompressor,
402
414
  enableGroupedBatching,
403
415
  explicitSchemaControl,
416
+ createBlobPayloadPending,
404
417
  };
405
418
  const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, requestHandler, undefined, // summaryConfiguration
406
419
  recentBatchInfo);
@@ -550,6 +563,7 @@ export class ContainerRuntime extends TypedEventEmitter {
550
563
  this.requestHandler = requestHandler;
551
564
  this.summaryConfiguration = summaryConfiguration;
552
565
  this.imminentClosure = false;
566
+ this.isReadOnly = () => this.deltaManager.readOnlyInfo.readonly === true;
553
567
  // We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
554
568
  // Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
555
569
  this.pendingIdCompressorOps = [];
@@ -567,6 +581,51 @@ export class ContainerRuntime extends TypedEventEmitter {
567
581
  this.snapshotCacheForLoadingGroupIds = new PromiseCache({
568
582
  expiry: { policy: "absolute", durationMs: 60000 },
569
583
  });
584
+ this.notifyReadOnlyState = (readonly) => this.channelCollection.notifyReadOnlyState(readonly);
585
+ /**
586
+ * Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
587
+ * To exit Staging Mode, call either discardChanges or commitChanges on the Stage Controls returned from this method.
588
+ *
589
+ * @returns StageControlsExperimental - Controls for exiting Staging Mode.
590
+ */
591
+ // eslint-disable-next-line import/no-deprecated
592
+ this.enterStagingMode = () => {
593
+ if (this.stageControls !== undefined) {
594
+ throw new Error("already in staging mode");
595
+ }
596
+ // Make sure all BatchManagers are empty before entering staging mode,
597
+ // since we mark whole batches as "staged" or not to indicate whether to submit them.
598
+ this.outbox.flush();
599
+ const exitStagingMode = (discardOrCommit) => () => {
600
+ // Final flush of any last staged changes
601
+ this.outbox.flush(undefined, true /* staged */);
602
+ this.stageControls = undefined;
603
+ discardOrCommit();
604
+ };
605
+ const stageControls = {
606
+ discardChanges: exitStagingMode(() => {
607
+ // Pop all staged batches from the PSM and roll them back in LIFO order
608
+ this.pendingStateManager.popStagedBatches(({ runtimeOp, localOpMetadata }) => {
609
+ assert(runtimeOp !== undefined, 0xb82 /* Staged batches expected to have runtimeOp defined */);
610
+ this.rollback(runtimeOp, localOpMetadata);
611
+ });
612
+ if (this.attachState === AttachState.Attached) {
613
+ this.updateDocumentDirtyState(this.pendingMessagesCount !== 0);
614
+ }
615
+ }),
616
+ commitChanges: exitStagingMode(() => {
617
+ // All staged changes are in the PSM, so just replay them (ignore pre-staging batches)
618
+ // FUTURE: Have this do squash-rebase instead of resubmitting all intermediate changes
619
+ if (this.connected) {
620
+ this.pendingStateManager.replayPendingStates(true /* onlyStagedBatched */);
621
+ }
622
+ else {
623
+ this.pendingStateManager.clearStagingFlags();
624
+ }
625
+ }),
626
+ };
627
+ return (this.stageControls = stageControls);
628
+ };
570
629
  this.readAndParseBlob = async (id) => readAndParse(this.storage, id);
571
630
  const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
572
631
  // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
@@ -686,19 +745,22 @@ export class ContainerRuntime extends TypedEventEmitter {
686
745
  isActiveConnection: () => this.innerDeltaManager.active,
687
746
  isAttached: () => this.attachState !== AttachState.Detached,
688
747
  }, pendingRuntimeState?.pending, this.baseLogger);
689
- let outerDeltaManager;
748
+ let outerDeltaManager = this.innerDeltaManager;
690
749
  this.useDeltaManagerOpsProxy =
691
750
  this.mc.config.getBoolean("Fluid.ContainerRuntime.DeltaManagerOpsProxy") === true;
692
751
  // The summarizerDeltaManager Proxy is used to lie to the summarizer to convince it is in the right state as a summarizer client.
693
- const summarizerDeltaManagerProxy = new DeltaManagerSummarizerProxy(this.innerDeltaManager);
694
- outerDeltaManager = summarizerDeltaManagerProxy;
752
+ outerDeltaManager = DeltaManagerSummarizerProxy.wrapIfSummarizer(outerDeltaManager);
695
753
  // The DeltaManagerPendingOpsProxy is used to control the minimum sequence number
696
754
  // It allows us to lie to the layers below so that they can maintain enough local state for rebasing ops.
697
755
  if (this.useDeltaManagerOpsProxy) {
698
- const pendingOpsDeltaManagerProxy = new DeltaManagerPendingOpsProxy(summarizerDeltaManagerProxy, this.pendingStateManager);
756
+ const pendingOpsDeltaManagerProxy = new DeltaManagerPendingOpsProxy(outerDeltaManager, this.pendingStateManager);
699
757
  outerDeltaManager = pendingOpsDeltaManagerProxy;
700
758
  }
701
- this._deltaManager = outerDeltaManager;
759
+ // always wrap the exposed delta manager in at least on layer of proxying
760
+ this._deltaManager =
761
+ outerDeltaManager instanceof BaseDeltaManagerProxy
762
+ ? outerDeltaManager
763
+ : new BaseDeltaManagerProxy(outerDeltaManager);
702
764
  this.handleContext = new ContainerFluidHandleContext("", this);
703
765
  if (this.summaryConfiguration.state === "enabled") {
704
766
  validateSummaryHeuristicConfiguration(this.summaryConfiguration);
@@ -809,6 +871,7 @@ export class ContainerRuntime extends TypedEventEmitter {
809
871
  ...props,
810
872
  timestampMs: props.timestampMs ?? this.getCurrentReferenceTimestampMs(),
811
873
  }), (path) => this.garbageCollector.isNodeDeleted(path), new Map(dataStoreAliasMap), async (runtime) => provideEntryPoint);
874
+ this._deltaManager.on("readonly", this.notifyReadOnlyState);
812
875
  this.blobManager = new BlobManager({
813
876
  routeContext: this.handleContext,
814
877
  blobManagerLoadInfo,
@@ -829,6 +892,7 @@ export class ContainerRuntime extends TypedEventEmitter {
829
892
  isBlobDeleted: (blobPath) => this.garbageCollector.isNodeDeleted(blobPath),
830
893
  runtime: this,
831
894
  stashedBlobs: pendingRuntimeState?.pendingAttachmentBlobs,
895
+ createBlobPayloadPending: this.sessionSchema.createBlobPayloadPending === true,
832
896
  });
833
897
  this.deltaScheduler = new DeltaScheduler(this.innerDeltaManager, this, createChildLogger({ logger: this.baseLogger, namespace: "DeltaScheduler" }));
834
898
  this.inboundBatchAggregator = new InboundBatchAggregator(this.innerDeltaManager, () => this.clientId, createChildLogger({ logger: this.baseLogger, namespace: "InboundBatchAggregator" }));
@@ -1080,6 +1144,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1080
1144
  this.pendingStateManager.dispose();
1081
1145
  this.inboundBatchAggregator.dispose();
1082
1146
  this.deltaScheduler.dispose();
1147
+ this._deltaManager.dispose();
1083
1148
  this.emit("dispose");
1084
1149
  this.removeAllListeners();
1085
1150
  }
@@ -1225,7 +1290,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1225
1290
  return this.resolveHandle(requestParser.createSubRequest(1));
1226
1291
  }
1227
1292
  if (id === blobManagerBasePath && requestParser.isLeaf(2)) {
1228
- const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
1293
+ const localId = requestParser.pathParts[1];
1294
+ const payloadPending = requestParser.headers?.[RuntimeHeaders.payloadPending] === true;
1295
+ if (!this.blobManager.hasBlob(localId) &&
1296
+ requestParser.headers?.[RuntimeHeaders.wait] === false) {
1297
+ return create404Response(request);
1298
+ }
1299
+ const blob = await this.blobManager.getBlob(localId, payloadPending);
1229
1300
  return {
1230
1301
  status: 200,
1231
1302
  mimeType: "fluid/object",
@@ -1865,11 +1936,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1865
1936
  *
1866
1937
  * @param resubmittingBatchId - If defined, indicates this is a resubmission of a batch
1867
1938
  * with the given Batch ID, which must be preserved
1939
+ * @param resubmittingStagedBatch - If defined, indicates this is a resubmission of a batch that is staged,
1940
+ * meaning it should not be sent to the ordering service yet.
1868
1941
  */
1869
- flush(resubmittingBatchId) {
1942
+ flush(resubmittingBatchId, resubmittingStagedBatch) {
1870
1943
  try {
1871
1944
  assert(!this.batchRunner.running, 0x24c /* "Cannot call `flush()` while manually accumulating a batch (e.g. under orderSequentially) */);
1872
- this.outbox.flush(resubmittingBatchId);
1945
+ this.outbox.flush(resubmittingBatchId, resubmittingStagedBatch);
1873
1946
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1874
1947
  }
1875
1948
  catch (error) {
@@ -1888,7 +1961,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1888
1961
  orderSequentially(callback) {
1889
1962
  let checkpoint;
1890
1963
  const checkpointDirtyState = this.dirtyContainer;
1964
+ // eslint-disable-next-line import/no-deprecated
1965
+ let stageControls;
1891
1966
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1967
+ if (!this.batchRunner.running && !this.inStagingMode) {
1968
+ stageControls = this.enterStagingMode();
1969
+ }
1892
1970
  // Note: we are not touching any batches other than mainBatch here, for two reasons:
1893
1971
  // 1. It would not help, as other batches are flushed independently from main batch.
1894
1972
  // 2. There is no way to undo process of data store creation, blob creation, ID compressor ops, or other things tracked by other batches.
@@ -1902,11 +1980,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1902
1980
  if (checkpoint) {
1903
1981
  // This will throw and close the container if rollback fails
1904
1982
  try {
1905
- checkpoint.rollback((message) => this.rollback(message.serializedOp, message.localOpMetadata));
1983
+ checkpoint.rollback((message) => this.rollback(message.runtimeOp, message.localOpMetadata));
1906
1984
  // reset the dirty state after rollback to what it was before to keep it consistent
1907
1985
  if (this.dirtyContainer !== checkpointDirtyState) {
1908
1986
  this.updateDocumentDirtyState(checkpointDirtyState);
1909
1987
  }
1988
+ stageControls?.discardChanges();
1989
+ stageControls = undefined;
1910
1990
  }
1911
1991
  catch (error_) {
1912
1992
  const error2 = wrapError(error_, (message) => {
@@ -1924,12 +2004,21 @@ export class ContainerRuntime extends TypedEventEmitter {
1924
2004
  throw error; // throw the original error for the consumer of the runtime
1925
2005
  }
1926
2006
  });
2007
+ stageControls?.commitChanges();
1927
2008
  // We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
1928
2009
  if (this.flushMode !== FlushMode.TurnBased && !this.batchRunner.running) {
1929
2010
  this.flush();
1930
2011
  }
1931
2012
  return result;
1932
2013
  }
2014
+ /**
2015
+ * If true, the ContainerRuntime is not submitting any new ops to the ordering service.
2016
+ * Ops submitted to the ContainerRuntime while in Staging Mode will be queued in the PendingStateManager,
2017
+ * either to be discarded or committed later (via the Stage Controls returned from enterStagingMode).
2018
+ */
2019
+ get inStagingMode() {
2020
+ return this.stageControls !== undefined;
2021
+ }
1933
2022
  /**
1934
2023
  * Returns the aliased data store's entryPoint, given the alias.
1935
2024
  * @param alias - The alias for the data store.
@@ -2662,8 +2751,11 @@ export class ContainerRuntime extends TypedEventEmitter {
2662
2751
  contents: idRange,
2663
2752
  };
2664
2753
  const idAllocationBatchMessage = {
2665
- serializedOp: serializeOp(idAllocationMessage),
2754
+ runtimeOp: idAllocationMessage,
2666
2755
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
2756
+ // Note: For now, we will never stage ID Allocation messages.
2757
+ // They won't contain personal info and no harm in extra allocations in case of discarding the staged changes
2758
+ staged: false,
2667
2759
  };
2668
2760
  this.outbox.submitIdAllocation(idAllocationBatchMessage);
2669
2761
  }
@@ -2705,17 +2797,19 @@ export class ContainerRuntime extends TypedEventEmitter {
2705
2797
  contents: schemaChangeMessage,
2706
2798
  };
2707
2799
  this.outbox.submit({
2708
- serializedOp: serializeOp(msg),
2800
+ runtimeOp: msg,
2709
2801
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
2802
+ staged: this.inStagingMode,
2710
2803
  });
2711
2804
  }
2712
2805
  const message = {
2713
2806
  // This will encode any handles present in this op before serializing to string
2714
2807
  // Note: handles may already have been encoded by the DDS layer, but encoding handles is idempotent so there's no problem.
2715
- serializedOp: serializeOp(containerRuntimeMessage),
2808
+ runtimeOp: containerRuntimeMessage,
2716
2809
  metadata,
2717
2810
  localOpMetadata,
2718
2811
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
2812
+ staged: this.inStagingMode,
2719
2813
  };
2720
2814
  if (type === ContainerMessageType.BlobAttach) {
2721
2815
  // BlobAttach ops must have their metadata visible and cannot be grouped (see opGroupingManager.ts)
@@ -2799,7 +2893,7 @@ export class ContainerRuntime extends TypedEventEmitter {
2799
2893
  * @remarks - If the "Offline Load" feature is enabled, the batchId is included in the resubmitted messages,
2800
2894
  * for correlation to detect container forking.
2801
2895
  */
2802
- reSubmitBatch(batch, batchId) {
2896
+ reSubmitBatch(batch, batchId, staged) {
2803
2897
  this.batchRunner.run(() => {
2804
2898
  for (const message of batch) {
2805
2899
  this.reSubmit(message);
@@ -2807,14 +2901,10 @@ export class ContainerRuntime extends TypedEventEmitter {
2807
2901
  });
2808
2902
  // Only include Batch ID if "Offline Load" feature is enabled
2809
2903
  // It's only needed to identify batches across container forks arising from misuse of offline load.
2810
- this.flush(this.offlineEnabled ? batchId : undefined);
2904
+ this.flush(this.offlineEnabled ? batchId : undefined, staged);
2811
2905
  }
2812
2906
  reSubmit(message) {
2813
- // Messages in the PendingStateManager and Outbox (both call resubmit) have serialized contents, so parse it here.
2814
- // Note that roundtripping handles through string like this means this parsed contents
2815
- // contains RemoteFluidObjectHandles, not the original handle.
2816
- const containerRuntimeMessage = this.parseLocalOpContent(message.content);
2817
- this.reSubmitCore(containerRuntimeMessage, message.localOpMetadata, message.opMetadata);
2907
+ this.reSubmitCore(message.runtimeOp, message.localOpMetadata, message.opMetadata);
2818
2908
  }
2819
2909
  /**
2820
2910
  * Finds the right store and asks it to resubmit the message. This typically happens when we
@@ -2869,11 +2959,8 @@ export class ContainerRuntime extends TypedEventEmitter {
2869
2959
  }
2870
2960
  }
2871
2961
  }
2872
- rollback(content, localOpMetadata) {
2873
- // Messages in the Outbox (which calls rollback) have serialized contents, so parse it here.
2874
- // Note that roundtripping handles through string like this means this parsed contents
2875
- // contains RemoteFluidObjectHandles, not the original handle.
2876
- const { type, contents } = this.parseLocalOpContent(content);
2962
+ rollback(runtimeOp, localOpMetadata) {
2963
+ const { type, contents } = runtimeOp;
2877
2964
  switch (type) {
2878
2965
  case ContainerMessageType.FluidDataStoreOp: {
2879
2966
  // For operations, call rollbackDataStoreOp which will find the right store