@fluidframework/container-runtime 2.0.0-internal.5.1.0 → 2.0.0-internal.5.2.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 +16 -0
  2. package/dist/containerRuntime.d.ts +22 -0
  3. package/dist/containerRuntime.d.ts.map +1 -1
  4. package/dist/containerRuntime.js +54 -10
  5. package/dist/containerRuntime.js.map +1 -1
  6. package/dist/dataStoreContext.d.ts +1 -2
  7. package/dist/dataStoreContext.d.ts.map +1 -1
  8. package/dist/dataStoreContext.js.map +1 -1
  9. package/dist/dataStoreContexts.d.ts +2 -1
  10. package/dist/dataStoreContexts.d.ts.map +1 -1
  11. package/dist/dataStoreContexts.js.map +1 -1
  12. package/dist/dataStores.d.ts +2 -1
  13. package/dist/dataStores.d.ts.map +1 -1
  14. package/dist/dataStores.js.map +1 -1
  15. package/dist/gc/gcDefinitions.d.ts +0 -1
  16. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  17. package/dist/gc/gcDefinitions.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/opLifecycle/batchManager.d.ts +2 -1
  22. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  23. package/dist/opLifecycle/batchManager.js +5 -1
  24. package/dist/opLifecycle/batchManager.js.map +1 -1
  25. package/dist/opLifecycle/definitions.d.ts +11 -0
  26. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  27. package/dist/opLifecycle/definitions.js.map +1 -1
  28. package/dist/opLifecycle/index.d.ts +1 -1
  29. package/dist/opLifecycle/index.d.ts.map +1 -1
  30. package/dist/opLifecycle/index.js +2 -1
  31. package/dist/opLifecycle/index.js.map +1 -1
  32. package/dist/opLifecycle/outbox.d.ts +29 -2
  33. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  34. package/dist/opLifecycle/outbox.js +87 -19
  35. package/dist/opLifecycle/outbox.js.map +1 -1
  36. package/dist/packageVersion.d.ts +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/dist/pendingStateManager.d.ts +8 -3
  40. package/dist/pendingStateManager.d.ts.map +1 -1
  41. package/dist/pendingStateManager.js +23 -16
  42. package/dist/pendingStateManager.js.map +1 -1
  43. package/dist/summary/index.d.ts +2 -2
  44. package/dist/summary/index.d.ts.map +1 -1
  45. package/dist/summary/index.js +2 -1
  46. package/dist/summary/index.js.map +1 -1
  47. package/dist/summary/runningSummarizer.d.ts +1 -1
  48. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  49. package/dist/summary/runningSummarizer.js.map +1 -1
  50. package/dist/summary/summarizerNode/index.d.ts +1 -1
  51. package/dist/summary/summarizerNode/index.d.ts.map +1 -1
  52. package/dist/summary/summarizerNode/index.js.map +1 -1
  53. package/dist/summary/summarizerNode/summarizerNode.d.ts +32 -6
  54. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  55. package/dist/summary/summarizerNode/summarizerNode.js +90 -22
  56. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  57. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  58. package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  59. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  60. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  61. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  62. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +56 -20
  63. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  64. package/dist/summary/summarizerTypes.d.ts +7 -1
  65. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  66. package/dist/summary/summarizerTypes.js.map +1 -1
  67. package/dist/summary/summaryCollection.d.ts +2 -1
  68. package/dist/summary/summaryCollection.d.ts.map +1 -1
  69. package/dist/summary/summaryCollection.js.map +1 -1
  70. package/dist/summary/summaryFormat.d.ts +1 -0
  71. package/dist/summary/summaryFormat.d.ts.map +1 -1
  72. package/dist/summary/summaryFormat.js +2 -1
  73. package/dist/summary/summaryFormat.js.map +1 -1
  74. package/dist/summary/summaryGenerator.d.ts +13 -4
  75. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  76. package/dist/summary/summaryGenerator.js +22 -8
  77. package/dist/summary/summaryGenerator.js.map +1 -1
  78. package/dist/summary/summaryManager.d.ts +2 -1
  79. package/dist/summary/summaryManager.d.ts.map +1 -1
  80. package/dist/summary/summaryManager.js.map +1 -1
  81. package/lib/containerRuntime.d.ts +22 -0
  82. package/lib/containerRuntime.d.ts.map +1 -1
  83. package/lib/containerRuntime.js +57 -13
  84. package/lib/containerRuntime.js.map +1 -1
  85. package/lib/dataStoreContext.d.ts +1 -2
  86. package/lib/dataStoreContext.d.ts.map +1 -1
  87. package/lib/dataStoreContext.js.map +1 -1
  88. package/lib/dataStoreContexts.d.ts +2 -1
  89. package/lib/dataStoreContexts.d.ts.map +1 -1
  90. package/lib/dataStoreContexts.js.map +1 -1
  91. package/lib/dataStores.d.ts +2 -1
  92. package/lib/dataStores.d.ts.map +1 -1
  93. package/lib/dataStores.js.map +1 -1
  94. package/lib/gc/gcDefinitions.d.ts +0 -1
  95. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  96. package/lib/gc/gcDefinitions.js.map +1 -1
  97. package/lib/index.d.ts +1 -1
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js.map +1 -1
  100. package/lib/opLifecycle/batchManager.d.ts +2 -1
  101. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  102. package/lib/opLifecycle/batchManager.js +5 -1
  103. package/lib/opLifecycle/batchManager.js.map +1 -1
  104. package/lib/opLifecycle/definitions.d.ts +11 -0
  105. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  106. package/lib/opLifecycle/definitions.js.map +1 -1
  107. package/lib/opLifecycle/index.d.ts +1 -1
  108. package/lib/opLifecycle/index.d.ts.map +1 -1
  109. package/lib/opLifecycle/index.js +1 -1
  110. package/lib/opLifecycle/index.js.map +1 -1
  111. package/lib/opLifecycle/outbox.d.ts +29 -2
  112. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  113. package/lib/opLifecycle/outbox.js +85 -18
  114. package/lib/opLifecycle/outbox.js.map +1 -1
  115. package/lib/packageVersion.d.ts +1 -1
  116. package/lib/packageVersion.js +1 -1
  117. package/lib/packageVersion.js.map +1 -1
  118. package/lib/pendingStateManager.d.ts +8 -3
  119. package/lib/pendingStateManager.d.ts.map +1 -1
  120. package/lib/pendingStateManager.js +23 -16
  121. package/lib/pendingStateManager.js.map +1 -1
  122. package/lib/summary/index.d.ts +2 -2
  123. package/lib/summary/index.d.ts.map +1 -1
  124. package/lib/summary/index.js +1 -1
  125. package/lib/summary/index.js.map +1 -1
  126. package/lib/summary/runningSummarizer.d.ts +1 -1
  127. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  128. package/lib/summary/runningSummarizer.js.map +1 -1
  129. package/lib/summary/summarizerNode/index.d.ts +1 -1
  130. package/lib/summary/summarizerNode/index.d.ts.map +1 -1
  131. package/lib/summary/summarizerNode/index.js.map +1 -1
  132. package/lib/summary/summarizerNode/summarizerNode.d.ts +32 -6
  133. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  134. package/lib/summary/summarizerNode/summarizerNode.js +90 -22
  135. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  136. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +22 -2
  137. package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
  138. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  139. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +17 -2
  140. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  141. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +56 -20
  142. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  143. package/lib/summary/summarizerTypes.d.ts +7 -1
  144. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  145. package/lib/summary/summarizerTypes.js.map +1 -1
  146. package/lib/summary/summaryCollection.d.ts +2 -1
  147. package/lib/summary/summaryCollection.d.ts.map +1 -1
  148. package/lib/summary/summaryCollection.js.map +1 -1
  149. package/lib/summary/summaryFormat.d.ts +1 -0
  150. package/lib/summary/summaryFormat.d.ts.map +1 -1
  151. package/lib/summary/summaryFormat.js +2 -1
  152. package/lib/summary/summaryFormat.js.map +1 -1
  153. package/lib/summary/summaryGenerator.d.ts +13 -4
  154. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  155. package/lib/summary/summaryGenerator.js +20 -7
  156. package/lib/summary/summaryGenerator.js.map +1 -1
  157. package/lib/summary/summaryManager.d.ts +2 -1
  158. package/lib/summary/summaryManager.d.ts.map +1 -1
  159. package/lib/summary/summaryManager.js.map +1 -1
  160. package/package.json +17 -17
  161. package/src/containerRuntime.ts +82 -14
  162. package/src/dataStoreContext.ts +8 -2
  163. package/src/dataStoreContexts.ts +2 -1
  164. package/src/dataStores.ts +2 -2
  165. package/src/gc/gcDefinitions.ts +0 -1
  166. package/src/index.ts +2 -0
  167. package/src/opLifecycle/batchManager.ts +9 -1
  168. package/src/opLifecycle/definitions.ts +11 -0
  169. package/src/opLifecycle/index.ts +1 -1
  170. package/src/opLifecycle/outbox.ts +107 -16
  171. package/src/packageVersion.ts +1 -1
  172. package/src/pendingStateManager.ts +38 -34
  173. package/src/summary/index.ts +3 -1
  174. package/src/summary/runningSummarizer.ts +1 -1
  175. package/src/summary/summarizerNode/index.ts +1 -0
  176. package/src/summary/summarizerNode/summarizerNode.ts +107 -25
  177. package/src/summary/summarizerNode/summarizerNodeUtils.ts +25 -2
  178. package/src/summary/summarizerNode/summarizerNodeWithGc.ts +61 -19
  179. package/src/summary/summarizerTypes.ts +10 -1
  180. package/src/summary/summaryCollection.ts +2 -1
  181. package/src/summary/summaryFormat.ts +5 -1
  182. package/src/summary/summaryGenerator.ts +31 -8
  183. package/src/summary/summaryManager.ts +2 -1
@@ -113,7 +113,11 @@ import { v4 as uuid } from "uuid";
113
113
  import { ContainerFluidHandleContext } from "./containerHandleContext";
114
114
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
115
115
  import { ReportOpPerfTelemetry, IPerfSignalReport } from "./connectionTelemetry";
116
- import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
116
+ import {
117
+ IPendingBatchMessage,
118
+ IPendingLocalState,
119
+ PendingStateManager,
120
+ } from "./pendingStateManager";
117
121
  import { pkgVersion } from "./packageVersion";
118
122
  import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
119
123
  import { DataStores, getSummaryForDatastores } from "./dataStores";
@@ -149,6 +153,8 @@ import {
149
153
  ISummarizerRuntime,
150
154
  IRefreshSummaryAckOptions,
151
155
  RunWhileConnectedCoordinator,
156
+ IGenerateSummaryTreeResult,
157
+ RetriableSummaryError,
152
158
  } from "./summary";
153
159
  import { formExponentialFn, Throttler } from "./throttler";
154
160
  import {
@@ -173,6 +179,7 @@ import {
173
179
  OpSplitter,
174
180
  RemoteMessageProcessor,
175
181
  OpGroupingManager,
182
+ getLongStack,
176
183
  } from "./opLifecycle";
177
184
  import { DeltaManagerSummarizerProxy } from "./deltaManagerSummarizerProxy";
178
185
 
@@ -429,9 +436,24 @@ export interface IContainerRuntimeOptions {
429
436
  * By default, the feature is disabled. If enabled from options, the `Fluid.ContainerRuntime.DisableGroupedBatching`
430
437
  * flag can be used to disable it at runtime.
431
438
  *
439
+ * For safety, {@link IContainerRuntimeOptions#enableBatchRebasing} needs to also be enabled to ensure
440
+ * consistency across clients.
441
+ *
432
442
  * @experimental Not ready for use.
433
443
  */
434
444
  readonly enableGroupedBatching?: boolean;
445
+ /**
446
+ * Configures if the runtime should rebase a batch of ops when it detects op reentrancy,
447
+ * when an op is created as the result of processing another op. Usually this is the case
448
+ * when changes are made to a DDS inside a DDS 'onChanged' event handler. This means that the
449
+ * reentrant op will have a different reference sequence number than the rest of the ops in
450
+ * the batch, resulting in a different view of the state of the data model. Therefore all ops
451
+ * must be resubmitted and rebased to the current reference sequence number to be in agreement
452
+ * about the state of the data model.
453
+ *
454
+ * @experimental Not ready for use.
455
+ */
456
+ readonly enableBatchRebasing?: boolean;
435
457
  }
436
458
 
437
459
  /**
@@ -679,6 +701,7 @@ export class ContainerRuntime
679
701
  chunkSizeInBytes = defaultChunkSizeInBytes,
680
702
  enableOpReentryCheck = false,
681
703
  enableGroupedBatching = false,
704
+ enableBatchRebasing = false,
682
705
  } = runtimeOptions;
683
706
 
684
707
  const registry = new FluidDataStoreRegistry(registryEntries);
@@ -777,6 +800,7 @@ export class ContainerRuntime
777
800
  enableRuntimeIdCompressor,
778
801
  enableOpReentryCheck,
779
802
  enableGroupedBatching,
803
+ enableBatchRebasing,
780
804
  },
781
805
  containerScope,
782
806
  logger,
@@ -952,6 +976,12 @@ export class ContainerRuntime
952
976
  private readonly disableAttachReorder: boolean | undefined;
953
977
  private readonly summaryStateUpdateMethod: string | undefined;
954
978
  private readonly closeSummarizerDelayMs: number;
979
+ /**
980
+ * If true, summary generated is validate before uploading it to the server. With single commit summaries,
981
+ * summaries will be accepted once uploaded, so they should be validated before upload. However, this can
982
+ * currently be controlled via a feature flag as its a new functionality.
983
+ */
984
+ private readonly validateSummaryBeforeUpload: boolean;
955
985
 
956
986
  private readonly defaultTelemetrySignalSampleCount = 100;
957
987
  private _perfSignalData: IPerfSignalReport = {
@@ -1206,7 +1236,6 @@ export class ContainerRuntime
1206
1236
  getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
1207
1237
  getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
1208
1238
  readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
1209
- getContainerDiagnosticId: () => this.context.id,
1210
1239
  // GC runs in summarizer client and needs access to the real (non-proxy) active information. The proxy
1211
1240
  // delta manager would always return false for summarizer client.
1212
1241
  activeConnection: () => this.innerDeltaManager.active,
@@ -1299,7 +1328,7 @@ export class ContainerRuntime
1299
1328
  close: this.closeFn,
1300
1329
  connected: () => this.connected,
1301
1330
  reSubmit: this.reSubmit.bind(this),
1302
- orderSequentially: this.orderSequentially.bind(this),
1331
+ reSubmitBatch: this.reSubmitBatch.bind(this),
1303
1332
  },
1304
1333
  pendingRuntimeState?.pending,
1305
1334
  );
@@ -1318,6 +1347,9 @@ export class ContainerRuntime
1318
1347
  const disablePartialFlush = this.mc.config.getBoolean(
1319
1348
  "Fluid.ContainerRuntime.DisablePartialFlush",
1320
1349
  );
1350
+ const enableBatchRebasing =
1351
+ runtimeOptions.enableBatchRebasing &&
1352
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableBatchRebasing") !== true;
1321
1353
  this.outbox = new Outbox({
1322
1354
  shouldSend: () => this.canSendOps(),
1323
1355
  pendingStateManager: this.pendingStateManager,
@@ -1328,6 +1360,7 @@ export class ContainerRuntime
1328
1360
  compressionOptions,
1329
1361
  maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
1330
1362
  disablePartialFlush: disablePartialFlush === true,
1363
+ enableBatchRebasing,
1331
1364
  },
1332
1365
  logger: this.mc.logger,
1333
1366
  groupingManager: opGroupingManager,
@@ -1335,6 +1368,9 @@ export class ContainerRuntime
1335
1368
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
1336
1369
  clientSequenceNumber: this._processedClientSequenceNumber,
1337
1370
  }),
1371
+ reSubmit: this.reSubmit.bind(this),
1372
+ opReentrancy: () => this.ensureNoDataModelChangesCalls > 0,
1373
+ closeContainer: this.closeFn,
1338
1374
  });
1339
1375
 
1340
1376
  this.context.quorum.on("removeMember", (clientId: string) => {
@@ -1342,12 +1378,15 @@ export class ContainerRuntime
1342
1378
  });
1343
1379
 
1344
1380
  this.summaryStateUpdateMethod = this.mc.config.getString(
1345
- "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethod",
1381
+ "Fluid.ContainerRuntime.Test.SummaryStateUpdateMethodV2",
1346
1382
  );
1347
1383
  const closeSummarizerDelayOverride = this.mc.config.getNumber(
1348
1384
  "Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
1349
1385
  );
1350
1386
  this.closeSummarizerDelayMs = closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
1387
+ this.validateSummaryBeforeUpload =
1388
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.Test.ValidateSummaryBeforeUpload") ??
1389
+ false;
1351
1390
 
1352
1391
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
1353
1392
 
@@ -1492,6 +1531,7 @@ export class ContainerRuntime
1492
1531
  idCompressorEnabled: this.idCompressorEnabled,
1493
1532
  summaryStateUpdateMethod: this.summaryStateUpdateMethod,
1494
1533
  closeSummarizerDelayOverride,
1534
+ enableBatchRebasing,
1495
1535
  }),
1496
1536
  telemetryDocumentId: this.telemetryDocumentId,
1497
1537
  groupedBatchingEnabled: this.groupedBatchingEnabled,
@@ -2808,7 +2848,7 @@ export class ContainerRuntime
2808
2848
  summaryNumber,
2809
2849
  ...partialStats,
2810
2850
  };
2811
- const generateSummaryData = {
2851
+ const generateSummaryData: Omit<IGenerateSummaryTreeResult, "stage" | "error"> = {
2812
2852
  referenceSequenceNumber: summaryRefSeqNum,
2813
2853
  minimumSequenceNumber,
2814
2854
  summaryTree,
@@ -2817,6 +2857,21 @@ export class ContainerRuntime
2817
2857
  forcedFullTree,
2818
2858
  } as const;
2819
2859
 
2860
+ // If validateSummaryBeforeUpload is true, validate that the summary generated by the summarizer nodes is
2861
+ // correct before this summary is uploaded.
2862
+ if (this.validateSummaryBeforeUpload) {
2863
+ const validateResult = this.summarizerNode.validateSummary();
2864
+ if (!validateResult.success) {
2865
+ const { success, ...loggingProps } = validateResult;
2866
+ const error = new RetriableSummaryError(
2867
+ validateResult.reason,
2868
+ validateResult.retryAfterSeconds,
2869
+ { ...loggingProps },
2870
+ );
2871
+ return { stage: "base", ...generateSummaryData, error };
2872
+ }
2873
+ }
2874
+
2820
2875
  continueResult = checkContinue();
2821
2876
  if (!continueResult.continue) {
2822
2877
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
@@ -2894,7 +2949,15 @@ export class ContainerRuntime
2894
2949
  submitOpDuration: trace.trace().duration,
2895
2950
  } as const;
2896
2951
 
2897
- this.summarizerNode.completeSummary(handle);
2952
+ try {
2953
+ // If validateSummaryBeforeUpload is false, the summary should be validated in this step.
2954
+ this.summarizerNode.completeSummary(
2955
+ handle,
2956
+ !this.validateSummaryBeforeUpload /* validate */,
2957
+ );
2958
+ } catch (error) {
2959
+ return { stage: "upload", ...uploadData, error };
2960
+ }
2898
2961
  return submitData;
2899
2962
  } finally {
2900
2963
  // Cleanup wip summary in case of failure
@@ -3157,7 +3220,7 @@ export class ContainerRuntime
3157
3220
  this.mc.logger.sendTelemetryEvent(
3158
3221
  { eventName: "OpReentry" },
3159
3222
  // We need to capture the call stack in order to inspect the source of this usage pattern
3160
- new UsageError(errorMessage),
3223
+ getLongStack(() => new UsageError(errorMessage)),
3161
3224
  );
3162
3225
  this.opReentryCallsToReport--;
3163
3226
  }
@@ -3180,14 +3243,19 @@ export class ContainerRuntime
3180
3243
  }
3181
3244
  }
3182
3245
 
3183
- private reSubmit(
3184
- content: string,
3185
- localOpMetadata: unknown,
3186
- opMetadata: Record<string, unknown> | undefined,
3187
- ) {
3246
+ private reSubmitBatch(batch: IPendingBatchMessage[]) {
3247
+ this.orderSequentially(() => {
3248
+ for (const message of batch) {
3249
+ this.reSubmit(message);
3250
+ }
3251
+ });
3252
+ this.flush();
3253
+ }
3254
+
3255
+ private reSubmit(message: IPendingBatchMessage) {
3188
3256
  // Need to parse from string for back-compat
3189
- const { contents, type } = this.parseOpContent(content);
3190
- this.reSubmitCore(type, contents, localOpMetadata, opMetadata);
3257
+ const { contents, type } = this.parseOpContent(message.content);
3258
+ this.reSubmitCore(type, contents, message.localOpMetadata, message.opMetadata);
3191
3259
  }
3192
3260
 
3193
3261
  /**
@@ -3,8 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryProperties } from "@fluidframework/common-definitions";
7
- import { FluidObject, IRequest, IResponse, IFluidHandle } from "@fluidframework/core-interfaces";
6
+ import { ITelemetryProperties } from "@fluidframework/common-definitions";
7
+ import {
8
+ IDisposable,
9
+ FluidObject,
10
+ IRequest,
11
+ IResponse,
12
+ IFluidHandle,
13
+ } from "@fluidframework/core-interfaces";
8
14
  import {
9
15
  IAudience,
10
16
  IDeltaManager,
@@ -3,7 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable, ITelemetryBaseLogger } from "@fluidframework/common-definitions";
6
+ import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
7
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
8
  import { assert, Deferred, Lazy } from "@fluidframework/common-utils";
8
9
  import { ChildLogger, ITelemetryLoggerExt } from "@fluidframework/telemetry-utils";
9
10
  import { FluidDataStoreContext, LocalFluidDataStoreContext } from "./dataStoreContext";
package/src/dataStores.ts CHANGED
@@ -3,12 +3,12 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryBaseLogger, IDisposable } from "@fluidframework/common-definitions";
6
+ import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
7
7
  import {
8
8
  DataCorruptionError,
9
9
  extractSafePropertiesFromMessage,
10
10
  } from "@fluidframework/container-utils";
11
- import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces";
11
+ import { IDisposable, IFluidHandle, IRequest } from "@fluidframework/core-interfaces";
12
12
  import { FluidObjectHandle } from "@fluidframework/datastore";
13
13
  import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
14
14
  import {
@@ -263,7 +263,6 @@ export interface IGarbageCollectorCreateParams {
263
263
  readonly getLastSummaryTimestampMs: () => number | undefined;
264
264
  readonly readAndParseBlob: ReadAndParseBlob;
265
265
  readonly activeConnection: () => boolean;
266
- readonly getContainerDiagnosticId: () => string;
267
266
  }
268
267
 
269
268
  export interface IGCRuntimeOptions {
package/src/index.ts CHANGED
@@ -67,6 +67,8 @@ export {
67
67
  OpActionEventListener,
68
68
  OpActionEventName,
69
69
  ICancellableSummarizerController,
70
+ SubmitSummaryFailureData,
71
+ SummaryStage,
70
72
  } from "./summary";
71
73
  export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
72
74
  export { generateStableId, isStableId, assertIsStableId } from "./id-compressor";
@@ -29,6 +29,7 @@ const opOverhead = 200;
29
29
  export class BatchManager {
30
30
  private pendingBatch: BatchMessage[] = [];
31
31
  private batchContentSize = 0;
32
+ private hasReentrantOps = false;
32
33
 
33
34
  public get length() {
34
35
  return this.pendingBatch.length;
@@ -54,9 +55,14 @@ export class BatchManager {
54
55
 
55
56
  constructor(public readonly options: IBatchManagerOptions) {}
56
57
 
57
- public push(message: BatchMessage, currentClientSequenceNumber?: number): boolean {
58
+ public push(
59
+ message: BatchMessage,
60
+ reentrant: boolean,
61
+ currentClientSequenceNumber?: number,
62
+ ): boolean {
58
63
  const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
59
64
  const opCount = this.pendingBatch.length;
65
+ this.hasReentrantOps = this.hasReentrantOps || reentrant;
60
66
 
61
67
  // Attempt to estimate batch size, aka socket message size.
62
68
  // Each op has pretty large envelope, estimating to be 200 bytes.
@@ -100,11 +106,13 @@ export class BatchManager {
100
106
  content: this.pendingBatch,
101
107
  contentSizeInBytes: this.batchContentSize,
102
108
  referenceSequenceNumber: this.referenceSequenceNumber,
109
+ hasReentrantOps: this.hasReentrantOps,
103
110
  };
104
111
 
105
112
  this.pendingBatch = [];
106
113
  this.batchContentSize = 0;
107
114
  this.clientSequenceNumber = undefined;
115
+ this.hasReentrantOps = false;
108
116
 
109
117
  return addBatchMetadata(batch);
110
118
  }
@@ -34,6 +34,17 @@ export interface IBatch {
34
34
  * The reference sequence number for the batch
35
35
  */
36
36
  readonly referenceSequenceNumber: number | undefined;
37
+ /**
38
+ * Wether or not the batch contains at least one op which was produced as the result
39
+ * of processing another op. This means that the batch must be rebased before
40
+ * submitted, to ensure that all ops have the same reference sequence numbers and a
41
+ * consistent view of the data model. This happens when the op is created within a
42
+ * 'changed' event handler of a DDS and will have a different reference sequence number
43
+ * than the rest of the ops in the batch, meaning that it has a different view of the
44
+ * state of the data model, therefore all ops must be resubmitted and rebased to the current
45
+ * reference sequence number to be in agreement about the data model state.
46
+ */
47
+ readonly hasReentrantOps?: boolean;
37
48
  }
38
49
 
39
50
  export interface IBatchCheckpoint {
@@ -11,7 +11,7 @@ export {
11
11
  IChunkedOp,
12
12
  IMessageProcessingResult,
13
13
  } from "./definitions";
14
- export { Outbox } from "./outbox";
14
+ export { Outbox, getLongStack } from "./outbox";
15
15
  export { OpCompressor } from "./opCompressor";
16
16
  export { OpDecompressor } from "./opDecompressor";
17
17
  export { OpSplitter, splitOp } from "./opSplitter";
@@ -10,11 +10,11 @@ import {
10
10
  MonitoringContext,
11
11
  } from "@fluidframework/telemetry-utils";
12
12
  import { assert } from "@fluidframework/common-utils";
13
- import { IContainerContext } from "@fluidframework/container-definitions";
13
+ import { IContainerContext, ICriticalContainerError } from "@fluidframework/container-definitions";
14
14
  import { GenericError, UsageError } from "@fluidframework/container-utils";
15
15
  import { MessageType } from "@fluidframework/protocol-definitions";
16
16
  import { ICompressionRuntimeOptions } from "../containerRuntime";
17
- import { PendingStateManager } from "../pendingStateManager";
17
+ import { IPendingBatchMessage, PendingStateManager } from "../pendingStateManager";
18
18
  import {
19
19
  BatchManager,
20
20
  BatchSequenceNumbers,
@@ -31,6 +31,7 @@ export interface IOutboxConfig {
31
31
  // The maximum size of a batch that we can send over the wire.
32
32
  readonly maxBatchSizeInBytes: number;
33
33
  readonly disablePartialFlush: boolean;
34
+ readonly enableBatchRebasing: boolean;
34
35
  }
35
36
 
36
37
  export interface IOutboxParameters {
@@ -43,18 +44,40 @@ export interface IOutboxParameters {
43
44
  readonly logger: ITelemetryLoggerExt;
44
45
  readonly groupingManager: OpGroupingManager;
45
46
  readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
47
+ readonly reSubmit: (message: IPendingBatchMessage) => void;
48
+ readonly opReentrancy: () => boolean;
49
+ readonly closeContainer: (error?: ICriticalContainerError) => void;
46
50
  }
47
51
 
48
- function getLongStack(action: () => Error): Error {
49
- // Increase the stack trace limit temporarily, so as to debug better in case it occurs.
52
+ /**
53
+ * Temporarily increase the stack limit while executing the provided action.
54
+ * If a negative value is provided for `length`, no stack frames will be collected.
55
+ * If Infinity is provided, all frames will be collected.
56
+ *
57
+ * ADO:4663 - add this to the common packages.
58
+ *
59
+ * @param action - action which returns an error
60
+ * @param length - number of stack frames to collect, 50 if unspecified.
61
+ * @returns the result of the action provided
62
+ */
63
+ export function getLongStack<T>(action: () => T, length: number = 50): T {
64
+ const errorObj = Error as any;
65
+ if (
66
+ (
67
+ Object.getOwnPropertyDescriptor(errorObj, "stackTraceLimit") ||
68
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(errorObj), "stackTraceLimit") ||
69
+ {}
70
+ ).writable !== true
71
+ ) {
72
+ return action();
73
+ }
74
+
75
+ const originalStackTraceLimit = errorObj.stackTraceLimit;
50
76
  try {
51
- const originalStackTraceLimit = (Error as any).stackTraceLimit;
52
- (Error as any).stackTraceLimit = 50;
53
- const result = action();
54
- (Error as any).stackTraceLimit = originalStackTraceLimit;
55
- return result;
56
- } catch (error) {
77
+ errorObj.stackTraceLimit = length;
57
78
  return action();
79
+ } finally {
80
+ errorObj.stackTraceLimit = originalStackTraceLimit;
58
81
  }
59
82
  }
60
83
 
@@ -63,6 +86,8 @@ export class Outbox {
63
86
  private readonly attachFlowBatch: BatchManager;
64
87
  private readonly mainBatch: BatchManager;
65
88
  private readonly defaultAttachFlowSoftLimitInBytes = 320 * 1024;
89
+ private batchRebasesToReport = 5;
90
+ private rebasing = false;
66
91
 
67
92
  /**
68
93
  * Track the number of ops which were detected to have a mismatched
@@ -132,7 +157,7 @@ export class Outbox {
132
157
  }
133
158
 
134
159
  if (!this.params.config.disablePartialFlush) {
135
- this.flush();
160
+ this.flushAll();
136
161
  }
137
162
  }
138
163
 
@@ -142,6 +167,7 @@ export class Outbox {
142
167
  if (
143
168
  !this.mainBatch.push(
144
169
  message,
170
+ this.isContextReentrant(),
145
171
  this.params.getCurrentSequenceNumbers().clientSequenceNumber,
146
172
  )
147
173
  ) {
@@ -160,16 +186,18 @@ export class Outbox {
160
186
  if (
161
187
  !this.attachFlowBatch.push(
162
188
  message,
189
+ this.isContextReentrant(),
163
190
  this.params.getCurrentSequenceNumbers().clientSequenceNumber,
164
191
  )
165
192
  ) {
166
193
  // BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
167
194
  // when queue is not empty.
168
195
  // Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
169
- this.flushInternal(this.attachFlowBatch.popBatch());
196
+ this.flushInternal(this.attachFlowBatch);
170
197
  if (
171
198
  !this.attachFlowBatch.push(
172
199
  message,
200
+ this.isContextReentrant(),
173
201
  this.params.getCurrentSequenceNumbers().clientSequenceNumber,
174
202
  )
175
203
  ) {
@@ -191,22 +219,85 @@ export class Outbox {
191
219
  this.attachFlowBatch.contentSizeInBytes >=
192
220
  this.params.config.compressionOptions.minimumBatchSizeInBytes
193
221
  ) {
194
- this.flushInternal(this.attachFlowBatch.popBatch());
222
+ this.flushInternal(this.attachFlowBatch);
195
223
  }
196
224
  }
197
225
 
198
226
  public flush() {
199
- this.flushInternal(this.attachFlowBatch.popBatch());
200
- this.flushInternal(this.mainBatch.popBatch());
227
+ if (this.isContextReentrant()) {
228
+ const error = new UsageError("Flushing is not supported inside DDS event handlers");
229
+ this.params.closeContainer(error);
230
+ throw error;
231
+ }
232
+
233
+ this.flushAll();
234
+ }
235
+
236
+ private flushAll() {
237
+ this.flushInternal(this.attachFlowBatch);
238
+ this.flushInternal(this.mainBatch);
201
239
  }
202
240
 
203
- private flushInternal(rawBatch: IBatch) {
241
+ private flushInternal(batchManager: BatchManager) {
242
+ if (batchManager.empty) {
243
+ return;
244
+ }
245
+
246
+ const rawBatch = batchManager.popBatch();
247
+ if (rawBatch.hasReentrantOps === true && this.params.config.enableBatchRebasing) {
248
+ assert(!this.rebasing, 0x6fa /* A rebased batch should never have reentrant ops */);
249
+ // If a batch contains reentrant ops (ops created as a result from processing another op)
250
+ // it needs to be rebased so that we can ensure consistent reference sequence numbers
251
+ // and eventual consistency at the DDS level.
252
+ this.rebase(rawBatch, batchManager);
253
+ return;
254
+ }
255
+
204
256
  const processedBatch = this.compressBatch(rawBatch);
205
257
  this.sendBatch(processedBatch);
206
258
 
207
259
  this.persistBatch(rawBatch.content);
208
260
  }
209
261
 
262
+ /**
263
+ * Rebases a batch. All the ops in the batch are resubmitted to the runtime and
264
+ * they will end up back in the same batch manager they were flushed from and subsequently flushed.
265
+ *
266
+ * @param rawBatch - the batch to be rebased
267
+ */
268
+ private rebase(rawBatch: IBatch, batchManager: BatchManager) {
269
+ assert(!this.rebasing, 0x6fb /* Reentrancy */);
270
+
271
+ this.rebasing = true;
272
+ for (const message of rawBatch.content) {
273
+ this.params.reSubmit({
274
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
275
+ content: message.contents!,
276
+ localOpMetadata: message.localOpMetadata,
277
+ opMetadata: message.metadata,
278
+ });
279
+ }
280
+
281
+ if (this.batchRebasesToReport > 0) {
282
+ this.mc.logger.sendTelemetryEvent(
283
+ {
284
+ eventName: "BatchRebase",
285
+ length: rawBatch.content.length,
286
+ referenceSequenceNumber: rawBatch.referenceSequenceNumber,
287
+ },
288
+ new UsageError("BatchRebase"),
289
+ );
290
+ this.batchRebasesToReport--;
291
+ }
292
+
293
+ this.flushInternal(batchManager);
294
+ this.rebasing = false;
295
+ }
296
+
297
+ private isContextReentrant(): boolean {
298
+ return this.params.opReentrancy() && !this.rebasing;
299
+ }
300
+
210
301
  private compressBatch(batch: IBatch): IBatch {
211
302
  if (
212
303
  batch.content.length === 0 ||
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.5.1.0";
9
+ export const pkgVersion = "2.0.0-internal.5.2.0";
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IDisposable } from "@fluidframework/common-definitions";
6
+ import { IDisposable } from "@fluidframework/core-interfaces";
7
7
  import { assert, Lazy } from "@fluidframework/common-utils";
8
8
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
9
9
  import { DataProcessingError } from "@fluidframework/container-utils";
@@ -50,17 +50,19 @@ export interface IPendingLocalState {
50
50
  pendingStates: IPendingState[];
51
51
  }
52
52
 
53
+ export interface IPendingBatchMessage {
54
+ content: string;
55
+ localOpMetadata: unknown;
56
+ opMetadata: Record<string, unknown> | undefined;
57
+ }
58
+
53
59
  export interface IRuntimeStateHandler {
54
60
  connected(): boolean;
55
61
  clientId(): string | undefined;
56
62
  close(error?: ICriticalContainerError): void;
57
63
  applyStashedOp(content: string): Promise<unknown>;
58
- reSubmit(
59
- content: string | undefined,
60
- localOpMetadata: unknown,
61
- opMetadata: Record<string, unknown> | undefined,
62
- ): void;
63
- orderSequentially(callback: () => void): void;
64
+ reSubmit(message: IPendingBatchMessage): void;
65
+ reSubmitBatch(batch: IPendingBatchMessage[]): void;
64
66
  }
65
67
 
66
68
  /**
@@ -379,35 +381,37 @@ export class PendingStateManager implements IDisposable {
379
381
  0x554 /* Last pending message cannot be a batch begin */,
380
382
  );
381
383
 
382
- this.stateHandler.orderSequentially(() => {
383
- while (pendingMessagesCount >= 0) {
384
- // check is >= because batch end may be last pending message
385
- this.stateHandler.reSubmit(
386
- pendingMessage.content,
387
- pendingMessage.localOpMetadata,
388
- pendingMessage.opMetadata,
389
- );
390
-
391
- if (pendingMessage.opMetadata?.batch === false) {
392
- break;
393
- }
394
- assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
395
-
396
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
397
- pendingMessage = this.pendingMessages.shift()!;
398
- pendingMessagesCount--;
399
- assert(
400
- pendingMessage.opMetadata?.batch !== true,
401
- 0x556 /* Batch start needs a corresponding batch end */,
402
- );
384
+ const batch: IPendingBatchMessage[] = [];
385
+
386
+ // check is >= because batch end may be last pending message
387
+ while (pendingMessagesCount >= 0) {
388
+ batch.push({
389
+ content: pendingMessage.content,
390
+ localOpMetadata: pendingMessage.localOpMetadata,
391
+ opMetadata: pendingMessage.opMetadata,
392
+ });
393
+
394
+ if (pendingMessage.opMetadata?.batch === false) {
395
+ break;
403
396
  }
404
- });
397
+ assert(pendingMessagesCount > 0, 0x555 /* No batch end found */);
398
+
399
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
400
+ pendingMessage = this.pendingMessages.shift()!;
401
+ pendingMessagesCount--;
402
+ assert(
403
+ pendingMessage.opMetadata?.batch !== true,
404
+ 0x556 /* Batch start needs a corresponding batch end */,
405
+ );
406
+ }
407
+
408
+ this.stateHandler.reSubmitBatch(batch);
405
409
  } else {
406
- this.stateHandler.reSubmit(
407
- pendingMessage.content,
408
- pendingMessage.localOpMetadata,
409
- pendingMessage.opMetadata,
410
- );
410
+ this.stateHandler.reSubmit({
411
+ content: pendingMessage.content,
412
+ localOpMetadata: pendingMessage.localOpMetadata,
413
+ opMetadata: pendingMessage.opMetadata,
414
+ });
411
415
  }
412
416
  }
413
417
  }