@fluidframework/container-runtime 2.91.0 → 2.92.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 (135) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/container-runtime.legacy.beta.api.md +2 -0
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/containerCompatibility.d.ts +1 -1
  5. package/dist/containerCompatibility.d.ts.map +1 -1
  6. package/dist/containerCompatibility.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +36 -9
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +97 -54
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/gc/garbageCollection.d.ts +1 -0
  12. package/dist/gc/garbageCollection.d.ts.map +1 -1
  13. package/dist/gc/garbageCollection.js +3 -8
  14. package/dist/gc/garbageCollection.js.map +1 -1
  15. package/dist/gc/gcDefinitions.d.ts +4 -0
  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 +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/legacy.d.ts +1 -1
  23. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  24. package/dist/opLifecycle/batchManager.js +2 -1
  25. package/dist/opLifecycle/batchManager.js.map +1 -1
  26. package/dist/opLifecycle/index.d.ts +1 -1
  27. package/dist/opLifecycle/index.d.ts.map +1 -1
  28. package/dist/opLifecycle/index.js +2 -1
  29. package/dist/opLifecycle/index.js.map +1 -1
  30. package/dist/opLifecycle/opGroupingManager.d.ts +6 -0
  31. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  32. package/dist/opLifecycle/opGroupingManager.js +11 -2
  33. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  34. package/dist/opLifecycle/opSerialization.d.ts +3 -1
  35. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  36. package/dist/opLifecycle/opSerialization.js +11 -9
  37. package/dist/opLifecycle/opSerialization.js.map +1 -1
  38. package/dist/opLifecycle/outbox.d.ts +0 -6
  39. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  40. package/dist/opLifecycle/outbox.js +2 -9
  41. package/dist/opLifecycle/outbox.js.map +1 -1
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.js +1 -1
  44. package/dist/packageVersion.js.map +1 -1
  45. package/dist/pendingStateManager.d.ts +7 -3
  46. package/dist/pendingStateManager.d.ts.map +1 -1
  47. package/dist/pendingStateManager.js +19 -7
  48. package/dist/pendingStateManager.js.map +1 -1
  49. package/dist/public.d.ts +1 -1
  50. package/dist/runtimeLayerCompatState.d.ts +1 -1
  51. package/dist/summary/documentSchema.d.ts +9 -3
  52. package/dist/summary/documentSchema.d.ts.map +1 -1
  53. package/dist/summary/documentSchema.js +19 -3
  54. package/dist/summary/documentSchema.js.map +1 -1
  55. package/dist/summary/orderedClientElection.js +2 -2
  56. package/dist/summary/orderedClientElection.js.map +1 -1
  57. package/dist/summary/summaryManager.d.ts +1 -0
  58. package/dist/summary/summaryManager.d.ts.map +1 -1
  59. package/dist/summary/summaryManager.js +9 -0
  60. package/dist/summary/summaryManager.js.map +1 -1
  61. package/internal.d.ts +1 -1
  62. package/legacy.d.ts +1 -1
  63. package/lib/containerCompatibility.d.ts +1 -1
  64. package/lib/containerCompatibility.d.ts.map +1 -1
  65. package/lib/containerCompatibility.js.map +1 -1
  66. package/lib/containerRuntime.d.ts +36 -9
  67. package/lib/containerRuntime.d.ts.map +1 -1
  68. package/lib/containerRuntime.js +97 -55
  69. package/lib/containerRuntime.js.map +1 -1
  70. package/lib/gc/garbageCollection.d.ts +1 -0
  71. package/lib/gc/garbageCollection.d.ts.map +1 -1
  72. package/lib/gc/garbageCollection.js +3 -8
  73. package/lib/gc/garbageCollection.js.map +1 -1
  74. package/lib/gc/gcDefinitions.d.ts +4 -0
  75. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  76. package/lib/gc/gcDefinitions.js.map +1 -1
  77. package/lib/index.d.ts +1 -1
  78. package/lib/index.d.ts.map +1 -1
  79. package/lib/index.js +1 -1
  80. package/lib/index.js.map +1 -1
  81. package/lib/legacy.d.ts +1 -1
  82. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  83. package/lib/opLifecycle/batchManager.js +2 -1
  84. package/lib/opLifecycle/batchManager.js.map +1 -1
  85. package/lib/opLifecycle/index.d.ts +1 -1
  86. package/lib/opLifecycle/index.d.ts.map +1 -1
  87. package/lib/opLifecycle/index.js +1 -1
  88. package/lib/opLifecycle/index.js.map +1 -1
  89. package/lib/opLifecycle/opGroupingManager.d.ts +6 -0
  90. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  91. package/lib/opLifecycle/opGroupingManager.js +10 -1
  92. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  93. package/lib/opLifecycle/opSerialization.d.ts +3 -1
  94. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  95. package/lib/opLifecycle/opSerialization.js +11 -9
  96. package/lib/opLifecycle/opSerialization.js.map +1 -1
  97. package/lib/opLifecycle/outbox.d.ts +0 -6
  98. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  99. package/lib/opLifecycle/outbox.js +2 -9
  100. package/lib/opLifecycle/outbox.js.map +1 -1
  101. package/lib/packageVersion.d.ts +1 -1
  102. package/lib/packageVersion.js +1 -1
  103. package/lib/packageVersion.js.map +1 -1
  104. package/lib/pendingStateManager.d.ts +7 -3
  105. package/lib/pendingStateManager.d.ts.map +1 -1
  106. package/lib/pendingStateManager.js +19 -7
  107. package/lib/pendingStateManager.js.map +1 -1
  108. package/lib/public.d.ts +1 -1
  109. package/lib/runtimeLayerCompatState.d.ts +1 -1
  110. package/lib/summary/documentSchema.d.ts +9 -3
  111. package/lib/summary/documentSchema.d.ts.map +1 -1
  112. package/lib/summary/documentSchema.js +19 -3
  113. package/lib/summary/documentSchema.js.map +1 -1
  114. package/lib/summary/orderedClientElection.js +2 -2
  115. package/lib/summary/orderedClientElection.js.map +1 -1
  116. package/lib/summary/summaryManager.d.ts +1 -0
  117. package/lib/summary/summaryManager.d.ts.map +1 -1
  118. package/lib/summary/summaryManager.js +9 -0
  119. package/lib/summary/summaryManager.js.map +1 -1
  120. package/package.json +27 -23
  121. package/src/containerCompatibility.ts +2 -0
  122. package/src/containerRuntime.ts +144 -66
  123. package/src/gc/garbageCollection.ts +4 -9
  124. package/src/gc/gcDefinitions.ts +4 -0
  125. package/src/index.ts +1 -0
  126. package/src/opLifecycle/batchManager.ts +2 -1
  127. package/src/opLifecycle/index.ts +1 -0
  128. package/src/opLifecycle/opGroupingManager.ts +11 -1
  129. package/src/opLifecycle/opSerialization.ts +14 -12
  130. package/src/opLifecycle/outbox.ts +2 -17
  131. package/src/packageVersion.ts +1 -1
  132. package/src/pendingStateManager.ts +27 -11
  133. package/src/summary/documentSchema.ts +25 -2
  134. package/src/summary/orderedClientElection.ts +2 -2
  135. package/src/summary/summaryManager.ts +11 -0
@@ -335,7 +335,9 @@ export class PendingStateManager implements IDisposable {
335
335
  return this.pendingMessagesCount !== 0;
336
336
  }
337
337
 
338
- public getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {
338
+ public getLocalState(snapshotSequenceNumber?: number): {
339
+ pending: IPendingLocalState;
340
+ } {
339
341
  assert(
340
342
  this.initialMessages.isEmpty(),
341
343
  0x2e9 /* "Must call getLocalState() after applying initial states" */,
@@ -359,10 +361,12 @@ export class PendingStateManager implements IDisposable {
359
361
  }
360
362
  }
361
363
  return {
362
- pendingStates: [
363
- ...newSavedOps,
364
- ...this.pendingMessages.toArray().map((message) => toSerializableForm(message)),
365
- ],
364
+ pending: {
365
+ pendingStates: [
366
+ ...newSavedOps,
367
+ ...this.pendingMessages.toArray().map((message) => toSerializableForm(message)),
368
+ ],
369
+ },
366
370
  };
367
371
  }
368
372
 
@@ -427,7 +431,7 @@ export class PendingStateManager implements IDisposable {
427
431
  clientId !== undefined,
428
432
  0xa33 /* clientId (from stateHandler) could only be undefined if we've never connected, but we have a CSN so we know that's not the case */,
429
433
  );
430
-
434
+ const batchInfo = { clientId, batchStartCsn, length: batch.length, ignoreBatchId, staged };
431
435
  for (const message of batch) {
432
436
  const {
433
437
  runtimeOp,
@@ -438,12 +442,11 @@ export class PendingStateManager implements IDisposable {
438
442
  const pendingMessage: IPendingMessage = {
439
443
  type: "message",
440
444
  referenceSequenceNumber,
441
- content: serializeOp(runtimeOp),
445
+ content: serializeOp(runtimeOp).content,
442
446
  runtimeOp,
443
447
  localOpMetadata,
444
448
  opMetadata,
445
- // Note: We only will read this off the first message, but put it on all for simplicity
446
- batchInfo: { clientId, batchStartCsn, length: batch.length, ignoreBatchId, staged },
449
+ batchInfo,
447
450
  };
448
451
  this.pendingMessages.push(pendingMessage);
449
452
  }
@@ -747,8 +750,12 @@ export class PendingStateManager implements IDisposable {
747
750
  * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
748
751
  * states in its queue. This includes triggering resubmission of unacked ops.
749
752
  * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
753
+ *
754
+ * @returns The unique batch infos for all batches that were replayed.
750
755
  */
751
- public replayPendingStates(options?: ReplayPendingStateOptions): void {
756
+ public replayPendingStates(
757
+ options?: ReplayPendingStateOptions,
758
+ ): IPendingMessage["batchInfo"][] {
752
759
  const { committingStagedBatches, squash } = {
753
760
  ...defaultReplayPendingStatesOptions,
754
761
  ...options,
@@ -775,6 +782,7 @@ export class PendingStateManager implements IDisposable {
775
782
 
776
783
  const initialPendingMessagesCount = this.pendingMessages.length;
777
784
  let remainingPendingMessagesCount = this.pendingMessages.length;
785
+ const replayedBatchSet = new Set<IPendingMessage["batchInfo"]>();
778
786
 
779
787
  let seenStagedBatch = false;
780
788
 
@@ -812,6 +820,7 @@ export class PendingStateManager implements IDisposable {
812
820
  if (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {
813
821
  // Resubmit no messages, with the batchId. Will result in another empty batch marker.
814
822
  this.stateHandler.reSubmitBatch([], { batchId, staged, squash });
823
+ replayedBatchSet.add(pendingMessage.batchInfo);
815
824
  continue;
816
825
  }
817
826
 
@@ -838,6 +847,7 @@ export class PendingStateManager implements IDisposable {
838
847
  ],
839
848
  { batchId, staged, squash },
840
849
  );
850
+ replayedBatchSet.add(pendingMessage.batchInfo);
841
851
  continue;
842
852
  }
843
853
  // else: batchMetadataFlag === true (It's a typical multi-message batch)
@@ -877,6 +887,7 @@ export class PendingStateManager implements IDisposable {
877
887
  }
878
888
 
879
889
  this.stateHandler.reSubmitBatch(batch, { batchId, staged, squash });
890
+ replayedBatchSet.add(pendingMessage.batchInfo);
880
891
  }
881
892
 
882
893
  if (!committingStagedBatches) {
@@ -894,6 +905,8 @@ export class PendingStateManager implements IDisposable {
894
905
  clientId: this.stateHandler.clientId(),
895
906
  });
896
907
  }
908
+
909
+ return [...replayedBatchSet];
897
910
  }
898
911
 
899
912
  /**
@@ -904,11 +917,13 @@ export class PendingStateManager implements IDisposable {
904
917
  // callback will only be given staged messages with a valid runtime op (i.e. not empty batch and not an initial message with only serialized content)
905
918
  stagedMessage: IPendingMessage & { runtimeOp: LocalContainerRuntimeMessage },
906
919
  ) => void,
907
- ): void {
920
+ ): IPendingMessage["batchInfo"][] {
921
+ const batchSet = new Set<IPendingMessage["batchInfo"]>();
908
922
  while (!this.pendingMessages.isEmpty()) {
909
923
  const stagedMessage = this.pendingMessages.peekBack();
910
924
  if (stagedMessage?.batchInfo.staged === true) {
911
925
  this.pendingMessages.pop();
926
+ batchSet.add(stagedMessage.batchInfo);
912
927
 
913
928
  if (hasTypicalRuntimeOp(stagedMessage)) {
914
929
  callback(stagedMessage);
@@ -921,6 +936,7 @@ export class PendingStateManager implements IDisposable {
921
936
  this.pendingMessages.toArray().every((m) => m.batchInfo.staged !== true),
922
937
  0xb89 /* Shouldn't be any more staged messages */,
923
938
  );
939
+ return [...batchSet];
924
940
  }
925
941
  }
926
942
 
@@ -512,7 +512,7 @@ function arrayToProp(arr: string[]): string[] | undefined {
512
512
  *
513
513
  * Users of this class need to use DocumentsSchemaController.sessionSchema to determine what features can be used.
514
514
  *
515
- * There are two modes this class can operate:
515
+ * There are three modes this class can operate:
516
516
  * 1) Legacy mode. In such mode it does not issue any ops to change document schema. Any changes happen implicitly,
517
517
  * right away, and new features are available right away
518
518
  * 2) Non-legacy mode. In such mode any changes to schema require an op roundtrip. This class will manage such transitions.
@@ -523,6 +523,9 @@ function arrayToProp(arr: string[]): string[] | undefined {
523
523
  * then eventually all documents that are modified will have that feature reflected in their schema. It could require
524
524
  * multiple reloads / new sessions to get there (depends on if code reacts to schema changes right away, or only consults
525
525
  * schema on document load).
526
+ * 3) Schema upgrade disabled mode (disableSchemaUpgrade = true). In this mode the controller will never send DocumentSchemaChange ops
527
+ * and will throw an error if any incoming schema change ops are received. The document schema is effectively frozen at the schema
528
+ * loaded for this session (snapshot) and will not accept further schema-change ops.
526
529
  *
527
530
  * How schemas are changed (in non-legacy mode):
528
531
  * If a client needs to change a schema, it will attempt to do so as part of normal ops sending process.
@@ -569,6 +572,7 @@ export class DocumentsSchemaController {
569
572
  * @param onSchemaChange - callback that is called whenever schema is changed (not called on creation / load, only when processing document schema change ops)
570
573
  * @param info - Informational properties of the document that are not subject to strict schema enforcement
571
574
  * @param logger - telemetry logger from the runtime
575
+ * @param disableSchemaUpgrade - when true, the controller will never send or accept DocumentSchemaChange ops
572
576
  */
573
577
  constructor(
574
578
  existing: boolean,
@@ -578,6 +582,7 @@ export class DocumentsSchemaController {
578
582
  private readonly onSchemaChange: (schema: IDocumentSchemaCurrent) => void,
579
583
  info: IDocumentSchemaInfo,
580
584
  logger: ITelemetryLoggerExt,
585
+ private readonly disableSchemaUpgrade: boolean,
581
586
  ) {
582
587
  // For simplicity, let's only support new schema features for explicit schema control mode
583
588
  assert(
@@ -704,9 +709,12 @@ export class DocumentsSchemaController {
704
709
  * Called by Container runtime whenever it is about to send some op.
705
710
  * It gives opportunity for controller to issue its own ops - we do not want to send ops if there are no local changes in document.
706
711
  * Please consider note above constructor about race conditions - current design is to generate op only once in a session lifetime.
707
- * @returns Optional message to send.
712
+ * @returns Optional message to send. Always returns undefined when disableSchemaUpgrade is true.
708
713
  */
709
714
  public maybeGenerateSchemaMessage(): IDocumentSchemaChangeMessageOutgoing | undefined {
715
+ if (this.disableSchemaUpgrade) {
716
+ return undefined;
717
+ }
710
718
  if (this.futureSchema !== undefined && !this.opPending) {
711
719
  this.opPending = true;
712
720
  assert(
@@ -739,6 +747,7 @@ export class DocumentsSchemaController {
739
747
  /**
740
748
  * Process document schema change messages
741
749
  * Called by ContainerRuntime whenever it sees document schema messages.
750
+ * When disableSchemaUpgrade is true, an error is thrown if any incoming schema change ops are received.
742
751
  * @param contents - contents of the messages
743
752
  * @param local - whether op is local
744
753
  * @param sequenceNumber - sequence number of the op
@@ -749,6 +758,20 @@ export class DocumentsSchemaController {
749
758
  local: boolean,
750
759
  sequenceNumber: number,
751
760
  ): boolean {
761
+ if (this.disableSchemaUpgrade) {
762
+ assert(
763
+ !local,
764
+ 0xceb /* local schema change messages should never be generated when disableSchemaUpgrade is enabled */,
765
+ );
766
+ // Clients with disableSchemaUpgrade enabled should never generate schema change messages, but they
767
+ // may receive them from misconfigured clients. In such case, throw on any incoming schema change ops
768
+ // to prevent unexpected schema upgrades.
769
+ throw DataProcessingError.create(
770
+ "DocSchema: Received schema change op while disableSchemaUpgrade is enabled",
771
+ "processDocumentSchemaMessages",
772
+ undefined,
773
+ );
774
+ }
752
775
  for (const content of contents) {
753
776
  this.validateSeqNumber(content.refSeq, this.documentSchema.refSeq, "content.refSeq");
754
777
  this.validateSeqNumber(this.documentSchema.refSeq, sequenceNumber, "refSeq");
@@ -515,7 +515,7 @@ export class OrderedClientElection
515
515
  "InteractiveClientElected",
516
516
  client,
517
517
  sequenceNumber,
518
- true /* forceSend */,
518
+ false /* forceSend */,
519
519
  reason,
520
520
  );
521
521
  // Changing the elected parent as well.
@@ -544,7 +544,7 @@ export class OrderedClientElection
544
544
  "ParentElected",
545
545
  client,
546
546
  sequenceNumber,
547
- true /* forceSend */,
547
+ false /* forceSend */,
548
548
  reason,
549
549
  );
550
550
  this._electedParent = client;
@@ -102,6 +102,7 @@ export class SummaryManager
102
102
  private latestClientId: string | undefined;
103
103
  private state = SummaryManagerState.Off;
104
104
  private summarizer?: ISummarizer;
105
+ private pendingStopReason?: SummarizerStopReason;
105
106
  private _disposed = false;
106
107
  private summarizerStopTimeout?: ReturnType<typeof setTimeout>;
107
108
  /**
@@ -287,6 +288,13 @@ export class SummaryManager
287
288
  this.summarizer = summarizer;
288
289
  this.setupForwardedEvents(summarizer);
289
290
 
291
+ // A stop may have been requested while we were awaiting summarizer creation.
292
+ // Replay it now so the summarizer can observe the stop intent and move to exit.
293
+ if (this.pendingStopReason !== undefined) {
294
+ summarizer.stop(this.pendingStopReason);
295
+ this.pendingStopReason = undefined;
296
+ }
297
+
290
298
  // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore
291
299
  // If we can't run the LastSummary, simply return as to avoid paying the cost of launching
292
300
  // the summarizer at all.
@@ -375,6 +383,7 @@ export class SummaryManager
375
383
  private cleanupAfterSummarizerStop(): void {
376
384
  this.summarizerGeneration++;
377
385
  this.state = SummaryManagerState.Off;
386
+ this.pendingStopReason = undefined;
378
387
 
379
388
  // Clear any pending stop timeout to avoid it firing for a different summarizer
380
389
  if (this.summarizerStopTimeout !== undefined) {
@@ -396,6 +405,7 @@ export class SummaryManager
396
405
  return;
397
406
  }
398
407
  this.state = SummaryManagerState.Stopping;
408
+ this.pendingStopReason = reason;
399
409
 
400
410
  // Stopping the running summarizer client should trigger a change
401
411
  // in states when the running summarizer closes
@@ -502,6 +512,7 @@ export class SummaryManager
502
512
  this.connectedState.off("connected", this.handleConnected);
503
513
  this.connectedState.off("disconnected", this.handleDisconnected);
504
514
  this.cleanupForwardedEvents();
515
+ this.pendingStopReason = undefined;
505
516
  if (this.summarizerStopTimeout !== undefined) {
506
517
  clearTimeout(this.summarizerStopTimeout);
507
518
  }