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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/blobManager.d.ts +3 -1
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +35 -2
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +45 -42
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +89 -40
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +1 -0
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +7 -2
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/garbageCollection.d.ts +15 -7
  14. package/dist/garbageCollection.d.ts.map +1 -1
  15. package/dist/garbageCollection.js +96 -36
  16. package/dist/garbageCollection.js.map +1 -1
  17. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  18. package/dist/opLifecycle/outbox.js +0 -1
  19. package/dist/opLifecycle/outbox.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/pendingStateManager.d.ts +4 -13
  24. package/dist/pendingStateManager.d.ts.map +1 -1
  25. package/dist/pendingStateManager.js +130 -160
  26. package/dist/pendingStateManager.js.map +1 -1
  27. package/dist/summarizer.js.map +1 -1
  28. package/dist/summarizerClientElection.d.ts +1 -2
  29. package/dist/summarizerClientElection.d.ts.map +1 -1
  30. package/dist/summarizerClientElection.js +3 -30
  31. package/dist/summarizerClientElection.js.map +1 -1
  32. package/dist/summarizerTypes.d.ts +0 -4
  33. package/dist/summarizerTypes.d.ts.map +1 -1
  34. package/dist/summarizerTypes.js.map +1 -1
  35. package/lib/blobManager.d.ts +3 -1
  36. package/lib/blobManager.d.ts.map +1 -1
  37. package/lib/blobManager.js +35 -2
  38. package/lib/blobManager.js.map +1 -1
  39. package/lib/containerRuntime.d.ts +45 -42
  40. package/lib/containerRuntime.d.ts.map +1 -1
  41. package/lib/containerRuntime.js +89 -40
  42. package/lib/containerRuntime.js.map +1 -1
  43. package/lib/dataStoreContext.d.ts +1 -0
  44. package/lib/dataStoreContext.d.ts.map +1 -1
  45. package/lib/dataStoreContext.js +7 -2
  46. package/lib/dataStoreContext.js.map +1 -1
  47. package/lib/garbageCollection.d.ts +15 -7
  48. package/lib/garbageCollection.d.ts.map +1 -1
  49. package/lib/garbageCollection.js +97 -37
  50. package/lib/garbageCollection.js.map +1 -1
  51. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  52. package/lib/opLifecycle/outbox.js +0 -1
  53. package/lib/opLifecycle/outbox.js.map +1 -1
  54. package/lib/packageVersion.d.ts +1 -1
  55. package/lib/packageVersion.js +1 -1
  56. package/lib/packageVersion.js.map +1 -1
  57. package/lib/pendingStateManager.d.ts +4 -13
  58. package/lib/pendingStateManager.d.ts.map +1 -1
  59. package/lib/pendingStateManager.js +130 -160
  60. package/lib/pendingStateManager.js.map +1 -1
  61. package/lib/summarizer.js.map +1 -1
  62. package/lib/summarizerClientElection.d.ts +1 -2
  63. package/lib/summarizerClientElection.d.ts.map +1 -1
  64. package/lib/summarizerClientElection.js +3 -30
  65. package/lib/summarizerClientElection.js.map +1 -1
  66. package/lib/summarizerTypes.d.ts +0 -4
  67. package/lib/summarizerTypes.d.ts.map +1 -1
  68. package/lib/summarizerTypes.js.map +1 -1
  69. package/package.json +55 -20
  70. package/src/blobManager.ts +41 -2
  71. package/src/containerRuntime.ts +118 -85
  72. package/src/dataStoreContext.ts +12 -6
  73. package/src/garbageCollection.ts +103 -34
  74. package/src/opLifecycle/outbox.ts +0 -2
  75. package/src/packageVersion.ts +1 -1
  76. package/src/pendingStateManager.ts +146 -187
  77. package/src/summarizer.ts +1 -1
  78. package/src/summarizerClientElection.ts +1 -30
  79. package/src/summarizerTypes.ts +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.0.0-internal.2.3.1",
3
+ "version": "2.0.0-internal.3.0.0",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -65,32 +65,32 @@
65
65
  "dependencies": {
66
66
  "@fluidframework/common-definitions": "^0.20.1",
67
67
  "@fluidframework/common-utils": "^1.0.0",
68
- "@fluidframework/container-definitions": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
69
- "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
70
- "@fluidframework/container-utils": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
71
- "@fluidframework/core-interfaces": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
72
- "@fluidframework/datastore": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
73
- "@fluidframework/driver-definitions": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
74
- "@fluidframework/driver-utils": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
75
- "@fluidframework/garbage-collector": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
68
+ "@fluidframework/container-definitions": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
69
+ "@fluidframework/container-runtime-definitions": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
70
+ "@fluidframework/container-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
71
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
72
+ "@fluidframework/datastore": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
73
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
74
+ "@fluidframework/driver-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
75
+ "@fluidframework/garbage-collector": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
76
76
  "@fluidframework/protocol-base": "^0.1038.2000",
77
77
  "@fluidframework/protocol-definitions": "^1.1.0",
78
- "@fluidframework/runtime-definitions": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
79
- "@fluidframework/runtime-utils": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
80
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
78
+ "@fluidframework/runtime-definitions": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
79
+ "@fluidframework/runtime-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
80
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
81
81
  "double-ended-queue": "^2.1.0-0",
82
82
  "events": "^3.1.0",
83
83
  "lz4js": "^0.2.0",
84
84
  "uuid": "^8.3.1"
85
85
  },
86
86
  "devDependencies": {
87
- "@fluid-tools/build-cli": "^0.7.0",
87
+ "@fluid-tools/build-cli": "^0.8.0",
88
88
  "@fluidframework/build-common": "^1.1.0",
89
- "@fluidframework/build-tools": "^0.7.0",
90
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.2.3.0",
89
+ "@fluidframework/build-tools": "^0.8.0",
90
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.2.2.0",
91
91
  "@fluidframework/eslint-config-fluid": "^2.0.0",
92
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
93
- "@fluidframework/test-runtime-utils": ">=2.0.0-internal.2.3.1 <2.0.0-internal.3.0.0",
92
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
93
+ "@fluidframework/test-runtime-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
94
94
  "@microsoft/api-extractor": "^7.22.2",
95
95
  "@rushstack/eslint-config": "^2.5.1",
96
96
  "@types/double-ended-queue": "^2.1.0",
@@ -109,8 +109,43 @@
109
109
  "typescript": "~4.5.5"
110
110
  },
111
111
  "typeValidation": {
112
- "version": "2.0.0-internal.2.3.1",
113
- "baselineRange": "2.0.0-internal.2.3.0",
114
- "broken": {}
112
+ "version": "2.0.0-internal.3.0.0",
113
+ "baselineRange": ">=2.0.0-internal.2.0.0 <2.0.0-internal.3.0.0",
114
+ "baselineVersion": "2.0.0-internal.2.2.0",
115
+ "broken": {
116
+ "RemovedVariableDeclaration_gcBlobPrefix": {
117
+ "forwardCompat": false,
118
+ "backCompat": false
119
+ },
120
+ "RemovedVariableDeclaration_gcTombstoneBlobKey": {
121
+ "forwardCompat": false,
122
+ "backCompat": false
123
+ },
124
+ "RemovedVariableDeclaration_gcTreeKey": {
125
+ "forwardCompat": false,
126
+ "backCompat": false
127
+ },
128
+ "VariableDeclaration_DefaultSummaryConfiguration": {
129
+ "backCompat": false
130
+ },
131
+ "InterfaceDeclaration_ISummaryBaseConfiguration": {
132
+ "backCompat": false
133
+ },
134
+ "TypeAliasDeclaration_ISummaryConfiguration": {
135
+ "backCompat": false
136
+ },
137
+ "InterfaceDeclaration_ISummaryConfigurationDisableHeuristics": {
138
+ "backCompat": false
139
+ },
140
+ "InterfaceDeclaration_ISummaryConfigurationHeuristics": {
141
+ "backCompat": false
142
+ },
143
+ "ClassDeclaration_ContainerRuntime": {
144
+ "forwardCompat": false
145
+ },
146
+ "InterfaceDeclaration_ISummarizerRuntime": {
147
+ "backCompat": false
148
+ }
149
+ }
115
150
  }
116
151
  }
@@ -101,12 +101,17 @@ enum PendingBlobStatus {
101
101
  OfflinePendingOp,
102
102
  }
103
103
 
104
+ type ICreateBlobResponseWithTTL = ICreateBlobResponse & Partial<Record<"minTTLInSeconds", number>>;
105
+
104
106
  interface PendingBlob {
105
107
  blob: ArrayBufferLike;
106
108
  status: PendingBlobStatus;
107
109
  storageId?: string;
108
110
  handleP: Deferred<IFluidHandle<ArrayBufferLike>>;
109
111
  uploadP: Promise<ICreateBlobResponse>;
112
+ localUploadTime?: number;
113
+ serverUploadTime?: number;
114
+ minTTLInSeconds?: number;
110
115
  }
111
116
 
112
117
  export interface IPendingBlobs { [id: string]: { blob: string; }; }
@@ -179,6 +184,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
179
184
  private readonly addedBlobReference: (fromNodePath: string, toNodePath: string) => void,
180
185
  private readonly runtime: IBlobManagerRuntime,
181
186
  stashedBlobs: IPendingBlobs = {},
187
+ private readonly getCurrentReferenceTimestampMs: () => number | undefined,
182
188
  ) {
183
189
  super();
184
190
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.runtime.logger, "BlobManager"));
@@ -398,12 +404,15 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
398
404
  }
399
405
  }
400
406
 
401
- private onUploadResolve(localId: string, response: ICreateBlobResponse) {
407
+ private onUploadResolve(localId: string, response: ICreateBlobResponseWithTTL) {
402
408
  const entry = this.pendingBlobs.get(localId);
403
409
  assert(entry?.status === PendingBlobStatus.OnlinePendingUpload ||
404
410
  entry?.status === PendingBlobStatus.OfflinePendingUpload,
405
411
  0x386 /* Must have pending blob entry for uploaded blob */);
406
412
  entry.storageId = response.id;
413
+ entry.localUploadTime = Date.now();
414
+ entry.minTTLInSeconds = response.minTTLInSeconds;
415
+ entry.serverUploadTime = this.getCurrentReferenceTimestampMs();
407
416
  if (this.runtime.connected) {
408
417
  if (entry.status === PendingBlobStatus.OnlinePendingUpload) {
409
418
  // Send a blob attach op. This serves two purposes:
@@ -411,6 +420,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
411
420
  // until its storage ID is added to the next summary.
412
421
  // 2. It will create a local ID to storage ID mapping in all clients which is needed to retrieve the
413
422
  // blob from the server via the storage ID.
423
+ this.logTimeInfo(entry, "sendBlobAttachResolveTTL");
414
424
  this.sendBlobAttachOp(localId, response.id);
415
425
  if (this.storageIds.has(response.id)) {
416
426
  // The blob is de-duped. Set up a local ID to storage ID mapping and return the blob. Since this is
@@ -472,6 +482,7 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
472
482
  * is called on reconnection.
473
483
  */
474
484
  if (entry.status !== PendingBlobStatus.OnlinePendingOp) {
485
+ this.logTimeInfo(entry, "sendBlobAttachTransitionOfflineTTL");
475
486
  this.sendBlobAttachOp(localId, entry.storageId);
476
487
  }
477
488
 
@@ -491,9 +502,12 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
491
502
  assert(!!metadata, 0x38b /* Resubmitted ops must have metadata */);
492
503
  const { localId, blobId }: { localId?: string; blobId?: string } = metadata;
493
504
  assert(localId !== undefined, 0x50d /* local ID not available on reSubmit */);
505
+ const pendingEntry = this.pendingBlobs.get(localId);
506
+ if (pendingEntry) {
507
+ this.logTimeInfo(pendingEntry, "sendBlobAttachResubmitTTL");
508
+ }
494
509
  if (!blobId) {
495
510
  // We submitted this op while offline. The blob should have been uploaded by now.
496
- const pendingEntry = this.pendingBlobs.get(localId);
497
511
  assert(pendingEntry?.status === PendingBlobStatus.OfflinePendingOp &&
498
512
  !!pendingEntry?.storageId, 0x38d /* blob must be uploaded before resubmitting BlobAttach op */);
499
513
  return this.sendBlobAttachOp(localId, pendingEntry.storageId);
@@ -501,6 +515,31 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
501
515
  return this.sendBlobAttachOp(localId, blobId);
502
516
  }
503
517
 
518
+ private logTimeInfo(pendingEntry: PendingBlob, eventName: string) {
519
+ let timeLapseSinceLocalUpload: number = 0;
520
+ let timeLapseSinceServerUpload: number = 0;
521
+ let expiredUsingLocalTime;
522
+ let expiredUsingServerTime;
523
+ if(pendingEntry.localUploadTime){
524
+ timeLapseSinceLocalUpload = (Date.now() - pendingEntry.localUploadTime) / 1000;
525
+ expiredUsingLocalTime = (pendingEntry.minTTLInSeconds?? 0) - timeLapseSinceLocalUpload < 0 ? true : false;
526
+ }
527
+ if(pendingEntry.serverUploadTime){
528
+ timeLapseSinceServerUpload = (Date.now() - pendingEntry.serverUploadTime) / 1000;
529
+ expiredUsingServerTime = (pendingEntry.minTTLInSeconds?? 0) - timeLapseSinceServerUpload < 0 ? true : false;
530
+ }
531
+ this.mc.logger.sendTelemetryEvent({
532
+ eventName,
533
+ entryStatus: pendingEntry.status,
534
+ timeLapseSinceLocalUpload,
535
+ timeLapseSinceServerUpload,
536
+ minTTLInSeconds: pendingEntry.minTTLInSeconds,
537
+ expiredUsingLocalTime,
538
+ expiredUsingServerTime,
539
+ });
540
+
541
+ }
542
+
504
543
  public processBlobAttachOp(message: ISequencedDocumentMessage, local: boolean) {
505
544
  const localId = message.metadata?.localId;
506
545
  const blobId = message.metadata?.blobId;
@@ -143,7 +143,6 @@ import {
143
143
  ISubmitSummaryOptions,
144
144
  ISummarizer,
145
145
  ISummarizerInternalsProvider,
146
- ISummarizerOptions,
147
146
  ISummarizerRuntime,
148
147
  IRefreshSummaryAckOptions,
149
148
  } from "./summarizerTypes";
@@ -205,13 +204,6 @@ export interface ISummaryBaseConfiguration {
205
204
  */
206
205
  initialSummarizerDelayMs: number;
207
206
 
208
- /**
209
- * @deprecated
210
- * Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
211
- * This defaults to false (disabled) and must be explicitly set to true to enable.
212
- */
213
- summarizerClientElection: boolean;
214
-
215
207
  /**
216
208
  * Defines the maximum allowed time to wait for a pending summary ack.
217
209
  * The maximum amount of time client will wait for a summarize is the minimum of
@@ -317,8 +309,6 @@ export const DefaultSummaryConfiguration: ISummaryConfiguration = {
317
309
 
318
310
  initialSummarizerDelayMs: 5 * 1000, // 5 secs.
319
311
 
320
- summarizerClientElection: false,
321
-
322
312
  nonRuntimeOpWeight: 0.1,
323
313
 
324
314
  runtimeOpWeight: 1.0,
@@ -386,40 +376,6 @@ export interface ISummaryRuntimeOptions {
386
376
  * {@link ISummaryBaseConfiguration.initialSummarizerDelayMs} instead.
387
377
  */
388
378
  initialSummarizerDelayMs?: number;
389
-
390
- /**
391
- * Flag that disables summaries if it is set to true.
392
- *
393
- * @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
394
- * {@link ISummaryConfigurationDisableSummarizer.state} instead.
395
- */
396
- disableSummaries?: boolean;
397
-
398
- /**
399
- * @defaultValue 7000 operations (ops)
400
- *
401
- * @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
402
- * {@link ISummaryBaseConfiguration.maxOpsSinceLastSummary} instead.
403
- */
404
- maxOpsSinceLastSummary?: number;
405
-
406
- /**
407
- * Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
408
- *
409
- * @defaultValue `false` (disabled) and must be explicitly set to true to enable.
410
- *
411
- * @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
412
- * {@link ISummaryBaseConfiguration.summarizerClientElection} instead.
413
- */
414
- summarizerClientElection?: boolean;
415
-
416
- /**
417
- * Options that control the running summarizer behavior.
418
- *
419
- * @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
420
- * `{@link ISummaryConfiguration.state} = "DisableHeuristics"` instead.
421
- * */
422
- summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
423
379
  }
424
380
 
425
381
  /**
@@ -491,9 +447,10 @@ export interface IContainerRuntimeOptions {
491
447
  */
492
448
  readonly chunkSizeInBytes?: number;
493
449
  /**
494
- * If enabled, the runtime will block all attempts to send an op with a different reference sequence number
495
- * from the previous ops submitted in the same JS turn. This happens when ops are reentrant (an op is created as a
496
- * response to another op, likely from an event handler).
450
+ * If enabled, the runtime will block all attempts to send an op inside the
451
+ * {@link ContainerRuntime#ensureNoDataModelChanges} callback. The callback is used by
452
+ * {@link @fluidframework/shared-object-base#SharedObjectCore} for event handlers so enabling this
453
+ * will disallow modifying DDSes while handling DDS events.
497
454
  *
498
455
  * By default, the feature is disabled. If enabled from options, the `Fluid.ContainerRuntime.DisableOpReentryCheck`
499
456
  * can be used to disable it at runtime.
@@ -663,6 +620,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
663
620
  public get IFluidRouter() { return this; }
664
621
 
665
622
  /**
623
+ * @deprecated - use loadRuntime instead.
666
624
  * Load the stores from a snapshot and returns the runtime.
667
625
  * @param context - Context of the container.
668
626
  * @param registryEntries - Mapping to the stores.
@@ -681,6 +639,54 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
681
639
  existing?: boolean,
682
640
  containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntime
683
641
  ): Promise<ContainerRuntime> {
642
+ let existingFlag = true;
643
+ if (!existing) {
644
+ existingFlag = false;
645
+ }
646
+ return this.loadRuntime({
647
+ context,
648
+ registryEntries,
649
+ existing: existingFlag,
650
+ requestHandler,
651
+ runtimeOptions,
652
+ containerScope,
653
+ containerRuntimeCtor,
654
+ });
655
+ }
656
+
657
+ /**
658
+ * Load the stores from a snapshot and returns the runtime.
659
+ * @param params - An object housing the runtime properties:
660
+ * - context - Context of the container.
661
+ * - registryEntries - Mapping to the stores.
662
+ * - existing - When loading from an existing snapshot
663
+ * - requestHandler - Request handlers for the container runtime
664
+ * - runtimeOptions - Additional options to be passed to the runtime
665
+ * - containerScope - runtime services provided with context
666
+ * - containerRuntimeCtor - Constructor to use to create the ContainerRuntime instance.
667
+ * This allows mixin classes to leverage this method to define their own async initializer.
668
+ */
669
+ public static async loadRuntime(
670
+ params: {
671
+ context: IContainerContext;
672
+ registryEntries: NamedFluidDataStoreRegistryEntries;
673
+ existing: boolean;
674
+ requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
675
+ runtimeOptions?: IContainerRuntimeOptions;
676
+ containerScope?: FluidObject;
677
+ containerRuntimeCtor?: typeof ContainerRuntime;
678
+ },
679
+ ): Promise<ContainerRuntime> {
680
+ const {
681
+ context,
682
+ registryEntries,
683
+ existing,
684
+ requestHandler,
685
+ runtimeOptions = {},
686
+ containerScope = {},
687
+ containerRuntimeCtor = ContainerRuntime
688
+ } = params;
689
+
684
690
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
685
691
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
686
692
  const backCompatContext: IContainerContext | OldContainerContextWithLogger = context;
@@ -762,7 +768,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
762
768
  if (loadSequenceNumberVerification === "log") {
763
769
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
764
770
  } else {
765
- // Call both close and dispose as close implementation will no longer dispose runtime in future (2.0.0-internal.3.0.0)
771
+ // Call both close and dispose as closeFn implementation will no longer dispose runtime in future
766
772
  context.closeFn(error);
767
773
  context.disposeFn?.(error);
768
774
  }
@@ -905,6 +911,33 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
905
911
  */
906
912
  private delayConnectClientId?: string;
907
913
 
914
+ private ensureNoDataModelChangesCalls = 0;
915
+
916
+ /**
917
+ * Tracks the number of detected reentrant ops to report,
918
+ * in order to self-throttle the telemetry events.
919
+ *
920
+ * This should be removed as part of ADO:2322
921
+ */
922
+ private opReentryCallsToReport = 5;
923
+
924
+ /**
925
+ * Invokes the given callback and expects that no ops are submitted
926
+ * until execution finishes. If an op is submitted, an error will be raised.
927
+ *
928
+ * Can be disabled by feature gate `Fluid.ContainerRuntime.DisableOpReentryCheck`
929
+ *
930
+ * @param callback - the callback to be invoked
931
+ */
932
+ public ensureNoDataModelChanges<T>(callback: () => T): T {
933
+ this.ensureNoDataModelChangesCalls++;
934
+ try {
935
+ return callback();
936
+ } finally {
937
+ this.ensureNoDataModelChangesCalls--;
938
+ }
939
+ }
940
+
908
941
  public get connected(): boolean {
909
942
  return this._connected;
910
943
  }
@@ -955,45 +988,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
955
988
 
956
989
  private readonly summariesDisabled: boolean;
957
990
  private isSummariesDisabled(): boolean {
958
- // back-compat: disableSummaries was moved from ISummaryRuntimeOptions
959
- // to ISummaryConfiguration in 0.60.
960
- if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
961
- return true;
962
- }
963
991
  return this.summaryConfiguration.state === "disabled";
964
992
  }
965
993
 
966
994
  private readonly heuristicsDisabled: boolean;
967
995
  private isHeuristicsDisabled(): boolean {
968
- // back-compat: disableHeuristics was moved from ISummarizerOptions
969
- // to ISummaryConfiguration in 0.60.
970
- if (this.runtimeOptions.summaryOptions.summarizerOptions?.disableHeuristics === true) {
971
- return true;
972
- }
973
996
  return this.summaryConfiguration.state === "disableHeuristics";
974
997
  }
975
998
 
976
- private readonly summarizerClientElectionEnabled: boolean;
977
- private isSummarizerClientElectionEnabled(): boolean {
978
- if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
979
- return this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection") ?? true;
980
- }
981
- // back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
982
- // to ISummaryConfiguration in 0.60.
983
- if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
984
- return true;
985
- }
986
- return this.summaryConfiguration.state !== "disabled"
987
- ? this.summaryConfiguration.summarizerClientElection === true
988
- : false;
989
- }
990
999
  private readonly maxOpsSinceLastSummary: number;
991
1000
  private getMaxOpsSinceLastSummary(): number {
992
- // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
993
- // to ISummaryConfiguration in 0.60.
994
- if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
995
- return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
996
- }
997
1001
  return this.summaryConfiguration.state !== "disabled"
998
1002
  ? this.summaryConfiguration.maxOpsSinceLastSummary
999
1003
  : 0;
@@ -1091,7 +1095,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1091
1095
 
1092
1096
  this.summariesDisabled = this.isSummariesDisabled();
1093
1097
  this.heuristicsDisabled = this.isHeuristicsDisabled();
1094
- this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
1095
1098
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
1096
1099
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
1097
1100
 
@@ -1198,6 +1201,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1198
1201
  (fromPath: string, toPath: string) => this.garbageCollector.addedOutboundReference(fromPath, toPath),
1199
1202
  this,
1200
1203
  pendingRuntimeState?.pendingAttachmentBlobs,
1204
+ () => this.getCurrentReferenceTimestampMs(),
1201
1205
  );
1202
1206
 
1203
1207
  this.scheduleManager = new ScheduleManager(
@@ -1213,7 +1217,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1213
1217
  clientId: () => this.clientId,
1214
1218
  close: this.closeFn,
1215
1219
  connected: () => this.connected,
1216
- flush: this.flush.bind(this),
1217
1220
  reSubmit: this.reSubmit.bind(this),
1218
1221
  rollback: this.rollback.bind(this),
1219
1222
  orderSequentially: this.orderSequentially.bind(this),
@@ -1272,7 +1275,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1272
1275
  this.summaryCollection,
1273
1276
  orderedClientElectionForSummarizer,
1274
1277
  this.maxOpsSinceLastSummary,
1275
- this.summarizerClientElectionEnabled,
1276
1278
  );
1277
1279
 
1278
1280
  if (this.context.clientDetails.type === summarizerClientType) {
@@ -1945,7 +1947,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1945
1947
  this._orderSequentiallyCalls--;
1946
1948
  }
1947
1949
 
1948
- if (this.flushMode === FlushMode.Immediate && this._orderSequentiallyCalls === 0) {
1950
+ // We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
1951
+ if (this.flushMode !== FlushMode.TurnBased && this._orderSequentiallyCalls === 0) {
1949
1952
  this.flush();
1950
1953
  }
1951
1954
  return result;
@@ -2630,6 +2633,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2630
2633
  metadata: Record<string, unknown> | undefined = undefined,
2631
2634
  ): void {
2632
2635
  this.verifyNotClosed();
2636
+ this.verifyCanSubmitOps();
2633
2637
 
2634
2638
  // There should be no ops in detached container state!
2635
2639
  assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
@@ -2723,6 +2727,36 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2723
2727
  }
2724
2728
  }
2725
2729
 
2730
+ private verifyCanSubmitOps() {
2731
+ if (this.ensureNoDataModelChangesCalls > 0) {
2732
+ const errorMessage = "Op was submitted from within a `ensureNoDataModelChanges` callback";
2733
+ if (this.opReentryCallsToReport > 0) {
2734
+ this.mc.logger.sendTelemetryEvent(
2735
+ { eventName: "OpReentry" },
2736
+ // We need to capture the call stack in order to inspect the source of this usage pattern
2737
+ new UsageError(errorMessage),
2738
+ );
2739
+ this.opReentryCallsToReport--;
2740
+ }
2741
+
2742
+ // Creating ops while processing ops can lead
2743
+ // to undefined behavior and events observed in the wrong order.
2744
+ // For example, we have two callbacks registered for a DDS, A and B.
2745
+ // Then if on change #1 callback A creates change #2, the invocation flow will be:
2746
+ //
2747
+ // A because of #1
2748
+ // A because of #2
2749
+ // B because of #2
2750
+ // B because of #1
2751
+ //
2752
+ // The runtime must enforce op coherence by not allowing ops to be submitted
2753
+ // while ops are being processed.
2754
+ if (this.enableOpReentryCheck) {
2755
+ throw new UsageError(errorMessage);
2756
+ }
2757
+ }
2758
+ }
2759
+
2726
2760
  /**
2727
2761
  * Finds the right store and asks it to resubmit the message. This typically happens when we
2728
2762
  * reconnect and there are pending messages.
@@ -2958,15 +2992,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2958
2992
  throw new UsageError("can't get state when offline load disabled");
2959
2993
  }
2960
2994
 
2995
+ if (this._orderSequentiallyCalls !== 0) {
2996
+ throw new UsageError("can't get state during orderSequentially");
2997
+ }
2961
2998
  // Flush pending batch.
2962
2999
  // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
2963
3000
  // to close current batch.
2964
3001
  this.flush();
2965
3002
 
2966
- if (this._orderSequentiallyCalls !== 0) {
2967
- throw new UsageError("can't get state during orderSequentially");
2968
- }
2969
-
2970
3003
  const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
2971
3004
  if (previousPendingState) {
2972
3005
  return {
@@ -191,6 +191,10 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
191
191
  return this._containerRuntime;
192
192
  }
193
193
 
194
+ public ensureNoDataModelChanges<T>(callback: () => T): T {
195
+ return this._containerRuntime.ensureNoDataModelChanges(callback);
196
+ }
197
+
194
198
  public get isLoaded(): boolean {
195
199
  return this.loaded;
196
200
  }
@@ -299,7 +303,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
299
303
 
300
304
  const thisSummarizeInternal =
301
305
  async (fullTree: boolean, trackState: boolean, telemetryContext?: ITelemetryContext) =>
302
- this.summarizeInternal(fullTree, trackState, telemetryContext);
306
+ this.summarizeInternal(fullTree, trackState, telemetryContext);
303
307
 
304
308
  this.summarizerNode = props.createSummarizerNodeFn(
305
309
  thisSummarizeInternal,
@@ -327,7 +331,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
327
331
  if (this.channelDeferred) {
328
332
  this.channelDeferred.promise.then((runtime) => {
329
333
  runtime.dispose();
330
- }).catch((error) => {});
334
+ }).catch((error) => { });
331
335
  }
332
336
  }
333
337
 
@@ -712,10 +716,12 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
712
716
  } catch (error) {
713
717
  this.channelDeferred?.reject(error);
714
718
  this.logger.sendErrorEvent(
715
- { eventName: "BindRuntimeError", fluidDataStoreId: {
716
- value: this.id,
717
- tag: TelemetryDataTag.CodeArtifact,
718
- } },
719
+ {
720
+ eventName: "BindRuntimeError", fluidDataStoreId: {
721
+ value: this.id,
722
+ tag: TelemetryDataTag.CodeArtifact,
723
+ },
724
+ },
719
725
  error);
720
726
  }
721
727
  }