@fluidframework/container-runtime 2.0.0-internal.1.0.0.84253 → 2.0.0-internal.1.1.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 (91) hide show
  1. package/.mocharc.js +12 -0
  2. package/dist/blobManager.d.ts +7 -1
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +18 -1
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +2 -66
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +37 -295
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStoreContext.d.ts +3 -5
  11. package/dist/dataStoreContext.d.ts.map +1 -1
  12. package/dist/dataStoreContext.js +13 -23
  13. package/dist/dataStoreContext.js.map +1 -1
  14. package/dist/dataStores.d.ts.map +1 -1
  15. package/dist/dataStores.js +1 -6
  16. package/dist/dataStores.js.map +1 -1
  17. package/dist/garbageCollection.d.ts +37 -6
  18. package/dist/garbageCollection.d.ts.map +1 -1
  19. package/dist/garbageCollection.js +61 -65
  20. package/dist/garbageCollection.js.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.d.ts.map +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/pendingStateManager.d.ts.map +1 -1
  30. package/dist/pendingStateManager.js +15 -2
  31. package/dist/pendingStateManager.js.map +1 -1
  32. package/dist/scheduleManager.d.ts +28 -0
  33. package/dist/scheduleManager.d.ts.map +1 -0
  34. package/dist/scheduleManager.js +235 -0
  35. package/dist/scheduleManager.js.map +1 -0
  36. package/dist/summaryCollection.js +1 -1
  37. package/dist/summaryCollection.js.map +1 -1
  38. package/dist/summaryManager.d.ts.map +1 -1
  39. package/dist/summaryManager.js +20 -5
  40. package/dist/summaryManager.js.map +1 -1
  41. package/lib/blobManager.d.ts +7 -1
  42. package/lib/blobManager.d.ts.map +1 -1
  43. package/lib/blobManager.js +19 -2
  44. package/lib/blobManager.js.map +1 -1
  45. package/lib/containerRuntime.d.ts +2 -66
  46. package/lib/containerRuntime.d.ts.map +1 -1
  47. package/lib/containerRuntime.js +38 -295
  48. package/lib/containerRuntime.js.map +1 -1
  49. package/lib/dataStoreContext.d.ts +3 -5
  50. package/lib/dataStoreContext.d.ts.map +1 -1
  51. package/lib/dataStoreContext.js +13 -23
  52. package/lib/dataStoreContext.js.map +1 -1
  53. package/lib/dataStores.d.ts.map +1 -1
  54. package/lib/dataStores.js +1 -6
  55. package/lib/dataStores.js.map +1 -1
  56. package/lib/garbageCollection.d.ts +37 -6
  57. package/lib/garbageCollection.d.ts.map +1 -1
  58. package/lib/garbageCollection.js +47 -52
  59. package/lib/garbageCollection.js.map +1 -1
  60. package/lib/index.d.ts +2 -1
  61. package/lib/index.d.ts.map +1 -1
  62. package/lib/index.js +2 -1
  63. package/lib/index.js.map +1 -1
  64. package/lib/packageVersion.d.ts +1 -1
  65. package/lib/packageVersion.d.ts.map +1 -1
  66. package/lib/packageVersion.js +1 -1
  67. package/lib/packageVersion.js.map +1 -1
  68. package/lib/pendingStateManager.d.ts.map +1 -1
  69. package/lib/pendingStateManager.js +15 -2
  70. package/lib/pendingStateManager.js.map +1 -1
  71. package/lib/scheduleManager.d.ts +28 -0
  72. package/lib/scheduleManager.d.ts.map +1 -0
  73. package/lib/scheduleManager.js +231 -0
  74. package/lib/scheduleManager.js.map +1 -0
  75. package/lib/summaryCollection.js +1 -1
  76. package/lib/summaryCollection.js.map +1 -1
  77. package/lib/summaryManager.d.ts.map +1 -1
  78. package/lib/summaryManager.js +20 -5
  79. package/lib/summaryManager.js.map +1 -1
  80. package/package.json +19 -15
  81. package/src/blobManager.ts +23 -1
  82. package/src/containerRuntime.ts +42 -392
  83. package/src/dataStoreContext.ts +10 -25
  84. package/src/dataStores.ts +0 -6
  85. package/src/garbageCollection.ts +64 -69
  86. package/src/index.ts +1 -2
  87. package/src/packageVersion.ts +1 -1
  88. package/src/pendingStateManager.ts +18 -2
  89. package/src/scheduleManager.ts +294 -0
  90. package/src/summaryCollection.ts +1 -1
  91. 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.
@@ -438,10 +426,6 @@ export interface IContainerRuntimeOptions {
438
426
  readonly enableOfflineLoad?: boolean;
439
427
  }
440
428
 
441
- type IRuntimeMessageMetadata = undefined | {
442
- batch?: boolean;
443
- };
444
-
445
429
  /**
446
430
  * The summary tree returned by the root node. It adds state relevant to the root of the tree.
447
431
  */
@@ -481,11 +465,15 @@ interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedL
481
465
  * instantiated runtime in a new instance of the container, so it can load to the
482
466
  * same state
483
467
  */
484
- export interface IPendingRuntimeState {
468
+ interface IPendingRuntimeState {
485
469
  /**
486
470
  * Pending ops from PendingStateManager
487
471
  */
488
472
  pending?: IPendingLocalState;
473
+ /**
474
+ * Pending blobs from BlobManager
475
+ */
476
+ pendingAttachmentBlobs?: IPendingBlobs;
489
477
  /**
490
478
  * A base snapshot at a sequence number prior to the first pending op
491
479
  */
@@ -505,11 +493,6 @@ export interface IPendingRuntimeState {
505
493
 
506
494
  const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
507
495
 
508
- // Feature gate for the max op size. If the value is negative, chunking is enabled
509
- // and all ops over 16k would be chunked. If the value is positive, all ops with
510
- // a size strictly larger will be rejected and the container closed with an error.
511
- const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
512
-
513
496
  // By default, we should reject any op larger than 768KB,
514
497
  // in order to account for some extra overhead from serialization
515
498
  // to not reach the 1MB limits in socket.io and Kafka.
@@ -555,281 +538,6 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
555
538
  return message;
556
539
  }
557
540
 
558
- /**
559
- * This class controls pausing and resuming of inbound queue to ensure that we never
560
- * start processing ops in a batch IF we do not have all ops in the batch.
561
- */
562
- class ScheduleManagerCore {
563
- private pauseSequenceNumber: number | undefined;
564
- private currentBatchClientId: string | undefined;
565
- private localPaused = false;
566
- private timePaused = 0;
567
- private batchCount = 0;
568
-
569
- constructor(
570
- private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
571
- private readonly logger: ITelemetryLogger,
572
- ) {
573
- // Listen for delta manager sends and add batch metadata to messages
574
- this.deltaManager.on("prepareSend", (messages: IDocumentMessage[]) => {
575
- if (messages.length === 0) {
576
- return;
577
- }
578
-
579
- // First message will have the batch flag set to true if doing a batched send
580
- const firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;
581
- if (!firstMessageMetadata?.batch) {
582
- return;
583
- }
584
-
585
- // If the batch contains only a single op, clear the batch flag.
586
- if (messages.length === 1) {
587
- delete firstMessageMetadata.batch;
588
- return;
589
- }
590
-
591
- // Set the batch flag to false on the last message to indicate the end of the send batch
592
- const lastMessage = messages[messages.length - 1];
593
- lastMessage.metadata = { ...lastMessage.metadata, batch: false };
594
- });
595
-
596
- // Listen for updates and peek at the inbound
597
- this.deltaManager.inbound.on(
598
- "push",
599
- (message: ISequencedDocumentMessage) => {
600
- this.trackPending(message);
601
- });
602
-
603
- // Start with baseline - empty inbound queue.
604
- assert(!this.localPaused, 0x293 /* "initial state" */);
605
-
606
- const allPending = this.deltaManager.inbound.toArray();
607
- for (const pending of allPending) {
608
- this.trackPending(pending);
609
- }
610
-
611
- // We are intentionally directly listening to the "op" to inspect system ops as well.
612
- // If we do not observe system ops, we are likely to hit 0x296 assert when system ops
613
- // precedes start of incomplete batch.
614
- this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
615
- }
616
-
617
- /**
618
- * The only public function in this class - called when we processed an op,
619
- * to make decision if op processing should be paused or not afer that.
620
- */
621
- public afterOpProcessing(sequenceNumber: number) {
622
- assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
623
-
624
- // If the inbound queue is ever empty, nothing to do!
625
- if (this.deltaManager.inbound.length === 0) {
626
- assert(this.pauseSequenceNumber === undefined,
627
- 0x295 /* "there should be no pending batch if we have no ops" */);
628
- return;
629
- }
630
-
631
- // The queue is
632
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
633
- // - here (processing ops until reaching start of incomplete batch)
634
- // - in trackPending(), when queue was empty and start of batch showed up.
635
- // 2. resumed when batch end comes in (in trackPending())
636
-
637
- // do we have incomplete batch to worry about?
638
- if (this.pauseSequenceNumber !== undefined) {
639
- assert(sequenceNumber < this.pauseSequenceNumber,
640
- 0x296 /* "we should never start processing incomplete batch!" */);
641
- // If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
642
- if (sequenceNumber + 1 === this.pauseSequenceNumber) {
643
- this.pauseQueue();
644
- }
645
- }
646
- }
647
-
648
- private pauseQueue() {
649
- assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
650
- this.localPaused = true;
651
- this.timePaused = performance.now();
652
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
653
- this.deltaManager.inbound.pause();
654
- }
655
-
656
- private resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage) {
657
- const endBatch = messageEndBatch.sequenceNumber;
658
- const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
659
-
660
- this.batchCount++;
661
- if (this.batchCount % 1000 === 1) {
662
- this.logger.sendTelemetryEvent({
663
- eventName: "BatchStats",
664
- sequenceNumber: endBatch,
665
- length: endBatch - startBatch + 1,
666
- msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
667
- duration,
668
- batchCount: this.batchCount,
669
- interrupted: this.localPaused,
670
- });
671
- }
672
-
673
- // Return early if no change in value
674
- if (!this.localPaused) {
675
- return;
676
- }
677
-
678
- this.localPaused = false;
679
-
680
- // Random round number - we want to know when batch waiting paused op processing.
681
- if (duration !== undefined && duration > latencyThreshold) {
682
- this.logger.sendErrorEvent({
683
- eventName: "MaxBatchWaitTimeExceeded",
684
- duration,
685
- sequenceNumber: endBatch,
686
- length: endBatch - startBatch,
687
- });
688
- }
689
- this.deltaManager.inbound.resume();
690
- }
691
-
692
- /**
693
- * Called for each incoming op (i.e. inbound "push" notification)
694
- */
695
- private trackPending(message: ISequencedDocumentMessage) {
696
- assert(this.deltaManager.inbound.length !== 0,
697
- 0x298 /* "we have something in the queue that generates this event" */);
698
-
699
- assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),
700
- 0x299 /* "non-synchronized state" */);
701
-
702
- const metadata = message.metadata as IRuntimeMessageMetadata;
703
- const batchMetadata = metadata?.batch;
704
-
705
- // Protocol messages are never part of a runtime batch of messages
706
- if (!isUnpackedRuntimeMessage(message)) {
707
- // Protocol messages should never show up in the middle of the batch!
708
- assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
709
- assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
710
- assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
711
- return;
712
- }
713
-
714
- if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
715
- assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
716
- return;
717
- }
718
-
719
- // If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
720
- // If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
721
- // the previous one
722
- if (this.currentBatchClientId !== undefined || batchMetadata === false) {
723
- if (this.currentBatchClientId !== message.clientId) {
724
- // "Batch not closed, yet message from another client!"
725
- throw new DataCorruptionError(
726
- "OpBatchIncomplete",
727
- {
728
- runtimeVersion: pkgVersion,
729
- batchClientId: this.currentBatchClientId,
730
- ...extractSafePropertiesFromMessage(message),
731
- });
732
- }
733
- }
734
-
735
- // The queue is
736
- // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
737
- // - in afterOpProcessing() - processing ops until reaching start of incomplete batch
738
- // - here (batchMetadata == false below), when queue was empty and start of batch showed up.
739
- // 2. resumed when batch end comes in (batchMetadata === true case below)
740
-
741
- if (batchMetadata) {
742
- assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
743
- assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
744
- this.pauseSequenceNumber = message.sequenceNumber;
745
- this.currentBatchClientId = message.clientId;
746
- // Start of the batch
747
- // Only pause processing if queue has no other ops!
748
- // If there are any other ops in the queue, processing will be stopped when they are processed!
749
- if (this.deltaManager.inbound.length === 1) {
750
- this.pauseQueue();
751
- }
752
- } else if (batchMetadata === false) {
753
- assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
754
- // Batch is complete, we can process it!
755
- this.resumeQueue(this.pauseSequenceNumber, message);
756
- this.pauseSequenceNumber = undefined;
757
- this.currentBatchClientId = undefined;
758
- } else {
759
- // Continuation of current batch. Do nothing
760
- assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
761
- }
762
- }
763
- }
764
-
765
- /**
766
- * This class has the following responsibilities:
767
- * 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
768
- * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
769
- * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
770
- * unless all ops of the batch are in.
771
- */
772
- export class ScheduleManager {
773
- private readonly deltaScheduler: DeltaScheduler;
774
- private batchClientId: string | undefined;
775
- private hitError = false;
776
-
777
- constructor(
778
- private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
779
- private readonly emitter: EventEmitter,
780
- private readonly logger: ITelemetryLogger,
781
- ) {
782
- this.deltaScheduler = new DeltaScheduler(
783
- this.deltaManager,
784
- ChildLogger.create(this.logger, "DeltaScheduler"),
785
- );
786
- void new ScheduleManagerCore(deltaManager, logger);
787
- }
788
-
789
- public beforeOpProcessing(message: ISequencedDocumentMessage) {
790
- if (this.batchClientId !== message.clientId) {
791
- assert(this.batchClientId === undefined,
792
- 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
793
-
794
- // This could be the beginning of a new batch or an individual message.
795
- this.emitter.emit("batchBegin", message);
796
- this.deltaScheduler.batchBegin(message);
797
-
798
- const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
799
- if (batch) {
800
- this.batchClientId = message.clientId;
801
- } else {
802
- this.batchClientId = undefined;
803
- }
804
- }
805
- }
806
-
807
- public afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {
808
- // If this is no longer true, we need to revisit what we do where we set this.hitError.
809
- assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
810
-
811
- if (error) {
812
- // We assume here that loader will close container and stop processing all future ops.
813
- // This is implicit dependency. If this flow changes, this code might no longer be correct.
814
- this.hitError = true;
815
- this.batchClientId = undefined;
816
- this.emitter.emit("batchEnd", error, message);
817
- this.deltaScheduler.batchEnd(message);
818
- return;
819
- }
820
-
821
- const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
822
- // If no batchClientId has been set then we're in an individual batch. Else, if we get
823
- // batch end metadata, this is end of the current batch.
824
- if (this.batchClientId === undefined || batch === false) {
825
- this.batchClientId = undefined;
826
- this.emitter.emit("batchEnd", undefined, message);
827
- this.deltaScheduler.batchEnd(message);
828
- return;
829
- }
830
- }
831
- }
832
-
833
541
  /**
834
542
  * Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
835
543
  * special-case for document dirty state. Ultimately we should have no special-cases from the
@@ -1060,10 +768,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1060
768
  private readonly summaryCollection: SummaryCollection;
1061
769
 
1062
770
  private readonly summarizerNode: IRootSummarizerNodeWithGC;
1063
- private readonly _maxOpSizeInBytes: number;
1064
771
 
1065
772
  private readonly maxConsecutiveReconnects: number;
1066
- private readonly defaultMaxConsecutiveReconnects = 15;
773
+ private readonly defaultMaxConsecutiveReconnects = 7;
1067
774
 
1068
775
  private _orderSequentiallyCalls: number = 0;
1069
776
  private _flushMode: FlushMode;
@@ -1123,12 +830,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1123
830
 
1124
831
  private readonly dataStores: DataStores;
1125
832
 
1126
- /**
1127
- * True if generating summaries with isolated channels is
1128
- * explicitly disabled. This only affects how summaries are written,
1129
- * and is the single source of truth for this container.
1130
- */
1131
- public readonly disableIsolatedChannels: boolean;
1132
833
  /** The last message processed at the time of the last summary. */
1133
834
  private messageAtLastSummary: ISummaryMetadataMessage | undefined;
1134
835
 
@@ -1232,9 +933,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1232
933
  super();
1233
934
  this.messageAtLastSummary = metadata?.message;
1234
935
 
1235
- // Default to false (enabled).
1236
- this.disableIsolatedChannels = this.runtimeOptions.summaryOptions.disableIsolatedChannels ?? false;
1237
-
1238
936
  this._connected = this.context.connected;
1239
937
  this.chunkMap = new Map<string, string[]>(chunks);
1240
938
 
@@ -1253,7 +951,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1253
951
  this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
1254
952
  this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
1255
953
 
1256
- this._maxOpSizeInBytes = (this.mc.config.getNumber(maxOpSizeInBytesKey) ?? defaultMaxOpSizeInBytes);
1257
954
  this.maxConsecutiveReconnects =
1258
955
  this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
1259
956
 
@@ -1334,10 +1031,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1334
1031
  this.handleContext,
1335
1032
  blobManagerSnapshot,
1336
1033
  () => this.storage,
1337
- (blobId, localId) => this.submit(
1338
- ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId }),
1034
+ (blobId, localId) => {
1035
+ if (!this.disposed) {
1036
+ this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
1037
+ }
1038
+ },
1339
1039
  (blobPath: string) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"),
1340
1040
  this,
1041
+ pendingRuntimeState?.pendingAttachmentBlobs,
1341
1042
  );
1342
1043
 
1343
1044
  this.scheduleManager = new ScheduleManager(
@@ -1669,7 +1370,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1669
1370
  // Increment the summary number for the next summary that will be generated.
1670
1371
  summaryNumber: this.nextSummaryNumber++,
1671
1372
  summaryFormatVersion: 1,
1672
- disableIsolatedChannels: this.disableIsolatedChannels || undefined,
1673
1373
  ...this.garbageCollector.getMetadata(),
1674
1374
  // The last message processed at the time of summary. If there are no new messages, use the message from the
1675
1375
  // last summary.
@@ -1839,7 +1539,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1839
1539
 
1840
1540
  // There might be no change of state due to Container calling this API after loading runtime.
1841
1541
  const changeOfState = this._connected !== connected;
1842
- const reconnection = changeOfState && connected;
1542
+ const reconnection = changeOfState && !connected;
1843
1543
  this._connected = connected;
1844
1544
 
1845
1545
  if (!connected) {
@@ -1848,6 +1548,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1848
1548
  this._perfSignalData.trackingSignalSequenceNumber = undefined;
1849
1549
  }
1850
1550
 
1551
+ // Fail while disconnected
1851
1552
  if (reconnection) {
1852
1553
  this.consecutiveReconnects++;
1853
1554
 
@@ -2267,10 +1968,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2267
1968
  }
2268
1969
 
2269
1970
  const summarizeResult = this.dataStores.createSummary(telemetryContext);
2270
- if (!this.disableIsolatedChannels) {
2271
- // Wrap data store summaries in .channels subtree.
2272
- wrapSummaryInChannelsTree(summarizeResult);
2273
- }
1971
+ // Wrap data store summaries in .channels subtree.
1972
+ wrapSummaryInChannelsTree(summarizeResult);
1973
+
2274
1974
  this.addContainerStateToSummary(
2275
1975
  summarizeResult,
2276
1976
  true /* fullTree */,
@@ -2296,13 +1996,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2296
1996
  telemetryContext?: ITelemetryContext,
2297
1997
  ): Promise<ISummarizeInternalResult> {
2298
1998
  const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
2299
- let pathPartsForChildren: string[] | undefined;
2300
1999
 
2301
- if (!this.disableIsolatedChannels) {
2302
- // Wrap data store summaries in .channels subtree.
2303
- wrapSummaryInChannelsTree(summarizeResult);
2304
- pathPartsForChildren = [channelsTreeName];
2305
- }
2000
+ // Wrap data store summaries in .channels subtree.
2001
+ wrapSummaryInChannelsTree(summarizeResult);
2002
+ const pathPartsForChildren = [channelsTreeName];
2003
+
2306
2004
  this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
2307
2005
  return {
2308
2006
  ...summarizeResult,
@@ -2628,7 +2326,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2628
2326
  // Counting dataStores and handles
2629
2327
  // Because handles are unchanged dataStores in the current logic,
2630
2328
  // summarized dataStore count is total dataStore count minus handle count
2631
- const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[channelsTreeName];
2329
+ const dataStoreTree = summaryTree.tree[channelsTreeName];
2632
2330
 
2633
2331
  assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
2634
2332
  const handleCount = Object.values(dataStoreTree.tree).filter(
@@ -2827,7 +2525,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2827
2525
 
2828
2526
  if (this.canSendOps()) {
2829
2527
  const serializedContent = JSON.stringify(content);
2830
- const maxOpSize = this.context.deltaManager.maxMessageSize;
2831
2528
 
2832
2529
  // If in TurnBased flush mode we will trigger a flush at the next turn break
2833
2530
  if (this.flushMode === FlushMode.TurnBased && !this.needsFlush) {
@@ -2847,13 +2544,21 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2847
2544
  }
2848
2545
  }
2849
2546
 
2850
- clientSequenceNumber = this.submitMaybeChunkedMessages(
2851
- type,
2852
- content,
2853
- serializedContent,
2854
- maxOpSize,
2855
- this._flushMode === FlushMode.TurnBased,
2856
- 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
+ }
2857
2562
  }
2858
2563
 
2859
2564
  // Let the PendingStateManager know that a message was submitted.
@@ -2870,63 +2575,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2870
2575
  }
2871
2576
  }
2872
2577
 
2873
- private submitMaybeChunkedMessages(
2874
- type: ContainerMessageType,
2875
- content: any,
2876
- serializedContent: string,
2877
- serverMaxOpSize: number,
2878
- batch: boolean,
2879
- opMetadataInternal: unknown = undefined,
2880
- ): number {
2881
- if (this._maxOpSizeInBytes >= 0) {
2882
- // Chunking disabled
2883
- if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
2884
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
2885
- }
2886
-
2887
- // When chunking is disabled, we ignore the server max message size
2888
- // and if the content length is larger than the client configured message size
2889
- // instead of splitting the content, we will fail by explicitly close the container
2890
- this.closeFn(new GenericError(
2891
- "OpTooLarge",
2892
- /* error */ undefined,
2893
- {
2894
- length: serializedContent.length,
2895
- limit: this._maxOpSizeInBytes,
2896
- }));
2897
- return -1;
2898
- }
2899
-
2900
- // Chunking enabled, fallback on the server's max message size
2901
- // and split the content accordingly
2902
- if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
2903
- return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
2904
- }
2905
-
2906
- return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
2907
- }
2908
-
2909
- private submitChunkedMessage(type: ContainerMessageType, content: string, maxOpSize: number): number {
2910
- const contentLength = content.length;
2911
- const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
2912
- let offset = 0;
2913
- let clientSequenceNumber: number = 0;
2914
- for (let i = 1; i <= chunkN; i = i + 1) {
2915
- const chunkedOp: IChunkedOp = {
2916
- chunkId: i,
2917
- contents: content.substr(offset, maxOpSize),
2918
- originalType: type,
2919
- totalChunks: chunkN,
2920
- };
2921
- offset += maxOpSize;
2922
- clientSequenceNumber = this.submitRuntimeMessage(
2923
- ContainerMessageType.ChunkedOp,
2924
- chunkedOp,
2925
- false);
2926
- }
2927
- return clientSequenceNumber;
2928
- }
2929
-
2930
2578
  private submitSystemMessage(
2931
2579
  type: MessageType,
2932
2580
  contents: any) {
@@ -3132,7 +2780,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3132
2780
  this.baseSnapshotBlobs = await SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
3133
2781
  }
3134
2782
 
3135
- public getPendingLocalState(): IPendingRuntimeState {
2783
+ public getPendingLocalState(): unknown {
3136
2784
  if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad)) {
3137
2785
  throw new UsageError("can't get state when offline load disabled");
3138
2786
  }
@@ -3141,6 +2789,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3141
2789
  if (previousPendingState) {
3142
2790
  return {
3143
2791
  pending: this.pendingStateManager.getLocalState(),
2792
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3144
2793
  snapshotBlobs: previousPendingState.snapshotBlobs,
3145
2794
  baseSnapshot: previousPendingState.baseSnapshot,
3146
2795
  savedOps: this.savedOps,
@@ -3150,6 +2799,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
3150
2799
  assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
3151
2800
  return {
3152
2801
  pending: this.pendingStateManager.getLocalState(),
2802
+ pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
3153
2803
  snapshotBlobs: this.baseSnapshotBlobs,
3154
2804
  baseSnapshot: this.context.baseSnapshot,
3155
2805
  savedOps: this.savedOps,