@fluidframework/container-runtime 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.2.0.153917

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 (152) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +69 -0
  3. package/dist/blobManager.d.ts +6 -14
  4. package/dist/blobManager.d.ts.map +1 -1
  5. package/dist/blobManager.js +50 -37
  6. package/dist/blobManager.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +14 -1
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +37 -12
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/gc/gcHelpers.d.ts.map +1 -1
  12. package/dist/gc/gcHelpers.js +6 -6
  13. package/dist/gc/gcHelpers.js.map +1 -1
  14. package/dist/opLifecycle/index.d.ts +1 -0
  15. package/dist/opLifecycle/index.d.ts.map +1 -1
  16. package/dist/opLifecycle/index.js +3 -1
  17. package/dist/opLifecycle/index.js.map +1 -1
  18. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  19. package/dist/opLifecycle/opDecompressor.js +2 -1
  20. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  21. package/dist/opLifecycle/opGroupingManager.d.ts +14 -0
  22. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -0
  23. package/dist/opLifecycle/opGroupingManager.js +56 -0
  24. package/dist/opLifecycle/opGroupingManager.js.map +1 -0
  25. package/dist/opLifecycle/opSplitter.d.ts +1 -1
  26. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  27. package/dist/opLifecycle/opSplitter.js +5 -6
  28. package/dist/opLifecycle/opSplitter.js.map +1 -1
  29. package/dist/opLifecycle/outbox.d.ts +2 -0
  30. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  31. package/dist/opLifecycle/outbox.js +3 -3
  32. package/dist/opLifecycle/outbox.js.map +1 -1
  33. package/dist/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  34. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  35. package/dist/opLifecycle/remoteMessageProcessor.js +30 -20
  36. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  37. package/dist/packageVersion.d.ts +1 -1
  38. package/dist/packageVersion.js +1 -1
  39. package/dist/packageVersion.js.map +1 -1
  40. package/dist/summary/index.d.ts +1 -1
  41. package/dist/summary/index.d.ts.map +1 -1
  42. package/dist/summary/index.js +3 -1
  43. package/dist/summary/index.js.map +1 -1
  44. package/dist/summary/runningSummarizer.d.ts +5 -3
  45. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  46. package/dist/summary/runningSummarizer.js +82 -67
  47. package/dist/summary/runningSummarizer.js.map +1 -1
  48. package/dist/summary/summarizer.d.ts.map +1 -1
  49. package/dist/summary/summarizer.js +1 -5
  50. package/dist/summary/summarizer.js.map +1 -1
  51. package/dist/summary/summarizerHeuristics.d.ts +1 -0
  52. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  53. package/dist/summary/summarizerHeuristics.js +3 -0
  54. package/dist/summary/summarizerHeuristics.js.map +1 -1
  55. package/dist/summary/summarizerNode/summarizerNode.js +1 -1
  56. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  57. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +128 -2
  58. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  59. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +4 -3
  60. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  61. package/dist/summary/summarizerTypes.d.ts +14 -2
  62. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  63. package/dist/summary/summarizerTypes.js.map +1 -1
  64. package/dist/summary/summaryGenerator.d.ts +28 -2
  65. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  66. package/dist/summary/summaryGenerator.js +19 -16
  67. package/dist/summary/summaryGenerator.js.map +1 -1
  68. package/lib/blobManager.d.ts +6 -14
  69. package/lib/blobManager.d.ts.map +1 -1
  70. package/lib/blobManager.js +50 -37
  71. package/lib/blobManager.js.map +1 -1
  72. package/lib/containerRuntime.d.ts +14 -1
  73. package/lib/containerRuntime.d.ts.map +1 -1
  74. package/lib/containerRuntime.js +38 -13
  75. package/lib/containerRuntime.js.map +1 -1
  76. package/lib/gc/gcHelpers.d.ts.map +1 -1
  77. package/lib/gc/gcHelpers.js +6 -6
  78. package/lib/gc/gcHelpers.js.map +1 -1
  79. package/lib/opLifecycle/index.d.ts +1 -0
  80. package/lib/opLifecycle/index.d.ts.map +1 -1
  81. package/lib/opLifecycle/index.js +1 -0
  82. package/lib/opLifecycle/index.js.map +1 -1
  83. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  84. package/lib/opLifecycle/opDecompressor.js +2 -1
  85. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  86. package/lib/opLifecycle/opGroupingManager.d.ts +14 -0
  87. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -0
  88. package/lib/opLifecycle/opGroupingManager.js +52 -0
  89. package/lib/opLifecycle/opGroupingManager.js.map +1 -0
  90. package/lib/opLifecycle/opSplitter.d.ts +1 -1
  91. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  92. package/lib/opLifecycle/opSplitter.js +5 -6
  93. package/lib/opLifecycle/opSplitter.js.map +1 -1
  94. package/lib/opLifecycle/outbox.d.ts +2 -0
  95. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  96. package/lib/opLifecycle/outbox.js +3 -3
  97. package/lib/opLifecycle/outbox.js.map +1 -1
  98. package/lib/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  99. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  100. package/lib/opLifecycle/remoteMessageProcessor.js +30 -20
  101. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  102. package/lib/packageVersion.d.ts +1 -1
  103. package/lib/packageVersion.js +1 -1
  104. package/lib/packageVersion.js.map +1 -1
  105. package/lib/summary/index.d.ts +1 -1
  106. package/lib/summary/index.d.ts.map +1 -1
  107. package/lib/summary/index.js +1 -0
  108. package/lib/summary/index.js.map +1 -1
  109. package/lib/summary/runningSummarizer.d.ts +5 -3
  110. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  111. package/lib/summary/runningSummarizer.js +82 -67
  112. package/lib/summary/runningSummarizer.js.map +1 -1
  113. package/lib/summary/summarizer.d.ts.map +1 -1
  114. package/lib/summary/summarizer.js +1 -5
  115. package/lib/summary/summarizer.js.map +1 -1
  116. package/lib/summary/summarizerHeuristics.d.ts +1 -0
  117. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  118. package/lib/summary/summarizerHeuristics.js +3 -0
  119. package/lib/summary/summarizerHeuristics.js.map +1 -1
  120. package/lib/summary/summarizerNode/summarizerNode.js +1 -1
  121. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  122. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +128 -2
  123. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  124. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +3 -3
  125. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  126. package/lib/summary/summarizerTypes.d.ts +14 -2
  127. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  128. package/lib/summary/summarizerTypes.js.map +1 -1
  129. package/lib/summary/summaryGenerator.d.ts +28 -2
  130. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  131. package/lib/summary/summaryGenerator.js +17 -15
  132. package/lib/summary/summaryGenerator.js.map +1 -1
  133. package/package.json +19 -16
  134. package/src/blobManager.ts +64 -41
  135. package/src/containerRuntime.ts +70 -9
  136. package/src/gc/gcHelpers.ts +9 -6
  137. package/src/opLifecycle/README.md +106 -0
  138. package/src/opLifecycle/index.ts +1 -0
  139. package/src/opLifecycle/opDecompressor.ts +1 -0
  140. package/src/opLifecycle/opGroupingManager.ts +78 -0
  141. package/src/opLifecycle/opSplitter.ts +1 -5
  142. package/src/opLifecycle/outbox.ts +7 -3
  143. package/src/opLifecycle/remoteMessageProcessor.ts +38 -22
  144. package/src/packageVersion.ts +1 -1
  145. package/src/summary/index.ts +1 -1
  146. package/src/summary/runningSummarizer.ts +102 -80
  147. package/src/summary/summarizer.ts +0 -8
  148. package/src/summary/summarizerHeuristics.ts +4 -0
  149. package/src/summary/summarizerNode/summarizerNode.ts +1 -1
  150. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +3 -3
  151. package/src/summary/summarizerTypes.ts +20 -3
  152. package/src/summary/summaryGenerator.ts +22 -16
@@ -135,7 +135,7 @@ export class OpSplitter {
135
135
  * @param batch - the compressed batch which needs to be processed
136
136
  * @returns A new adjusted batch which can be sent over the wire
137
137
  */
138
- public splitCompressedBatch(batch: IBatch): IBatch {
138
+ public splitFirstBatchMessage(batch: IBatch): IBatch {
139
139
  assert(this.isBatchChunkingEnabled, 0x513 /* Chunking needs to be enabled */);
140
140
  assert(
141
141
  batch.contentSizeInBytes > 0 && batch.content.length > 0,
@@ -152,10 +152,6 @@ export class OpSplitter {
152
152
  );
153
153
 
154
154
  const firstMessage = batch.content[0]; // we expect this to be the large compressed op, which needs to be split
155
- assert(
156
- firstMessage.metadata?.compressed === true || firstMessage.compression !== undefined,
157
- 0x517 /* Batch needs to be compressed */,
158
- );
159
155
  assert(
160
156
  (firstMessage.contents?.length ?? 0) >= this.chunkSizeInBytes,
161
157
  0x518 /* First message in the batch needs to be chunkable */,
@@ -18,6 +18,7 @@ import { PendingStateManager } from "../pendingStateManager";
18
18
  import { BatchManager, estimateSocketSize } from "./batchManager";
19
19
  import { BatchMessage, IBatch } from "./definitions";
20
20
  import { OpCompressor } from "./opCompressor";
21
+ import { OpGroupingManager } from "./opGroupingManager";
21
22
  import { OpSplitter } from "./opSplitter";
22
23
 
23
24
  export interface IOutboxConfig {
@@ -35,6 +36,7 @@ export interface IOutboxParameters {
35
36
  readonly compressor: OpCompressor;
36
37
  readonly splitter: OpSplitter;
37
38
  readonly logger: ITelemetryLogger;
39
+ readonly groupingManager: OpGroupingManager;
38
40
  }
39
41
 
40
42
  export class Outbox {
@@ -180,15 +182,17 @@ export class Outbox {
180
182
  this.params.containerContext.submitBatchFn === undefined
181
183
  ) {
182
184
  // Nothing to do if the batch is empty or if compression is disabled or not supported, or if we don't need to compress
183
- return batch;
185
+ return this.params.groupingManager.groupBatch(batch);
184
186
  }
185
187
 
186
- const compressedBatch = this.params.compressor.compressBatch(batch);
188
+ const compressedBatch = this.params.groupingManager.groupBatch(
189
+ this.params.compressor.compressBatch(batch),
190
+ );
187
191
 
188
192
  if (this.params.splitter.isBatchChunkingEnabled) {
189
193
  return compressedBatch.contentSizeInBytes <= this.params.splitter.chunkSizeInBytes
190
194
  ? compressedBatch
191
- : this.params.splitter.splitCompressedBatch(compressedBatch);
195
+ : this.params.splitter.splitFirstBatchMessage(compressedBatch);
192
196
  }
193
197
 
194
198
  if (compressedBatch.contentSizeInBytes >= this.params.config.maxBatchSizeInBytes) {
@@ -6,12 +6,14 @@
6
6
  import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
7
7
  import { ContainerMessageType, ContainerRuntimeMessage } from "../containerRuntime";
8
8
  import { OpDecompressor } from "./opDecompressor";
9
+ import { OpGroupingManager } from "./opGroupingManager";
9
10
  import { OpSplitter } from "./opSplitter";
10
11
 
11
12
  export class RemoteMessageProcessor {
12
13
  constructor(
13
14
  private readonly opSplitter: OpSplitter,
14
15
  private readonly opDecompressor: OpDecompressor,
16
+ private readonly opGroupingManager: OpGroupingManager,
15
17
  ) {}
16
18
 
17
19
  public get partialMessages(): ReadonlyMap<string, string[]> {
@@ -22,30 +24,44 @@ export class RemoteMessageProcessor {
22
24
  this.opSplitter.clearPartialChunks(clientId);
23
25
  }
24
26
 
25
- public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage {
26
- let message = copy(remoteMessage);
27
- message = this.opDecompressor.processMessage(message).message;
28
- unpackRuntimeMessage(message);
29
-
30
- const chunkProcessingResult = this.opSplitter.processRemoteMessage(message);
31
- message = chunkProcessingResult.message;
32
- if (chunkProcessingResult.state !== "Processed") {
33
- // If the message is not chunked or if the splitter is still rebuilding the original message,
34
- // there is no need to continue processing
35
- return message;
36
- }
27
+ public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
28
+ const result: ISequencedDocumentMessage[] = [];
37
29
 
38
- const decompressionAfterChunking = this.opDecompressor.processMessage(message);
39
- message = decompressionAfterChunking.message;
40
- if (decompressionAfterChunking.state === "Skipped") {
41
- // After chunking, if the original message was not compressed,
42
- // there is no need to continue processing
43
- return message;
44
- }
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);
45
34
 
46
- // The message needs to be unpacked after chunking + decompression
47
- unpack(message);
48
- return message;
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,
54
+ // there is no need to continue processing
55
+ result.push(ungroupedMessageAfterChunking);
56
+ continue;
57
+ }
58
+
59
+ // The message needs to be unpacked after chunking + decompression
60
+ unpack(ungroupedMessageAfterChunking);
61
+ result.push(ungroupedMessageAfterChunking);
62
+ }
63
+ }
64
+ return result;
49
65
  }
50
66
  }
51
67
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-dev.4.1.0.148229";
9
+ export const pkgVersion = "2.0.0-dev.4.2.0.153917";
@@ -95,7 +95,7 @@ export {
95
95
  WriteFluidDataStoreAttributes,
96
96
  wrapSummaryInChannelsTree,
97
97
  } from "./summaryFormat";
98
- export { SummarizeReason } from "./summaryGenerator";
98
+ export { getFailMessage, SummarizeReason } from "./summaryGenerator";
99
99
  export {
100
100
  IConnectedEvents,
101
101
  IConnectedState,
@@ -46,6 +46,8 @@ import {
46
46
  const maxSummarizeAckWaitTime = 10 * 60 * 1000; // 10 minutes
47
47
 
48
48
  const defaultNumberSummarizationAttempts = 2; // only up to 2 attempts
49
+ const numberOfAttemptsOnRestartAsRecovery = 1; // Only summarize once
50
+
49
51
  /**
50
52
  * An instance of RunningSummarizer manages the heuristics for summarizing.
51
53
  * Until disposed, the instance of RunningSummarizer can assume that it is
@@ -61,7 +63,6 @@ export class RunningSummarizer implements IDisposable {
61
63
  submitSummaryCallback: (options: ISubmitSummaryOptions) => Promise<SubmitSummaryResult>,
62
64
  refreshLatestSummaryAckCallback: (options: IRefreshSummaryAckOptions) => Promise<void>,
63
65
  heuristicData: ISummarizeHeuristicData,
64
- raiseSummarizingError: (errorMessage: string) => void,
65
66
  summaryCollection: SummaryCollection,
66
67
  cancellationToken: ISummaryCancellationToken,
67
68
  stopSummarizerCallback: (reason: SummarizerStopReason) => void,
@@ -74,7 +75,6 @@ export class RunningSummarizer implements IDisposable {
74
75
  submitSummaryCallback,
75
76
  refreshLatestSummaryAckCallback,
76
77
  heuristicData,
77
- raiseSummarizingError,
78
78
  summaryCollection,
79
79
  cancellationToken,
80
80
  stopSummarizerCallback,
@@ -133,6 +133,7 @@ export class RunningSummarizer implements IDisposable {
133
133
  private heuristicRunner?: ISummarizeHeuristicRunner;
134
134
  private readonly generator: SummaryGenerator;
135
135
  private readonly mc: MonitoringContext;
136
+ private readonly shouldAbortOnSummaryFailure: boolean;
136
137
 
137
138
  private enqueuedSummary:
138
139
  | {
@@ -146,6 +147,9 @@ export class RunningSummarizer implements IDisposable {
146
147
  private totalSuccessfulAttempts = 0;
147
148
  private initialized = false;
148
149
 
150
+ private readonly deltaManagerListener;
151
+ private readonly runtimeListener;
152
+
149
153
  private constructor(
150
154
  baseLogger: ITelemetryLogger,
151
155
  private readonly summaryWatcher: IClientSummaryWatcher,
@@ -157,7 +161,6 @@ export class RunningSummarizer implements IDisposable {
157
161
  options: IRefreshSummaryAckOptions,
158
162
  ) => Promise<void>,
159
163
  private readonly heuristicData: ISummarizeHeuristicData,
160
- private readonly raiseSummarizingError: (errorMessage: string) => void,
161
164
  private readonly summaryCollection: SummaryCollection,
162
165
  private readonly cancellationToken: ISummaryCancellationToken,
163
166
  private readonly stopSummarizerCallback: (reason: SummarizerStopReason) => void,
@@ -174,6 +177,10 @@ export class RunningSummarizer implements IDisposable {
174
177
  }),
175
178
  );
176
179
 
180
+ this.shouldAbortOnSummaryFailure =
181
+ this.mc.config.getString("Fluid.ContainerRuntime.Test.SummarizationRecoveryMethod") ===
182
+ "restart";
183
+
177
184
  if (configuration.state !== "disableHeuristics") {
178
185
  assert(
179
186
  this.configuration.state === "enabled",
@@ -198,13 +205,12 @@ export class RunningSummarizer implements IDisposable {
198
205
  const maxAckWaitTime = Math.min(this.configuration.maxAckWaitTime, maxSummarizeAckWaitTime);
199
206
 
200
207
  this.pendingAckTimer = new PromiseTimer(maxAckWaitTime, () => {
201
- // pre-0.58 error message: summaryAckWaitTimeout
202
- this.raiseSummarizingError("Pending summary ack not received in time");
203
208
  // Note: summarizeCount (from ChildLogger definition) may be 0,
204
209
  // since this code path is hit when RunningSummarizer first starts up,
205
210
  // before this instance has kicked off a new summarize run.
206
211
  this.mc.logger.sendErrorEvent({
207
212
  eventName: "SummaryAckWaitTimeout",
213
+ message: "Pending summary ack not received in time",
208
214
  maxAckWaitTime,
209
215
  referenceSequenceNumber: this.heuristicData.lastAttempt.refSequenceNumber,
210
216
  summarySequenceNumber: this.heuristicData.lastAttempt.summarySequenceNumber,
@@ -227,7 +233,6 @@ export class RunningSummarizer implements IDisposable {
227
233
  this.pendingAckTimer,
228
234
  this.heuristicData,
229
235
  this.submitSummaryCallback,
230
- this.raiseSummarizingError,
231
236
  () => {
232
237
  this.totalSuccessfulAttempts++;
233
238
  },
@@ -235,10 +240,25 @@ export class RunningSummarizer implements IDisposable {
235
240
  this.mc.logger,
236
241
  );
237
242
 
238
- // Listen for ops
239
- this.runtime.deltaManager.on("op", (op) => {
240
- this.handleOp(op);
241
- });
243
+ // Listen to deltaManager for non-runtime ops
244
+ this.deltaManagerListener = (op) => {
245
+ if (!isRuntimeMessage(op)) {
246
+ this.handleOp(op, false);
247
+ }
248
+ };
249
+
250
+ // Listen to runtime for runtime ops
251
+ this.runtimeListener = (op, runtimeMessage) => {
252
+ if (runtimeMessage) {
253
+ this.handleOp(op, true);
254
+ }
255
+ };
256
+
257
+ // Purpose of listening to deltaManager is for back-compat
258
+ // Can remove and only listen to runtime once loader version is past 2.0.0-internal.1.2.0 (https://github.com/microsoft/FluidFramework/pull/11832)
259
+ // Tracked by AB#3883
260
+ this.runtime.deltaManager.on("op", this.deltaManagerListener);
261
+ this.runtime.on?.("op", this.runtimeListener);
242
262
  }
243
263
 
244
264
  private async handleSummaryAck(): Promise<number> {
@@ -248,67 +268,54 @@ export class RunningSummarizer implements IDisposable {
248
268
  if (lastAck !== undefined) {
249
269
  refSequenceNumber = lastAck.summaryOp.referenceSequenceNumber;
250
270
  const summaryLogger = this.tryGetCorrelatedLogger(refSequenceNumber) ?? this.mc.logger;
251
- try {
252
- const summaryOpHandle = lastAck.summaryOp.contents.handle;
253
- const summaryAckHandle = lastAck.summaryAck.contents.handle;
254
- while (this.summarizingLock !== undefined) {
255
- summaryLogger.sendTelemetryEvent({
256
- eventName: "RefreshAttemptWithSummarizerRunning",
257
- referenceSequenceNumber: refSequenceNumber,
271
+ const summaryOpHandle = lastAck.summaryOp.contents.handle;
272
+ const summaryAckHandle = lastAck.summaryAck.contents.handle;
273
+ while (this.summarizingLock !== undefined) {
274
+ summaryLogger.sendTelemetryEvent({
275
+ eventName: "RefreshAttemptWithSummarizerRunning",
276
+ referenceSequenceNumber: refSequenceNumber,
277
+ proposalHandle: summaryOpHandle,
278
+ ackHandle: summaryAckHandle,
279
+ });
280
+ await this.summarizingLock;
281
+ }
282
+
283
+ // Make sure we block any summarizer from being executed/enqueued while
284
+ // executing the refreshLatestSummaryAck.
285
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/779
286
+ await this.lockedSummaryAction(
287
+ () => {},
288
+ async () =>
289
+ this.refreshLatestSummaryAckCallback({
258
290
  proposalHandle: summaryOpHandle,
259
291
  ackHandle: summaryAckHandle,
260
- });
261
- await this.summarizingLock;
262
- }
263
-
264
- // Make sure we block any summarizer from being executed/enqueued while
265
- // executing the refreshLatestSummaryAck.
266
- // https://dev.azure.com/fluidframework/internal/_workitems/edit/779
267
- await this.lockedSummaryAction(
268
- () => {},
269
- async () =>
270
- this.refreshLatestSummaryAckCallback({
271
- proposalHandle: summaryOpHandle,
272
- ackHandle: summaryAckHandle,
273
- summaryRefSeq: refSequenceNumber,
274
- summaryLogger,
275
- }).catch(async (error) => {
276
- // If the error is 404, so maybe the fetched version no longer exists on server. We just
277
- // ignore this error in that case, as that means we will have another summaryAck for the
278
- // latest version with which we will refresh the state. However in case of single commit
279
- // summary, we might me missing a summary ack, so in that case we are still fine as the
280
- // code in `submitSummary` function in container runtime, will refresh the latest state
281
- // by calling `refreshLatestSummaryAckFromServer` and we will be fine.
282
- if (
283
- isFluidError(error) &&
284
- error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError
285
- ) {
286
- summaryLogger.sendTelemetryEvent(
287
- {
288
- eventName: "HandleSummaryAckErrorIgnored",
289
- referenceSequenceNumber: refSequenceNumber,
290
- proposalHandle: summaryOpHandle,
291
- ackHandle: summaryAckHandle,
292
- },
293
- error,
294
- );
295
- } else {
296
- throw error;
297
- }
298
- }),
299
- () => {},
300
- );
301
- } catch (error) {
302
- summaryLogger.sendErrorEvent(
303
- {
304
- eventName: "HandleLastSummaryAckError",
305
- referenceSequenceNumber: refSequenceNumber,
306
- handle: lastAck?.summaryOp?.contents?.handle,
307
- ackHandle: lastAck?.summaryAck?.contents?.handle,
308
- },
309
- error,
310
- );
311
- }
292
+ summaryRefSeq: refSequenceNumber,
293
+ summaryLogger,
294
+ }).catch(async (error) => {
295
+ // If the error is 404, so maybe the fetched version no longer exists on server. We just
296
+ // ignore this error in that case, as that means we will have another summaryAck for the
297
+ // latest version with which we will refresh the state. However in case of single commit
298
+ // summary, we might me missing a summary ack, so in that case we are still fine as the
299
+ // code in `submitSummary` function in container runtime, will refresh the latest state
300
+ // by calling `refreshLatestSummaryAckFromServer` and we will be fine.
301
+ const isIgnoredError =
302
+ isFluidError(error) &&
303
+ error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError;
304
+
305
+ summaryLogger.sendTelemetryEvent(
306
+ {
307
+ eventName: isIgnoredError
308
+ ? "HandleSummaryAckErrorIgnored"
309
+ : "HandleLastSummaryAckError",
310
+ referenceSequenceNumber: refSequenceNumber,
311
+ proposalHandle: summaryOpHandle,
312
+ ackHandle: summaryAckHandle,
313
+ },
314
+ error,
315
+ );
316
+ }),
317
+ () => {},
318
+ );
312
319
  refSequenceNumber++;
313
320
  }
314
321
  return refSequenceNumber;
@@ -345,9 +352,8 @@ export class RunningSummarizer implements IDisposable {
345
352
  }
346
353
 
347
354
  public dispose(): void {
348
- this.runtime.deltaManager.off("op", (op) => {
349
- this.handleOp(op);
350
- });
355
+ this.runtime.deltaManager.off("op", this.deltaManagerListener);
356
+ this.runtime.off?.("op", this.runtimeListener);
351
357
  this.summaryWatcher.dispose();
352
358
  this.heuristicRunner?.dispose();
353
359
  this.heuristicRunner = undefined;
@@ -372,10 +378,10 @@ export class RunningSummarizer implements IDisposable {
372
378
  /** We only want a single heuristic runner micro-task (will provide better optimized grouping of ops) */
373
379
  private heuristicRunnerMicroTaskExists = false;
374
380
 
375
- public handleOp(op: ISequencedDocumentMessage) {
381
+ public handleOp(op: ISequencedDocumentMessage, runtimeMessage: boolean) {
376
382
  this.heuristicData.lastOpSequenceNumber = op.sequenceNumber;
377
383
 
378
- if (isRuntimeMessage(op)) {
384
+ if (runtimeMessage) {
379
385
  this.heuristicData.numRuntimeOps++;
380
386
  } else {
381
387
  this.heuristicData.numNonRuntimeOps++;
@@ -386,7 +392,7 @@ export class RunningSummarizer implements IDisposable {
386
392
  // Check for enqueued on-demand summaries; Intentionally do nothing otherwise
387
393
  if (
388
394
  this.initialized &&
389
- this.opCanTriggerSummary(op) &&
395
+ this.opCanTriggerSummary(op, runtimeMessage) &&
390
396
  !this.tryRunEnqueuedSummary() &&
391
397
  !this.heuristicRunnerMicroTaskExists
392
398
  ) {
@@ -407,14 +413,14 @@ export class RunningSummarizer implements IDisposable {
407
413
  * @param op - op to check
408
414
  * @returns true if this op can trigger a summary
409
415
  */
410
- private opCanTriggerSummary(op: ISequencedDocumentMessage): boolean {
416
+ private opCanTriggerSummary(op: ISequencedDocumentMessage, runtimeMessage: boolean): boolean {
411
417
  switch (op.type) {
412
418
  case MessageType.Summarize:
413
419
  case MessageType.SummaryAck:
414
420
  case MessageType.SummaryNack:
415
421
  return false;
416
422
  default:
417
- return isRuntimeMessage(op) || this.nonRuntimeOpCanTriggerSummary();
423
+ return runtimeMessage || this.nonRuntimeOpCanTriggerSummary();
418
424
  }
419
425
  }
420
426
 
@@ -595,9 +601,10 @@ export class RunningSummarizer implements IDisposable {
595
601
  let summaryAttempts = 0;
596
602
  let summaryAttemptsPerPhase = 0;
597
603
  // Reducing the default number of attempts to defaultNumberofSummarizationAttempts.
598
- let totalAttempts =
599
- this.mc.config.getNumber("Fluid.Summarizer.Attempts") ??
600
- defaultNumberSummarizationAttempts;
604
+ let totalAttempts = this.shouldAbortOnSummaryFailure
605
+ ? numberOfAttemptsOnRestartAsRecovery
606
+ : this.mc.config.getNumber("Fluid.Summarizer.Attempts") ??
607
+ defaultNumberSummarizationAttempts;
601
608
 
602
609
  if (totalAttempts > attempts.length) {
603
610
  this.mc.logger.sendTelemetryEvent({
@@ -646,6 +653,7 @@ export class RunningSummarizer implements IDisposable {
646
653
  if (result.success) {
647
654
  return;
648
655
  }
656
+
649
657
  // Check for retryDelay that can come from summaryNack or upload summary flow.
650
658
  // Retry the same step only once per retryAfter response.
651
659
  overrideDelaySeconds = result.retryAfterSeconds;
@@ -668,6 +676,20 @@ export class RunningSummarizer implements IDisposable {
668
676
  }
669
677
  }
670
678
 
679
+ if (this.shouldAbortOnSummaryFailure) {
680
+ this.mc.logger.sendTelemetryEvent(
681
+ {
682
+ eventName: "ClosingSummarizerOnSummaryStale",
683
+ reason,
684
+ message: lastResult?.message,
685
+ },
686
+ lastResult?.error,
687
+ );
688
+
689
+ this.stopSummarizerCallback("latestSummaryStateStale");
690
+ this.runtime.closeFn();
691
+ return;
692
+ }
671
693
  // If all attempts failed, log error (with last attempt info) and close the summarizer container
672
694
  this.mc.logger.sendErrorEvent(
673
695
  {
@@ -263,14 +263,6 @@ export class Summarizer extends EventEmitter implements ISummarizer {
263
263
  refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
264
264
  summaryTime: Date.now(),
265
265
  } as const),
266
- (errorMessage: string) => {
267
- if (!this._disposed) {
268
- this.logger.sendErrorEvent(
269
- { eventName: "summarizingError" },
270
- createSummarizingWarning(errorMessage, true),
271
- );
272
- }
273
- },
274
266
  this.summaryCollection,
275
267
  runCoordinator /* cancellationToken */,
276
268
  (reason) => runCoordinator.stop(reason) /* stopSummarizerCallback */,
@@ -26,6 +26,10 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
26
26
  return this._lastSuccessfulSummary;
27
27
  }
28
28
 
29
+ public get opsSinceLastSummary(): number {
30
+ return this.numSystemOpsBefore + this.numNonSystemOpsBefore;
31
+ }
32
+
29
33
  public numNonRuntimeOps: number = 0;
30
34
  public totalOpsSize: number = 0;
31
35
  public hasMissingOpData: boolean = false;
@@ -164,7 +164,7 @@ export class SummarizerNode implements IRootSummarizerNode {
164
164
  // complains if this assert isn't done this way
165
165
  assert(
166
166
  this.wipReferenceSequenceNumber !== undefined,
167
- "Summarize should not be called when not tracking the summary",
167
+ 0x5df /* Summarize should not be called when not tracking the summary */,
168
168
  );
169
169
  const incrementalSummaryContext: IExperimentalIncrementalSummaryContext | undefined =
170
170
  this._latestSummary !== undefined
@@ -69,7 +69,7 @@ class SummaryNodeWithGC extends SummaryNode {
69
69
  * - Adds trackState param to summarize. If trackState is false, it bypasses the SummarizerNode and calls
70
70
  * directly into summarizeInternal method.
71
71
  */
72
- class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
72
+ export class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNodeWithGC {
73
73
  // Tracks the work-in-progress used routes during summary.
74
74
  private wipSerializedUsedRoutes: string | undefined;
75
75
 
@@ -536,8 +536,8 @@ class SummarizerNodeWithGC extends SummarizerNode implements IRootSummarizerNode
536
536
  JSON.stringify(newSerializedRoutes),
537
537
  {
538
538
  referenceSequenceNumber: value.referenceSequenceNumber,
539
- basePath: value.basePath,
540
- localPath: value.localPath,
539
+ basePath: child.latestSummary.basePath,
540
+ localPath: child.latestSummary.localPath,
541
541
  },
542
542
  );
543
543
  child.addPendingSummary(key, newLatestSummaryNode);
@@ -78,6 +78,14 @@ export interface ISummarizerRuntime extends IConnectableRuntime {
78
78
  readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
79
79
  disposeFn?(): void;
80
80
  closeFn(): void;
81
+ on?(
82
+ event: "op",
83
+ listener: (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => void,
84
+ ): this;
85
+ off?(
86
+ event: "op",
87
+ listener: (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => void,
88
+ ): this;
81
89
  }
82
90
 
83
91
  /** Options affecting summarize behavior. */
@@ -294,8 +302,13 @@ export type SummarizerStopReason =
294
302
  | "notElectedClient"
295
303
  /** Summarizer client was disconnected */
296
304
  | "summarizerClientDisconnected"
297
- /* running summarizer threw an exception */
298
- | "summarizerException";
305
+ /** running summarizer threw an exception */
306
+ | "summarizerException"
307
+ /**
308
+ * The previous summary state on the summarizer is not the most recently acked summary. this also happens when the
309
+ * first submitSummary attempt fails for any reason and there's a 2nd summary attempt without an ack
310
+ */
311
+ | "latestSummaryStateStale";
299
312
 
300
313
  export interface ISummarizerEvents extends IEvent {
301
314
  /**
@@ -398,6 +411,8 @@ export interface ISummarizeHeuristicData {
398
411
 
399
412
  /** Mark that the last sent summary attempt has received an ack */
400
413
  markLastAttemptAsSuccessful(): void;
414
+
415
+ opsSinceLastSummary: number;
401
416
  }
402
417
 
403
418
  /** Responsible for running heuristics determining when to summarize. */
@@ -491,7 +506,9 @@ type SummaryGeneratorOptionalTelemetryProperties =
491
506
  /** Actual sequence number of the summary op proposal. */
492
507
  | "summarySequenceNumber"
493
508
  /** Optional Retry-After time in seconds. If specified, the client should wait this many seconds before retrying. */
494
- | "nackRetryAfter";
509
+ | "nackRetryAfter"
510
+ /** The stage at which the submit summary method failed at. This can help determine what type of failure we have */
511
+ | "stage";
495
512
 
496
513
  export type SummaryGeneratorTelemetry = Pick<
497
514
  ITelemetryProperties,