@fluidframework/container-runtime 2.0.0-internal.2.1.1 → 2.0.0-internal.2.2.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/.eslintrc.js +1 -1
- package/dist/blobManager.d.ts +20 -5
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +57 -15
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +39 -40
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +115 -278
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +3 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +21 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +8 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +26 -13
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +15 -17
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +92 -106
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +19 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -0
- package/dist/garbageCollectionConstants.js +34 -0
- package/dist/garbageCollectionConstants.js.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +2 -2
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +30 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -0
- package/dist/{batchManager.js → opLifecycle/batchManager.js} +25 -10
- package/dist/opLifecycle/batchManager.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +40 -0
- package/dist/opLifecycle/definitions.d.ts.map +1 -0
- package/dist/opLifecycle/definitions.js +7 -0
- package/dist/opLifecycle/definitions.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +12 -0
- package/dist/opLifecycle/index.d.ts.map +1 -0
- package/dist/opLifecycle/index.js +21 -0
- package/dist/opLifecycle/index.js.map +1 -0
- package/dist/opLifecycle/opCompressor.d.ts +18 -0
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opCompressor.js +53 -0
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/opLifecycle/opDecompressor.d.ts +20 -0
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opDecompressor.js +72 -0
- package/dist/opLifecycle/opDecompressor.js.map +1 -0
- package/dist/opLifecycle/opSplitter.d.ts +17 -0
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
- package/dist/opLifecycle/opSplitter.js +61 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -0
- package/dist/opLifecycle/outbox.d.ts +47 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -0
- package/dist/opLifecycle/outbox.js +153 -0
- package/dist/opLifecycle/outbox.js.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.js +81 -0
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/summaryFormat.js +2 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/blobManager.d.ts +20 -5
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +59 -17
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +39 -40
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +113 -275
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +3 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +23 -5
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +8 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +28 -15
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +15 -17
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +72 -86
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +19 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -0
- package/lib/garbageCollectionConstants.js +31 -0
- package/lib/garbageCollectionConstants.js.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +1 -1
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +30 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -0
- package/lib/{batchManager.js → opLifecycle/batchManager.js} +25 -10
- package/lib/opLifecycle/batchManager.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +40 -0
- package/lib/opLifecycle/definitions.d.ts.map +1 -0
- package/lib/opLifecycle/definitions.js +6 -0
- package/lib/opLifecycle/definitions.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +12 -0
- package/lib/opLifecycle/index.d.ts.map +1 -0
- package/lib/opLifecycle/index.js +11 -0
- package/lib/opLifecycle/index.js.map +1 -0
- package/lib/opLifecycle/opCompressor.d.ts +18 -0
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opCompressor.js +49 -0
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/opLifecycle/opDecompressor.d.ts +20 -0
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opDecompressor.js +68 -0
- package/lib/opLifecycle/opDecompressor.js.map +1 -0
- package/lib/opLifecycle/opSplitter.d.ts +17 -0
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
- package/lib/opLifecycle/opSplitter.js +57 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -0
- package/lib/opLifecycle/outbox.d.ts +47 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -0
- package/lib/opLifecycle/outbox.js +149 -0
- package/lib/opLifecycle/outbox.js.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.js +76 -0
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/summaryFormat.js +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +35 -21
- package/prettier.config.cjs +8 -0
- package/src/blobManager.ts +74 -19
- package/src/containerRuntime.ts +144 -341
- package/src/dataStoreContext.ts +33 -5
- package/src/dataStores.ts +32 -16
- package/src/garbageCollection.ts +106 -82
- package/src/garbageCollectionConstants.ts +35 -0
- package/src/gcSweepReadyUsageDetection.ts +1 -1
- package/src/index.ts +6 -4
- package/src/{batchManager.ts → opLifecycle/batchManager.ts} +41 -23
- package/src/opLifecycle/definitions.ts +44 -0
- package/src/opLifecycle/index.ts +17 -0
- package/src/opLifecycle/opCompressor.ts +64 -0
- package/src/opLifecycle/opDecompressor.ts +84 -0
- package/src/opLifecycle/opSplitter.ts +78 -0
- package/src/opLifecycle/outbox.ts +204 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +90 -0
- package/src/packageVersion.ts +1 -1
- package/src/summaryFormat.ts +1 -1
- package/dist/batchManager.d.ts +0 -36
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js.map +0 -1
- package/lib/batchManager.d.ts +0 -36
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js.map +0 -1
package/src/containerRuntime.ts
CHANGED
|
@@ -16,14 +16,12 @@ import {
|
|
|
16
16
|
IFluidTokenProvider,
|
|
17
17
|
IContainerContext,
|
|
18
18
|
IDeltaManager,
|
|
19
|
-
IDeltaSender,
|
|
20
19
|
IRuntime,
|
|
21
20
|
ICriticalContainerError,
|
|
22
21
|
AttachState,
|
|
23
22
|
ILoaderOptions,
|
|
24
23
|
LoaderHeader,
|
|
25
24
|
ISnapshotTreeWithBlobContents,
|
|
26
|
-
IBatchMessage,
|
|
27
25
|
} from "@fluidframework/container-definitions";
|
|
28
26
|
import {
|
|
29
27
|
IContainerRuntime,
|
|
@@ -34,7 +32,6 @@ import {
|
|
|
34
32
|
Trace,
|
|
35
33
|
TypedEventEmitter,
|
|
36
34
|
unreachableCase,
|
|
37
|
-
IsoBuffer,
|
|
38
35
|
} from "@fluidframework/common-utils";
|
|
39
36
|
import {
|
|
40
37
|
ChildLogger,
|
|
@@ -108,7 +105,6 @@ import {
|
|
|
108
105
|
} from "@fluidframework/runtime-utils";
|
|
109
106
|
import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
|
|
110
107
|
import { v4 as uuid } from "uuid";
|
|
111
|
-
import { compress, decompress } from "lz4js";
|
|
112
108
|
import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
113
109
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
114
110
|
import { Summarizer } from "./summarizer";
|
|
@@ -121,7 +117,6 @@ import {
|
|
|
121
117
|
IPendingLocalState,
|
|
122
118
|
PendingStateManager,
|
|
123
119
|
} from "./pendingStateManager";
|
|
124
|
-
import { BatchManager, BatchMessage } from "./batchManager";
|
|
125
120
|
import { pkgVersion } from "./packageVersion";
|
|
126
121
|
import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
|
|
127
122
|
import { DataStores, getSummaryForDatastores } from "./dataStores";
|
|
@@ -156,12 +151,13 @@ import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
|
|
|
156
151
|
import {
|
|
157
152
|
GarbageCollector,
|
|
158
153
|
GCNodeType,
|
|
159
|
-
gcTreeKey,
|
|
160
154
|
IGarbageCollectionRuntime,
|
|
161
155
|
IGarbageCollector,
|
|
162
156
|
IGCStats,
|
|
163
|
-
testTombstoneKey,
|
|
164
157
|
} from "./garbageCollection";
|
|
158
|
+
import {
|
|
159
|
+
gcTreeKey,
|
|
160
|
+
} from "./garbageCollectionConstants";
|
|
165
161
|
import {
|
|
166
162
|
channelToDataStore,
|
|
167
163
|
IDataStoreAliasMessage,
|
|
@@ -170,6 +166,15 @@ import {
|
|
|
170
166
|
import { BindBatchTracker } from "./batchTracker";
|
|
171
167
|
import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
|
|
172
168
|
import { ScheduleManager } from "./scheduleManager";
|
|
169
|
+
import {
|
|
170
|
+
BatchMessage,
|
|
171
|
+
IBatchCheckpoint,
|
|
172
|
+
OpCompressor,
|
|
173
|
+
OpDecompressor,
|
|
174
|
+
Outbox,
|
|
175
|
+
OpSplitter,
|
|
176
|
+
RemoteMessageProcessor,
|
|
177
|
+
} from "./opLifecycle";
|
|
173
178
|
|
|
174
179
|
export enum ContainerMessageType {
|
|
175
180
|
// An op to be delivered to store
|
|
@@ -191,16 +196,6 @@ export enum ContainerMessageType {
|
|
|
191
196
|
Alias = "alias",
|
|
192
197
|
}
|
|
193
198
|
|
|
194
|
-
export interface IChunkedOp {
|
|
195
|
-
chunkId: number;
|
|
196
|
-
|
|
197
|
-
totalChunks: number;
|
|
198
|
-
|
|
199
|
-
contents: string;
|
|
200
|
-
|
|
201
|
-
originalType: MessageType | ContainerMessageType;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
199
|
export interface ContainerRuntimeMessage {
|
|
205
200
|
contents: any;
|
|
206
201
|
type: ContainerMessageType;
|
|
@@ -213,6 +208,7 @@ export interface ISummaryBaseConfiguration {
|
|
|
213
208
|
initialSummarizerDelayMs: number;
|
|
214
209
|
|
|
215
210
|
/**
|
|
211
|
+
* @deprecated
|
|
216
212
|
* Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
|
|
217
213
|
* This defaults to false (disabled) and must be explicitly set to true to enable.
|
|
218
214
|
*/
|
|
@@ -434,10 +430,14 @@ export interface ISummaryRuntimeOptions {
|
|
|
434
430
|
*/
|
|
435
431
|
export interface ICompressionRuntimeOptions {
|
|
436
432
|
/**
|
|
437
|
-
* The minimum size the
|
|
438
|
-
* Compression is disabled if undefined.
|
|
433
|
+
* The minimum size the batch's payload must exceed before the batch's contents will be compressed.
|
|
439
434
|
*/
|
|
440
|
-
readonly
|
|
435
|
+
readonly minimumBatchSizeInBytes: number;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The compression algorithm that will be used to compress the op.
|
|
439
|
+
*/
|
|
440
|
+
readonly compressionAlgorithm: CompressionAlgorithms;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
/**
|
|
@@ -467,7 +467,7 @@ export interface IContainerRuntimeOptions {
|
|
|
467
467
|
*/
|
|
468
468
|
readonly enableOfflineLoad?: boolean;
|
|
469
469
|
/**
|
|
470
|
-
* Enables the runtime to compress ops.
|
|
470
|
+
* Enables the runtime to compress ops. Compression is disabled when undefined.
|
|
471
471
|
* @experimental Not ready for use.
|
|
472
472
|
*/
|
|
473
473
|
readonly compressionOptions?: ICompressionRuntimeOptions;
|
|
@@ -507,6 +507,13 @@ export enum RuntimeHeaders {
|
|
|
507
507
|
viaHandle = "viaHandle",
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Available compression algorithms for op compression.
|
|
512
|
+
*/
|
|
513
|
+
export enum CompressionAlgorithms {
|
|
514
|
+
lz4 = "lz4",
|
|
515
|
+
}
|
|
516
|
+
|
|
510
517
|
/**
|
|
511
518
|
* @deprecated
|
|
512
519
|
* Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
|
|
@@ -579,46 +586,6 @@ export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
|
|
|
579
586
|
return (Object.values(RuntimeMessage) as string[]).includes(message.type);
|
|
580
587
|
}
|
|
581
588
|
|
|
582
|
-
/**
|
|
583
|
-
* Unpacks runtime messages
|
|
584
|
-
*
|
|
585
|
-
* @remarks This API makes no promises regarding backward-compatibility. This is internal API.
|
|
586
|
-
* @param message - message (as it observed in storage / service)
|
|
587
|
-
* @returns unpacked runtime message
|
|
588
|
-
*
|
|
589
|
-
* @internal
|
|
590
|
-
*/
|
|
591
|
-
export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
592
|
-
if (message.metadata?.compressed) {
|
|
593
|
-
const contents = IsoBuffer.from(message.contents.contents, "base64");
|
|
594
|
-
const decompressedMessage = decompress(contents);
|
|
595
|
-
const intoString = new TextDecoder().decode(decompressedMessage);
|
|
596
|
-
const asObj = JSON.parse(intoString);
|
|
597
|
-
message.contents.contents = asObj;
|
|
598
|
-
message.metadata.compressed = false;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (message.type === MessageType.Operation) {
|
|
602
|
-
// legacy op format?
|
|
603
|
-
if (message.contents.address !== undefined && message.contents.type === undefined) {
|
|
604
|
-
message.type = ContainerMessageType.FluidDataStoreOp;
|
|
605
|
-
} else {
|
|
606
|
-
// new format
|
|
607
|
-
const innerContents = message.contents as ContainerRuntimeMessage;
|
|
608
|
-
assert(innerContents.type !== undefined, 0x121 /* "Undefined inner contents type!" */);
|
|
609
|
-
message.type = innerContents.type;
|
|
610
|
-
message.contents = innerContents.contents;
|
|
611
|
-
}
|
|
612
|
-
return true;
|
|
613
|
-
} else {
|
|
614
|
-
// Legacy format, but it's already "unpacked",
|
|
615
|
-
// i.e. message.type is actually ContainerMessageType.
|
|
616
|
-
// Or it's non-runtime message.
|
|
617
|
-
// Nothing to do in such case.
|
|
618
|
-
return false;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
589
|
/**
|
|
623
590
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
624
591
|
* special-case for document dirty state. Ultimately we should have no special-cases from the
|
|
@@ -661,6 +628,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
661
628
|
* @param requestHandler - Request handlers for the container runtime
|
|
662
629
|
* @param runtimeOptions - Additional options to be passed to the runtime
|
|
663
630
|
* @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
|
|
631
|
+
* @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
|
|
632
|
+
* allows mixin classes to leverage this method to define their own async initializer.
|
|
664
633
|
*/
|
|
665
634
|
public static async load(
|
|
666
635
|
context: IContainerContext,
|
|
@@ -669,6 +638,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
669
638
|
runtimeOptions: IContainerRuntimeOptions = {},
|
|
670
639
|
containerScope: FluidObject = context.scope,
|
|
671
640
|
existing?: boolean,
|
|
641
|
+
containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntime
|
|
672
642
|
): Promise<ContainerRuntime> {
|
|
673
643
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
674
644
|
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
|
|
@@ -687,7 +657,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
687
657
|
loadSequenceNumberVerification = "close",
|
|
688
658
|
flushMode = defaultFlushMode,
|
|
689
659
|
enableOfflineLoad = false,
|
|
690
|
-
compressionOptions = {
|
|
660
|
+
compressionOptions = {
|
|
661
|
+
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
|
|
662
|
+
compressionAlgorithm: CompressionAlgorithms.lz4
|
|
663
|
+
},
|
|
691
664
|
maxBatchSizeInBytes = defaultMaxBatchSizeInBytes,
|
|
692
665
|
} = runtimeOptions;
|
|
693
666
|
|
|
@@ -751,7 +724,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
751
724
|
}
|
|
752
725
|
}
|
|
753
726
|
|
|
754
|
-
const runtime = new
|
|
727
|
+
const runtime = new containerRuntimeCtor(
|
|
755
728
|
context,
|
|
756
729
|
registry,
|
|
757
730
|
metadata,
|
|
@@ -781,7 +754,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
781
754
|
pendingRuntimeState.savedOps = [];
|
|
782
755
|
}
|
|
783
756
|
|
|
784
|
-
|
|
757
|
+
// Initialize the base state of the runtime before it's returned.
|
|
758
|
+
await runtime.initializeBaseState();
|
|
785
759
|
|
|
786
760
|
return runtime;
|
|
787
761
|
}
|
|
@@ -843,6 +817,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
843
817
|
|
|
844
818
|
// internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
|
|
845
819
|
private readonly mc: MonitoringContext;
|
|
820
|
+
|
|
846
821
|
private readonly summarizerClientElection?: SummarizerClientElection;
|
|
847
822
|
/**
|
|
848
823
|
* summaryManager will only be created if this client is permitted to spawn a summarizing client
|
|
@@ -867,7 +842,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
867
842
|
private baseSnapshotBlobs?: ISerializedBaseSnapshotBlobs;
|
|
868
843
|
|
|
869
844
|
private consecutiveReconnects = 0;
|
|
870
|
-
private compressedOpCount = 0;
|
|
871
845
|
|
|
872
846
|
/**
|
|
873
847
|
* Used to delay transition to "connected" state while we upload
|
|
@@ -904,27 +878,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
904
878
|
* It is created only by summarizing container (i.e. one with clientType === "summarizer")
|
|
905
879
|
*/
|
|
906
880
|
private readonly _summarizer?: Summarizer;
|
|
907
|
-
private readonly deltaSender: IDeltaSender;
|
|
908
881
|
private readonly scheduleManager: ScheduleManager;
|
|
909
882
|
private readonly blobManager: BlobManager;
|
|
910
883
|
private readonly pendingStateManager: PendingStateManager;
|
|
911
|
-
private readonly
|
|
912
|
-
private readonly pendingBatch: BatchManager;
|
|
884
|
+
private readonly outbox: Outbox;
|
|
913
885
|
|
|
914
886
|
private readonly garbageCollector: IGarbageCollector;
|
|
915
887
|
|
|
916
|
-
// Local copy of incomplete received chunks.
|
|
917
|
-
private readonly chunkMap: Map<string, string[]>;
|
|
918
|
-
|
|
919
888
|
private readonly dataStores: DataStores;
|
|
889
|
+
private readonly remoteMessageProcessor: RemoteMessageProcessor;
|
|
920
890
|
|
|
921
891
|
/** The last message processed at the time of the last summary. */
|
|
922
892
|
private messageAtLastSummary: ISummaryMetadataMessage | undefined;
|
|
923
893
|
|
|
924
|
-
private get emptyBatch() {
|
|
925
|
-
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
894
|
private get summarizer(): Summarizer {
|
|
929
895
|
assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
930
896
|
return this._summarizer;
|
|
@@ -995,7 +961,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
995
961
|
*/
|
|
996
962
|
private nextSummaryNumber: number;
|
|
997
963
|
|
|
998
|
-
|
|
964
|
+
/**
|
|
965
|
+
* @internal
|
|
966
|
+
*/
|
|
967
|
+
protected constructor(
|
|
999
968
|
private readonly context: IContainerContext,
|
|
1000
969
|
private readonly registry: IFluidDataStoreRegistry,
|
|
1001
970
|
metadata: IContainerRuntimeMetadata | undefined,
|
|
@@ -1017,10 +986,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1017
986
|
},
|
|
1018
987
|
) {
|
|
1019
988
|
super();
|
|
989
|
+
|
|
990
|
+
let loadSummaryNumber: number;
|
|
991
|
+
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
992
|
+
// get the values from the metadata blob.
|
|
993
|
+
if (existing) {
|
|
994
|
+
this.createContainerMetadata = {
|
|
995
|
+
createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
|
|
996
|
+
createContainerTimestamp: metadata?.createContainerTimestamp,
|
|
997
|
+
};
|
|
998
|
+
// summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
|
|
999
|
+
// the count is reset to 0.
|
|
1000
|
+
loadSummaryNumber = metadata?.summaryNumber ?? 0;
|
|
1001
|
+
} else {
|
|
1002
|
+
this.createContainerMetadata = {
|
|
1003
|
+
createContainerRuntimeVersion: pkgVersion,
|
|
1004
|
+
createContainerTimestamp: Date.now(),
|
|
1005
|
+
};
|
|
1006
|
+
loadSummaryNumber = 0;
|
|
1007
|
+
}
|
|
1008
|
+
this.nextSummaryNumber = loadSummaryNumber + 1;
|
|
1009
|
+
|
|
1020
1010
|
this.messageAtLastSummary = metadata?.message;
|
|
1021
1011
|
|
|
1022
1012
|
this._connected = this.context.connected;
|
|
1023
|
-
this.
|
|
1013
|
+
this.remoteMessageProcessor = new RemoteMessageProcessor(new OpSplitter(chunks), new OpDecompressor());
|
|
1024
1014
|
|
|
1025
1015
|
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
1026
1016
|
|
|
@@ -1042,14 +1032,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1042
1032
|
|
|
1043
1033
|
this._flushMode = runtimeOptions.flushMode;
|
|
1044
1034
|
|
|
1045
|
-
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression
|
|
1046
|
-
// & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
|
|
1047
|
-
// latency of processing a batch.
|
|
1048
|
-
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
1049
|
-
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
1050
|
-
this.pendingAttachBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes, 64 * 1024);
|
|
1051
|
-
this.pendingBatch = new BatchManager(runtimeOptions.maxBatchSizeInBytes);
|
|
1052
|
-
|
|
1053
1035
|
const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
1054
1036
|
const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
|
|
1055
1037
|
|
|
@@ -1068,6 +1050,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1068
1050
|
baseLogger: this.mc.logger,
|
|
1069
1051
|
existing,
|
|
1070
1052
|
metadata,
|
|
1053
|
+
createContainerMetadata: this.createContainerMetadata,
|
|
1071
1054
|
isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
|
|
1072
1055
|
getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1073
1056
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
@@ -1151,8 +1134,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1151
1134
|
ChildLogger.create(this.logger, "ScheduleManager"),
|
|
1152
1135
|
);
|
|
1153
1136
|
|
|
1154
|
-
this.deltaSender = this.deltaManager;
|
|
1155
|
-
|
|
1156
1137
|
this.pendingStateManager = new PendingStateManager(
|
|
1157
1138
|
{
|
|
1158
1139
|
applyStashedOp: this.applyStashedOp.bind(this),
|
|
@@ -1166,8 +1147,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1166
1147
|
},
|
|
1167
1148
|
pendingRuntimeState?.pending);
|
|
1168
1149
|
|
|
1150
|
+
this.outbox = new Outbox({
|
|
1151
|
+
shouldSend: () => this.canSendOps(),
|
|
1152
|
+
pendingStateManager: this.pendingStateManager,
|
|
1153
|
+
containerContext: this.context,
|
|
1154
|
+
compressor: new OpCompressor(this.mc.logger),
|
|
1155
|
+
config: {
|
|
1156
|
+
compressionOptions: runtimeOptions.compressionOptions,
|
|
1157
|
+
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1158
|
+
},
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1169
1161
|
this.context.quorum.on("removeMember", (clientId: string) => {
|
|
1170
|
-
this.
|
|
1162
|
+
this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
|
|
1171
1163
|
});
|
|
1172
1164
|
|
|
1173
1165
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
@@ -1283,26 +1275,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1283
1275
|
...getDeviceSpec(),
|
|
1284
1276
|
});
|
|
1285
1277
|
|
|
1286
|
-
let loadSummaryNumber: number;
|
|
1287
|
-
// Get the container creation metadata. For new container, we initialize these. For existing containers,
|
|
1288
|
-
// get the values from the metadata blob.
|
|
1289
|
-
if (existing) {
|
|
1290
|
-
this.createContainerMetadata = {
|
|
1291
|
-
createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
|
|
1292
|
-
createContainerTimestamp: metadata?.createContainerTimestamp,
|
|
1293
|
-
};
|
|
1294
|
-
// summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
|
|
1295
|
-
// the count is reset to 0.
|
|
1296
|
-
loadSummaryNumber = metadata?.summaryNumber ?? 0;
|
|
1297
|
-
} else {
|
|
1298
|
-
this.createContainerMetadata = {
|
|
1299
|
-
createContainerRuntimeVersion: pkgVersion,
|
|
1300
|
-
createContainerTimestamp: Date.now(),
|
|
1301
|
-
};
|
|
1302
|
-
loadSummaryNumber = 0;
|
|
1303
|
-
}
|
|
1304
|
-
this.nextSummaryNumber = loadSummaryNumber + 1;
|
|
1305
|
-
|
|
1306
1278
|
this.logger.sendTelemetryEvent({
|
|
1307
1279
|
eventName: "ContainerLoadStats",
|
|
1308
1280
|
...this.createContainerMetadata,
|
|
@@ -1317,6 +1289,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1317
1289
|
BindBatchTracker(this, this.logger);
|
|
1318
1290
|
}
|
|
1319
1291
|
|
|
1292
|
+
/**
|
|
1293
|
+
* Initializes the state from the base snapshot this container runtime loaded from.
|
|
1294
|
+
*/
|
|
1295
|
+
private async initializeBaseState(): Promise<void> {
|
|
1296
|
+
await this.initializeBaseSnapshotBlobs();
|
|
1297
|
+
await this.garbageCollector.initializeBaseState();
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1320
1300
|
public dispose(error?: Error): void {
|
|
1321
1301
|
if (this._disposed) {
|
|
1322
1302
|
return;
|
|
@@ -1480,7 +1460,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1480
1460
|
addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
|
|
1481
1461
|
}
|
|
1482
1462
|
|
|
1483
|
-
|
|
1463
|
+
protected addContainerStateToSummary(
|
|
1484
1464
|
summaryTree: ISummaryTreeWithStats,
|
|
1485
1465
|
fullTree: boolean,
|
|
1486
1466
|
trackState: boolean,
|
|
@@ -1488,8 +1468,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1488
1468
|
) {
|
|
1489
1469
|
this.addMetadataToSummary(summaryTree);
|
|
1490
1470
|
|
|
1491
|
-
if (this.
|
|
1492
|
-
const content = JSON.stringify([...this.
|
|
1471
|
+
if (this.remoteMessageProcessor.partialMessages.size > 0) {
|
|
1472
|
+
const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
|
|
1493
1473
|
addBlobToSummary(summaryTree, chunksBlobName, content);
|
|
1494
1474
|
}
|
|
1495
1475
|
|
|
@@ -1684,37 +1664,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1684
1664
|
public process(messageArg: ISequencedDocumentMessage, local: boolean) {
|
|
1685
1665
|
this.verifyNotClosed();
|
|
1686
1666
|
|
|
1687
|
-
// Do shallow copy of message, as methods below will modify it.
|
|
1688
|
-
// There might be multiple container instances receiving same message
|
|
1689
|
-
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
1690
|
-
// but would not modify contents details
|
|
1691
|
-
let message = { ...messageArg };
|
|
1692
|
-
|
|
1693
|
-
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
1694
|
-
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
1695
|
-
// Old ops may contain empty string (I assume noops).
|
|
1696
|
-
if (typeof message.contents === "string" && message.contents !== "") {
|
|
1697
|
-
message.contents = JSON.parse(message.contents);
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
1701
|
-
// This format was not shipped to production workflows.
|
|
1702
|
-
const runtimeMessage = unpackRuntimeMessage(message);
|
|
1703
|
-
|
|
1704
1667
|
if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
|
|
1705
1668
|
this.savedOps.push(messageArg);
|
|
1706
1669
|
}
|
|
1707
1670
|
|
|
1671
|
+
|
|
1672
|
+
// Whether or not the message is actually a runtime message.
|
|
1673
|
+
// It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
|
|
1674
|
+
// or something different, like a system message.
|
|
1675
|
+
const runtimeMessage = messageArg.type === MessageType.Operation;
|
|
1676
|
+
|
|
1677
|
+
// Do shallow copy of message, as the processing flow will modify it.
|
|
1678
|
+
const messageCopy = { ...messageArg };
|
|
1679
|
+
const message = this.remoteMessageProcessor.process(messageCopy);
|
|
1680
|
+
|
|
1708
1681
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1709
1682
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1710
1683
|
// messages once a batch has been fully processed.
|
|
1711
1684
|
this.scheduleManager.beforeOpProcessing(message);
|
|
1712
1685
|
|
|
1713
1686
|
try {
|
|
1714
|
-
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
1715
|
-
// once all pieces are available
|
|
1716
|
-
message = this.processRemoteChunkedMessage(message);
|
|
1717
|
-
|
|
1718
1687
|
let localOpMetadata: unknown;
|
|
1719
1688
|
if (local && runtimeMessage) {
|
|
1720
1689
|
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
@@ -1847,117 +1816,18 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1847
1816
|
assert(this._orderSequentiallyCalls === 0,
|
|
1848
1817
|
0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1849
1818
|
|
|
1850
|
-
this.
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
assert(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
protected flushBatch(batch: BatchMessage[]): void {
|
|
1857
|
-
const length = batch.length;
|
|
1858
|
-
|
|
1859
|
-
if (length > 1) {
|
|
1860
|
-
batch[0].metadata = { ...batch[0].metadata, batch: true };
|
|
1861
|
-
batch[length - 1].metadata = { ...batch[length - 1].metadata, batch: false };
|
|
1862
|
-
|
|
1863
|
-
// This assert fires for the following reason (there might be more cases like that):
|
|
1864
|
-
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1865
|
-
// i.e. in the middle of op processing!
|
|
1866
|
-
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1867
|
-
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1868
|
-
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1869
|
-
// Tracked via ADO #1834
|
|
1870
|
-
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1871
|
-
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
let clientSequenceNumber: number = -1;
|
|
1875
|
-
|
|
1876
|
-
// Did we disconnect in the middle of turn-based batch?
|
|
1877
|
-
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1878
|
-
if (this.canSendOps()) {
|
|
1879
|
-
if (this.context.submitBatchFn !== undefined) {
|
|
1880
|
-
const batchToSend: IBatchMessage[] = [];
|
|
1881
|
-
for (const message of batch) {
|
|
1882
|
-
let contents = message.contents;
|
|
1883
|
-
let metadata = message.metadata;
|
|
1884
|
-
if (this.runtimeOptions.compressionOptions.minimumSize &&
|
|
1885
|
-
this.runtimeOptions.compressionOptions.minimumSize < message.contents.length) {
|
|
1886
|
-
this.compressedOpCount++;
|
|
1887
|
-
const copiedMessage = { ...message.deserializedContent };
|
|
1888
|
-
|
|
1889
|
-
const compressionStart = Date.now();
|
|
1890
|
-
const contentsAsBuffer = new TextEncoder().encode(JSON.stringify(copiedMessage.contents));
|
|
1891
|
-
const compressedContents = compress(contentsAsBuffer);
|
|
1892
|
-
const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
|
|
1893
|
-
const duration = Date.now() - compressionStart;
|
|
1894
|
-
|
|
1895
|
-
if (this.compressedOpCount % 100) {
|
|
1896
|
-
this.mc.logger.sendPerformanceEvent({
|
|
1897
|
-
eventName: "compressedOp",
|
|
1898
|
-
duration,
|
|
1899
|
-
sizeBeforeCompression: message.contents.length,
|
|
1900
|
-
sizeAfterCompression: compressedContent.length,
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
copiedMessage.contents = compressedContent;
|
|
1905
|
-
const stringifiedContents = JSON.stringify(copiedMessage);
|
|
1906
|
-
|
|
1907
|
-
if (stringifiedContents.length < message.contents.length) {
|
|
1908
|
-
contents = JSON.stringify(copiedMessage);
|
|
1909
|
-
metadata = { ...message.metadata, compressed: true };
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
batchToSend.push({ contents, metadata });
|
|
1914
|
-
}
|
|
1915
|
-
// returns clientSequenceNumber of last message in a batch
|
|
1916
|
-
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1917
|
-
} else {
|
|
1918
|
-
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1919
|
-
// version that has support for batches (submitBatchFn)
|
|
1920
|
-
for (const message of batch) {
|
|
1921
|
-
clientSequenceNumber = this.context.submitFn(
|
|
1922
|
-
MessageType.Operation,
|
|
1923
|
-
message.deserializedContent,
|
|
1924
|
-
true, // batch
|
|
1925
|
-
message.metadata);
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
this.deltaSender.flush();
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1932
|
-
clientSequenceNumber -= batch.length - 1;
|
|
1933
|
-
assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
// Let the PendingStateManager know that a message was submitted.
|
|
1937
|
-
// In future, need to shift toward keeping batch as a whole!
|
|
1938
|
-
for (const message of batch) {
|
|
1939
|
-
this.pendingStateManager.onSubmitMessage(
|
|
1940
|
-
message.deserializedContent.type,
|
|
1941
|
-
clientSequenceNumber,
|
|
1942
|
-
message.referenceSequenceNumber,
|
|
1943
|
-
message.deserializedContent.contents,
|
|
1944
|
-
message.localOpMetadata,
|
|
1945
|
-
message.metadata,
|
|
1946
|
-
);
|
|
1947
|
-
clientSequenceNumber++;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
this.pendingStateManager.onFlush();
|
|
1819
|
+
this.outbox.flush();
|
|
1820
|
+
assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
|
|
1951
1821
|
}
|
|
1952
1822
|
|
|
1953
1823
|
public orderSequentially(callback: () => void): void {
|
|
1954
|
-
let checkpoint:
|
|
1824
|
+
let checkpoint: IBatchCheckpoint | undefined;
|
|
1955
1825
|
|
|
1956
1826
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1957
1827
|
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1958
1828
|
// 1. It would not help, as we flush attach ops as they become available.
|
|
1959
1829
|
// 2. There is no way to undo process of data store creation.
|
|
1960
|
-
checkpoint = this.
|
|
1830
|
+
checkpoint = this.outbox.checkpoint().mainBatch;
|
|
1961
1831
|
}
|
|
1962
1832
|
try {
|
|
1963
1833
|
this._orderSequentiallyCalls++;
|
|
@@ -2268,35 +2138,28 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2268
2138
|
// always referenced, so the used routes is only self-route (empty string).
|
|
2269
2139
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
2270
2140
|
|
|
2141
|
+
const blobManagerUsedRoutes: string[] = [];
|
|
2271
2142
|
const dataStoreUsedRoutes: string[] = [];
|
|
2272
2143
|
for (const route of usedRoutes) {
|
|
2273
|
-
if (
|
|
2144
|
+
if (this.isBlobPath(route)) {
|
|
2145
|
+
blobManagerUsedRoutes.push(route);
|
|
2146
|
+
} else {
|
|
2274
2147
|
dataStoreUsedRoutes.push(route);
|
|
2275
2148
|
}
|
|
2276
2149
|
}
|
|
2277
2150
|
|
|
2278
|
-
|
|
2151
|
+
this.blobManager.updateUsedRoutes(blobManagerUsedRoutes);
|
|
2152
|
+
this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
|
|
2279
2153
|
}
|
|
2280
2154
|
|
|
2281
2155
|
/**
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
* @param unusedRoutes - The routes that are unused in all data stores and blobs in this Container.
|
|
2156
|
+
* This is called to update objects whose routes are unused. The unused objects are either deleted or marked as
|
|
2157
|
+
* tombstones.
|
|
2158
|
+
* @param unusedRoutes - The routes that are unused in all data stores and attachment blobs in this Container.
|
|
2159
|
+
* @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
|
|
2160
|
+
* are deleted.
|
|
2285
2161
|
*/
|
|
2286
|
-
public
|
|
2287
|
-
/**
|
|
2288
|
-
* When running GC in tombstone mode, this is called to tombstone datastore routes that are unused. This
|
|
2289
|
-
* enables testing scenarios without actually deleting content. The content acts as if it's deleted to the
|
|
2290
|
-
* external user, but the internal runtime does not delete it in summarizes, etc.
|
|
2291
|
-
*/
|
|
2292
|
-
const tombstone = this.mc.config.getBoolean(testTombstoneKey) ?? false;
|
|
2293
|
-
// TODO: add blobs
|
|
2294
|
-
if (tombstone) {
|
|
2295
|
-
// If blob routes are passed in here, tombstone will fail and hit an assert
|
|
2296
|
-
this.dataStores.deleteUnusedRoutes(unusedRoutes, tombstone);
|
|
2297
|
-
return;
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2162
|
+
public updateUnusedRoutes(unusedRoutes: string[], tombstone: boolean) {
|
|
2300
2163
|
const blobManagerUnusedRoutes: string[] = [];
|
|
2301
2164
|
const dataStoreUnusedRoutes: string[] = [];
|
|
2302
2165
|
for (const route of unusedRoutes) {
|
|
@@ -2307,8 +2170,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2307
2170
|
}
|
|
2308
2171
|
}
|
|
2309
2172
|
|
|
2310
|
-
this.blobManager.
|
|
2311
|
-
this.dataStores.
|
|
2173
|
+
this.blobManager.updateUnusedRoutes(blobManagerUnusedRoutes, tombstone);
|
|
2174
|
+
this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
|
|
2312
2175
|
}
|
|
2313
2176
|
|
|
2314
2177
|
/**
|
|
@@ -2406,7 +2269,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2406
2269
|
},
|
|
2407
2270
|
);
|
|
2408
2271
|
|
|
2409
|
-
assert(this.
|
|
2272
|
+
assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
2410
2273
|
|
|
2411
2274
|
let latestSnapshotVersionId: string | undefined;
|
|
2412
2275
|
if (refreshLatestAck) {
|
|
@@ -2616,45 +2479,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2616
2479
|
}
|
|
2617
2480
|
}
|
|
2618
2481
|
|
|
2619
|
-
private processRemoteChunkedMessage(message: ISequencedDocumentMessage) {
|
|
2620
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
2621
|
-
return message;
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
const clientId = message.clientId;
|
|
2625
|
-
const chunkedContent = message.contents as IChunkedOp;
|
|
2626
|
-
this.addChunk(clientId, chunkedContent);
|
|
2627
|
-
if (chunkedContent.chunkId === chunkedContent.totalChunks) {
|
|
2628
|
-
const newMessage = { ...message };
|
|
2629
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2630
|
-
const serializedContent = this.chunkMap.get(clientId)!.join("");
|
|
2631
|
-
newMessage.contents = JSON.parse(serializedContent);
|
|
2632
|
-
newMessage.type = chunkedContent.originalType;
|
|
2633
|
-
this.clearPartialChunks(clientId);
|
|
2634
|
-
return newMessage;
|
|
2635
|
-
}
|
|
2636
|
-
return message;
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
private addChunk(clientId: string, chunkedContent: IChunkedOp) {
|
|
2640
|
-
let map = this.chunkMap.get(clientId);
|
|
2641
|
-
if (map === undefined) {
|
|
2642
|
-
map = [];
|
|
2643
|
-
this.chunkMap.set(clientId, map);
|
|
2644
|
-
}
|
|
2645
|
-
assert(chunkedContent.chunkId === map.length + 1,
|
|
2646
|
-
0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
|
|
2647
|
-
map.push(chunkedContent.contents);
|
|
2648
|
-
}
|
|
2649
|
-
|
|
2650
|
-
private clearPartialChunks(clientId: string) {
|
|
2651
|
-
if (this.chunkMap.has(clientId)) {
|
|
2652
|
-
this.chunkMap.delete(clientId);
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
|
|
2656
2482
|
private hasPendingMessages() {
|
|
2657
|
-
return this.pendingStateManager.hasPendingMessages() || !this.
|
|
2483
|
+
return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
|
|
2658
2484
|
}
|
|
2659
2485
|
|
|
2660
2486
|
private updateDocumentDirtyState(dirty: boolean) {
|
|
@@ -2717,7 +2543,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2717
2543
|
const serializedContent = JSON.stringify(deserializedContent);
|
|
2718
2544
|
|
|
2719
2545
|
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
2720
|
-
this.logger.
|
|
2546
|
+
this.logger.sendTelemetryEvent({ eventName: "SubmitOpInReadonly", connected: this.connected });
|
|
2721
2547
|
}
|
|
2722
2548
|
|
|
2723
2549
|
const message: BatchMessage = {
|
|
@@ -2750,45 +2576,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2750
2576
|
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
2751
2577
|
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
2752
2578
|
if (this.currentlyBatching() && type === ContainerMessageType.Attach &&
|
|
2753
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.
|
|
2754
|
-
|
|
2755
|
-
// BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
|
|
2756
|
-
// when queue is not empty.
|
|
2757
|
-
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
2758
|
-
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
2759
|
-
if (!this.pendingAttachBatch.push(message)) {
|
|
2760
|
-
throw new GenericError(
|
|
2761
|
-
"BatchTooLarge",
|
|
2762
|
-
/* error */ undefined,
|
|
2763
|
-
{
|
|
2764
|
-
opSize: message.contents.length,
|
|
2765
|
-
count: this.pendingAttachBatch.length,
|
|
2766
|
-
limit: this.pendingAttachBatch.limit,
|
|
2767
|
-
});
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2579
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
2580
|
+
this.outbox.submitAttach(message);
|
|
2770
2581
|
} else {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2582
|
+
this.outbox.submit(message);
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
if (!this.currentlyBatching()) {
|
|
2586
|
+
this.flush();
|
|
2587
|
+
} else if (!this.flushMicroTaskExists) {
|
|
2588
|
+
this.flushMicroTaskExists = true;
|
|
2589
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
2590
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2591
|
+
Promise.resolve().then(() => {
|
|
2592
|
+
this.flushMicroTaskExists = false;
|
|
2782
2593
|
this.flush();
|
|
2783
|
-
}
|
|
2784
|
-
this.flushMicroTaskExists = true;
|
|
2785
|
-
// Queue a microtask to detect the end of the turn and force a flush.
|
|
2786
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2787
|
-
Promise.resolve().then(() => {
|
|
2788
|
-
this.flushMicroTaskExists = false;
|
|
2789
|
-
this.flush();
|
|
2790
|
-
});
|
|
2791
|
-
}
|
|
2594
|
+
}).catch((error) => { this.closeFn(error as GenericError) });
|
|
2792
2595
|
}
|
|
2793
2596
|
} catch (error) {
|
|
2794
2597
|
this.closeFn(error as GenericError);
|
|
@@ -2805,7 +2608,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2805
2608
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
2806
2609
|
|
|
2807
2610
|
// System message should not be sent in the middle of the batch.
|
|
2808
|
-
assert(this.
|
|
2611
|
+
assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
|
|
2809
2612
|
|
|
2810
2613
|
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
2811
2614
|
return this.context.submitSummaryFn !== undefined
|
|
@@ -2917,12 +2720,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2917
2720
|
|
|
2918
2721
|
const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
|
|
2919
2722
|
summaryLogger.sendTelemetryEvent(
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2723
|
+
{
|
|
2724
|
+
eventName: "LatestSummaryRetrieved",
|
|
2725
|
+
ackHandle,
|
|
2726
|
+
lastSequenceNumber: latestSnapshotRefSeq,
|
|
2727
|
+
targetSequenceNumber: summaryRefSeq,
|
|
2728
|
+
});
|
|
2926
2729
|
|
|
2927
2730
|
// In case we had to retrieve the latest snapshot and it is different than summaryRefSeq,
|
|
2928
2731
|
// wait for the delta manager to catch up before refreshing the latest Summary.
|
|
@@ -3013,7 +2816,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
3013
2816
|
}
|
|
3014
2817
|
}
|
|
3015
2818
|
|
|
3016
|
-
|
|
2819
|
+
private async initializeBaseSnapshotBlobs(): Promise<void> {
|
|
3017
2820
|
if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) ||
|
|
3018
2821
|
this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
|
|
3019
2822
|
return;
|