@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.
- package/.mocharc.js +12 -0
- package/dist/batchTracker.js +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +7 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +34 -17
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +3 -104
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +83 -395
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +2 -3
- package/dist/dataStore.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 +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -8
- 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/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.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/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +20 -1
- package/dist/summarizer.js.map +1 -1
- package/dist/summaryCollection.js +1 -1
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryGenerator.js +1 -1
- package/dist/summaryGenerator.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/batchTracker.js +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +7 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +35 -18
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +3 -104
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +84 -395
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +2 -3
- package/lib/dataStore.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 +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -8
- 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/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.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/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +22 -3
- package/lib/summarizer.js.map +1 -1
- package/lib/summaryCollection.js +1 -1
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryGenerator.js +1 -1
- package/lib/summaryGenerator.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 +32 -19
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +43 -17
- package/src/containerRuntime.ts +113 -547
- package/src/dataStore.ts +1 -4
- package/src/dataStoreContext.ts +10 -25
- package/src/dataStores.ts +13 -19
- 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/runningSummarizer.ts +1 -1
- package/src/scheduleManager.ts +294 -0
- package/src/summarizer.ts +28 -3
- package/src/summaryCollection.ts +1 -1
- package/src/summaryGenerator.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.
|
|
@@ -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
|
-
|
|
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 =
|
|
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) =>
|
|
1352
|
-
|
|
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,
|
|
1833
|
-
|
|
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,
|
|
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
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
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
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
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
|
|
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 (
|
|
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:
|
|
2233
|
+
targetSequenceNumber: latestSnapshotRefSeq,
|
|
2641
2234
|
lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
|
|
2642
2235
|
},
|
|
2643
|
-
async () => waitForSeq(this.deltaManager,
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
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
|
-
|
|
2942
|
-
type,
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
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
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
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(
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
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
|
|
2722
|
+
const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
|
|
3159
2723
|
|
|
3160
2724
|
const result = await this.summarizerNode.refreshLatestSummary(
|
|
3161
2725
|
undefined,
|
|
3162
|
-
|
|
3163
|
-
async () =>
|
|
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
|
|
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():
|
|
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,
|