@fluidframework/container-runtime 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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 (183) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +3 -2
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/connectionTelemetry.d.ts.map +1 -1
  6. package/dist/connectionTelemetry.js +1 -0
  7. package/dist/connectionTelemetry.js.map +1 -1
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +20 -11
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStoreContext.d.ts.map +1 -1
  12. package/dist/dataStoreContext.js +1 -2
  13. package/dist/dataStoreContext.js.map +1 -1
  14. package/dist/dataStores.d.ts +5 -5
  15. package/dist/dataStores.d.ts.map +1 -1
  16. package/dist/dataStores.js +3 -6
  17. package/dist/dataStores.js.map +1 -1
  18. package/dist/gc/garbageCollection.d.ts +56 -70
  19. package/dist/gc/garbageCollection.d.ts.map +1 -1
  20. package/dist/gc/garbageCollection.js +227 -408
  21. package/dist/gc/garbageCollection.js.map +1 -1
  22. package/dist/gc/gcConfigs.d.ts.map +1 -1
  23. package/dist/gc/gcConfigs.js +8 -10
  24. package/dist/gc/gcConfigs.js.map +1 -1
  25. package/dist/gc/gcDefinitions.d.ts +2 -0
  26. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  27. package/dist/gc/gcDefinitions.js.map +1 -1
  28. package/dist/gc/gcHelpers.d.ts +11 -11
  29. package/dist/gc/gcHelpers.d.ts.map +1 -1
  30. package/dist/gc/gcHelpers.js +18 -22
  31. package/dist/gc/gcHelpers.js.map +1 -1
  32. package/dist/gc/gcSummaryStateTracker.d.ts +6 -2
  33. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  34. package/dist/gc/gcSummaryStateTracker.js +16 -6
  35. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  36. package/dist/gc/gcTelemetry.d.ts +91 -0
  37. package/dist/gc/gcTelemetry.d.ts.map +1 -0
  38. package/dist/gc/gcTelemetry.js +282 -0
  39. package/dist/gc/gcTelemetry.js.map +1 -0
  40. package/dist/gc/index.d.ts +2 -2
  41. package/dist/gc/index.d.ts.map +1 -1
  42. package/dist/gc/index.js +5 -6
  43. package/dist/gc/index.js.map +1 -1
  44. package/dist/opLifecycle/opGroupingManager.js +1 -1
  45. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  46. package/dist/opLifecycle/outbox.js +1 -1
  47. package/dist/opLifecycle/outbox.js.map +1 -1
  48. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  49. package/dist/opLifecycle/remoteMessageProcessor.js +25 -22
  50. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  51. package/dist/packageVersion.d.ts +1 -1
  52. package/dist/packageVersion.js +1 -1
  53. package/dist/packageVersion.js.map +1 -1
  54. package/dist/pendingStateManager.d.ts +1 -1
  55. package/dist/pendingStateManager.d.ts.map +1 -1
  56. package/dist/pendingStateManager.js.map +1 -1
  57. package/dist/scheduleManager.js +15 -4
  58. package/dist/scheduleManager.js.map +1 -1
  59. package/dist/summary/orderedClientElection.d.ts.map +1 -1
  60. package/dist/summary/orderedClientElection.js +14 -17
  61. package/dist/summary/orderedClientElection.js.map +1 -1
  62. package/dist/summary/summarizer.d.ts +2 -0
  63. package/dist/summary/summarizer.d.ts.map +1 -1
  64. package/dist/summary/summarizer.js +9 -4
  65. package/dist/summary/summarizer.js.map +1 -1
  66. package/dist/summary/summarizerHeuristics.d.ts +8 -9
  67. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  68. package/dist/summary/summarizerHeuristics.js +15 -16
  69. package/dist/summary/summarizerHeuristics.js.map +1 -1
  70. package/dist/summary/summarizerTypes.d.ts +2 -0
  71. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  72. package/dist/summary/summarizerTypes.js.map +1 -1
  73. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  74. package/dist/summary/summaryGenerator.js +4 -3
  75. package/dist/summary/summaryGenerator.js.map +1 -1
  76. package/lib/blobManager.d.ts.map +1 -1
  77. package/lib/blobManager.js +3 -2
  78. package/lib/blobManager.js.map +1 -1
  79. package/lib/connectionTelemetry.d.ts.map +1 -1
  80. package/lib/connectionTelemetry.js +1 -0
  81. package/lib/connectionTelemetry.js.map +1 -1
  82. package/lib/containerRuntime.d.ts.map +1 -1
  83. package/lib/containerRuntime.js +20 -11
  84. package/lib/containerRuntime.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts.map +1 -1
  86. package/lib/dataStoreContext.js +1 -2
  87. package/lib/dataStoreContext.js.map +1 -1
  88. package/lib/dataStores.d.ts +5 -5
  89. package/lib/dataStores.d.ts.map +1 -1
  90. package/lib/dataStores.js +3 -6
  91. package/lib/dataStores.js.map +1 -1
  92. package/lib/gc/garbageCollection.d.ts +56 -70
  93. package/lib/gc/garbageCollection.d.ts.map +1 -1
  94. package/lib/gc/garbageCollection.js +231 -412
  95. package/lib/gc/garbageCollection.js.map +1 -1
  96. package/lib/gc/gcConfigs.d.ts.map +1 -1
  97. package/lib/gc/gcConfigs.js +8 -10
  98. package/lib/gc/gcConfigs.js.map +1 -1
  99. package/lib/gc/gcDefinitions.d.ts +2 -0
  100. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  101. package/lib/gc/gcDefinitions.js.map +1 -1
  102. package/lib/gc/gcHelpers.d.ts +11 -11
  103. package/lib/gc/gcHelpers.d.ts.map +1 -1
  104. package/lib/gc/gcHelpers.js +16 -20
  105. package/lib/gc/gcHelpers.js.map +1 -1
  106. package/lib/gc/gcSummaryStateTracker.d.ts +6 -2
  107. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  108. package/lib/gc/gcSummaryStateTracker.js +16 -6
  109. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  110. package/lib/gc/gcTelemetry.d.ts +91 -0
  111. package/lib/gc/gcTelemetry.d.ts.map +1 -0
  112. package/lib/gc/gcTelemetry.js +277 -0
  113. package/lib/gc/gcTelemetry.js.map +1 -0
  114. package/lib/gc/index.d.ts +2 -2
  115. package/lib/gc/index.d.ts.map +1 -1
  116. package/lib/gc/index.js +2 -2
  117. package/lib/gc/index.js.map +1 -1
  118. package/lib/opLifecycle/opGroupingManager.js +1 -1
  119. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  120. package/lib/opLifecycle/outbox.js +1 -1
  121. package/lib/opLifecycle/outbox.js.map +1 -1
  122. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  123. package/lib/opLifecycle/remoteMessageProcessor.js +25 -22
  124. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  125. package/lib/packageVersion.d.ts +1 -1
  126. package/lib/packageVersion.js +1 -1
  127. package/lib/packageVersion.js.map +1 -1
  128. package/lib/pendingStateManager.d.ts +1 -1
  129. package/lib/pendingStateManager.d.ts.map +1 -1
  130. package/lib/pendingStateManager.js.map +1 -1
  131. package/lib/scheduleManager.js +15 -4
  132. package/lib/scheduleManager.js.map +1 -1
  133. package/lib/summary/orderedClientElection.d.ts.map +1 -1
  134. package/lib/summary/orderedClientElection.js +14 -17
  135. package/lib/summary/orderedClientElection.js.map +1 -1
  136. package/lib/summary/summarizer.d.ts +2 -0
  137. package/lib/summary/summarizer.d.ts.map +1 -1
  138. package/lib/summary/summarizer.js +9 -4
  139. package/lib/summary/summarizer.js.map +1 -1
  140. package/lib/summary/summarizerHeuristics.d.ts +8 -9
  141. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  142. package/lib/summary/summarizerHeuristics.js +15 -16
  143. package/lib/summary/summarizerHeuristics.js.map +1 -1
  144. package/lib/summary/summarizerTypes.d.ts +2 -0
  145. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  146. package/lib/summary/summarizerTypes.js.map +1 -1
  147. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  148. package/lib/summary/summaryGenerator.js +4 -3
  149. package/lib/summary/summaryGenerator.js.map +1 -1
  150. package/package.json +15 -16
  151. package/src/blobManager.ts +3 -2
  152. package/src/connectionTelemetry.ts +1 -0
  153. package/src/containerRuntime.ts +22 -15
  154. package/src/dataStoreContext.ts +1 -2
  155. package/src/dataStores.ts +4 -7
  156. package/src/gc/garbageCollection.ts +316 -561
  157. package/src/gc/gcConfigs.ts +12 -11
  158. package/src/gc/gcDefinitions.ts +2 -0
  159. package/src/gc/gcHelpers.ts +21 -40
  160. package/src/gc/gcSummaryStateTracker.ts +19 -7
  161. package/src/gc/gcTelemetry.ts +408 -0
  162. package/src/gc/index.ts +2 -6
  163. package/src/opLifecycle/README.md +13 -0
  164. package/src/opLifecycle/opGroupingManager.ts +1 -1
  165. package/src/opLifecycle/outbox.ts +2 -2
  166. package/src/opLifecycle/remoteMessageProcessor.ts +37 -28
  167. package/src/packageVersion.ts +1 -1
  168. package/src/pendingStateManager.ts +1 -4
  169. package/src/scheduleManager.ts +19 -7
  170. package/src/summary/orderedClientElection.ts +14 -17
  171. package/src/summary/summarizer.ts +17 -5
  172. package/src/summary/summarizerHeuristics.ts +15 -16
  173. package/src/summary/summarizerTypes.ts +2 -0
  174. package/src/summary/summaryGenerator.ts +5 -4
  175. package/dist/gc/gcSweepReadyUsageDetection.d.ts +0 -53
  176. package/dist/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
  177. package/dist/gc/gcSweepReadyUsageDetection.js +0 -130
  178. package/dist/gc/gcSweepReadyUsageDetection.js.map +0 -1
  179. package/lib/gc/gcSweepReadyUsageDetection.d.ts +0 -53
  180. package/lib/gc/gcSweepReadyUsageDetection.d.ts.map +0 -1
  181. package/lib/gc/gcSweepReadyUsageDetection.js +0 -125
  182. package/lib/gc/gcSweepReadyUsageDetection.js.map +0 -1
  183. package/src/gc/gcSweepReadyUsageDetection.ts +0 -145
@@ -39,7 +39,6 @@ export class OpGroupingManager {
39
39
  }
40
40
  }
41
41
 
42
- // Need deserializedContent for back-compat
43
42
  const deserializedContent = {
44
43
  type: OpGroupingManager.groupedBatchOp,
45
44
  contents: batch.content.map<IGroupedMessage>((message) => ({
@@ -56,6 +55,7 @@ export class OpGroupingManager {
56
55
  localOpMetadata: undefined,
57
56
  metadata: undefined,
58
57
  referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
58
+ // Need deserializedContent for back-compat
59
59
  deserializedContent: deserializedContent as ContainerRuntimeMessage,
60
60
  contents: JSON.stringify(deserializedContent),
61
61
  },
@@ -219,8 +219,8 @@ export class Outbox {
219
219
  return this.params.groupingManager.groupBatch(batch);
220
220
  }
221
221
 
222
- const compressedBatch = this.params.groupingManager.groupBatch(
223
- this.params.compressor.compressBatch(batch),
222
+ const compressedBatch = this.params.compressor.compressBatch(
223
+ this.params.groupingManager.groupBatch(batch),
224
224
  );
225
225
 
226
226
  if (this.params.splitter.isBatchChunkingEnabled) {
@@ -27,40 +27,49 @@ export class RemoteMessageProcessor {
27
27
  public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
28
28
  const result: ISequencedDocumentMessage[] = [];
29
29
 
30
- // Ungroup before processing chunks
31
- for (let ungroupedMessage of this.opGroupingManager.ungroupOp(copy(remoteMessage))) {
32
- ungroupedMessage = this.opDecompressor.processMessage(ungroupedMessage).message;
33
- unpackRuntimeMessage(ungroupedMessage);
34
-
35
- const chunkProcessingResult = this.opSplitter.processRemoteMessage(ungroupedMessage);
36
- ungroupedMessage = chunkProcessingResult.message;
37
- if (chunkProcessingResult.state !== "Processed") {
38
- // If the message is not chunked or if the splitter is still rebuilding the original message,
39
- // there is no need to continue processing
40
- result.push(ungroupedMessage);
41
- continue;
42
- }
43
-
44
- // Ungroup the chunked message before decompressing
45
- for (let ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
46
- ungroupedMessage,
47
- )) {
48
- const decompressionAfterChunking = this.opDecompressor.processMessage(
49
- ungroupedMessageAfterChunking,
50
- );
51
- ungroupedMessageAfterChunking = decompressionAfterChunking.message;
52
- if (decompressionAfterChunking.state === "Skipped") {
53
- // After chunking, if the original message was not compressed,
30
+ // Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
31
+ for (const ungroupedMessage of this.opGroupingManager.ungroupOp(copy(remoteMessage))) {
32
+ const message = this.opDecompressor.processMessage(ungroupedMessage).message;
33
+
34
+ for (let ungroupedMessage2 of this.opGroupingManager.ungroupOp(message)) {
35
+ unpackRuntimeMessage(ungroupedMessage2);
36
+
37
+ const chunkProcessingResult =
38
+ this.opSplitter.processRemoteMessage(ungroupedMessage2);
39
+ ungroupedMessage2 = chunkProcessingResult.message;
40
+ if (chunkProcessingResult.state !== "Processed") {
41
+ // If the message is not chunked or if the splitter is still rebuilding the original message,
54
42
  // there is no need to continue processing
55
- result.push(ungroupedMessageAfterChunking);
43
+ result.push(ungroupedMessage2);
56
44
  continue;
57
45
  }
58
46
 
59
- // The message needs to be unpacked after chunking + decompression
60
- unpack(ungroupedMessageAfterChunking);
61
- result.push(ungroupedMessageAfterChunking);
47
+ // Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
48
+ for (const ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
49
+ ungroupedMessage2,
50
+ )) {
51
+ const decompressionAfterChunking = this.opDecompressor.processMessage(
52
+ ungroupedMessageAfterChunking,
53
+ );
54
+
55
+ for (const ungroupedMessageAfterChunking2 of this.opGroupingManager.ungroupOp(
56
+ decompressionAfterChunking.message,
57
+ )) {
58
+ if (decompressionAfterChunking.state === "Skipped") {
59
+ // After chunking, if the original message was not compressed,
60
+ // there is no need to continue processing
61
+ result.push(ungroupedMessageAfterChunking2);
62
+ continue;
63
+ }
64
+
65
+ // The message needs to be unpacked after chunking + decompression
66
+ unpack(ungroupedMessageAfterChunking2);
67
+ result.push(ungroupedMessageAfterChunking2);
68
+ }
69
+ }
62
70
  }
63
71
  }
72
+
64
73
  return result;
65
74
  }
66
75
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.4.2.1";
9
+ export const pkgVersion = "2.0.0-internal.4.4.0";
@@ -49,10 +49,7 @@ export interface IRuntimeStateHandler {
49
49
  connected(): boolean;
50
50
  clientId(): string | undefined;
51
51
  close(error?: ICriticalContainerError): void;
52
- applyStashedOp: (
53
- type: ContainerMessageType,
54
- content: ISequencedDocumentMessage,
55
- ) => Promise<unknown>;
52
+ applyStashedOp: (type: ContainerMessageType, content: unknown) => Promise<unknown>;
56
53
  reSubmit(
57
54
  type: ContainerMessageType,
58
55
  content: any,
@@ -147,14 +147,14 @@ class ScheduleManagerCore {
147
147
  // We are intentionally directly listening to the "op" to inspect system ops as well.
148
148
  // If we do not observe system ops, we are likely to hit 0x296 assert when system ops
149
149
  // precedes start of incomplete batch.
150
- this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
150
+ this.deltaManager.on("op", (message) => this.afterOpProcessing(message));
151
151
  }
152
152
 
153
153
  /**
154
154
  * The only public function in this class - called when we processed an op,
155
155
  * to make decision if op processing should be paused or not after that.
156
156
  */
157
- public afterOpProcessing(sequenceNumber: number) {
157
+ public afterOpProcessing(message: ISequencedDocumentMessage) {
158
158
  assert(
159
159
  !this.localPaused,
160
160
  0x294 /* "can't have op processing paused if we are processing an op" */,
@@ -177,12 +177,24 @@ class ScheduleManagerCore {
177
177
 
178
178
  // do we have incomplete batch to worry about?
179
179
  if (this.pauseSequenceNumber !== undefined) {
180
- assert(
181
- sequenceNumber < this.pauseSequenceNumber,
182
- 0x296 /* "we should never start processing incomplete batch!" */,
183
- );
180
+ if (message.sequenceNumber >= this.pauseSequenceNumber) {
181
+ throw DataProcessingError.create(
182
+ // Former assert 0x296
183
+ "Incomplete batch",
184
+ "ScheduleManager",
185
+ message,
186
+ {
187
+ type: message.type,
188
+ contentType: typeof message.contents,
189
+ batch: message.metadata?.batch,
190
+ compression: message.compression,
191
+ pauseSeqNum: this.pauseSequenceNumber,
192
+ },
193
+ );
194
+ }
195
+
184
196
  // If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
185
- if (sequenceNumber + 1 === this.pauseSequenceNumber) {
197
+ if (message.sequenceNumber + 1 === this.pauseSequenceNumber) {
186
198
  this.pauseQueue();
187
199
  }
188
200
  }
@@ -416,14 +416,13 @@ export class OrderedClientElection
416
416
  change = true;
417
417
  }
418
418
  if (change) {
419
- if (isSummarizerClient) {
420
- this.logger.sendTelemetryEvent({
421
- eventName: "SummarizerClientElected",
422
- electedClientId: this._electedClient?.clientId,
423
- electedParentId: this._electedParent?.clientId,
424
- electionSequenceNumber: sequenceNumber,
425
- });
426
- }
419
+ this.logger.sendTelemetryEvent({
420
+ eventName: "SummarizerClientElected",
421
+ electedClientId: this._electedClient?.clientId,
422
+ electedParentId: this._electedParent?.clientId,
423
+ electionSequenceNumber: sequenceNumber,
424
+ isSummarizerClient,
425
+ });
427
426
  this.emit("election", client, sequenceNumber, prevClient);
428
427
  }
429
428
  }
@@ -431,14 +430,12 @@ export class OrderedClientElection
431
430
  private tryElectingParent(client: ILinkedClient | undefined, sequenceNumber: number): void {
432
431
  if (this._electedParent !== client) {
433
432
  this._electedParent = client;
434
- if (client?.client.details.type === summarizerClientType) {
435
- this.logger.sendTelemetryEvent({
436
- eventName: "SummarizerParentElected",
437
- electedClientId: this._electedClient?.clientId,
438
- electedParentId: this._electedParent?.clientId,
439
- electionSequenceNumber: sequenceNumber,
440
- });
441
- }
433
+ this.logger.sendTelemetryEvent({
434
+ eventName: "SummarizerParentElected",
435
+ electedClientId: this._electedClient?.clientId,
436
+ electedParentId: this._electedParent?.clientId,
437
+ electionSequenceNumber: sequenceNumber,
438
+ });
442
439
  this.emit("election", this._electedClient, sequenceNumber, this._electedClient);
443
440
  }
444
441
  }
@@ -473,7 +470,7 @@ export class OrderedClientElection
473
470
  const newClientIsSummarizer = client.client.details.type === summarizerClientType;
474
471
  const electedClientIsSummarizer =
475
472
  this._electedClient?.client.details.type === summarizerClientType;
476
- // Note that we allow a summarizer client to supercede an interactive client as elected client.
473
+ // Note that we allow a summarizer client to supersede an interactive client as elected client.
477
474
  if (
478
475
  this._electedClient === undefined ||
479
476
  (!electedClientIsSummarizer && newClientIsSummarizer)
@@ -25,6 +25,7 @@ import { RunningSummarizer } from "./runningSummarizer";
25
25
  import {
26
26
  IConnectableRuntime,
27
27
  ISummarizer,
28
+ ISummarizeHeuristicData,
28
29
  ISummarizerInternalsProvider,
29
30
  ISummarizerRuntime,
30
31
  ISummarizingWarning,
@@ -215,6 +216,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
215
216
  return stopReason === "parentNotConnected";
216
217
  }
217
218
 
219
+ private _heuristicData: ISummarizeHeuristicData | undefined;
220
+
218
221
  /**
219
222
  * Put the summarizer in a started state, including creating and initializing the RunningSummarizer.
220
223
  * The start request can come either from the SummaryManager (in the auto-summarize case) or from the user
@@ -252,17 +255,22 @@ export class Summarizer extends EventEmitter implements ISummarizer {
252
255
  throw new UsageError("clientId should be defined if connected.");
253
256
  }
254
257
 
258
+ this._heuristicData = new SummarizeHeuristicData(
259
+ this.runtime.deltaManager.lastSequenceNumber,
260
+ {
261
+ /** summary attempt baseline for heuristics */
262
+ refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
263
+ summaryTime: Date.now(),
264
+ } as const,
265
+ );
266
+
255
267
  const runningSummarizer = await RunningSummarizer.start(
256
268
  this.logger,
257
269
  this.summaryCollection.createWatcher(clientId),
258
270
  this.configurationGetter(),
259
271
  async (...args) => this.internalsProvider.submitSummary(...args), // submitSummaryCallback
260
272
  async (...args) => this.internalsProvider.refreshLatestSummaryAck(...args), // refreshLatestSummaryCallback
261
- new SummarizeHeuristicData(this.runtime.deltaManager.lastSequenceNumber, {
262
- /** summary attempt baseline for heuristics */
263
- refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
264
- summaryTime: Date.now(),
265
- } as const),
273
+ this._heuristicData,
266
274
  this.summaryCollection,
267
275
  runCoordinator /* cancellationToken */,
268
276
  (reason) => runCoordinator.stop(reason) /* stopSummarizerCallback */,
@@ -361,4 +369,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
361
369
  }
362
370
  return this.runningSummarizer.enqueueSummarize(...args);
363
371
  };
372
+
373
+ public recordSummaryAttempt?(summaryRefSeqNum?: number) {
374
+ this._heuristicData?.recordAttempt(summaryRefSeqNum);
375
+ }
364
376
  }
@@ -27,32 +27,31 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
27
27
  }
28
28
 
29
29
  public get opsSinceLastSummary(): number {
30
- return this.numSystemOpsBefore + this.numNonSystemOpsBefore;
30
+ return this.numNonRuntimeOpsBefore + this.numRuntimeOpsBefore;
31
31
  }
32
32
 
33
- public numNonRuntimeOps: number = 0;
34
- public totalOpsSize: number = 0;
35
33
  public hasMissingOpData: boolean = false;
36
34
 
35
+ public totalOpsSize: number = 0;
37
36
  /**
38
37
  * Cumulative size in bytes of all the ops at the beginning of the summarization attempt.
39
38
  * Is used to adjust totalOpsSize appropriately after successful summarization.
40
39
  */
41
- /** */
42
40
  private totalOpsSizeBefore: number = 0;
43
41
 
42
+ public numNonRuntimeOps: number = 0;
44
43
  /**
45
- * Number of system ops at beginning of attempting to summarize.
46
- * Is used to adjust numSystemOps appropriately after successful summarization.
44
+ * Number of non-runtime ops at beginning of attempting to summarize.
45
+ * Is used to adjust numNonRuntimeOps appropriately after successful summarization.
47
46
  */
48
- private numSystemOpsBefore: number = 0;
47
+ private numNonRuntimeOpsBefore: number = 0;
49
48
 
50
49
  public numRuntimeOps: number = 0;
51
50
  /**
52
- * Number of non-system ops at beginning of attempting to summarize.
53
- * Is used to adjust numNonSystemOps appropriately after successful summarization.
51
+ * Number of runtime ops at beginning of attempting to summarize.
52
+ * Is used to adjust numRuntimeOps appropriately after successful summarization.
54
53
  */
55
- private numNonSystemOpsBefore: number = 0;
54
+ private numRuntimeOpsBefore: number = 0;
56
55
 
57
56
  constructor(
58
57
  public lastOpSequenceNumber: number,
@@ -74,19 +73,19 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
74
73
  summaryTime: Date.now(),
75
74
  };
76
75
 
77
- this.numSystemOpsBefore = this.numNonRuntimeOps;
78
- this.numNonSystemOpsBefore = this.numRuntimeOps;
76
+ this.numNonRuntimeOpsBefore = this.numNonRuntimeOps;
77
+ this.numRuntimeOpsBefore = this.numRuntimeOps;
79
78
  this.totalOpsSizeBefore = this.totalOpsSize;
80
79
  }
81
80
 
82
81
  public markLastAttemptAsSuccessful() {
83
82
  this._lastSuccessfulSummary = { ...this.lastAttempt };
84
83
 
85
- this.numNonRuntimeOps -= this.numSystemOpsBefore;
86
- this.numSystemOpsBefore = 0;
84
+ this.numNonRuntimeOps -= this.numNonRuntimeOpsBefore;
85
+ this.numNonRuntimeOpsBefore = 0;
87
86
 
88
- this.numRuntimeOps -= this.numNonSystemOpsBefore;
89
- this.numNonSystemOpsBefore = 0;
87
+ this.numRuntimeOps -= this.numRuntimeOpsBefore;
88
+ this.numRuntimeOpsBefore = 0;
90
89
 
91
90
  this.totalOpsSize -= this.totalOpsSizeBefore;
92
91
  this.totalOpsSizeBefore = 0;
@@ -489,6 +489,8 @@ type SummaryGeneratorOptionalTelemetryProperties =
489
489
  | "opsSizesSinceLastSummary"
490
490
  /** Delta between the number of non-runtime ops since the last summary */
491
491
  | "nonRuntimeOpsSinceLastSummary"
492
+ /** Delta between the number of runtime ops since the last summary */
493
+ | "runtimeOpsSinceLastSummary"
492
494
  /** Wether or not this instance contains adjusted metrics due to missing op data */
493
495
  | "hasMissingOpData"
494
496
  /** Time it took to generate the summary tree and stats. */
@@ -275,6 +275,9 @@ export class SummaryGenerator {
275
275
  // Use record type to prevent unexpected value types
276
276
  let summaryData: SubmitSummaryResult | undefined;
277
277
  try {
278
+ // Need to save refSeqNum before we record new attempt (happens as part of submitSummaryCallback)
279
+ const lastAttemptRefSeqNum = this.heuristicData.lastAttempt.refSequenceNumber;
280
+
278
281
  summaryData = await this.submitSummaryCallback({
279
282
  fullTree,
280
283
  refreshLatestAck,
@@ -282,16 +285,13 @@ export class SummaryGenerator {
282
285
  cancellationToken,
283
286
  });
284
287
 
285
- this.heuristicData.recordAttempt(summaryData.referenceSequenceNumber);
286
-
287
288
  // Cumulatively add telemetry properties based on how far generateSummary went.
288
289
  const referenceSequenceNumber = summaryData.referenceSequenceNumber;
289
290
  summarizeTelemetryProps = {
290
291
  ...summarizeTelemetryProps,
291
292
  referenceSequenceNumber,
292
293
  minimumSequenceNumber: summaryData.minimumSequenceNumber,
293
- opsSinceLastAttempt:
294
- referenceSequenceNumber - this.heuristicData.lastAttempt.refSequenceNumber,
294
+ opsSinceLastAttempt: referenceSequenceNumber - lastAttemptRefSeqNum,
295
295
  opsSinceLastSummary:
296
296
  referenceSequenceNumber -
297
297
  this.heuristicData.lastSuccessfulSummary.refSequenceNumber,
@@ -481,6 +481,7 @@ export class SummaryGenerator {
481
481
  hasMissingOpData: this.heuristicData.hasMissingOpData,
482
482
  opsSizesSinceLastSummary: this.heuristicData.totalOpsSize,
483
483
  nonRuntimeOpsSinceLastSummary: this.heuristicData.numNonRuntimeOps,
484
+ runtimeOpsSinceLastSummary: this.heuristicData.numRuntimeOps,
484
485
  };
485
486
 
486
487
  default:
@@ -1,53 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { ITelemetryProperties } from "@fluidframework/common-definitions";
6
- import { ICriticalContainerError } from "@fluidframework/container-definitions";
7
- import { IFluidErrorBase, LoggingError, MonitoringContext } from "@fluidframework/telemetry-utils";
8
- /**
9
- * Feature Gate Key -
10
- * How many days between closing the container from this error (avoids locking user out of their file altogether)
11
- */
12
- export declare const skipClosureForXDaysKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
13
- /**
14
- * LocalStorage key (NOT via feature gate / monitoring context)
15
- * A map from docId to info about the last time we closed due to this error
16
- */
17
- export declare const closuresMapLocalStorageKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
18
- /**
19
- * Error class raised when a SweepReady object is used, indicating a bug in how
20
- * references are managed in the container by the application, or a bug in how
21
- * GC tracks those references.
22
- *
23
- * There's a chance for false positives when this error is raised by an Interactive Container,
24
- * since only the Summarizer has the latest truth about unreferenced node tracking
25
- */
26
- export declare class SweepReadyUsageError extends LoggingError implements IFluidErrorBase {
27
- /** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
28
- errorType: string;
29
- }
30
- /**
31
- * This class encapsulates the logic around what to do when a SweepReady object is used.
32
- * There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:
33
- * - Closing the interactive container when either the interactive or summarizer client detects this kind of violation
34
- * (via sweepReadyUsageDetectionSetting above)
35
- * - Throttling the frequency of these crashes via a "Skip Closure Period" per container per device
36
- * (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)
37
- */
38
- export declare class SweepReadyUsageDetectionHandler {
39
- private readonly uniqueContainerKey;
40
- private readonly mc;
41
- private readonly closeFn;
42
- private readonly localStorage;
43
- constructor(uniqueContainerKey: string, mc: MonitoringContext, closeFn: (error?: ICriticalContainerError) => void, localStorageOverride?: Pick<Storage, "getItem" | "setItem">);
44
- /**
45
- * If SweepReady Usage Detection is enabled, close the interactive container.
46
- * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
47
- *
48
- * Once Sweep is fully implemented, this will be removed since the objects will be gone
49
- * and errors will arise elsewhere in the runtime
50
- */
51
- usageDetectedInInteractiveClient(errorProps: ITelemetryProperties): void;
52
- }
53
- //# sourceMappingURL=gcSweepReadyUsageDetection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gcSweepReadyUsageDetection.d.ts","sourceRoot":"","sources":["../../src/gc/gcSweepReadyUsageDetection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAEN,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,MAAM,iCAAiC,CAAC;AAGzC;;;GAGG;AACH,eAAO,MAAM,sBAAsB,iFAC4C,CAAC;AAEhF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,sEAC6B,CAAC;AAqBrE;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,YAAa,YAAW,eAAe;IAChF,oHAAoH;IAC7G,SAAS,EAAE,MAAM,CAAiD;CACzE;AAED;;;;;;;GAOG;AACH,qBAAa,+BAA+B;IAI1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALzB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;gBAGlD,kBAAkB,EAAE,MAAM,EAC1B,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,uBAAuB,KAAK,IAAI,EACnE,oBAAoB,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAc5D;;;;;;OAMG;IACI,gCAAgC,CAAC,UAAU,EAAE,oBAAoB;CA+CxE"}
@@ -1,130 +0,0 @@
1
- "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.SweepReadyUsageDetectionHandler = exports.SweepReadyUsageError = exports.closuresMapLocalStorageKey = exports.skipClosureForXDaysKey = void 0;
8
- const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
9
- const gcDefinitions_1 = require("./gcDefinitions");
10
- /**
11
- * Feature Gate Key -
12
- * How many days between closing the container from this error (avoids locking user out of their file altogether)
13
- */
14
- exports.skipClosureForXDaysKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
15
- /**
16
- * LocalStorage key (NOT via feature gate / monitoring context)
17
- * A map from docId to info about the last time we closed due to this error
18
- */
19
- exports.closuresMapLocalStorageKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
20
- /**
21
- * Feature gate key to enable closing the container if SweepReady objects are used.
22
- * Value should contain keywords "interactiveClient" and/or "summarizer" to enable detection in each container type
23
- */
24
- const sweepReadyUsageDetectionSetting = {
25
- read(config) {
26
- const sweepReadyUsageDetectionKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection";
27
- const value = config.getString(sweepReadyUsageDetectionKey);
28
- if (value === undefined) {
29
- return { interactiveClient: false, summarizer: false };
30
- }
31
- return {
32
- interactiveClient: value.includes("interactiveClient"),
33
- summarizer: value.includes("summarizer"),
34
- };
35
- },
36
- };
37
- /**
38
- * Error class raised when a SweepReady object is used, indicating a bug in how
39
- * references are managed in the container by the application, or a bug in how
40
- * GC tracks those references.
41
- *
42
- * There's a chance for false positives when this error is raised by an Interactive Container,
43
- * since only the Summarizer has the latest truth about unreferenced node tracking
44
- */
45
- class SweepReadyUsageError extends telemetry_utils_1.LoggingError {
46
- constructor() {
47
- super(...arguments);
48
- /** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
49
- this.errorType = "unreferencedObjectUsedAfterGarbageCollected";
50
- }
51
- }
52
- exports.SweepReadyUsageError = SweepReadyUsageError;
53
- /**
54
- * This class encapsulates the logic around what to do when a SweepReady object is used.
55
- * There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:
56
- * - Closing the interactive container when either the interactive or summarizer client detects this kind of violation
57
- * (via sweepReadyUsageDetectionSetting above)
58
- * - Throttling the frequency of these crashes via a "Skip Closure Period" per container per device
59
- * (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)
60
- */
61
- class SweepReadyUsageDetectionHandler {
62
- constructor(uniqueContainerKey, mc, closeFn, localStorageOverride) {
63
- var _a;
64
- this.uniqueContainerKey = uniqueContainerKey;
65
- this.mc = mc;
66
- this.closeFn = closeFn;
67
- const noopStorage = { getItem: () => null, setItem: () => { } };
68
- // localStorage is not defined in Node environment, so fall back to noopStorage if needed.
69
- this.localStorage = (_a = localStorageOverride !== null && localStorageOverride !== void 0 ? localStorageOverride : globalThis.localStorage) !== null && _a !== void 0 ? _a : noopStorage;
70
- if (this.localStorage === noopStorage) {
71
- // This means the Skip Closure Period logic will not work.
72
- this.mc.logger.sendTelemetryEvent({
73
- eventName: "SweepReadyUsageDetectionHandlerNoopStorage",
74
- });
75
- }
76
- }
77
- /**
78
- * If SweepReady Usage Detection is enabled, close the interactive container.
79
- * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
80
- *
81
- * Once Sweep is fully implemented, this will be removed since the objects will be gone
82
- * and errors will arise elsewhere in the runtime
83
- */
84
- usageDetectedInInteractiveClient(errorProps) {
85
- var _a;
86
- if (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {
87
- return;
88
- }
89
- // Default stance is we close every time - this reflects the severity of SweepReady Object Usage.
90
- // However, we may choose to "throttle" the closures by setting the SkipClosureForXDays setting,
91
- // which will only allow the container to close once during that period, to avoid locking users out.
92
- let shouldClose = true;
93
- let pastClosuresMap = {};
94
- let lastCloseTime;
95
- const skipClosureForXDays = this.mc.config.getNumber(exports.skipClosureForXDaysKey);
96
- if (skipClosureForXDays !== undefined) {
97
- // Read pastClosuresMap from localStorage then extract the lastCloseTime from the map
98
- try {
99
- const rawValue = this.localStorage.getItem(exports.closuresMapLocalStorageKey);
100
- const parsedValue = rawValue === null ? {} : JSON.parse(rawValue);
101
- if (typeof parsedValue === "object") {
102
- pastClosuresMap = parsedValue;
103
- }
104
- }
105
- catch (e) { }
106
- lastCloseTime = (_a = pastClosuresMap[this.uniqueContainerKey]) === null || _a === void 0 ? void 0 : _a.lastCloseTime;
107
- // Don't close if we did already within the Skip Closure Period
108
- if (lastCloseTime !== undefined &&
109
- Date.now() < lastCloseTime + skipClosureForXDays * gcDefinitions_1.oneDayMs) {
110
- shouldClose = false;
111
- }
112
- }
113
- const error = new SweepReadyUsageError("SweepReady object used in Non-Summarizer Client", {
114
- errorDetails: JSON.stringify(Object.assign(Object.assign({}, errorProps), { lastCloseTime, skipClosureForXDays })),
115
- });
116
- if (shouldClose) {
117
- // Update closures map in localStorage before closing
118
- // Note there is a race condition between different tabs updating localStorage and overwriting
119
- // each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck
120
- pastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };
121
- this.localStorage.setItem(exports.closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));
122
- this.closeFn(error);
123
- }
124
- else {
125
- this.mc.logger.sendErrorEvent({ eventName: "SweepReadyObject_UsageAllowed" }, error);
126
- }
127
- }
128
- }
129
- exports.SweepReadyUsageDetectionHandler = SweepReadyUsageDetectionHandler;
130
- //# sourceMappingURL=gcSweepReadyUsageDetection.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gcSweepReadyUsageDetection.js","sourceRoot":"","sources":["../../src/gc/gcSweepReadyUsageDetection.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryProperties } from \"@fluidframework/common-definitions\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport {\n\tIConfigProvider,\n\tIFluidErrorBase,\n\tLoggingError,\n\tMonitoringContext,\n} from \"@fluidframework/telemetry-utils\";\nimport { oneDayMs } from \"./gcDefinitions\";\n\n/**\n * Feature Gate Key -\n * How many days between closing the container from this error (avoids locking user out of their file altogether)\n */\nexport const skipClosureForXDaysKey =\n\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays\";\n\n/**\n * LocalStorage key (NOT via feature gate / monitoring context)\n * A map from docId to info about the last time we closed due to this error\n */\nexport const closuresMapLocalStorageKey =\n\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures\";\n\n/**\n * Feature gate key to enable closing the container if SweepReady objects are used.\n * Value should contain keywords \"interactiveClient\" and/or \"summarizer\" to enable detection in each container type\n */\nconst sweepReadyUsageDetectionSetting = {\n\tread(config: IConfigProvider) {\n\t\tconst sweepReadyUsageDetectionKey =\n\t\t\t\"Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection\";\n\t\tconst value = config.getString(sweepReadyUsageDetectionKey);\n\t\tif (value === undefined) {\n\t\t\treturn { interactiveClient: false, summarizer: false };\n\t\t}\n\t\treturn {\n\t\t\tinteractiveClient: value.includes(\"interactiveClient\"),\n\t\t\tsummarizer: value.includes(\"summarizer\"),\n\t\t};\n\t},\n};\n\n/**\n * Error class raised when a SweepReady object is used, indicating a bug in how\n * references are managed in the container by the application, or a bug in how\n * GC tracks those references.\n *\n * There's a chance for false positives when this error is raised by an Interactive Container,\n * since only the Summarizer has the latest truth about unreferenced node tracking\n */\nexport class SweepReadyUsageError extends LoggingError implements IFluidErrorBase {\n\t/** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */\n\tpublic errorType: string = \"unreferencedObjectUsedAfterGarbageCollected\";\n}\n\n/**\n * This class encapsulates the logic around what to do when a SweepReady object is used.\n * There are several tactics we plan to use in Dogfood environments to aid diagnosis of these cases:\n * - Closing the interactive container when either the interactive or summarizer client detects this kind of violation\n * (via sweepReadyUsageDetectionSetting above)\n * - Throttling the frequency of these crashes via a \"Skip Closure Period\" per container per device\n * (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)\n */\nexport class SweepReadyUsageDetectionHandler {\n\tprivate readonly localStorage: Pick<Storage, \"getItem\" | \"setItem\">;\n\n\tconstructor(\n\t\tprivate readonly uniqueContainerKey: string,\n\t\tprivate readonly mc: MonitoringContext,\n\t\tprivate readonly closeFn: (error?: ICriticalContainerError) => void,\n\t\tlocalStorageOverride?: Pick<Storage, \"getItem\" | \"setItem\">,\n\t) {\n\t\tconst noopStorage = { getItem: () => null, setItem: () => {} };\n\t\t// localStorage is not defined in Node environment, so fall back to noopStorage if needed.\n\t\tthis.localStorage = localStorageOverride ?? globalThis.localStorage ?? noopStorage;\n\n\t\tif (this.localStorage === noopStorage) {\n\t\t\t// This means the Skip Closure Period logic will not work.\n\t\t\tthis.mc.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"SweepReadyUsageDetectionHandlerNoopStorage\",\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * If SweepReady Usage Detection is enabled, close the interactive container.\n\t * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.\n\t *\n\t * Once Sweep is fully implemented, this will be removed since the objects will be gone\n\t * and errors will arise elsewhere in the runtime\n\t */\n\tpublic usageDetectedInInteractiveClient(errorProps: ITelemetryProperties) {\n\t\tif (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Default stance is we close every time - this reflects the severity of SweepReady Object Usage.\n\t\t// However, we may choose to \"throttle\" the closures by setting the SkipClosureForXDays setting,\n\t\t// which will only allow the container to close once during that period, to avoid locking users out.\n\t\tlet shouldClose: boolean = true;\n\t\tlet pastClosuresMap: Record<string, { lastCloseTime: number } | undefined> = {};\n\t\tlet lastCloseTime: number | undefined;\n\t\tconst skipClosureForXDays = this.mc.config.getNumber(skipClosureForXDaysKey);\n\t\tif (skipClosureForXDays !== undefined) {\n\t\t\t// Read pastClosuresMap from localStorage then extract the lastCloseTime from the map\n\t\t\ttry {\n\t\t\t\tconst rawValue = this.localStorage.getItem(closuresMapLocalStorageKey);\n\t\t\t\tconst parsedValue = rawValue === null ? {} : JSON.parse(rawValue);\n\t\t\t\tif (typeof parsedValue === \"object\") {\n\t\t\t\t\tpastClosuresMap = parsedValue;\n\t\t\t\t}\n\t\t\t} catch (e) {}\n\t\t\tlastCloseTime = pastClosuresMap[this.uniqueContainerKey]?.lastCloseTime;\n\n\t\t\t// Don't close if we did already within the Skip Closure Period\n\t\t\tif (\n\t\t\t\tlastCloseTime !== undefined &&\n\t\t\t\tDate.now() < lastCloseTime + skipClosureForXDays * oneDayMs\n\t\t\t) {\n\t\t\t\tshouldClose = false;\n\t\t\t}\n\t\t}\n\n\t\tconst error = new SweepReadyUsageError(\"SweepReady object used in Non-Summarizer Client\", {\n\t\t\terrorDetails: JSON.stringify({ ...errorProps, lastCloseTime, skipClosureForXDays }),\n\t\t});\n\t\tif (shouldClose) {\n\t\t\t// Update closures map in localStorage before closing\n\t\t\t// Note there is a race condition between different tabs updating localStorage and overwriting\n\t\t\t// each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck\n\t\t\tpastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };\n\t\t\tthis.localStorage.setItem(closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));\n\n\t\t\tthis.closeFn(error);\n\t\t} else {\n\t\t\tthis.mc.logger.sendErrorEvent({ eventName: \"SweepReadyObject_UsageAllowed\" }, error);\n\t\t}\n\t}\n}\n"]}