@fluidframework/container-runtime 1.2.3-83900 → 2.0.0-internal.1.0.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 (136) hide show
  1. package/dist/batchTracker.js +1 -1
  2. package/dist/batchTracker.js.map +1 -1
  3. package/dist/blobManager.d.ts +81 -25
  4. package/dist/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager.js +301 -100
  6. package/dist/blobManager.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +66 -49
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +129 -164
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStore.js +29 -24
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +3 -4
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +16 -23
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +6 -3
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +13 -5
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts.map +1 -1
  22. package/dist/garbageCollection.js +17 -12
  23. package/dist/garbageCollection.js.map +1 -1
  24. package/dist/opProperties.d.ts +7 -0
  25. package/dist/opProperties.d.ts.map +1 -0
  26. package/dist/opProperties.js +20 -0
  27. package/dist/opProperties.js.map +1 -0
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.d.ts.map +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/dist/runningSummarizer.d.ts +28 -4
  33. package/dist/runningSummarizer.d.ts.map +1 -1
  34. package/dist/runningSummarizer.js +93 -26
  35. package/dist/runningSummarizer.js.map +1 -1
  36. package/dist/summarizer.d.ts +0 -2
  37. package/dist/summarizer.d.ts.map +1 -1
  38. package/dist/summarizer.js +34 -15
  39. package/dist/summarizer.js.map +1 -1
  40. package/dist/summarizerHeuristics.d.ts +26 -4
  41. package/dist/summarizerHeuristics.d.ts.map +1 -1
  42. package/dist/summarizerHeuristics.js +95 -18
  43. package/dist/summarizerHeuristics.js.map +1 -1
  44. package/dist/summarizerTypes.d.ts +30 -10
  45. package/dist/summarizerTypes.d.ts.map +1 -1
  46. package/dist/summarizerTypes.js.map +1 -1
  47. package/dist/summaryCollection.js +1 -1
  48. package/dist/summaryCollection.js.map +1 -1
  49. package/dist/summaryFormat.d.ts +0 -5
  50. package/dist/summaryFormat.d.ts.map +1 -1
  51. package/dist/summaryFormat.js.map +1 -1
  52. package/dist/summaryGenerator.d.ts +1 -0
  53. package/dist/summaryGenerator.d.ts.map +1 -1
  54. package/dist/summaryGenerator.js +11 -9
  55. package/dist/summaryGenerator.js.map +1 -1
  56. package/lib/batchTracker.js +1 -1
  57. package/lib/batchTracker.js.map +1 -1
  58. package/lib/blobManager.d.ts +81 -25
  59. package/lib/blobManager.d.ts.map +1 -1
  60. package/lib/blobManager.js +302 -101
  61. package/lib/blobManager.js.map +1 -1
  62. package/lib/containerRuntime.d.ts +66 -49
  63. package/lib/containerRuntime.d.ts.map +1 -1
  64. package/lib/containerRuntime.js +131 -166
  65. package/lib/containerRuntime.js.map +1 -1
  66. package/lib/dataStore.js +29 -24
  67. package/lib/dataStore.js.map +1 -1
  68. package/lib/dataStoreContext.d.ts +3 -4
  69. package/lib/dataStoreContext.d.ts.map +1 -1
  70. package/lib/dataStoreContext.js +17 -24
  71. package/lib/dataStoreContext.js.map +1 -1
  72. package/lib/dataStores.d.ts +6 -3
  73. package/lib/dataStores.d.ts.map +1 -1
  74. package/lib/dataStores.js +13 -5
  75. package/lib/dataStores.js.map +1 -1
  76. package/lib/garbageCollection.d.ts.map +1 -1
  77. package/lib/garbageCollection.js +17 -12
  78. package/lib/garbageCollection.js.map +1 -1
  79. package/lib/opProperties.d.ts +7 -0
  80. package/lib/opProperties.d.ts.map +1 -0
  81. package/lib/opProperties.js +16 -0
  82. package/lib/opProperties.js.map +1 -0
  83. package/lib/packageVersion.d.ts +1 -1
  84. package/lib/packageVersion.d.ts.map +1 -1
  85. package/lib/packageVersion.js +1 -1
  86. package/lib/packageVersion.js.map +1 -1
  87. package/lib/runningSummarizer.d.ts +28 -4
  88. package/lib/runningSummarizer.d.ts.map +1 -1
  89. package/lib/runningSummarizer.js +93 -26
  90. package/lib/runningSummarizer.js.map +1 -1
  91. package/lib/summarizer.d.ts +0 -2
  92. package/lib/summarizer.d.ts.map +1 -1
  93. package/lib/summarizer.js +36 -17
  94. package/lib/summarizer.js.map +1 -1
  95. package/lib/summarizerHeuristics.d.ts +26 -4
  96. package/lib/summarizerHeuristics.d.ts.map +1 -1
  97. package/lib/summarizerHeuristics.js +95 -18
  98. package/lib/summarizerHeuristics.js.map +1 -1
  99. package/lib/summarizerTypes.d.ts +30 -10
  100. package/lib/summarizerTypes.d.ts.map +1 -1
  101. package/lib/summarizerTypes.js.map +1 -1
  102. package/lib/summaryCollection.js +1 -1
  103. package/lib/summaryCollection.js.map +1 -1
  104. package/lib/summaryFormat.d.ts +0 -5
  105. package/lib/summaryFormat.d.ts.map +1 -1
  106. package/lib/summaryFormat.js.map +1 -1
  107. package/lib/summaryGenerator.d.ts +1 -0
  108. package/lib/summaryGenerator.d.ts.map +1 -1
  109. package/lib/summaryGenerator.js +11 -9
  110. package/lib/summaryGenerator.js.map +1 -1
  111. package/package.json +55 -21
  112. package/src/batchTracker.ts +1 -1
  113. package/src/blobManager.ts +364 -119
  114. package/src/containerRuntime.ts +232 -216
  115. package/src/dataStore.ts +49 -37
  116. package/src/dataStoreContext.ts +16 -23
  117. package/src/dataStores.ts +27 -16
  118. package/src/garbageCollection.ts +13 -7
  119. package/src/opProperties.ts +19 -0
  120. package/src/packageVersion.ts +1 -1
  121. package/src/runningSummarizer.ts +108 -23
  122. package/src/summarizer.ts +47 -28
  123. package/src/summarizerHeuristics.ts +133 -19
  124. package/src/summarizerTypes.ts +37 -10
  125. package/src/summaryCollection.ts +1 -1
  126. package/src/summaryFormat.ts +0 -6
  127. package/src/summaryGenerator.ts +40 -22
  128. package/dist/opTelemetry.d.ts +0 -22
  129. package/dist/opTelemetry.d.ts.map +0 -1
  130. package/dist/opTelemetry.js +0 -59
  131. package/dist/opTelemetry.js.map +0 -1
  132. package/lib/opTelemetry.d.ts +0 -22
  133. package/lib/opTelemetry.d.ts.map +0 -1
  134. package/lib/opTelemetry.js +0 -55
  135. package/lib/opTelemetry.js.map +0 -1
  136. package/src/opTelemetry.ts +0 -71
@@ -43,9 +43,13 @@ import {
43
43
  TaggedLoggerAdapter,
44
44
  MonitoringContext,
45
45
  loggerToMonitoringContext,
46
- TelemetryDataTag,
47
46
  } from "@fluidframework/telemetry-utils";
48
- import { DriverHeader, IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
47
+ import {
48
+ DriverHeader,
49
+ FetchSource,
50
+ IDocumentStorageService,
51
+ ISummaryContext,
52
+ } from "@fluidframework/driver-definitions";
49
53
  import { readAndParse, isUnpackedRuntimeMessage } from "@fluidframework/driver-utils";
50
54
  import {
51
55
  DataCorruptionError,
@@ -160,7 +164,6 @@ import {
160
164
  } from "./dataStore";
161
165
  import { BindBatchTracker } from "./batchTracker";
162
166
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
163
- import { OpTracker } from "./opTelemetry";
164
167
 
165
168
  export enum ContainerMessageType {
166
169
  // An op to be delivered to store
@@ -224,12 +227,14 @@ export interface ISummaryBaseConfiguration {
224
227
  export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfiguration {
225
228
  state: "enabled";
226
229
  /**
227
- * Defines the maximum allowed time in between summarizations.
230
+ * @deprecated - please move all implementation to minIdleTime and maxIdleTime
228
231
  */
229
232
  idleTime: number;
230
233
  /**
231
- * Defines the maximum allowed time, since the last received Ack, before running the summary
234
+ * Defines the maximum allowed time, since the last received Ack, before running the summary
232
235
  * with reason maxTime.
236
+ * For example, say we receive ops one by one just before the idle time is triggered.
237
+ * In this case, we still want to run a summary since it's been a while since the last summary.
233
238
  */
234
239
  maxTime: number;
235
240
  /**
@@ -242,6 +247,34 @@ export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfigurati
242
247
  * before running the last summary.
243
248
  */
244
249
  minOpsForLastSummaryAttempt: number;
250
+ /**
251
+ * Defines the lower boundary for the allowed time in between summarizations.
252
+ * Pairs with maxIdleTime to form a range.
253
+ * For example, if we only receive 1 op, we don't want to have the same idle time as say 100 ops.
254
+ * Based on the boundaries we set in minIdleTime and maxIdleTime, the idle time will change
255
+ * linearly depending on the number of ops we receive.
256
+ */
257
+ minIdleTime: number;
258
+ /**
259
+ * Defines the upper boundary for the allowed time in between summarizations.
260
+ * Pairs with minIdleTime to form a range.
261
+ * For example, if we only receive 1 op, we don't want to have the same idle time as say 100 ops.
262
+ * Based on the boundaries we set in minIdleTime and maxIdleTime, the idle time will change
263
+ * linearly depending on the number of ops we receive.
264
+ */
265
+ maxIdleTime: number;
266
+ /**
267
+ * Runtime op weight to use in heuristic summarizing.
268
+ * This number is a multiplier on the number of runtime ops we process when running summarize heuristics.
269
+ * For example: (multiplier) * (number of runtime ops) = weighted number of runtime ops
270
+ */
271
+ runtimeOpWeight: number;
272
+ /**
273
+ * Non-runtime op weight to use in heuristic summarizing
274
+ * This number is a multiplier on the number of non-runtime ops we process when running summarize heuristics.
275
+ * For example: (multiplier) * (number of non-runtime ops) = weighted number of non-runtime ops
276
+ */
277
+ nonRuntimeOpWeight: number;
245
278
  }
246
279
 
247
280
  export interface ISummaryConfigurationDisableSummarizer {
@@ -260,44 +293,57 @@ export type ISummaryConfiguration =
260
293
  export const DefaultSummaryConfiguration: ISummaryConfiguration = {
261
294
  state: "enabled",
262
295
 
263
- idleTime: 5000 * 3,
296
+ idleTime: 15 * 1000, // 15 secs.
264
297
 
265
- maxTime: 5000 * 12,
298
+ minIdleTime: 0,
266
299
 
267
- maxOps: 100, // Summarize if 100 ops received since last snapshot.
300
+ maxIdleTime: 30 * 1000, // 30 secs.
301
+
302
+ maxTime: 60 * 1000, // 1 min.
303
+
304
+ maxOps: 100, // Summarize if 100 weighted ops received since last snapshot.
268
305
 
269
306
  minOpsForLastSummaryAttempt: 10,
270
307
 
271
- maxAckWaitTime: 6 * 10 * 1000, // 6 min.
308
+ maxAckWaitTime: 10 * 60 * 1000, // 10 mins.
272
309
 
273
310
  maxOpsSinceLastSummary: 7000,
274
311
 
275
- initialSummarizerDelayMs: 5000, // 5 secs.
312
+ initialSummarizerDelayMs: 5 * 1000, // 5 secs.
276
313
 
277
314
  summarizerClientElection: false,
315
+
316
+ nonRuntimeOpWeight: 0.1,
317
+
318
+ runtimeOpWeight: 1.0,
278
319
  };
279
320
 
280
321
  export interface IGCRuntimeOptions {
281
322
  /**
282
- * Flag that if true, will enable running garbage collection (GC) in a container. GC has mark phase and sweep phase.
283
- * In mark phase, unreferenced objects are identified and marked as such in the summary. This option enables the
284
- * mark phase.
323
+ * Flag that if true, will enable running garbage collection (GC) for a new container.
324
+ *
325
+ * GC has mark phase and sweep phase. In mark phase, unreferenced objects are identified
326
+ * and marked as such in the summary. This option enables the mark phase.
285
327
  * In sweep phase, unreferenced objects are eventually deleted from the container if they meet certain conditions.
286
328
  * Sweep phase can be enabled via the "sweepAllowed" option.
287
- * Note: This setting becomes part of the container's summary and cannot be changed.
329
+ *
330
+ * Note: This setting is persisted in the container's summary and cannot be changed.
288
331
  */
289
332
  gcAllowed?: boolean;
290
333
 
291
334
  /**
292
- * Flag that if true, enables GC's sweep phase which will eventually delete unreferenced objects from the container.
335
+ * Flag that if true, enables GC's sweep phase for a new container.
336
+ *
337
+ * This will allow GC to eventually delete unreferenced objects from the container.
293
338
  * This flag should only be set to true if "gcAllowed" is true.
294
- * Note: This setting becomes part of the container's summary and cannot be changed.
339
+ *
340
+ * Note: This setting is persisted in the container's summary and cannot be changed.
295
341
  */
296
342
  sweepAllowed?: boolean;
297
343
 
298
344
  /**
299
- * Flag that will disable garbage collection if set to true. Can be used to disable running GC on container where
300
- * is allowed via the gcAllowed option.
345
+ * Flag that if true, will disable garbage collection for the session.
346
+ * Can be used to disable running GC on containers where it is allowed via the gcAllowed option.
301
347
  */
302
348
  disableGC?: boolean;
303
349
 
@@ -307,6 +353,13 @@ export interface IGCRuntimeOptions {
307
353
  */
308
354
  runFullGC?: boolean;
309
355
 
356
+ /**
357
+ * Maximum session duration for a new container. If not present, a default value will be used.
358
+ *
359
+ * Note: This setting is persisted in the container's summary and cannot be changed.
360
+ */
361
+ sessionExpiryTimeoutMs?: number;
362
+
310
363
  /**
311
364
  * Allows additional GC options to be passed.
312
365
  */
@@ -318,9 +371,12 @@ export interface ISummaryRuntimeOptions {
318
371
  /** Override summary configurations set by the server. */
319
372
  summaryConfigOverrides?: ISummaryConfiguration;
320
373
 
321
- // Flag that disables putting channels in isolated subtrees for each data store
322
- // and the root node when generating a summary if set to true.
323
- // Defaults to FALSE (enabled) for now.
374
+ /**
375
+ * @deprecated - this option will not be supported on the next versions.
376
+ * Flag that disables putting channels in isolated subtrees for each data store
377
+ * and the root node when generating a summary if set to true.
378
+ * Defaults to FALSE (enabled) for now.
379
+ */
324
380
  disableIsolatedChannels?: boolean;
325
381
 
326
382
  /**
@@ -369,12 +425,6 @@ export interface IContainerRuntimeOptions {
369
425
  * 3. "bypass" will skip the check entirely. This is not recommended.
370
426
  */
371
427
  readonly loadSequenceNumberVerification?: "close" | "log" | "bypass";
372
- /**
373
- * Should the runtime use data store aliasing for creating root datastores.
374
- * In case of aliasing conflicts, the runtime will raise an exception which does
375
- * not effect the status of the container.
376
- */
377
- readonly useDataStoreAliasing?: boolean;
378
428
  /**
379
429
  * Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
380
430
  * send all operations to the driver layer, while in TurnBased the operations will be buffered
@@ -453,7 +503,6 @@ export interface IPendingRuntimeState {
453
503
  savedOps: ISequencedDocumentMessage[];
454
504
  }
455
505
 
456
- const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
457
506
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
458
507
 
459
508
  // Feature gate for the max op size. If the value is negative, chunking is enabled
@@ -466,11 +515,6 @@ const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
466
515
  // to not reach the 1MB limits in socket.io and Kafka.
467
516
  const defaultMaxOpSizeInBytes = 768000;
468
517
 
469
- // By default, the size of the contents for the incoming ops is tracked.
470
- // However, in certain situations, this may incur a performance hit.
471
- // The feature-gate below can be used to disable this feature.
472
- const disableOpTrackingKey = "Fluid.ContainerRuntime.DisableOpTracking";
473
-
474
518
  const defaultFlushMode = FlushMode.TurnBased;
475
519
 
476
520
  export enum RuntimeMessage {
@@ -852,7 +896,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
852
896
  summaryOptions = {},
853
897
  gcOptions = {},
854
898
  loadSequenceNumberVerification = "close",
855
- useDataStoreAliasing = false,
856
899
  flushMode = defaultFlushMode,
857
900
  enableOfflineLoad = false,
858
901
  } = runtimeOptions;
@@ -928,7 +971,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
928
971
  summaryOptions,
929
972
  gcOptions,
930
973
  loadSequenceNumberVerification,
931
- useDataStoreAliasing,
932
974
  flushMode,
933
975
  enableOfflineLoad,
934
976
  },
@@ -1018,7 +1060,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1018
1060
  private readonly summaryCollection: SummaryCollection;
1019
1061
 
1020
1062
  private readonly summarizerNode: IRootSummarizerNodeWithGC;
1021
- private readonly _aliasingEnabled: boolean;
1022
1063
  private readonly _maxOpSizeInBytes: number;
1023
1064
 
1024
1065
  private readonly maxConsecutiveReconnects: number;
@@ -1036,6 +1077,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1036
1077
 
1037
1078
  private consecutiveReconnects = 0;
1038
1079
 
1080
+ /**
1081
+ * Used to delay transition to "connected" state while we upload
1082
+ * attachment blobs that were added while disconnected
1083
+ */
1084
+ private delayConnectClientId?: string;
1085
+
1039
1086
  public get connected(): boolean {
1040
1087
  return this._connected;
1041
1088
  }
@@ -1160,7 +1207,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1160
1207
  * a summary is generated.
1161
1208
  */
1162
1209
  private nextSummaryNumber: number;
1163
- private readonly opTracker: OpTracker;
1164
1210
 
1165
1211
  private constructor(
1166
1212
  private readonly context: IContainerContext,
@@ -1197,16 +1243,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1197
1243
  this.mc = loggerToMonitoringContext(
1198
1244
  ChildLogger.create(this.logger, "ContainerRuntime"));
1199
1245
 
1246
+ if (this.summaryConfiguration.state === "enabled") {
1247
+ this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
1248
+ }
1249
+
1200
1250
  this.summariesDisabled = this.isSummariesDisabled();
1201
1251
  this.heuristicsDisabled = this.isHeuristicsDisabled();
1202
1252
  this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
1203
1253
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
1204
1254
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
1205
1255
 
1206
- this._aliasingEnabled =
1207
- (this.mc.config.getBoolean(useDataStoreAliasingKey) ?? false) ||
1208
- (runtimeOptions.useDataStoreAliasing ?? false);
1209
-
1210
1256
  this._maxOpSizeInBytes = (this.mc.config.getNumber(maxOpSizeInBytesKey) ?? defaultMaxOpSizeInBytes);
1211
1257
  this.maxConsecutiveReconnects =
1212
1258
  this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
@@ -1288,10 +1334,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1288
1334
  this.handleContext,
1289
1335
  blobManagerSnapshot,
1290
1336
  () => this.storage,
1291
- (blobId: string) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }),
1337
+ (blobId, localId) => this.submit(
1338
+ ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId }),
1292
1339
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
1293
1340
  this,
1294
- this.logger,
1295
1341
  );
1296
1342
 
1297
1343
  this.scheduleManager = new ScheduleManager(
@@ -1442,9 +1488,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1442
1488
  createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
1443
1489
  createContainerTimestamp: metadata?.createContainerTimestamp,
1444
1490
  };
1445
- // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
1446
- // not write it, initialize summaryNumber to 0.
1447
- loadSummaryNumber = metadata?.summaryNumber ?? metadata?.summaryCount ?? 0;
1491
+ // summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
1492
+ // the count is reset to 0.
1493
+ loadSummaryNumber = metadata?.summaryNumber ?? 0;
1448
1494
  } else {
1449
1495
  this.createContainerMetadata = {
1450
1496
  createContainerRuntimeVersion: pkgVersion,
@@ -1466,7 +1512,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1466
1512
 
1467
1513
  ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
1468
1514
  BindBatchTracker(this, this.logger);
1469
- this.opTracker = new OpTracker(this.deltaManager, this.mc.config.getBoolean(disableOpTrackingKey) === true);
1470
1515
  }
1471
1516
 
1472
1517
  public dispose(error?: Error): void {
@@ -1546,12 +1591,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1546
1591
  }
1547
1592
 
1548
1593
  if (id === BlobManager.basePath && requestParser.isLeaf(2)) {
1549
- const handle = await this.blobManager.getBlob(requestParser.pathParts[1]);
1550
- if (handle) {
1594
+ const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
1595
+ if (blob) {
1551
1596
  return {
1552
1597
  status: 200,
1553
1598
  mimeType: "fluid/object",
1554
- value: handle.get(),
1599
+ value: blob,
1555
1600
  };
1556
1601
  } else {
1557
1602
  return create404Response(request);
@@ -1573,7 +1618,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1573
1618
  }
1574
1619
 
1575
1620
  private internalId(maybeAlias: string): string {
1576
- return this.dataStores.aliases().get(maybeAlias) ?? maybeAlias;
1621
+ return this.dataStores.aliases.get(maybeAlias) ?? maybeAlias;
1577
1622
  }
1578
1623
 
1579
1624
  private async getDataStoreFromRequest(id: string, request: IRequest): Promise<IFluidRouter> {
@@ -1581,6 +1626,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1581
1626
  ? request.headers?.[RuntimeHeaders.wait]
1582
1627
  : true;
1583
1628
 
1629
+ await this.dataStores.waitIfPendingAlias(id);
1584
1630
  const internalId = this.internalId(id);
1585
1631
  const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
1586
1632
 
@@ -1620,8 +1666,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1620
1666
  private addMetadataToSummary(summaryTree: ISummaryTreeWithStats) {
1621
1667
  const metadata: IContainerRuntimeMetadata = {
1622
1668
  ...this.createContainerMetadata,
1623
- // back-compat 0.59.3000: This is renamed to summaryNumber. Can be removed when 0.59.3000 saturates.
1624
- summaryCount: this.nextSummaryNumber,
1625
1669
  // Increment the summary number for the next summary that will be generated.
1626
1670
  summaryNumber: this.nextSummaryNumber++,
1627
1671
  summaryFormatVersion: 1,
@@ -1647,7 +1691,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1647
1691
  addBlobToSummary(summaryTree, chunksBlobName, content);
1648
1692
  }
1649
1693
 
1650
- const dataStoreAliases = this.dataStores.aliases();
1694
+ const dataStoreAliases = this.dataStores.aliases;
1651
1695
  if (dataStoreAliases.size > 0) {
1652
1696
  addBlobToSummary(summaryTree, aliasBlobName, JSON.stringify([...dataStoreAliases]));
1653
1697
  }
@@ -1757,6 +1801,40 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1757
1801
  }
1758
1802
 
1759
1803
  public setConnectionState(connected: boolean, clientId?: string) {
1804
+ if (connected === false && this.delayConnectClientId !== undefined) {
1805
+ this.delayConnectClientId = undefined;
1806
+ this.mc.logger.sendTelemetryEvent({
1807
+ eventName: "UnsuccessfulConnectedTransition",
1808
+ });
1809
+ // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
1810
+ return;
1811
+ }
1812
+
1813
+ // If attachment blobs were added while disconnected, we need to delay
1814
+ // propagation of the "connected" event until we have uploaded them to
1815
+ // ensure we don't submit ops referencing a blob that has not been uploaded
1816
+ const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
1817
+ if (connecting && this.blobManager.hasPendingOfflineUploads) {
1818
+ assert(!this.delayConnectClientId,
1819
+ 0x392 /* Connect event delay must be canceled before subsequent connect event */);
1820
+ assert(!!clientId, 0x393 /* Must have clientId when connecting */);
1821
+ this.delayConnectClientId = clientId;
1822
+ this.blobManager.onConnected().then(() => {
1823
+ // make sure we didn't reconnect before the promise resolved
1824
+ if (this.delayConnectClientId === clientId && !this.disposed) {
1825
+ this.delayConnectClientId = undefined;
1826
+ this.setConnectionStateCore(connected, clientId);
1827
+ }
1828
+ }, (error) => this.closeFn(error));
1829
+ return;
1830
+ }
1831
+
1832
+ this.setConnectionStateCore(connected, clientId);
1833
+ }
1834
+
1835
+ private setConnectionStateCore(connected: boolean, clientId?: string) {
1836
+ assert(!this.delayConnectClientId,
1837
+ 0x394 /* connect event delay must be cleared before propagating connect event */);
1760
1838
  this.verifyNotClosed();
1761
1839
 
1762
1840
  // There might be no change of state due to Container calling this API after loading runtime.
@@ -1780,11 +1858,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1780
1858
  "Runtime detected too many reconnects with no progress syncing local ops",
1781
1859
  "setConnectionState",
1782
1860
  undefined,
1783
- {
1784
- dataLoss: 1,
1785
- attempts: this.consecutiveReconnects,
1786
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
1787
- }));
1861
+ {
1862
+ dataLoss: 1,
1863
+ attempts: this.consecutiveReconnects,
1864
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1865
+ }));
1788
1866
  return;
1789
1867
  }
1790
1868
  }
@@ -1854,8 +1932,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1854
1932
  this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1855
1933
  break;
1856
1934
  case ContainerMessageType.BlobAttach:
1857
- assert(message?.metadata?.blobId, 0x12a /* "Missing blob id on metadata" */);
1858
- this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1935
+ this.blobManager.processBlobAttachOp(message, local);
1859
1936
  break;
1860
1937
  default:
1861
1938
  }
@@ -1937,6 +2014,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1937
2014
  }
1938
2015
 
1939
2016
  public async getRootDataStore(id: string, wait = true): Promise<IFluidRouter> {
2017
+ return this.getRootDataStoreChannel(id, wait);
2018
+ }
2019
+
2020
+ private async getRootDataStoreChannel(id: string, wait = true): Promise<IFluidDataStoreChannel> {
2021
+ await this.dataStores.waitIfPendingAlias(id);
1940
2022
  const internalId = this.internalId(id);
1941
2023
  const context = await this.dataStores.getDataStore(internalId, wait);
1942
2024
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
@@ -2043,79 +2125,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2043
2125
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2044
2126
  const internalId = uuid();
2045
2127
  return channelToDataStore(
2046
- await this._createDataStore(pkg, false /* isRoot */, internalId),
2128
+ await this._createDataStore(pkg, internalId),
2047
2129
  internalId,
2048
2130
  this,
2049
2131
  this.dataStores,
2050
2132
  this.mc.logger);
2051
2133
  }
2052
2134
 
2053
- /**
2054
- * Creates a root datastore directly with a user generated id and attaches it to storage.
2055
- * It is vulnerable to name collisions and should not be used.
2056
- *
2057
- * This method will be removed. See #6465.
2058
- */
2059
- private async createRootDataStoreLegacy(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
2060
- const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
2061
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
2062
- // older versions, we still have to call bindToContext.
2063
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
2064
- fluidDataStore.makeVisibleAndAttachGraph();
2065
- } else {
2066
- fluidDataStore.bindToContext();
2067
- }
2068
- return fluidDataStore;
2069
- }
2070
-
2071
- /**
2072
- * @deprecated - will be removed in an upcoming release. See #9660.
2073
- */
2074
- public async createRootDataStore(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
2075
- if (rootDataStoreId.includes("/")) {
2076
- throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
2077
- }
2078
- return this._aliasingEnabled === true ?
2079
- this.createAndAliasDataStore(pkg, rootDataStoreId) :
2080
- this.createRootDataStoreLegacy(pkg, rootDataStoreId);
2081
- }
2082
-
2083
- /**
2084
- * Creates a data store then attempts to alias it.
2085
- * If aliasing fails, it will raise an exception.
2086
- *
2087
- * This method will be removed. See #6465.
2088
- *
2089
- * @param pkg - Package name of the data store
2090
- * @param alias - Alias to be assigned to the data store
2091
- * @param props - Properties for the data store
2092
- * @returns - An aliased data store which can can be found / loaded by alias.
2093
- */
2094
- private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<IDataStore> {
2095
- const internalId = uuid();
2096
- const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
2097
- const aliasedDataStore = channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
2098
- const result = await aliasedDataStore.trySetAlias(alias);
2099
- if (result !== "Success") {
2100
- throw new GenericError(
2101
- "dataStoreAliasFailure",
2102
- undefined /* error */,
2103
- {
2104
- alias: {
2105
- value: alias,
2106
- tag: TelemetryDataTag.UserData,
2107
- },
2108
- internalId: {
2109
- value: internalId,
2110
- tag: TelemetryDataTag.PackageData,
2111
- },
2112
- aliasResult: result,
2113
- });
2114
- }
2115
-
2116
- return aliasedDataStore;
2117
- }
2118
-
2119
2135
  public createDetachedRootDataStore(
2120
2136
  pkg: Readonly<string[]>,
2121
2137
  rootDataStoreId: string): IFluidDataStoreContextDetached {
@@ -2129,55 +2145,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2129
2145
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
2130
2146
  }
2131
2147
 
2132
- /**
2133
- * Creates a possibly root datastore directly with a possibly user generated id and attaches it to storage.
2134
- * It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
2135
- *
2136
- * This method will be removed. See #6465.
2137
- */
2138
- private async _createDataStoreWithPropsLegacy(
2148
+ public async _createDataStoreWithProps(
2139
2149
  pkg: string | string[],
2140
2150
  props?: any,
2141
2151
  id = uuid(),
2142
- isRoot = false,
2143
2152
  ): Promise<IDataStore> {
2144
2153
  const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
2145
- Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
2146
- if (isRoot) {
2147
- // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
2148
- // For older versions, we still have to call bindToContext.
2149
- if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
2150
- fluidDataStore.makeVisibleAndAttachGraph();
2151
- } else {
2152
- fluidDataStore.bindToContext();
2153
- }
2154
- this.logger.sendTelemetryEvent({
2155
- eventName: "Root datastore with props",
2156
- hasProps: props !== undefined,
2157
- });
2158
- }
2154
+ Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
2159
2155
  return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
2160
2156
  }
2161
2157
 
2162
- public async _createDataStoreWithProps(
2163
- pkg: string | string[],
2164
- props?: any,
2165
- id = uuid(),
2166
- isRoot = false,
2167
- ): Promise<IDataStore> {
2168
- return this._aliasingEnabled === true && isRoot ?
2169
- this.createAndAliasDataStore(pkg, id, props) :
2170
- this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
2171
- }
2172
-
2173
2158
  private async _createDataStore(
2174
2159
  pkg: string | string[],
2175
- isRoot: boolean,
2176
2160
  id = uuid(),
2177
2161
  props?: any,
2178
2162
  ): Promise<IFluidDataStoreChannel> {
2179
2163
  return this.dataStores
2180
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props)
2164
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2181
2165
  .realize();
2182
2166
  }
2183
2167
 
@@ -2534,21 +2518,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2534
2518
  },
2535
2519
  );
2536
2520
 
2521
+ let latestSnapshotVersionId: string | undefined;
2537
2522
  if (refreshLatestAck) {
2538
- const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(
2523
+ const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
2539
2524
  ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
2525
+ const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2526
+ latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
2540
2527
 
2541
- if (latestSummaryRefSeq > this.deltaManager.lastSequenceNumber) {
2528
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2542
2529
  // We need to catch up to the latest summary's reference sequence number before pausing.
2543
2530
  await PerformanceEvent.timedExecAsync(
2544
2531
  summaryNumberLogger,
2545
2532
  {
2546
2533
  eventName: "WaitingForSeq",
2547
2534
  lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2548
- targetSequenceNumber: latestSummaryRefSeq,
2535
+ targetSequenceNumber: latestSnapshotRefSeq,
2549
2536
  lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2550
2537
  },
2551
- async () => waitForSeq(this.deltaManager, latestSummaryRefSeq),
2538
+ async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2552
2539
  { start: true, end: true, cancel: "error" }, // definitely want start event
2553
2540
  );
2554
2541
  }
@@ -2560,15 +2547,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2560
2547
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
2561
2548
  const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
2562
2549
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
2563
-
2564
- // We should be here is we haven't processed be here. If we are of if the last message's sequence number
2565
- // doesn't match the last processed sequence number, log an error.
2566
- if (summaryRefSeqNum !== this.deltaManager.lastMessage?.sequenceNumber) {
2567
- summaryNumberLogger.sendErrorEvent({
2568
- eventName: "LastSequenceMismatch",
2569
- error: message,
2570
- });
2571
- }
2550
+ const lastAck = this.summaryCollection.latestAck;
2572
2551
 
2573
2552
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger);
2574
2553
 
@@ -2598,6 +2577,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2598
2577
  error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
2599
2578
  };
2600
2579
  }
2580
+ assert(summaryRefSeqNum === this.deltaManager.lastMessage?.sequenceNumber,
2581
+ 0x395 /* it's one and the same thing */);
2582
+
2583
+ if (lastAck !== this.summaryCollection.latestAck) {
2584
+ return {
2585
+ continue: false,
2586
+ // eslint-disable-next-line max-len
2587
+ error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
2588
+ };
2589
+ }
2601
2590
  return { continue: true };
2602
2591
  };
2603
2592
 
@@ -2654,8 +2643,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2654
2643
  gcStateUpdatedDataStoreCount: summarizeResult.gcStats?.updatedDataStoreCount,
2655
2644
  gcBlobNodeCount: gcSummaryTreeStats?.blobNodeCount,
2656
2645
  gcTotalBlobsSize: gcSummaryTreeStats?.totalBlobSize,
2657
- opsSizesSinceLastSummary: this.opTracker.opsSizeAccumulator,
2658
- nonSystemOpsSinceLastSummary: this.opTracker.nonSystemOpCount,
2659
2646
  summaryNumber,
2660
2647
  ...partialStats,
2661
2648
  };
@@ -2673,19 +2660,32 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2673
2660
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
2674
2661
  }
2675
2662
 
2676
- const lastAck = this.summaryCollection.latestAck;
2677
- const summaryContext: ISummaryContext =
2678
- lastAck === undefined
2679
- ? {
2680
- proposalHandle: undefined,
2681
- ackHandle: this.context.getLoadedFromVersion()?.id,
2682
- referenceSequenceNumber: summaryRefSeqNum,
2683
- }
2684
- : {
2685
- proposalHandle: lastAck.summaryOp.contents.handle,
2686
- ackHandle: lastAck.summaryAck.contents.handle,
2687
- referenceSequenceNumber: summaryRefSeqNum,
2688
- };
2663
+ // It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
2664
+ // summary. So if the previous summarizer closes just after submitting the summary and before
2665
+ // submitting the summaryOp then we can't rely on summaryAck. So in case we have
2666
+ // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
2667
+ // the one fetched from storage as parent as that is the latest.
2668
+ let summaryContext: ISummaryContext;
2669
+ if (lastAck?.summaryAck.contents.handle !== latestSnapshotVersionId
2670
+ && latestSnapshotVersionId !== undefined) {
2671
+ summaryContext = {
2672
+ proposalHandle: undefined,
2673
+ ackHandle: latestSnapshotVersionId,
2674
+ referenceSequenceNumber: summaryRefSeqNum,
2675
+ };
2676
+ } else if (lastAck === undefined) {
2677
+ summaryContext = {
2678
+ proposalHandle: undefined,
2679
+ ackHandle: this.context.getLoadedFromVersion()?.id,
2680
+ referenceSequenceNumber: summaryRefSeqNum,
2681
+ };
2682
+ } else {
2683
+ summaryContext = {
2684
+ proposalHandle: lastAck.summaryOp.contents.handle,
2685
+ ackHandle: lastAck.summaryAck.contents.handle,
2686
+ referenceSequenceNumber: summaryRefSeqNum,
2687
+ };
2688
+ }
2689
2689
 
2690
2690
  let handle: string;
2691
2691
  try {
@@ -2728,7 +2728,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2728
2728
  } as const;
2729
2729
 
2730
2730
  this.summarizerNode.completeSummary(handle);
2731
- this.opTracker.reset();
2732
2731
  return submitData;
2733
2732
  } finally {
2734
2733
  // Cleanup wip summary in case of failure
@@ -2892,14 +2891,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2892
2891
  "OpTooLarge",
2893
2892
  /* error */ undefined,
2894
2893
  {
2895
- length: {
2896
- value: serializedContent.length,
2897
- tag: TelemetryDataTag.PackageData,
2898
- },
2899
- limit: {
2900
- value: this._maxOpSizeInBytes,
2901
- tag: TelemetryDataTag.PackageData,
2902
- },
2894
+ length: serializedContent.length,
2895
+ limit: this._maxOpSizeInBytes,
2903
2896
  }));
2904
2897
  return -1;
2905
2898
  }
@@ -3005,7 +2998,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3005
2998
  case ContainerMessageType.ChunkedOp:
3006
2999
  throw new Error(`chunkedOp not expected here`);
3007
3000
  case ContainerMessageType.BlobAttach:
3008
- this.submit(type, content, localOpMetadata, opMetadata);
3001
+ this.blobManager.reSubmit(opMetadata);
3009
3002
  break;
3010
3003
  case ContainerMessageType.Rejoin:
3011
3004
  this.submit(type, content);
@@ -3039,15 +3032,20 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3039
3032
  summaryLogger: ITelemetryLogger,
3040
3033
  ) {
3041
3034
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3042
- const result = await this.summarizerNode.refreshLatestSummary(
3043
- proposalHandle,
3044
- summaryRefSeq,
3045
- async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
3035
+ const { snapshotTree } = await this.fetchSnapshotFromStorage(
3036
+ ackHandle,
3037
+ summaryLogger,
3038
+ {
3046
3039
  eventName: "RefreshLatestSummaryGetSnapshot",
3047
3040
  ackHandle,
3048
3041
  summaryRefSeq,
3049
3042
  fetchLatest: false,
3050
- }),
3043
+ },
3044
+ );
3045
+ const result = await this.summarizerNode.refreshLatestSummary(
3046
+ proposalHandle,
3047
+ summaryRefSeq,
3048
+ async () => snapshotTree,
3051
3049
  readAndParseBlob,
3052
3050
  summaryLogger,
3053
3051
  );
@@ -3062,19 +3060,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3062
3060
  * @param summaryLogger - logger to use when fetching snapshot from storage
3063
3061
  * @returns downloaded snapshot's reference sequence number
3064
3062
  */
3065
- private async refreshLatestSummaryAckFromServer(summaryLogger: ITelemetryLogger): Promise<number> {
3066
- const snapshot = await this.fetchSnapshotFromStorage(null, summaryLogger, {
3063
+ private async refreshLatestSummaryAckFromServer(
3064
+ summaryLogger: ITelemetryLogger,
3065
+ ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
3066
+ const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
3067
3067
  eventName: "RefreshLatestSummaryGetSnapshot",
3068
3068
  fetchLatest: true,
3069
- });
3069
+ },
3070
+ FetchSource.noCache,
3071
+ );
3070
3072
 
3071
3073
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3072
- const snapshotRefSeq = await seqFromTree(snapshot, readAndParseBlob);
3074
+ const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
3073
3075
 
3074
3076
  const result = await this.summarizerNode.refreshLatestSummary(
3075
3077
  undefined,
3076
- snapshotRefSeq,
3077
- async () => snapshot,
3078
+ latestSnapshotRefSeq,
3079
+ async () => snapshotTree,
3078
3080
  readAndParseBlob,
3079
3081
  summaryLogger,
3080
3082
  );
@@ -3082,11 +3084,15 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3082
3084
  // Notify the garbage collector so it can update its latest summary state.
3083
3085
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
3084
3086
 
3085
- return snapshotRefSeq;
3087
+ return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
3086
3088
  }
3087
3089
 
3088
3090
  private async fetchSnapshotFromStorage(
3089
- versionId: string | null, logger: ITelemetryLogger, event: ITelemetryGenericEvent) {
3091
+ versionId: string | null,
3092
+ logger: ITelemetryLogger,
3093
+ event: ITelemetryGenericEvent,
3094
+ fetchSource?: FetchSource,
3095
+ ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
3090
3096
  return PerformanceEvent.timedExecAsync(
3091
3097
  logger, event, async (perfEvent: {
3092
3098
  end: (arg0: {
@@ -3097,7 +3103,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3097
3103
  const stats: { getVersionDuration?: number; getSnapshotDuration?: number; } = {};
3098
3104
  const trace = Trace.start();
3099
3105
 
3100
- const versions = await this.storage.getVersions(versionId, 1);
3106
+ const versions = await this.storage.getVersions(
3107
+ versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
3101
3108
  assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
3102
3109
  stats.getVersionDuration = trace.trace().duration;
3103
3110
 
@@ -3106,7 +3113,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3106
3113
  stats.getSnapshotDuration = trace.trace().duration;
3107
3114
 
3108
3115
  perfEvent.end(stats);
3109
- return maybeSnapshot;
3116
+ return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
3110
3117
  });
3111
3118
  }
3112
3119
 
@@ -3218,6 +3225,15 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3218
3225
  // don't have any more saved ops
3219
3226
  await this.pendingStateManager.applyStashedOpsAt();
3220
3227
  }
3228
+
3229
+ private validateSummaryHeuristicConfiguration(configuration: ISummaryConfigurationHeuristics) {
3230
+ // eslint-disable-next-line no-restricted-syntax
3231
+ for (const prop in configuration) {
3232
+ if (typeof configuration[prop] === "number" && configuration[prop] < 0) {
3233
+ throw new UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
3234
+ }
3235
+ }
3236
+ }
3221
3237
  }
3222
3238
 
3223
3239
  /**