@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.
- package/.mocharc.js +12 -0
- package/dist/blobManager.d.ts +7 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +18 -1
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +2 -66
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +37 -295
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +3 -5
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +13 -23
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +1 -6
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +37 -6
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +61 -65
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +15 -2
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts +28 -0
- package/dist/scheduleManager.d.ts.map +1 -0
- package/dist/scheduleManager.js +235 -0
- package/dist/scheduleManager.js.map +1 -0
- package/dist/summaryCollection.js +1 -1
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +20 -5
- package/dist/summaryManager.js.map +1 -1
- package/lib/blobManager.d.ts +7 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +19 -2
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +2 -66
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +38 -295
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +3 -5
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +13 -23
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +1 -6
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +37 -6
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +47 -52
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +15 -2
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts +28 -0
- package/lib/scheduleManager.d.ts.map +1 -0
- package/lib/scheduleManager.js +231 -0
- package/lib/scheduleManager.js.map +1 -0
- package/lib/summaryCollection.js +1 -1
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +20 -5
- package/lib/summaryManager.js.map +1 -1
- package/package.json +19 -15
- package/src/blobManager.ts +23 -1
- package/src/containerRuntime.ts +42 -392
- package/src/dataStoreContext.ts +10 -25
- package/src/dataStores.ts +0 -6
- package/src/garbageCollection.ts +64 -69
- package/src/index.ts +1 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +18 -2
- package/src/scheduleManager.ts +294 -0
- package/src/summaryCollection.ts +1 -1
- package/src/summaryManager.ts +20 -5
package/src/containerRuntime.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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) =>
|
|
1338
|
-
|
|
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
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
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
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
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 =
|
|
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
|
-
|
|
2851
|
-
type,
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
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():
|
|
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,
|