@fluidframework/container-runtime 2.0.0-internal.1.0.0.83139 → 2.0.0-internal.1.1.1

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 (124) hide show
  1. package/.mocharc.js +12 -0
  2. package/dist/batchTracker.js +1 -1
  3. package/dist/batchTracker.js.map +1 -1
  4. package/dist/blobManager.d.ts +7 -1
  5. package/dist/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager.js +34 -17
  7. package/dist/blobManager.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +3 -104
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +83 -395
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/dataStore.d.ts +1 -1
  13. package/dist/dataStore.d.ts.map +1 -1
  14. package/dist/dataStore.js +2 -3
  15. package/dist/dataStore.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +3 -5
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js +13 -23
  19. package/dist/dataStoreContext.js.map +1 -1
  20. package/dist/dataStores.d.ts +1 -1
  21. package/dist/dataStores.d.ts.map +1 -1
  22. package/dist/dataStores.js +3 -8
  23. package/dist/dataStores.js.map +1 -1
  24. package/dist/garbageCollection.d.ts +37 -6
  25. package/dist/garbageCollection.d.ts.map +1 -1
  26. package/dist/garbageCollection.js +61 -65
  27. package/dist/garbageCollection.js.map +1 -1
  28. package/dist/index.d.ts +2 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/packageVersion.d.ts +1 -1
  33. package/dist/packageVersion.d.ts.map +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/pendingStateManager.d.ts.map +1 -1
  37. package/dist/pendingStateManager.js +15 -2
  38. package/dist/pendingStateManager.js.map +1 -1
  39. package/dist/runningSummarizer.js +1 -1
  40. package/dist/runningSummarizer.js.map +1 -1
  41. package/dist/scheduleManager.d.ts +28 -0
  42. package/dist/scheduleManager.d.ts.map +1 -0
  43. package/dist/scheduleManager.js +235 -0
  44. package/dist/scheduleManager.js.map +1 -0
  45. package/dist/summarizer.d.ts.map +1 -1
  46. package/dist/summarizer.js +20 -1
  47. package/dist/summarizer.js.map +1 -1
  48. package/dist/summaryCollection.js +1 -1
  49. package/dist/summaryCollection.js.map +1 -1
  50. package/dist/summaryGenerator.js +1 -1
  51. package/dist/summaryGenerator.js.map +1 -1
  52. package/dist/summaryManager.d.ts.map +1 -1
  53. package/dist/summaryManager.js +20 -5
  54. package/dist/summaryManager.js.map +1 -1
  55. package/lib/batchTracker.js +1 -1
  56. package/lib/batchTracker.js.map +1 -1
  57. package/lib/blobManager.d.ts +7 -1
  58. package/lib/blobManager.d.ts.map +1 -1
  59. package/lib/blobManager.js +35 -18
  60. package/lib/blobManager.js.map +1 -1
  61. package/lib/containerRuntime.d.ts +3 -104
  62. package/lib/containerRuntime.d.ts.map +1 -1
  63. package/lib/containerRuntime.js +84 -395
  64. package/lib/containerRuntime.js.map +1 -1
  65. package/lib/dataStore.d.ts +1 -1
  66. package/lib/dataStore.d.ts.map +1 -1
  67. package/lib/dataStore.js +2 -3
  68. package/lib/dataStore.js.map +1 -1
  69. package/lib/dataStoreContext.d.ts +3 -5
  70. package/lib/dataStoreContext.d.ts.map +1 -1
  71. package/lib/dataStoreContext.js +13 -23
  72. package/lib/dataStoreContext.js.map +1 -1
  73. package/lib/dataStores.d.ts +1 -1
  74. package/lib/dataStores.d.ts.map +1 -1
  75. package/lib/dataStores.js +3 -8
  76. package/lib/dataStores.js.map +1 -1
  77. package/lib/garbageCollection.d.ts +37 -6
  78. package/lib/garbageCollection.d.ts.map +1 -1
  79. package/lib/garbageCollection.js +47 -52
  80. package/lib/garbageCollection.js.map +1 -1
  81. package/lib/index.d.ts +2 -1
  82. package/lib/index.d.ts.map +1 -1
  83. package/lib/index.js +2 -1
  84. package/lib/index.js.map +1 -1
  85. package/lib/packageVersion.d.ts +1 -1
  86. package/lib/packageVersion.d.ts.map +1 -1
  87. package/lib/packageVersion.js +1 -1
  88. package/lib/packageVersion.js.map +1 -1
  89. package/lib/pendingStateManager.d.ts.map +1 -1
  90. package/lib/pendingStateManager.js +15 -2
  91. package/lib/pendingStateManager.js.map +1 -1
  92. package/lib/runningSummarizer.js +1 -1
  93. package/lib/runningSummarizer.js.map +1 -1
  94. package/lib/scheduleManager.d.ts +28 -0
  95. package/lib/scheduleManager.d.ts.map +1 -0
  96. package/lib/scheduleManager.js +231 -0
  97. package/lib/scheduleManager.js.map +1 -0
  98. package/lib/summarizer.d.ts.map +1 -1
  99. package/lib/summarizer.js +22 -3
  100. package/lib/summarizer.js.map +1 -1
  101. package/lib/summaryCollection.js +1 -1
  102. package/lib/summaryCollection.js.map +1 -1
  103. package/lib/summaryGenerator.js +1 -1
  104. package/lib/summaryGenerator.js.map +1 -1
  105. package/lib/summaryManager.d.ts.map +1 -1
  106. package/lib/summaryManager.js +20 -5
  107. package/lib/summaryManager.js.map +1 -1
  108. package/package.json +32 -19
  109. package/src/batchTracker.ts +1 -1
  110. package/src/blobManager.ts +43 -17
  111. package/src/containerRuntime.ts +113 -547
  112. package/src/dataStore.ts +1 -4
  113. package/src/dataStoreContext.ts +10 -25
  114. package/src/dataStores.ts +13 -19
  115. package/src/garbageCollection.ts +64 -69
  116. package/src/index.ts +1 -2
  117. package/src/packageVersion.ts +1 -1
  118. package/src/pendingStateManager.ts +18 -2
  119. package/src/runningSummarizer.ts +1 -1
  120. package/src/scheduleManager.ts +294 -0
  121. package/src/summarizer.ts +28 -3
  122. package/src/summaryCollection.ts +1 -1
  123. package/src/summaryGenerator.ts +1 -1
  124. package/src/summaryManager.ts +20 -5
@@ -2,7 +2,6 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { EventEmitter } from "events";
6
5
  import { ITelemetryBaseLogger, ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
7
6
  import {
8
7
  FluidObject,
@@ -34,7 +33,6 @@ import {
34
33
  Trace,
35
34
  TypedEventEmitter,
36
35
  unreachableCase,
37
- performance,
38
36
  } from "@fluidframework/common-utils";
39
37
  import {
40
38
  ChildLogger,
@@ -56,7 +54,6 @@ import {
56
54
  DataProcessingError,
57
55
  GenericError,
58
56
  UsageError,
59
- extractSafePropertiesFromMessage,
60
57
  } from "@fluidframework/container-utils";
61
58
  import {
62
59
  IClientDetails,
@@ -112,15 +109,13 @@ import { ContainerFluidHandleContext } from "./containerHandleContext";
112
109
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
113
110
  import { Summarizer } from "./summarizer";
114
111
  import { SummaryManager } from "./summaryManager";
115
- import { DeltaScheduler } from "./deltaScheduler";
116
112
  import {
117
113
  ReportOpPerfTelemetry,
118
- latencyThreshold,
119
114
  IPerfSignalReport,
120
115
  } from "./connectionTelemetry";
121
116
  import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
122
117
  import { pkgVersion } from "./packageVersion";
123
- import { BlobManager, IBlobManagerLoadInfo } from "./blobManager";
118
+ import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
124
119
  import { DataStores, getSummaryForDatastores } from "./dataStores";
125
120
  import {
126
121
  aliasBlobName,
@@ -164,6 +159,7 @@ import {
164
159
  } from "./dataStore";
165
160
  import { BindBatchTracker } from "./batchTracker";
166
161
  import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
162
+ import { ScheduleManager } from "./scheduleManager";
167
163
 
168
164
  export enum ContainerMessageType {
169
165
  // An op to be delivered to store
@@ -371,14 +367,6 @@ export interface ISummaryRuntimeOptions {
371
367
  /** Override summary configurations set by the server. */
372
368
  summaryConfigOverrides?: ISummaryConfiguration;
373
369
 
374
- /**
375
- * @deprecated - this option will not be supported on the next versions.
376
- * Flag that disables putting channels in isolated subtrees for each data store
377
- * and the root node when generating a summary if set to true.
378
- * Defaults to FALSE (enabled) for now.
379
- */
380
- disableIsolatedChannels?: boolean;
381
-
382
370
  /**
383
371
  * @deprecated - use `summaryConfigOverrides.initialSummarizerDelayMs` instead.
384
372
  * Delay before first attempt to spawn summarizing container.
@@ -425,12 +413,6 @@ export interface IContainerRuntimeOptions {
425
413
  * 3. "bypass" will skip the check entirely. This is not recommended.
426
414
  */
427
415
  readonly loadSequenceNumberVerification?: "close" | "log" | "bypass";
428
- /**
429
- * Should the runtime use data store aliasing for creating root datastores.
430
- * In case of aliasing conflicts, the runtime will raise an exception which does
431
- * not effect the status of the container.
432
- */
433
- readonly useDataStoreAliasing?: boolean;
434
416
  /**
435
417
  * Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
436
418
  * send all operations to the driver layer, while in TurnBased the operations will be buffered
@@ -444,10 +426,6 @@ export interface IContainerRuntimeOptions {
444
426
  readonly enableOfflineLoad?: boolean;
445
427
  }
446
428
 
447
- type IRuntimeMessageMetadata = undefined | {
448
- batch?: boolean;
449
- };
450
-
451
429
  /**
452
430
  * The summary tree returned by the root node. It adds state relevant to the root of the tree.
453
431
  */
@@ -487,11 +465,15 @@ interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedL
487
465
  * instantiated runtime in a new instance of the container, so it can load to the
488
466
  * same state
489
467
  */
490
- export interface IPendingRuntimeState {
468
+ interface IPendingRuntimeState {
491
469
  /**
492
470
  * Pending ops from PendingStateManager
493
471
  */
494
472
  pending?: IPendingLocalState;
473
+ /**
474
+ * Pending blobs from BlobManager
475
+ */
476
+ pendingAttachmentBlobs?: IPendingBlobs;
495
477
  /**
496
478
  * A base snapshot at a sequence number prior to the first pending op
497
479
  */
@@ -509,14 +491,8 @@ export interface IPendingRuntimeState {
509
491
  savedOps: ISequencedDocumentMessage[];
510
492
  }
511
493
 
512
- const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
513
494
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
514
495
 
515
- // Feature gate for the max op size. If the value is negative, chunking is enabled
516
- // and all ops over 16k would be chunked. If the value is positive, all ops with
517
- // a size strictly larger will be rejected and the container closed with an error.
518
- const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
519
-
520
496
  // By default, we should reject any op larger than 768KB,
521
497
  // in order to account for some extra overhead from serialization
522
498
  // to not reach the 1MB limits in socket.io and Kafka.
@@ -562,281 +538,6 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
562
538
  return message;
563
539
  }
564
540
 
565
- /**
566
- * This class controls pausing and resuming of inbound queue to ensure that we never
567
- * start processing ops in a batch IF we do not have all ops in the batch.
568
- */
569
- class ScheduleManagerCore {
570
- private pauseSequenceNumber: number | undefined;
571
- private currentBatchClientId: string | undefined;
572
- private localPaused = false;
573
- private timePaused = 0;
574
- private batchCount = 0;
575
-
576
- constructor(
577
- private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
578
- private readonly logger: ITelemetryLogger,
579
- ) {
580
- // Listen for delta manager sends and add batch metadata to messages
581
- this.deltaManager.on("prepareSend", (messages: IDocumentMessage[]) => {
582
- if (messages.length === 0) {
583
- return;
584
- }
585
-
586
- // First message will have the batch flag set to true if doing a batched send
587
- const firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;
588
- if (!firstMessageMetadata?.batch) {
589
- return;
590
- }
591
-
592
- // If the batch contains only a single op, clear the batch flag.
593
- if (messages.length === 1) {
594
- delete firstMessageMetadata.batch;
595
- return;
596
- }
597
-
598
- // Set the batch flag to false on the last message to indicate the end of the send batch
599
- const lastMessage = messages[messages.length - 1];
600
- lastMessage.metadata = { ...lastMessage.metadata, batch: false };
601
- });
602
-
603
- // Listen for updates and peek at the inbound
604
- this.deltaManager.inbound.on(
605
- "push",
606
- (message: ISequencedDocumentMessage) => {
607
- this.trackPending(message);
608
- });
609
-
610
- // Start with baseline - empty inbound queue.
611
- assert(!this.localPaused, 0x293 /* "initial state" */);
612
-
613
- const allPending = this.deltaManager.inbound.toArray();
614
- for (const pending of allPending) {
615
- this.trackPending(pending);
616
- }
617
-
618
- // We are intentionally directly listening to the "op" to inspect system ops as well.
619
- // If we do not observe system ops, we are likely to hit 0x296 assert when system ops
620
- // precedes start of incomplete batch.
621
- this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
622
- }
623
-
624
- /**
625
- * The only public function in this class - called when we processed an op,
626
- * to make decision if op processing should be paused or not afer that.
627
- */
628
- public afterOpProcessing(sequenceNumber: number) {
629
- assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
630
-
631
- // If the inbound queue is ever empty, nothing to do!
632
- if (this.deltaManager.inbound.length === 0) {
633
- assert(this.pauseSequenceNumber === undefined,
634
- 0x295 /* "there should be no pending batch if we have no ops" */);
635
- return;
636
- }
637
-
638
- // The queue is
639
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
640
- // - here (processing ops until reaching start of incomplete batch)
641
- // - in trackPending(), when queue was empty and start of batch showed up.
642
- // 2. resumed when batch end comes in (in trackPending())
643
-
644
- // do we have incomplete batch to worry about?
645
- if (this.pauseSequenceNumber !== undefined) {
646
- assert(sequenceNumber < this.pauseSequenceNumber,
647
- 0x296 /* "we should never start processing incomplete batch!" */);
648
- // If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
649
- if (sequenceNumber + 1 === this.pauseSequenceNumber) {
650
- this.pauseQueue();
651
- }
652
- }
653
- }
654
-
655
- private pauseQueue() {
656
- assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
657
- this.localPaused = true;
658
- this.timePaused = performance.now();
659
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
660
- this.deltaManager.inbound.pause();
661
- }
662
-
663
- private resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage) {
664
- const endBatch = messageEndBatch.sequenceNumber;
665
- const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
666
-
667
- this.batchCount++;
668
- if (this.batchCount % 1000 === 1) {
669
- this.logger.sendTelemetryEvent({
670
- eventName: "BatchStats",
671
- sequenceNumber: endBatch,
672
- length: endBatch - startBatch + 1,
673
- msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
674
- duration,
675
- batchCount: this.batchCount,
676
- interrupted: this.localPaused,
677
- });
678
- }
679
-
680
- // Return early if no change in value
681
- if (!this.localPaused) {
682
- return;
683
- }
684
-
685
- this.localPaused = false;
686
-
687
- // Random round number - we want to know when batch waiting paused op processing.
688
- if (duration !== undefined && duration > latencyThreshold) {
689
- this.logger.sendErrorEvent({
690
- eventName: "MaxBatchWaitTimeExceeded",
691
- duration,
692
- sequenceNumber: endBatch,
693
- length: endBatch - startBatch,
694
- });
695
- }
696
- this.deltaManager.inbound.resume();
697
- }
698
-
699
- /**
700
- * Called for each incoming op (i.e. inbound "push" notification)
701
- */
702
- private trackPending(message: ISequencedDocumentMessage) {
703
- assert(this.deltaManager.inbound.length !== 0,
704
- 0x298 /* "we have something in the queue that generates this event" */);
705
-
706
- assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),
707
- 0x299 /* "non-synchronized state" */);
708
-
709
- const metadata = message.metadata as IRuntimeMessageMetadata;
710
- const batchMetadata = metadata?.batch;
711
-
712
- // Protocol messages are never part of a runtime batch of messages
713
- if (!isUnpackedRuntimeMessage(message)) {
714
- // Protocol messages should never show up in the middle of the batch!
715
- assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
716
- assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
717
- assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
718
- return;
719
- }
720
-
721
- if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
722
- assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
723
- return;
724
- }
725
-
726
- // If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
727
- // If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
728
- // the previous one
729
- if (this.currentBatchClientId !== undefined || batchMetadata === false) {
730
- if (this.currentBatchClientId !== message.clientId) {
731
- // "Batch not closed, yet message from another client!"
732
- throw new DataCorruptionError(
733
- "OpBatchIncomplete",
734
- {
735
- runtimeVersion: pkgVersion,
736
- batchClientId: this.currentBatchClientId,
737
- ...extractSafePropertiesFromMessage(message),
738
- });
739
- }
740
- }
741
-
742
- // The queue is
743
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
744
- // - in afterOpProcessing() - processing ops until reaching start of incomplete batch
745
- // - here (batchMetadata == false below), when queue was empty and start of batch showed up.
746
- // 2. resumed when batch end comes in (batchMetadata === true case below)
747
-
748
- if (batchMetadata) {
749
- assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
750
- assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
751
- this.pauseSequenceNumber = message.sequenceNumber;
752
- this.currentBatchClientId = message.clientId;
753
- // Start of the batch
754
- // Only pause processing if queue has no other ops!
755
- // If there are any other ops in the queue, processing will be stopped when they are processed!
756
- if (this.deltaManager.inbound.length === 1) {
757
- this.pauseQueue();
758
- }
759
- } else if (batchMetadata === false) {
760
- assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
761
- // Batch is complete, we can process it!
762
- this.resumeQueue(this.pauseSequenceNumber, message);
763
- this.pauseSequenceNumber = undefined;
764
- this.currentBatchClientId = undefined;
765
- } else {
766
- // Continuation of current batch. Do nothing
767
- assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
768
- }
769
- }
770
- }
771
-
772
- /**
773
- * This class has the following responsibilities:
774
- * 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
775
- * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
776
- * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
777
- * unless all ops of the batch are in.
778
- */
779
- export class ScheduleManager {
780
- private readonly deltaScheduler: DeltaScheduler;
781
- private batchClientId: string | undefined;
782
- private hitError = false;
783
-
784
- constructor(
785
- private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
786
- private readonly emitter: EventEmitter,
787
- private readonly logger: ITelemetryLogger,
788
- ) {
789
- this.deltaScheduler = new DeltaScheduler(
790
- this.deltaManager,
791
- ChildLogger.create(this.logger, "DeltaScheduler"),
792
- );
793
- void new ScheduleManagerCore(deltaManager, logger);
794
- }
795
-
796
- public beforeOpProcessing(message: ISequencedDocumentMessage) {
797
- if (this.batchClientId !== message.clientId) {
798
- assert(this.batchClientId === undefined,
799
- 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
800
-
801
- // This could be the beginning of a new batch or an individual message.
802
- this.emitter.emit("batchBegin", message);
803
- this.deltaScheduler.batchBegin(message);
804
-
805
- const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
806
- if (batch) {
807
- this.batchClientId = message.clientId;
808
- } else {
809
- this.batchClientId = undefined;
810
- }
811
- }
812
- }
813
-
814
- public afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {
815
- // If this is no longer true, we need to revisit what we do where we set this.hitError.
816
- assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
817
-
818
- if (error) {
819
- // We assume here that loader will close container and stop processing all future ops.
820
- // This is implicit dependency. If this flow changes, this code might no longer be correct.
821
- this.hitError = true;
822
- this.batchClientId = undefined;
823
- this.emitter.emit("batchEnd", error, message);
824
- this.deltaScheduler.batchEnd(message);
825
- return;
826
- }
827
-
828
- const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
829
- // If no batchClientId has been set then we're in an individual batch. Else, if we get
830
- // batch end metadata, this is end of the current batch.
831
- if (this.batchClientId === undefined || batch === false) {
832
- this.batchClientId = undefined;
833
- this.emitter.emit("batchEnd", undefined, message);
834
- this.deltaScheduler.batchEnd(message);
835
- return;
836
- }
837
- }
838
- }
839
-
840
541
  /**
841
542
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
842
543
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -903,7 +604,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
903
604
  summaryOptions = {},
904
605
  gcOptions = {},
905
606
  loadSequenceNumberVerification = "close",
906
- useDataStoreAliasing = false,
907
607
  flushMode = defaultFlushMode,
908
608
  enableOfflineLoad = false,
909
609
  } = runtimeOptions;
@@ -979,7 +679,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
979
679
  summaryOptions,
980
680
  gcOptions,
981
681
  loadSequenceNumberVerification,
982
- useDataStoreAliasing,
983
682
  flushMode,
984
683
  enableOfflineLoad,
985
684
  },
@@ -1069,11 +768,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1069
768
  private readonly summaryCollection: SummaryCollection;
1070
769
 
1071
770
  private readonly summarizerNode: IRootSummarizerNodeWithGC;
1072
- private readonly _aliasingEnabled: boolean;
1073
- private readonly _maxOpSizeInBytes: number;
1074
771
 
1075
772
  private readonly maxConsecutiveReconnects: number;
1076
- private readonly defaultMaxConsecutiveReconnects = 15;
773
+ private readonly defaultMaxConsecutiveReconnects = 7;
1077
774
 
1078
775
  private _orderSequentiallyCalls: number = 0;
1079
776
  private _flushMode: FlushMode;
@@ -1133,12 +830,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1133
830
 
1134
831
  private readonly dataStores: DataStores;
1135
832
 
1136
- /**
1137
- * True if generating summaries with isolated channels is
1138
- * explicitly disabled. This only affects how summaries are written,
1139
- * and is the single source of truth for this container.
1140
- */
1141
- public readonly disableIsolatedChannels: boolean;
1142
833
  /** The last message processed at the time of the last summary. */
1143
834
  private messageAtLastSummary: ISummaryMetadataMessage | undefined;
1144
835
 
@@ -1242,9 +933,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1242
933
  super();
1243
934
  this.messageAtLastSummary = metadata?.message;
1244
935
 
1245
- // Default to false (enabled).
1246
- this.disableIsolatedChannels = this.runtimeOptions.summaryOptions.disableIsolatedChannels ?? false;
1247
-
1248
936
  this._connected = this.context.connected;
1249
937
  this.chunkMap = new Map<string, string[]>(chunks);
1250
938
 
@@ -1263,11 +951,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1263
951
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
1264
952
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
1265
953
 
1266
- this._aliasingEnabled =
1267
- (this.mc.config.getBoolean(useDataStoreAliasingKey) ?? false) ||
1268
- (runtimeOptions.useDataStoreAliasing ?? false);
1269
-
1270
- this._maxOpSizeInBytes = (this.mc.config.getNumber(maxOpSizeInBytesKey) ?? defaultMaxOpSizeInBytes);
1271
954
  this.maxConsecutiveReconnects =
1272
955
  this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
1273
956
 
@@ -1348,10 +1031,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1348
1031
  this.handleContext,
1349
1032
  blobManagerSnapshot,
1350
1033
  () => this.storage,
1351
- (blobId, localId) => this.submit(
1352
- ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId }),
1034
+ (blobId, localId) => {
1035
+ if (!this.disposed) {
1036
+ this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
1037
+ }
1038
+ },
1353
1039
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
1354
1040
  this,
1041
+ pendingRuntimeState?.pendingAttachmentBlobs,
1355
1042
  );
1356
1043
 
1357
1044
  this.scheduleManager = new ScheduleManager(
@@ -1683,7 +1370,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1683
1370
  // Increment the summary number for the next summary that will be generated.
1684
1371
  summaryNumber: this.nextSummaryNumber++,
1685
1372
  summaryFormatVersion: 1,
1686
- disableIsolatedChannels: this.disableIsolatedChannels || undefined,
1687
1373
  ...this.garbageCollector.getMetadata(),
1688
1374
  // The last message processed at the time of summary. If there are no new messages, use the message from the
1689
1375
  // last summary.
@@ -1829,8 +1515,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1829
1515
  // ensure we don't submit ops referencing a blob that has not been uploaded
1830
1516
  const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
1831
1517
  if (connecting && this.blobManager.hasPendingOfflineUploads) {
1832
- assert(!this.delayConnectClientId, "Connect event delay must be canceled before subsequent connect event");
1833
- assert(!!clientId, "Must have clientId when connecting");
1518
+ assert(!this.delayConnectClientId,
1519
+ 0x392 /* Connect event delay must be canceled before subsequent connect event */);
1520
+ assert(!!clientId, 0x393 /* Must have clientId when connecting */);
1834
1521
  this.delayConnectClientId = clientId;
1835
1522
  this.blobManager.onConnected().then(() => {
1836
1523
  // make sure we didn't reconnect before the promise resolved
@@ -1846,12 +1533,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1846
1533
  }
1847
1534
 
1848
1535
  private setConnectionStateCore(connected: boolean, clientId?: string) {
1849
- assert(!this.delayConnectClientId, "connect event delay must be cleared before propagating connect event");
1536
+ assert(!this.delayConnectClientId,
1537
+ 0x394 /* connect event delay must be cleared before propagating connect event */);
1850
1538
  this.verifyNotClosed();
1851
1539
 
1852
1540
  // There might be no change of state due to Container calling this API after loading runtime.
1853
1541
  const changeOfState = this._connected !== connected;
1854
- const reconnection = changeOfState && connected;
1542
+ const reconnection = changeOfState && !connected;
1855
1543
  this._connected = connected;
1856
1544
 
1857
1545
  if (!connected) {
@@ -1860,6 +1548,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1860
1548
  this._perfSignalData.trackingSignalSequenceNumber = undefined;
1861
1549
  }
1862
1550
 
1551
+ // Fail while disconnected
1863
1552
  if (reconnection) {
1864
1553
  this.consecutiveReconnects++;
1865
1554
 
@@ -1870,11 +1559,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1870
1559
  "Runtime detected too many reconnects with no progress syncing local ops",
1871
1560
  "setConnectionState",
1872
1561
  undefined,
1873
- {
1874
- dataLoss: 1,
1875
- attempts: this.consecutiveReconnects,
1876
- pendingMessages: this.pendingStateManager.pendingMessagesCount,
1877
- }));
1562
+ {
1563
+ dataLoss: 1,
1564
+ attempts: this.consecutiveReconnects,
1565
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1566
+ }));
1878
1567
  return;
1879
1568
  }
1880
1569
  }
@@ -2137,83 +1826,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2137
1826
  public async createDataStore(pkg: string | string[]): Promise<IDataStore> {
2138
1827
  const internalId = uuid();
2139
1828
  return channelToDataStore(
2140
- await this._createDataStore(pkg, false /* isRoot */, internalId),
1829
+ await this._createDataStore(pkg, internalId),
2141
1830
  internalId,
2142
1831
  this,
2143
1832
  this.dataStores,
2144
1833
  this.mc.logger);
2145
1834
  }
2146
1835
 
2147
- /**
2148
- * Creates a root datastore directly with a user generated id and attaches it to storage.
2149
- * It is vulnerable to name collisions and should not be used.
2150
- *
2151
- * This method will be removed. See #6465.
2152
- */
2153
- private async createRootDataStoreLegacy(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
2154
- const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
2155
- fluidDataStore.makeVisibleAndAttachGraph();
2156
- return fluidDataStore;
2157
- }
2158
-
2159
- /**
2160
- * @deprecated - will be removed in an upcoming release. See #9660.
2161
- */
2162
- public async createRootDataStore(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
2163
- if (rootDataStoreId.includes("/")) {
2164
- throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
2165
- }
2166
- return this._aliasingEnabled === true ?
2167
- this.createAndAliasDataStore(pkg, rootDataStoreId) :
2168
- this.createRootDataStoreLegacy(pkg, rootDataStoreId);
2169
- }
2170
-
2171
- /**
2172
- * Creates a data store then attempts to alias it.
2173
- * If aliasing fails, it will raise an exception.
2174
- *
2175
- * This method will be removed. See #6465.
2176
- *
2177
- * @param pkg - Package name of the data store
2178
- * @param alias - Alias to be assigned to the data store
2179
- * @param props - Properties for the data store
2180
- * @returns - An aliased data store which can can be found / loaded by alias.
2181
- */
2182
- private async createAndAliasDataStore(pkg: string | string[], alias: string, props?: any): Promise<IDataStore> {
2183
- const internalId = uuid();
2184
-
2185
- try {
2186
- // A similar call may have been initiated by the same client, so we should try to get
2187
- // a possible existing aliased datastore first.
2188
- const existingDataStore = await this.getRootDataStoreChannel(alias, /* wait */ false);
2189
- return channelToDataStore(
2190
- existingDataStore,
2191
- internalId,
2192
- this,
2193
- this.dataStores,
2194
- this.mc.logger,
2195
- true, // AlreadyAliased. This will block further alias attempts for the datastore
2196
- );
2197
- } catch (err) {
2198
- const newChannel = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
2199
- const newDataStore = channelToDataStore(newChannel, internalId, this, this.dataStores, this.mc.logger);
2200
- const aliasResult = await newDataStore.trySetAlias(alias);
2201
- if (aliasResult === "Success") {
2202
- return newDataStore;
2203
- }
2204
-
2205
- const existingDataStore = await this.getRootDataStoreChannel(alias, /* wait */ false);
2206
- return channelToDataStore(
2207
- existingDataStore,
2208
- internalId,
2209
- this,
2210
- this.dataStores,
2211
- this.mc.logger,
2212
- true, // AlreadyAliased. This will block further alias attempts for the datastore
2213
- );
2214
- }
2215
- }
2216
-
2217
1836
  public createDetachedRootDataStore(
2218
1837
  pkg: Readonly<string[]>,
2219
1838
  rootDataStoreId: string): IFluidDataStoreContextDetached {
@@ -2227,49 +1846,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2227
1846
  return this.dataStores.createDetachedDataStoreCore(pkg, false);
2228
1847
  }
2229
1848
 
2230
- /**
2231
- * Creates a possibly root datastore directly with a possibly user generated id and attaches it to storage.
2232
- * It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
2233
- *
2234
- * This method will be removed. See #6465.
2235
- */
2236
- private async _createDataStoreWithPropsLegacy(
1849
+ public async _createDataStoreWithProps(
2237
1850
  pkg: string | string[],
2238
1851
  props?: any,
2239
1852
  id = uuid(),
2240
- isRoot = false,
2241
1853
  ): Promise<IDataStore> {
2242
1854
  const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
2243
- Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
2244
- if (isRoot) {
2245
- fluidDataStore.makeVisibleAndAttachGraph();
2246
- this.logger.sendTelemetryEvent({
2247
- eventName: "Root datastore with props",
2248
- hasProps: props !== undefined,
2249
- });
2250
- }
1855
+ Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
2251
1856
  return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
2252
1857
  }
2253
1858
 
2254
- public async _createDataStoreWithProps(
2255
- pkg: string | string[],
2256
- props?: any,
2257
- id = uuid(),
2258
- isRoot = false,
2259
- ): Promise<IDataStore> {
2260
- return this._aliasingEnabled === true && isRoot ?
2261
- this.createAndAliasDataStore(pkg, id, props) :
2262
- this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
2263
- }
2264
-
2265
1859
  private async _createDataStore(
2266
1860
  pkg: string | string[],
2267
- isRoot: boolean,
2268
1861
  id = uuid(),
2269
1862
  props?: any,
2270
1863
  ): Promise<IFluidDataStoreChannel> {
2271
1864
  return this.dataStores
2272
- ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props)
1865
+ ._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
2273
1866
  .realize();
2274
1867
  }
2275
1868
 
@@ -2375,10 +1968,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2375
1968
  }
2376
1969
 
2377
1970
  const summarizeResult = this.dataStores.createSummary(telemetryContext);
2378
- if (!this.disableIsolatedChannels) {
2379
- // Wrap data store summaries in .channels subtree.
2380
- wrapSummaryInChannelsTree(summarizeResult);
2381
- }
1971
+ // Wrap data store summaries in .channels subtree.
1972
+ wrapSummaryInChannelsTree(summarizeResult);
1973
+
2382
1974
  this.addContainerStateToSummary(
2383
1975
  summarizeResult,
2384
1976
  true /* fullTree */,
@@ -2404,13 +1996,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2404
1996
  telemetryContext?: ITelemetryContext,
2405
1997
  ): Promise<ISummarizeInternalResult> {
2406
1998
  const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
2407
- let pathPartsForChildren: string[] | undefined;
2408
1999
 
2409
- if (!this.disableIsolatedChannels) {
2410
- // Wrap data store summaries in .channels subtree.
2411
- wrapSummaryInChannelsTree(summarizeResult);
2412
- pathPartsForChildren = [channelsTreeName];
2413
- }
2000
+ // Wrap data store summaries in .channels subtree.
2001
+ wrapSummaryInChannelsTree(summarizeResult);
2002
+ const pathPartsForChildren = [channelsTreeName];
2003
+
2414
2004
  this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
2415
2005
  return {
2416
2006
  ...summarizeResult,
@@ -2626,21 +2216,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2626
2216
  },
2627
2217
  );
2628
2218
 
2219
+ let latestSnapshotVersionId: string | undefined;
2629
2220
  if (refreshLatestAck) {
2630
- const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(
2221
+ const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
2631
2222
  ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
2223
+ const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
2224
+ latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
2632
2225
 
2633
- if (latestSummaryRefSeq > this.deltaManager.lastSequenceNumber) {
2226
+ if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
2634
2227
  // We need to catch up to the latest summary's reference sequence number before pausing.
2635
2228
  await PerformanceEvent.timedExecAsync(
2636
2229
  summaryNumberLogger,
2637
2230
  {
2638
2231
  eventName: "WaitingForSeq",
2639
2232
  lastSequenceNumber: this.deltaManager.lastSequenceNumber,
2640
- targetSequenceNumber: latestSummaryRefSeq,
2233
+ targetSequenceNumber: latestSnapshotRefSeq,
2641
2234
  lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
2642
2235
  },
2643
- async () => waitForSeq(this.deltaManager, latestSummaryRefSeq),
2236
+ async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq),
2644
2237
  { start: true, end: true, cancel: "error" }, // definitely want start event
2645
2238
  );
2646
2239
  }
@@ -2683,7 +2276,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2683
2276
  };
2684
2277
  }
2685
2278
  assert(summaryRefSeqNum === this.deltaManager.lastMessage?.sequenceNumber,
2686
- "it's one and the same thing");
2279
+ 0x395 /* it's one and the same thing */);
2687
2280
 
2688
2281
  if (lastAck !== this.summaryCollection.latestAck) {
2689
2282
  return {
@@ -2733,7 +2326,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2733
2326
  // Counting dataStores and handles
2734
2327
  // Because handles are unchanged dataStores in the current logic,
2735
2328
  // summarized dataStore count is total dataStore count minus handle count
2736
- const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[channelsTreeName];
2329
+ const dataStoreTree = summaryTree.tree[channelsTreeName];
2737
2330
 
2738
2331
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
2739
2332
  const handleCount = Object.values(dataStoreTree.tree).filter(
@@ -2765,18 +2358,32 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2765
2358
  return { stage: "generate", ...generateSummaryData, error: continueResult.error };
2766
2359
  }
2767
2360
 
2768
- const summaryContext: ISummaryContext =
2769
- lastAck === undefined
2770
- ? {
2771
- proposalHandle: undefined,
2772
- ackHandle: this.context.getLoadedFromVersion()?.id,
2773
- referenceSequenceNumber: summaryRefSeqNum,
2774
- }
2775
- : {
2776
- proposalHandle: lastAck.summaryOp.contents.handle,
2777
- ackHandle: lastAck.summaryAck.contents.handle,
2778
- referenceSequenceNumber: summaryRefSeqNum,
2779
- };
2361
+ // It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
2362
+ // summary. So if the previous summarizer closes just after submitting the summary and before
2363
+ // submitting the summaryOp then we can't rely on summaryAck. So in case we have
2364
+ // latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
2365
+ // the one fetched from storage as parent as that is the latest.
2366
+ let summaryContext: ISummaryContext;
2367
+ if (lastAck?.summaryAck.contents.handle !== latestSnapshotVersionId
2368
+ && latestSnapshotVersionId !== undefined) {
2369
+ summaryContext = {
2370
+ proposalHandle: undefined,
2371
+ ackHandle: latestSnapshotVersionId,
2372
+ referenceSequenceNumber: summaryRefSeqNum,
2373
+ };
2374
+ } else if (lastAck === undefined) {
2375
+ summaryContext = {
2376
+ proposalHandle: undefined,
2377
+ ackHandle: this.context.getLoadedFromVersion()?.id,
2378
+ referenceSequenceNumber: summaryRefSeqNum,
2379
+ };
2380
+ } else {
2381
+ summaryContext = {
2382
+ proposalHandle: lastAck.summaryOp.contents.handle,
2383
+ ackHandle: lastAck.summaryAck.contents.handle,
2384
+ referenceSequenceNumber: summaryRefSeqNum,
2385
+ };
2386
+ }
2780
2387
 
2781
2388
  let handle: string;
2782
2389
  try {
@@ -2918,7 +2525,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2918
2525
 
2919
2526
  if (this.canSendOps()) {
2920
2527
  const serializedContent = JSON.stringify(content);
2921
- const maxOpSize = this.context.deltaManager.maxMessageSize;
2922
2528
 
2923
2529
  // If in TurnBased flush mode we will trigger a flush at the next turn break
2924
2530
  if (this.flushMode === FlushMode.TurnBased && !this.needsFlush) {
@@ -2938,13 +2544,21 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2938
2544
  }
2939
2545
  }
2940
2546
 
2941
- clientSequenceNumber = this.submitMaybeChunkedMessages(
2942
- type,
2943
- content,
2944
- serializedContent,
2945
- maxOpSize,
2946
- this._flushMode === FlushMode.TurnBased,
2947
- opMetadataInternal);
2547
+ if (!serializedContent || serializedContent.length <= defaultMaxOpSizeInBytes) {
2548
+ clientSequenceNumber = this.submitRuntimeMessage(type,
2549
+ content, this._flushMode === FlushMode.TurnBased /* batch */, opMetadataInternal);
2550
+ } else {
2551
+ // If the content length is larger than the client configured message size
2552
+ // instead of splitting the content, we will fail by explicitly closing the container
2553
+ this.closeFn(new GenericError(
2554
+ "OpTooLarge",
2555
+ /* error */ undefined,
2556
+ {
2557
+ length: serializedContent.length,
2558
+ limit: defaultMaxOpSizeInBytes,
2559
+ }));
2560
+ clientSequenceNumber = -1;
2561
+ }
2948
2562
  }
2949
2563
 
2950
2564
  // Let the PendingStateManager know that a message was submitted.
@@ -2961,63 +2575,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2961
2575
  }
2962
2576
  }
2963
2577
 
2964
- private submitMaybeChunkedMessages(
2965
- type: ContainerMessageType,
2966
- content: any,
2967
- serializedContent: string,
2968
- serverMaxOpSize: number,
2969
- batch: boolean,
2970
- opMetadataInternal: unknown = undefined,
2971
- ): number {
2972
- if (this._maxOpSizeInBytes >= 0) {
2973
- // Chunking disabled
2974
- if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
2975
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
2976
- }
2977
-
2978
- // When chunking is disabled, we ignore the server max message size
2979
- // and if the content length is larger than the client configured message size
2980
- // instead of splitting the content, we will fail by explicitly close the container
2981
- this.closeFn(new GenericError(
2982
- "OpTooLarge",
2983
- /* error */ undefined,
2984
- {
2985
- length: serializedContent.length,
2986
- limit: this._maxOpSizeInBytes,
2987
- }));
2988
- return -1;
2989
- }
2990
-
2991
- // Chunking enabled, fallback on the server's max message size
2992
- // and split the content accordingly
2993
- if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
2994
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
2995
- }
2996
-
2997
- return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
2998
- }
2999
-
3000
- private submitChunkedMessage(type: ContainerMessageType, content: string, maxOpSize: number): number {
3001
- const contentLength = content.length;
3002
- const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
3003
- let offset = 0;
3004
- let clientSequenceNumber: number = 0;
3005
- for (let i = 1; i <= chunkN; i = i + 1) {
3006
- const chunkedOp: IChunkedOp = {
3007
- chunkId: i,
3008
- contents: content.substr(offset, maxOpSize),
3009
- originalType: type,
3010
- totalChunks: chunkN,
3011
- };
3012
- offset += maxOpSize;
3013
- clientSequenceNumber = this.submitRuntimeMessage(
3014
- ContainerMessageType.ChunkedOp,
3015
- chunkedOp,
3016
- false);
3017
- }
3018
- return clientSequenceNumber;
3019
- }
3020
-
3021
2578
  private submitSystemMessage(
3022
2579
  type: MessageType,
3023
2580
  contents: any) {
@@ -3123,15 +2680,20 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3123
2680
  summaryLogger: ITelemetryLogger,
3124
2681
  ) {
3125
2682
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3126
- const result = await this.summarizerNode.refreshLatestSummary(
3127
- proposalHandle,
3128
- summaryRefSeq,
3129
- async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
2683
+ const { snapshotTree } = await this.fetchSnapshotFromStorage(
2684
+ ackHandle,
2685
+ summaryLogger,
2686
+ {
3130
2687
  eventName: "RefreshLatestSummaryGetSnapshot",
3131
2688
  ackHandle,
3132
2689
  summaryRefSeq,
3133
2690
  fetchLatest: false,
3134
- }),
2691
+ },
2692
+ );
2693
+ const result = await this.summarizerNode.refreshLatestSummary(
2694
+ proposalHandle,
2695
+ summaryRefSeq,
2696
+ async () => snapshotTree,
3135
2697
  readAndParseBlob,
3136
2698
  summaryLogger,
3137
2699
  );
@@ -3146,21 +2708,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3146
2708
  * @param summaryLogger - logger to use when fetching snapshot from storage
3147
2709
  * @returns downloaded snapshot's reference sequence number
3148
2710
  */
3149
- private async refreshLatestSummaryAckFromServer(summaryLogger: ITelemetryLogger): Promise<number> {
3150
- const snapshot = await this.fetchSnapshotFromStorage(null, summaryLogger, {
3151
- eventName: "RefreshLatestSummaryGetSnapshot",
3152
- fetchLatest: true,
3153
- },
2711
+ private async refreshLatestSummaryAckFromServer(
2712
+ summaryLogger: ITelemetryLogger,
2713
+ ): Promise<{ latestSnapshotRefSeq: number; latestSnapshotVersionId: string | undefined; }> {
2714
+ const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
2715
+ eventName: "RefreshLatestSummaryGetSnapshot",
2716
+ fetchLatest: true,
2717
+ },
3154
2718
  FetchSource.noCache,
3155
2719
  );
3156
2720
 
3157
2721
  const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
3158
- const snapshotRefSeq = await seqFromTree(snapshot, readAndParseBlob);
2722
+ const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
3159
2723
 
3160
2724
  const result = await this.summarizerNode.refreshLatestSummary(
3161
2725
  undefined,
3162
- snapshotRefSeq,
3163
- async () => snapshot,
2726
+ latestSnapshotRefSeq,
2727
+ async () => snapshotTree,
3164
2728
  readAndParseBlob,
3165
2729
  summaryLogger,
3166
2730
  );
@@ -3168,7 +2732,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3168
2732
  // Notify the garbage collector so it can update its latest summary state.
3169
2733
  await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
3170
2734
 
3171
- return snapshotRefSeq;
2735
+ return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
3172
2736
  }
3173
2737
 
3174
2738
  private async fetchSnapshotFromStorage(
@@ -3176,7 +2740,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3176
2740
  logger: ITelemetryLogger,
3177
2741
  event: ITelemetryGenericEvent,
3178
2742
  fetchSource?: FetchSource,
3179
- ) {
2743
+ ): Promise<{ snapshotTree: ISnapshotTree; versionId: string; }> {
3180
2744
  return PerformanceEvent.timedExecAsync(
3181
2745
  logger, event, async (perfEvent: {
3182
2746
  end: (arg0: {
@@ -3197,7 +2761,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3197
2761
  stats.getSnapshotDuration = trace.trace().duration;
3198
2762
 
3199
2763
  perfEvent.end(stats);
3200
- return maybeSnapshot;
2764
+ return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
3201
2765
  });
3202
2766
  }
3203
2767
 
@@ -3216,7 +2780,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3216
2780
  this.baseSnapshotBlobs = await SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
3217
2781
  }
3218
2782
 
3219
- public getPendingLocalState(): IPendingRuntimeState {
2783
+ public getPendingLocalState(): unknown {
3220
2784
  if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad)) {
3221
2785
  throw new UsageError("can't get state when offline load disabled");
3222
2786
  }
@@ -3225,6 +2789,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3225
2789
  if (previousPendingState) {
3226
2790
  return {
3227
2791
  pending: this.pendingStateManager.getLocalState(),
2792
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3228
2793
  snapshotBlobs: previousPendingState.snapshotBlobs,
3229
2794
  baseSnapshot: previousPendingState.baseSnapshot,
3230
2795
  savedOps: this.savedOps,
@@ -3234,6 +2799,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3234
2799
  assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
3235
2800
  return {
3236
2801
  pending: this.pendingStateManager.getLocalState(),
2802
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3237
2803
  snapshotBlobs: this.baseSnapshotBlobs,
3238
2804
  baseSnapshot: this.context.baseSnapshot,
3239
2805
  savedOps: this.savedOps,