@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.1

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 (195) hide show
  1. package/.eslintrc.js +19 -8
  2. package/dist/batchTracker.d.ts +1 -2
  3. package/dist/batchTracker.d.ts.map +1 -1
  4. package/dist/batchTracker.js.map +1 -1
  5. package/dist/blobManager.d.ts +45 -34
  6. package/dist/blobManager.d.ts.map +1 -1
  7. package/dist/blobManager.js +135 -102
  8. package/dist/blobManager.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +54 -8
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +143 -72
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +1 -1
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +6 -8
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +12 -9
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +41 -35
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +41 -20
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +205 -150
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/garbageCollectionConstants.d.ts +7 -3
  26. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  27. package/dist/garbageCollectionConstants.js +10 -8
  28. package/dist/garbageCollectionConstants.js.map +1 -1
  29. package/dist/garbageCollectionTombstoneUtils.d.ts +14 -0
  30. package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  31. package/dist/garbageCollectionTombstoneUtils.js +23 -0
  32. package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
  33. package/dist/index.d.ts +1 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/opLifecycle/batchManager.d.ts +13 -1
  38. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  39. package/dist/opLifecycle/batchManager.js +35 -1
  40. package/dist/opLifecycle/batchManager.js.map +1 -1
  41. package/dist/opLifecycle/definitions.d.ts +25 -1
  42. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  43. package/dist/opLifecycle/definitions.js.map +1 -1
  44. package/dist/opLifecycle/index.d.ts +2 -2
  45. package/dist/opLifecycle/index.d.ts.map +1 -1
  46. package/dist/opLifecycle/index.js +2 -1
  47. package/dist/opLifecycle/index.js.map +1 -1
  48. package/dist/opLifecycle/opCompressor.d.ts +1 -1
  49. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  50. package/dist/opLifecycle/opCompressor.js +24 -10
  51. package/dist/opLifecycle/opCompressor.js.map +1 -1
  52. package/dist/opLifecycle/opDecompressor.d.ts +2 -1
  53. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  54. package/dist/opLifecycle/opDecompressor.js +30 -17
  55. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  56. package/dist/opLifecycle/opSplitter.d.ts +34 -2
  57. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  58. package/dist/opLifecycle/opSplitter.js +114 -5
  59. package/dist/opLifecycle/opSplitter.js.map +1 -1
  60. package/dist/opLifecycle/outbox.d.ts +5 -0
  61. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  62. package/dist/opLifecycle/outbox.js +24 -14
  63. package/dist/opLifecycle/outbox.js.map +1 -1
  64. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  65. package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
  66. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  67. package/dist/packageVersion.d.ts +1 -1
  68. package/dist/packageVersion.js +1 -1
  69. package/dist/packageVersion.js.map +1 -1
  70. package/dist/runningSummarizer.d.ts.map +1 -1
  71. package/dist/runningSummarizer.js +0 -1
  72. package/dist/runningSummarizer.js.map +1 -1
  73. package/dist/scheduleManager.d.ts +0 -1
  74. package/dist/scheduleManager.d.ts.map +1 -1
  75. package/dist/scheduleManager.js +9 -20
  76. package/dist/scheduleManager.js.map +1 -1
  77. package/dist/summarizer.d.ts +0 -1
  78. package/dist/summarizer.d.ts.map +1 -1
  79. package/dist/summarizer.js +2 -1
  80. package/dist/summarizer.js.map +1 -1
  81. package/dist/summarizerTypes.d.ts +1 -0
  82. package/dist/summarizerTypes.d.ts.map +1 -1
  83. package/dist/summarizerTypes.js.map +1 -1
  84. package/dist/summaryFormat.d.ts.map +1 -1
  85. package/dist/summaryFormat.js +1 -2
  86. package/dist/summaryFormat.js.map +1 -1
  87. package/lib/batchTracker.d.ts +1 -2
  88. package/lib/batchTracker.d.ts.map +1 -1
  89. package/lib/batchTracker.js.map +1 -1
  90. package/lib/blobManager.d.ts +45 -34
  91. package/lib/blobManager.d.ts.map +1 -1
  92. package/lib/blobManager.js +137 -104
  93. package/lib/blobManager.js.map +1 -1
  94. package/lib/containerRuntime.d.ts +54 -8
  95. package/lib/containerRuntime.d.ts.map +1 -1
  96. package/lib/containerRuntime.js +140 -69
  97. package/lib/containerRuntime.js.map +1 -1
  98. package/lib/dataStoreContext.d.ts +1 -1
  99. package/lib/dataStoreContext.d.ts.map +1 -1
  100. package/lib/dataStoreContext.js +7 -9
  101. package/lib/dataStoreContext.js.map +1 -1
  102. package/lib/dataStores.d.ts +12 -9
  103. package/lib/dataStores.d.ts.map +1 -1
  104. package/lib/dataStores.js +44 -38
  105. package/lib/dataStores.js.map +1 -1
  106. package/lib/garbageCollection.d.ts +41 -20
  107. package/lib/garbageCollection.d.ts.map +1 -1
  108. package/lib/garbageCollection.js +201 -146
  109. package/lib/garbageCollection.js.map +1 -1
  110. package/lib/garbageCollectionConstants.d.ts +7 -3
  111. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  112. package/lib/garbageCollectionConstants.js +9 -7
  113. package/lib/garbageCollectionConstants.js.map +1 -1
  114. package/lib/garbageCollectionTombstoneUtils.d.ts +14 -0
  115. package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
  116. package/lib/garbageCollectionTombstoneUtils.js +19 -0
  117. package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
  118. package/lib/index.d.ts +1 -2
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +1 -2
  121. package/lib/index.js.map +1 -1
  122. package/lib/opLifecycle/batchManager.d.ts +13 -1
  123. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  124. package/lib/opLifecycle/batchManager.js +35 -1
  125. package/lib/opLifecycle/batchManager.js.map +1 -1
  126. package/lib/opLifecycle/definitions.d.ts +25 -1
  127. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  128. package/lib/opLifecycle/definitions.js.map +1 -1
  129. package/lib/opLifecycle/index.d.ts +2 -2
  130. package/lib/opLifecycle/index.d.ts.map +1 -1
  131. package/lib/opLifecycle/index.js +1 -1
  132. package/lib/opLifecycle/index.js.map +1 -1
  133. package/lib/opLifecycle/opCompressor.d.ts +1 -1
  134. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  135. package/lib/opLifecycle/opCompressor.js +24 -10
  136. package/lib/opLifecycle/opCompressor.js.map +1 -1
  137. package/lib/opLifecycle/opDecompressor.d.ts +2 -1
  138. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  139. package/lib/opLifecycle/opDecompressor.js +30 -17
  140. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  141. package/lib/opLifecycle/opSplitter.d.ts +34 -2
  142. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  143. package/lib/opLifecycle/opSplitter.js +112 -4
  144. package/lib/opLifecycle/opSplitter.js.map +1 -1
  145. package/lib/opLifecycle/outbox.d.ts +5 -0
  146. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  147. package/lib/opLifecycle/outbox.js +24 -14
  148. package/lib/opLifecycle/outbox.js.map +1 -1
  149. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  150. package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
  151. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  152. package/lib/packageVersion.d.ts +1 -1
  153. package/lib/packageVersion.js +1 -1
  154. package/lib/packageVersion.js.map +1 -1
  155. package/lib/runningSummarizer.d.ts.map +1 -1
  156. package/lib/runningSummarizer.js +0 -1
  157. package/lib/runningSummarizer.js.map +1 -1
  158. package/lib/scheduleManager.d.ts +0 -1
  159. package/lib/scheduleManager.d.ts.map +1 -1
  160. package/lib/scheduleManager.js +9 -20
  161. package/lib/scheduleManager.js.map +1 -1
  162. package/lib/summarizer.d.ts +0 -1
  163. package/lib/summarizer.d.ts.map +1 -1
  164. package/lib/summarizer.js +2 -1
  165. package/lib/summarizer.js.map +1 -1
  166. package/lib/summarizerTypes.d.ts +1 -0
  167. package/lib/summarizerTypes.d.ts.map +1 -1
  168. package/lib/summarizerTypes.js.map +1 -1
  169. package/lib/summaryFormat.d.ts.map +1 -1
  170. package/lib/summaryFormat.js +1 -2
  171. package/lib/summaryFormat.js.map +1 -1
  172. package/package.json +20 -19
  173. package/src/batchTracker.ts +1 -1
  174. package/src/blobManager.ts +159 -111
  175. package/src/containerRuntime.ts +202 -73
  176. package/src/dataStoreContext.ts +15 -16
  177. package/src/dataStores.ts +61 -45
  178. package/src/garbageCollection.ts +258 -183
  179. package/src/garbageCollectionConstants.ts +10 -7
  180. package/src/garbageCollectionTombstoneUtils.ts +28 -0
  181. package/src/index.ts +2 -5
  182. package/src/opLifecycle/batchManager.ts +59 -1
  183. package/src/opLifecycle/definitions.ts +27 -1
  184. package/src/opLifecycle/index.ts +2 -1
  185. package/src/opLifecycle/opCompressor.ts +29 -12
  186. package/src/opLifecycle/opDecompressor.ts +39 -18
  187. package/src/opLifecycle/opSplitter.ts +141 -7
  188. package/src/opLifecycle/outbox.ts +32 -16
  189. package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
  190. package/src/packageVersion.ts +1 -1
  191. package/src/runningSummarizer.ts +0 -1
  192. package/src/scheduleManager.ts +19 -30
  193. package/src/summarizer.ts +1 -1
  194. package/src/summarizerTypes.ts +1 -0
  195. package/src/summaryFormat.ts +1 -2
@@ -69,6 +69,7 @@ import {
69
69
  } from "@fluidframework/protocol-definitions";
70
70
  import {
71
71
  FlushMode,
72
+ gcTreeKey,
72
73
  InboundAttachMessage,
73
74
  IFluidDataStoreContextDetached,
74
75
  IFluidDataStoreRegistry,
@@ -155,9 +156,6 @@ import {
155
156
  IGarbageCollector,
156
157
  IGCStats,
157
158
  } from "./garbageCollection";
158
- import {
159
- gcTreeKey,
160
- } from "./garbageCollectionConstants";
161
159
  import {
162
160
  channelToDataStore,
163
161
  IDataStoreAliasMessage,
@@ -482,6 +480,25 @@ export interface IContainerRuntimeOptions {
482
480
  * @experimental This config should be driven by the connection with the service and will be moved in the future.
483
481
  */
484
482
  readonly maxBatchSizeInBytes?: number;
483
+ /**
484
+ * If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
485
+ * how large the individual chunks will be. This is only supported when compression is enabled.
486
+ *
487
+ * If unspecified, if a batch exceeds `maxBatchSizeInBytes` after compression, the container will close with an instance
488
+ * of `GenericError` with the `BatchTooLarge` message.
489
+ *
490
+ * @experimental Not ready for use.
491
+ */
492
+ readonly chunkSizeInBytes?: number;
493
+ /**
494
+ * If enabled, the runtime will block all attempts to send an op with a different reference sequence number
495
+ * from the previous ops submitted in the same JS turn. This happens when ops are reentrant (an op is created as a
496
+ * response to another op, likely from an event handler).
497
+ *
498
+ * By default, the feature is disabled. If enabled from options, the `Fluid.ContainerRuntime.DisableOpReentryCheck`
499
+ * can be used to disable it at runtime.
500
+ */
501
+ readonly enableOpReentryCheck?: boolean;
485
502
  }
486
503
 
487
504
  /**
@@ -507,6 +524,30 @@ export enum RuntimeHeaders {
507
524
  viaHandle = "viaHandle",
508
525
  }
509
526
 
527
+ /** True if a tombstoned object should be returned without erroring */
528
+ export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in the enum above, but avoiding the breaking change
529
+
530
+ /** Tombstone error responses will have this header set to true */
531
+ export const TombstoneResponseHeaderKey = "isTombstoned"
532
+
533
+ /**
534
+ * The full set of parsed header data that may be found on Runtime requests
535
+ */
536
+ export interface RuntimeHeaderData {
537
+ wait?: boolean;
538
+ externalRequest?: boolean;
539
+ viaHandle?: boolean;
540
+ allowTombstone?: boolean;
541
+ }
542
+
543
+ /** Default values for Runtime Headers */
544
+ export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
545
+ wait: true,
546
+ externalRequest: false,
547
+ viaHandle: false,
548
+ allowTombstone: false,
549
+ }
550
+
510
551
  /**
511
552
  * Available compression algorithms for op compression.
512
553
  */
@@ -662,6 +703,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
662
703
  compressionAlgorithm: CompressionAlgorithms.lz4
663
704
  },
664
705
  maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
706
+ chunkSizeInBytes = Number.POSITIVE_INFINITY,
707
+ enableOpReentryCheck = false,
665
708
  } = runtimeOptions;
666
709
 
667
710
  const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
@@ -719,7 +762,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
719
762
  if (loadSequenceNumberVerification === "log") {
720
763
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
721
764
  } else {
765
+ // Call both close and dispose as close implementation will no longer dispose runtime in future (2.0.0-internal.3.0.0)
722
766
  context.closeFn(error);
767
+ context.disposeFn?.(error);
723
768
  }
724
769
  }
725
770
  }
@@ -739,6 +784,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
739
784
  enableOfflineLoad,
740
785
  compressionOptions,
741
786
  maxBatchSizeInBytes,
787
+ chunkSizeInBytes,
788
+ enableOpReentryCheck,
742
789
  },
743
790
  containerScope,
744
791
  logger,
@@ -790,8 +837,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
790
837
  return this.reSubmit;
791
838
  }
792
839
 
840
+ public get disposeFn(): (error?: ICriticalContainerError) => void {
841
+ // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
842
+ return this.context.disposeFn ?? this.context.closeFn;
843
+ }
844
+
793
845
  public get closeFn(): (error?: ICriticalContainerError) => void {
794
- return this.context.closeFn;
846
+ // Also call disposeFn to retain functionality of runtime being disposed on close
847
+ return (error?: ICriticalContainerError) => {
848
+ this.context.closeFn(error);
849
+ this.context.disposeFn?.(error);
850
+ };
795
851
  }
796
852
 
797
853
  public get flushMode(): FlushMode {
@@ -863,6 +919,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
863
919
 
864
920
  private dirtyContainer: boolean;
865
921
  private emitDirtyDocumentEvent = true;
922
+ private readonly enableOpReentryCheck: boolean;
866
923
 
867
924
  private readonly defaultTelemetrySignalSampleCount = 100;
868
925
  private _perfSignalData: IPerfSignalReport = {
@@ -1010,17 +1067,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1010
1067
  this.messageAtLastSummary = metadata?.message;
1011
1068
 
1012
1069
  this._connected = this.context.connected;
1013
- this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
1014
1070
 
1015
- this.handleContext = new ContainerFluidHandleContext("", this);
1071
+ this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
1072
+
1073
+ const opSplitter = new OpSplitter(
1074
+ chunks,
1075
+ this.context.submitBatchFn,
1076
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompressionChunking") === true ?
1077
+ Number.POSITIVE_INFINITY : runtimeOptions.chunkSizeInBytes,
1078
+ runtimeOptions.maxBatchSizeInBytes,
1079
+ this.mc.logger);
1080
+ this.remoteMessageProcessor = new RemoteMessageProcessor(opSplitter, new OpDecompressor());
1016
1081
 
1017
- this.mc = loggerToMonitoringContext(
1018
- ChildLogger.create(this.logger, "ContainerRuntime"));
1082
+ this.handleContext = new ContainerFluidHandleContext("", this);
1019
1083
 
1020
1084
  if (this.summaryConfiguration.state === "enabled") {
1021
1085
  this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
1022
1086
  }
1023
1087
 
1088
+ this.enableOpReentryCheck = runtimeOptions.enableOpReentryCheck === true
1089
+ // Allow for a break-glass config to override the options
1090
+ && this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableOpReentryCheck") !== true;
1091
+
1024
1092
  this.summariesDisabled = this.isSummariesDisabled();
1025
1093
  this.heuristicsDisabled = this.isHeuristicsDisabled();
1026
1094
  this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
@@ -1079,6 +1147,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1079
1147
  // If GC should not run, let the summarizer node know so that it does not track GC state.
1080
1148
  gcDisabled: !this.garbageCollector.shouldRunGC,
1081
1149
  },
1150
+ // Function to get GC data if needed. This will always be called by the root summarizer node to get GC data.
1151
+ async (fullGC?: boolean) => this.getGCDataInternal(fullGC),
1152
+ // Function to get the GC details from the base snapshot we loaded from.
1153
+ async () => this.garbageCollector.getBaseGCDetails(),
1082
1154
  );
1083
1155
 
1084
1156
  if (baseSnapshot) {
@@ -1092,7 +1164,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1092
1164
  (id: string, createParam: CreateChildSummarizerNodeParam) => (
1093
1165
  summarizeInternal: SummarizeInternalFn,
1094
1166
  getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
1095
- getBaseGCDetailsFn: () => Promise<IGarbageCollectionDetailsBase>,
1167
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
1096
1168
  ) => this.summarizerNode.createChild(
1097
1169
  summarizeInternal,
1098
1170
  id,
@@ -1117,12 +1189,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1117
1189
  this.handleContext,
1118
1190
  blobManagerSnapshot,
1119
1191
  () => this.storage,
1120
- (blobId, localId) => {
1192
+ (localId: string, blobId?: string) => {
1121
1193
  if (!this.disposed) {
1122
- this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
1194
+ this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { localId, blobId });
1123
1195
  }
1124
1196
  },
1125
1197
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
1198
+ (fromPath: string, toPath: string) => this.garbageCollector.addedOutboundReference(fromPath, toPath),
1126
1199
  this,
1127
1200
  pendingRuntimeState?.pendingAttachmentBlobs,
1128
1201
  );
@@ -1147,15 +1220,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1147
1220
  },
1148
1221
  pendingRuntimeState?.pending);
1149
1222
 
1223
+ const compressionOptions = this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompression") === true ?
1224
+ {
1225
+ minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
1226
+ compressionAlgorithm: CompressionAlgorithms.lz4
1227
+ } : runtimeOptions.compressionOptions;
1228
+
1150
1229
  this.outbox = new Outbox({
1151
1230
  shouldSend: () => this.canSendOps(),
1152
1231
  pendingStateManager: this.pendingStateManager,
1153
1232
  containerContext: this.context,
1154
1233
  compressor: new OpCompressor(this.mc.logger),
1234
+ splitter: opSplitter,
1155
1235
  config: {
1156
- compressionOptions: runtimeOptions.compressionOptions,
1236
+ compressionOptions,
1157
1237
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1238
+ enableOpReentryCheck: this.enableOpReentryCheck,
1158
1239
  },
1240
+ logger: this.mc.logger,
1159
1241
  });
1160
1242
 
1161
1243
  this.context.quorum.on("removeMember", (clientId: string) => {
@@ -1402,16 +1484,20 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1402
1484
  }
1403
1485
 
1404
1486
  private async getDataStoreFromRequest(id: string, request: IRequest): Promise<IFluidRouter> {
1405
- const wait = typeof request.headers?.[RuntimeHeaders.wait] === "boolean"
1406
- ? request.headers?.[RuntimeHeaders.wait]
1407
- : true;
1408
- const viaHandle = typeof request.headers?.[RuntimeHeaders.viaHandle] === "boolean"
1409
- ? request.headers?.[RuntimeHeaders.viaHandle]
1410
- : false;
1487
+ const headerData: RuntimeHeaderData = {};
1488
+ if (typeof request.headers?.[RuntimeHeaders.wait] === "boolean") {
1489
+ headerData.wait = request.headers[RuntimeHeaders.wait];
1490
+ }
1491
+ if (typeof request.headers?.[RuntimeHeaders.viaHandle] === "boolean") {
1492
+ headerData.viaHandle = request.headers[RuntimeHeaders.viaHandle];
1493
+ }
1494
+ if (typeof request.headers?.[AllowTombstoneRequestHeaderKey] === "boolean") {
1495
+ headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
1496
+ }
1411
1497
 
1412
1498
  await this.dataStores.waitIfPendingAlias(id);
1413
1499
  const internalId = this.internalId(id);
1414
- const dataStoreContext = await this.dataStores.getDataStore(internalId, wait, viaHandle);
1500
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
1415
1501
 
1416
1502
  /**
1417
1503
  * If GC should run and this an external app request with "externalRequest" header, we need to return
@@ -1638,7 +1724,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1638
1724
  if (!this.shouldContinueReconnecting()) {
1639
1725
  this.closeFn(
1640
1726
  DataProcessingError.create(
1641
- // eslint-disable-next-line max-len
1642
1727
  "Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)",
1643
1728
  "setConnectionState",
1644
1729
  undefined,
@@ -1685,7 +1770,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1685
1770
 
1686
1771
  try {
1687
1772
  let localOpMetadata: unknown;
1688
- if (local && runtimeMessage) {
1773
+ if (local && runtimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
1689
1774
  localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1690
1775
  }
1691
1776
 
@@ -1803,7 +1888,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1803
1888
  private async getRootDataStoreChannel(id: string, wait = true): Promise<IFluidDataStoreChannel> {
1804
1889
  await this.dataStores.waitIfPendingAlias(id);
1805
1890
  const internalId = this.internalId(id);
1806
- const context = await this.dataStores.getDataStore(internalId, wait, false /* viaHandle */);
1891
+ const context = await this.dataStores.getDataStore(internalId, { wait });
1807
1892
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1808
1893
  return context.realize();
1809
1894
  }
@@ -1820,9 +1905,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1820
1905
  assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
1821
1906
  }
1822
1907
 
1823
- public orderSequentially(callback: () => void): void {
1908
+ public orderSequentially<T>(callback: () => T): T {
1824
1909
  let checkpoint: IBatchCheckpoint | undefined;
1825
-
1910
+ let result: T;
1826
1911
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1827
1912
  // Note: we are not touching this.pendingAttachBatch here, for two reasons:
1828
1913
  // 1. It would not help, as we flush attach ops as they become available.
@@ -1831,7 +1916,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1831
1916
  }
1832
1917
  try {
1833
1918
  this._orderSequentiallyCalls++;
1834
- callback();
1919
+ result = callback();
1835
1920
  } catch (error) {
1836
1921
  if (checkpoint) {
1837
1922
  // This will throw and close the container if rollback fails
@@ -1863,6 +1948,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1863
1948
  if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1864
1949
  this.flush();
1865
1950
  }
1951
+ return result;
1866
1952
  }
1867
1953
 
1868
1954
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
@@ -2112,6 +2198,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2112
2198
  return this.dataStores.updateStateBeforeGC();
2113
2199
  }
2114
2200
 
2201
+ private async getGCDataInternal(fullGC?: boolean): Promise<IGarbageCollectionData> {
2202
+ return this.dataStores.getGCData(fullGC);
2203
+ }
2204
+
2115
2205
  /**
2116
2206
  * Implementation of IGarbageCollectionRuntime::getGCData.
2117
2207
  * Generates and returns the GC data for this container.
@@ -2119,7 +2209,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2119
2209
  */
2120
2210
  public async getGCData(fullGC?: boolean): Promise<IGarbageCollectionData> {
2121
2211
  const builder = new GCDataBuilder();
2122
- const dsGCData = await this.dataStores.getGCData(fullGC);
2212
+ const dsGCData = await this.summarizerNode.getGCData(fullGC);
2123
2213
  builder.addNodes(dsGCData.gcNodes);
2124
2214
 
2125
2215
  const blobsGCData = this.blobManager.getGCData(fullGC);
@@ -2138,40 +2228,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2138
2228
  // always referenced, so the used routes is only self-route (empty string).
2139
2229
  this.summarizerNode.updateUsedRoutes([""]);
2140
2230
 
2141
- const blobManagerUsedRoutes: string[] = [];
2142
- const dataStoreUsedRoutes: string[] = [];
2143
- for (const route of usedRoutes) {
2144
- if (this.isBlobPath(route)) {
2145
- blobManagerUsedRoutes.push(route);
2146
- } else {
2147
- dataStoreUsedRoutes.push(route);
2148
- }
2149
- }
2150
-
2151
- this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
2152
- this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
2231
+ const { dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(usedRoutes);
2232
+ this.dataStores.updateUsedRoutes(dataStoreRoutes);
2153
2233
  }
2154
2234
 
2155
2235
  /**
2156
- * This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
2157
- * tombstones.
2158
- * @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
2159
- * @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
2160
- * are deleted.
2236
+ * This is called to update objects whose routes are unused.
2237
+ * @param unusedRoutes - Data store and attachment blob routes that are unused in this Container.
2161
2238
  */
2162
- public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
2163
- const blobManagerUnusedRoutes: string[] = [];
2164
- const dataStoreUnusedRoutes: string[] = [];
2165
- for (const route of unusedRoutes) {
2166
- if (this.isBlobPath(route)) {
2167
- blobManagerUnusedRoutes.push(route);
2168
- } else {
2169
- dataStoreUnusedRoutes.push(route);
2170
- }
2171
- }
2239
+ public updateUnusedRoutes(unusedRoutes: string[]) {
2240
+ const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(unusedRoutes);
2241
+ this.blobManager.updateUnusedRoutes(blobManagerRoutes);
2242
+ this.dataStores.updateUnusedRoutes(dataStoreRoutes);
2243
+ }
2172
2244
 
2173
- this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
2174
- this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
2245
+ /**
2246
+ * This is called to update objects that are tombstones.
2247
+ * @param tombstonedRoutes - Data store and attachment blob routes that are tombstones in this Container.
2248
+ */
2249
+ public updateTombstonedRoutes(tombstonedRoutes: string[]) {
2250
+ const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(tombstonedRoutes);
2251
+ this.blobManager.updateTombstonedRoutes(blobManagerRoutes);
2252
+ this.dataStores.updateTombstonedRoutes(dataStoreRoutes);
2175
2253
  }
2176
2254
 
2177
2255
  /**
@@ -2201,7 +2279,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2201
2279
  public async getGCNodePackagePath(nodePath: string): Promise<readonly string[] | undefined> {
2202
2280
  switch (this.getNodeType(nodePath)) {
2203
2281
  case GCNodeType.Blob:
2204
- return ["_blobs"];
2282
+ return [BlobManager.basePath];
2205
2283
  case GCNodeType.DataStore:
2206
2284
  case GCNodeType.SubDataStore:
2207
2285
  return this.dataStores.getDataStorePackagePath(nodePath);
@@ -2221,6 +2299,25 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2221
2299
  return true;
2222
2300
  }
2223
2301
 
2302
+ /**
2303
+ * From a given list of routes, separate and return routes that belong to blob manager and data stores.
2304
+ * @param routes - A list of routes that can belong to data stores or blob manager.
2305
+ * @returns - Two route lists - One that contains routes for blob manager and another one that contains routes
2306
+ * for data stores.
2307
+ */
2308
+ private getDataStoreAndBlobManagerRoutes(routes: string[]) {
2309
+ const blobManagerRoutes: string[] = [];
2310
+ const dataStoreRoutes: string[] = [];
2311
+ for (const route of routes) {
2312
+ if (this.isBlobPath(route)) {
2313
+ blobManagerRoutes.push(route);
2314
+ } else {
2315
+ dataStoreRoutes.push(route);
2316
+ }
2317
+ }
2318
+ return { blobManagerRoutes, dataStoreRoutes };
2319
+ }
2320
+
2224
2321
  /**
2225
2322
  * Runs garbage collection and updates the reference / used state of the nodes in the container.
2226
2323
  * @returns the statistics of the garbage collection run; undefined if GC did not run.
@@ -2315,7 +2412,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2315
2412
  if (this.deltaManager.lastSequenceNumber !== summaryRefSeqNum) {
2316
2413
  return {
2317
2414
  continue: false,
2318
- // eslint-disable-next-line max-len
2319
2415
  error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
2320
2416
  };
2321
2417
  }
@@ -2325,7 +2421,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2325
2421
  if (lastAck !== this.summaryCollection.latestAck) {
2326
2422
  return {
2327
2423
  continue: false,
2328
- // eslint-disable-next-line max-len
2329
2424
  error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
2330
2425
  };
2331
2426
  }
@@ -2587,7 +2682,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2587
2682
  } else if (!this.flushMicroTaskExists) {
2588
2683
  this.flushMicroTaskExists = true;
2589
2684
  // Queue a microtask to detect the end of the turn and force a flush.
2590
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
2591
2685
  Promise.resolve().then(() => {
2592
2686
  this.flushMicroTaskExists = false;
2593
2687
  this.flush();
@@ -2708,17 +2802,42 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2708
2802
  // It should only be done by the summarizerNode, if required.
2709
2803
  // When fetching from storage we will always get the latest version and do not use the ackHandle.
2710
2804
  const snapshotTreeFetcher = async () => {
2711
- const fetchResult = await this.fetchSnapshotFromStorage(
2712
- null,
2805
+ const fetchResult = await this.fetchLatestSnapshotFromStorage(
2713
2806
  summaryLogger,
2714
2807
  {
2715
2808
  eventName: "RefreshLatestSummaryGetSnapshot",
2716
2809
  ackHandle,
2717
2810
  summaryRefSeq,
2718
2811
  fetchLatest: true,
2719
- });
2812
+ },
2813
+ );
2720
2814
 
2721
2815
  const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
2816
+ /**
2817
+ * If the fetched snapshot is older than the one for which the ack was received, close the container.
2818
+ * This should never happen because an ack should be sent after the latest summary is updated in the server.
2819
+ * However, there are couple of scenarios where it's possible:
2820
+ * 1. A file was modified externally resulting in modifying the snapshot's sequence number. This can lead to
2821
+ * the document being unusable and we should not proceed.
2822
+ * 2. The server DB failed after the ack was sent which may delete the corresponding snapshot. Ideally, in
2823
+ * such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
2824
+ * state.
2825
+ */
2826
+ if (latestSnapshotRefSeq < summaryRefSeq) {
2827
+ const error = DataProcessingError.create(
2828
+ "Fetched snapshot is older than the received ack",
2829
+ "RefreshLatestSummaryAck",
2830
+ undefined /* sequencedMessage */,
2831
+ {
2832
+ ackHandle,
2833
+ summaryRefSeq,
2834
+ latestSnapshotRefSeq,
2835
+ },
2836
+ );
2837
+ this.closeFn(error);
2838
+ throw error;
2839
+ }
2840
+
2722
2841
  summaryLogger.sendTelemetryEvent(
2723
2842
  {
2724
2843
  eventName: "LatestSummaryRetrieved",
@@ -2744,7 +2863,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2744
2863
  );
2745
2864
 
2746
2865
  // Notify the garbage collector so it can update its latest summary state.
2747
- await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2866
+ await this.garbageCollector.refreshLatestSummary(
2867
+ result,
2868
+ proposalHandle,
2869
+ summaryRefSeq,
2870
+ readAndParseBlob,
2871
+ );
2748
2872
  }
2749
2873
 
2750
2874
  /**
@@ -2756,11 +2880,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2756
2880
  private async refreshLatestSummaryAckFromServer(
2757
2881
  summaryLogger: ITelemetryLogger,
2758
2882
  ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
2759
- const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
2760
- eventName: "RefreshLatestSummaryGetSnapshot",
2761
- fetchLatest: true,
2762
- },
2763
- FetchSource.noCache,
2883
+ const { snapshotTree, versionId } = await this.fetchLatestSnapshotFromStorage(
2884
+ summaryLogger,
2885
+ {
2886
+ eventName: "RefreshLatestSummaryGetSnapshot",
2887
+ fetchLatest: true,
2888
+ },
2764
2889
  );
2765
2890
 
2766
2891
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
@@ -2775,16 +2900,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2775
2900
  );
2776
2901
 
2777
2902
  // Notify the garbage collector so it can update its latest summary state.
2778
- await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
2903
+ await this.garbageCollector.refreshLatestSummary(
2904
+ result,
2905
+ undefined,
2906
+ latestSnapshotRefSeq,
2907
+ readAndParseBlob,
2908
+ )
2779
2909
 
2780
2910
  return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
2781
2911
  }
2782
2912
 
2783
- private async fetchSnapshotFromStorage(
2784
- versionId: string | null,
2913
+ private async fetchLatestSnapshotFromStorage(
2785
2914
  logger: ITelemetryLogger,
2786
2915
  event: ITelemetryGenericEvent,
2787
- fetchSource?: FetchSource,
2788
2916
  ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
2789
2917
  return PerformanceEvent.timedExecAsync(
2790
2918
  logger, event, async (perfEvent: {
@@ -2797,7 +2925,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2797
2925
  const trace = Trace.start();
2798
2926
 
2799
2927
  const versions = await this.storage.getVersions(
2800
- versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
2928
+ null, 1, "refreshLatestSummaryAckFromServer", FetchSource.noCache);
2801
2929
  assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
2802
2930
  stats.getVersionDuration = trace.trace().duration;
2803
2931
 
@@ -2960,6 +3088,7 @@ const waitForSeq = async (
2960
3088
  ): Promise<void> => new Promise<void>((resolve, reject) => {
2961
3089
  // TODO: remove cast to any when actual event is determined
2962
3090
  deltaManager.on("closed" as any, reject);
3091
+ deltaManager.on("disposed" as any, reject);
2963
3092
 
2964
3093
  // If we already reached target sequence number, simply resolve the promise.
2965
3094
  if (deltaManager.lastSequenceNumber >= targetSeq) {
@@ -61,7 +61,6 @@ import {
61
61
  import {
62
62
  addBlobToSummary,
63
63
  convertSummaryTreeToITree,
64
- packagePathToTelemetryProperty,
65
64
  } from "@fluidframework/runtime-utils";
66
65
  import {
67
66
  ChildLogger,
@@ -88,6 +87,7 @@ import {
88
87
  getFluidDataStoreAttributes,
89
88
  } from "./summaryFormat";
90
89
  import { throwOnTombstoneUsageKey } from "./garbageCollectionConstants";
90
+ import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
91
91
  import { summarizerClientType } from "./summarizerClientElection";
92
92
 
93
93
  function createAttributes(
@@ -775,20 +775,19 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
775
775
 
776
776
  if (checkTombstone && this.tombstoned) {
777
777
  const messageString = `Context is tombstoned! Call site [${callSite}]`;
778
- const error = new DataCorruptionError(messageString, {
779
- errorMessage: messageString,
780
- ...safeTelemetryProps,
781
- });
782
-
783
- // Always log an error when tombstoned data store is used. However, throw an error only if
784
- // throwOnTombstoneUsage is set.
785
- this.mc.logger.sendErrorEvent({
786
- eventName: "GC_Tombstone_DataStore_Changed",
787
- callSite,
788
- pkg: packagePathToTelemetryProperty(this.pkg),
789
- }, error);
790
- // Always log an error when tombstoned data store is used. However, throw an error only if
791
- // throwOnTombstoneUsage is set and the client is not a summarizer.
778
+ const error = new DataCorruptionError(messageString, safeTelemetryProps);
779
+
780
+ sendGCTombstoneEvent(
781
+ this.mc,
782
+ {
783
+ eventName: "GC_Tombstone_DataStore_Changed",
784
+ category: this.throwOnTombstoneUsage ? "error" : "generic",
785
+ isSummarizerClient: this.clientDetails.type === summarizerClientType,
786
+ callSite,
787
+ },
788
+ this.pkg,
789
+ error,
790
+ );
792
791
  if (this.throwOnTombstoneUsage) {
793
792
  throw error;
794
793
  }
@@ -799,7 +798,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
799
798
  return (
800
799
  summarizeInternal: SummarizeInternalFn,
801
800
  getGCDataFn: (fullGC?: boolean) => Promise<IGarbageCollectionData>,
802
- getBaseGCDetailsFn: () => Promise<IGarbageCollectionDetailsBase>,
801
+ getBaseGCDetailsFn?: () => Promise<IGarbageCollectionDetailsBase>,
803
802
  ) => this.summarizerNode.createChild(
804
803
  summarizeInternal,
805
804
  id,