@fluidframework/container-runtime 2.0.0-internal.1.1.2 → 2.0.0-internal.1.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/dist/batchManager.d.ts +37 -0
- package/dist/batchManager.d.ts.map +1 -0
- package/dist/batchManager.js +73 -0
- package/dist/batchManager.js.map +1 -0
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +1 -2
- package/dist/batchTracker.js.map +1 -1
- package/dist/containerRuntime.d.ts +52 -20
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +252 -126
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +18 -9
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +24 -16
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +6 -2
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +7 -9
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +6 -4
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +6 -4
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -12
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +176 -98
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts +53 -0
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +135 -0
- package/dist/gcSweepReadyUsageDetection.js.map +1 -0
- package/dist/orderedClientElection.d.ts +28 -10
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +14 -4
- package/dist/orderedClientElection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +0 -11
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +9 -44
- 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 +6 -3
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +22 -14
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizerTypes.d.ts +16 -9
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts +1 -0
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +29 -13
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -2
- package/dist/summaryManager.js +2 -2
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchManager.d.ts +37 -0
- package/lib/batchManager.d.ts.map +1 -0
- package/lib/batchManager.js +69 -0
- package/lib/batchManager.js.map +1 -0
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +1 -2
- package/lib/batchTracker.js.map +1 -1
- package/lib/containerRuntime.d.ts +52 -20
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +255 -129
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +18 -9
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +25 -17
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +6 -2
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +7 -9
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +6 -4
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +6 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -12
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +175 -97
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts +53 -0
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +130 -0
- package/lib/gcSweepReadyUsageDetection.js.map +1 -0
- package/lib/orderedClientElection.d.ts +28 -10
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +14 -4
- package/lib/orderedClientElection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +0 -11
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +9 -44
- 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 +6 -3
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +24 -16
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizerTypes.d.ts +16 -9
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts +1 -0
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +29 -13
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -2
- package/lib/summaryManager.js +2 -2
- package/lib/summaryManager.js.map +1 -1
- package/package.json +21 -18
- package/src/batchManager.ts +91 -0
- package/src/batchTracker.ts +1 -2
- package/src/containerRuntime.ts +331 -176
- package/src/dataStoreContext.ts +27 -17
- package/src/dataStores.ts +7 -8
- package/src/deltaScheduler.ts +6 -4
- package/src/garbageCollection.ts +224 -134
- package/src/gcSweepReadyUsageDetection.ts +147 -0
- package/src/orderedClientElection.ts +31 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +9 -57
- package/src/runningSummarizer.ts +1 -1
- package/src/scheduleManager.ts +32 -12
- package/src/summarizerTypes.ts +17 -9
- package/src/summaryCollection.ts +31 -16
- package/src/summaryManager.ts +2 -2
package/src/containerRuntime.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ILoaderOptions,
|
|
24
24
|
LoaderHeader,
|
|
25
25
|
ISnapshotTreeWithBlobContents,
|
|
26
|
+
IBatchMessage,
|
|
26
27
|
} from "@fluidframework/container-definitions";
|
|
27
28
|
import {
|
|
28
29
|
IContainerRuntime,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
TaggedLoggerAdapter,
|
|
42
43
|
MonitoringContext,
|
|
43
44
|
loggerToMonitoringContext,
|
|
45
|
+
wrapError,
|
|
44
46
|
} from "@fluidframework/telemetry-utils";
|
|
45
47
|
import {
|
|
46
48
|
DriverHeader,
|
|
@@ -48,7 +50,7 @@ import {
|
|
|
48
50
|
IDocumentStorageService,
|
|
49
51
|
ISummaryContext,
|
|
50
52
|
} from "@fluidframework/driver-definitions";
|
|
51
|
-
import { readAndParse
|
|
53
|
+
import { readAndParse } from "@fluidframework/driver-utils";
|
|
52
54
|
import {
|
|
53
55
|
DataCorruptionError,
|
|
54
56
|
DataProcessingError,
|
|
@@ -113,7 +115,11 @@ import {
|
|
|
113
115
|
ReportOpPerfTelemetry,
|
|
114
116
|
IPerfSignalReport,
|
|
115
117
|
} from "./connectionTelemetry";
|
|
116
|
-
import {
|
|
118
|
+
import {
|
|
119
|
+
IPendingLocalState,
|
|
120
|
+
PendingStateManager,
|
|
121
|
+
} from "./pendingStateManager";
|
|
122
|
+
import { BatchManager, BatchMessage } from "./batchManager";
|
|
117
123
|
import { pkgVersion } from "./packageVersion";
|
|
118
124
|
import { BlobManager, IBlobManagerLoadInfo, IPendingBlobs } from "./blobManager";
|
|
119
125
|
import { DataStores, getSummaryForDatastores } from "./dataStores";
|
|
@@ -195,9 +201,10 @@ export interface ContainerRuntimeMessage {
|
|
|
195
201
|
contents: any;
|
|
196
202
|
type: ContainerMessageType;
|
|
197
203
|
}
|
|
204
|
+
|
|
198
205
|
export interface ISummaryBaseConfiguration {
|
|
199
206
|
/**
|
|
200
|
-
*
|
|
207
|
+
* Delay before first attempt to spawn summarizing container.
|
|
201
208
|
*/
|
|
202
209
|
initialSummarizerDelayMs: number;
|
|
203
210
|
|
|
@@ -223,7 +230,8 @@ export interface ISummaryBaseConfiguration {
|
|
|
223
230
|
export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfiguration {
|
|
224
231
|
state: "enabled";
|
|
225
232
|
/**
|
|
226
|
-
* @deprecated
|
|
233
|
+
* @deprecated Please move all implementations to {@link ISummaryConfigurationHeuristics.minIdleTime} and
|
|
234
|
+
* {@link ISummaryConfigurationHeuristics.maxIdleTime} instead.
|
|
227
235
|
*/
|
|
228
236
|
idleTime: number;
|
|
229
237
|
/**
|
|
@@ -368,33 +376,45 @@ export interface ISummaryRuntimeOptions {
|
|
|
368
376
|
summaryConfigOverrides?: ISummaryConfiguration;
|
|
369
377
|
|
|
370
378
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
|
|
379
|
+
* Delay before first attempt to spawn summarizing container.
|
|
380
|
+
*
|
|
381
|
+
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
|
|
382
|
+
* {@link ISummaryBaseConfiguration.initialSummarizerDelayMs} instead.
|
|
383
|
+
*/
|
|
374
384
|
initialSummarizerDelayMs?: number;
|
|
375
385
|
|
|
376
386
|
/**
|
|
377
|
-
* @deprecated - use `summaryConfigOverrides.disableSummaries` instead.
|
|
378
387
|
* Flag that disables summaries if it is set to true.
|
|
388
|
+
*
|
|
389
|
+
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
|
|
390
|
+
* {@link ISummaryConfigurationDisableSummarizer.state} instead.
|
|
379
391
|
*/
|
|
380
392
|
disableSummaries?: boolean;
|
|
381
393
|
|
|
382
394
|
/**
|
|
383
|
-
* @
|
|
384
|
-
*
|
|
395
|
+
* @defaultValue 7000 operations (ops)
|
|
396
|
+
*
|
|
397
|
+
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
|
|
398
|
+
* {@link ISummaryBaseConfiguration.maxOpsSinceLastSummary} instead.
|
|
385
399
|
*/
|
|
386
400
|
maxOpsSinceLastSummary?: number;
|
|
387
401
|
|
|
388
402
|
/**
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
403
|
+
* Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
|
|
404
|
+
*
|
|
405
|
+
* @defaultValue `false` (disabled) and must be explicitly set to true to enable.
|
|
406
|
+
*
|
|
407
|
+
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
|
|
408
|
+
* {@link ISummaryBaseConfiguration.summarizerClientElection} instead.
|
|
409
|
+
*/
|
|
393
410
|
summarizerClientElection?: boolean;
|
|
394
411
|
|
|
395
412
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
413
|
+
* Options that control the running summarizer behavior.
|
|
414
|
+
*
|
|
415
|
+
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
|
|
416
|
+
* `{@link ISummaryConfiguration.state} = "DisableHeuristics"` instead.
|
|
417
|
+
* */
|
|
398
418
|
summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
|
|
399
419
|
}
|
|
400
420
|
|
|
@@ -493,13 +513,11 @@ interface IPendingRuntimeState {
|
|
|
493
513
|
|
|
494
514
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
495
515
|
|
|
496
|
-
// By default, we should reject any op larger than 768KB,
|
|
497
|
-
// in order to account for some extra overhead from serialization
|
|
498
|
-
// to not reach the 1MB limits in socket.io and Kafka.
|
|
499
|
-
const defaultMaxOpSizeInBytes = 768000;
|
|
500
|
-
|
|
501
516
|
const defaultFlushMode = FlushMode.TurnBased;
|
|
502
517
|
|
|
518
|
+
/**
|
|
519
|
+
* @deprecated - use ContainerRuntimeMessage instead
|
|
520
|
+
*/
|
|
503
521
|
export enum RuntimeMessage {
|
|
504
522
|
FluidDataStoreOp = "component",
|
|
505
523
|
Attach = "attach",
|
|
@@ -510,6 +528,9 @@ export enum RuntimeMessage {
|
|
|
510
528
|
Operation = "op",
|
|
511
529
|
}
|
|
512
530
|
|
|
531
|
+
/**
|
|
532
|
+
* @deprecated - please use version in driver-utils
|
|
533
|
+
*/
|
|
513
534
|
export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
|
|
514
535
|
if ((Object.values(RuntimeMessage) as string[]).includes(message.type)) {
|
|
515
536
|
return true;
|
|
@@ -517,6 +538,15 @@ export function isRuntimeMessage(message: ISequencedDocumentMessage): boolean {
|
|
|
517
538
|
return false;
|
|
518
539
|
}
|
|
519
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Unpacks runtime messages
|
|
543
|
+
*
|
|
544
|
+
* @remarks This API makes no promises regarding backward-compatability. This is internal API.
|
|
545
|
+
* @param message - message (as it observed in storage / service)
|
|
546
|
+
* @returns unpacked runtime message
|
|
547
|
+
*
|
|
548
|
+
* @internal
|
|
549
|
+
*/
|
|
520
550
|
export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
521
551
|
if (message.type === MessageType.Operation) {
|
|
522
552
|
// legacy op format?
|
|
@@ -529,13 +559,14 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
|
529
559
|
message.type = innerContents.type;
|
|
530
560
|
message.contents = innerContents.contents;
|
|
531
561
|
}
|
|
532
|
-
|
|
562
|
+
return true;
|
|
533
563
|
} else {
|
|
534
564
|
// Legacy format, but it's already "unpacked",
|
|
535
565
|
// i.e. message.type is actually ContainerMessageType.
|
|
566
|
+
// Or it's non-runtime message.
|
|
536
567
|
// Nothing to do in such case.
|
|
568
|
+
return false;
|
|
537
569
|
}
|
|
538
|
-
return message;
|
|
539
570
|
}
|
|
540
571
|
|
|
541
572
|
/**
|
|
@@ -774,7 +805,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
774
805
|
|
|
775
806
|
private _orderSequentiallyCalls: number = 0;
|
|
776
807
|
private _flushMode: FlushMode;
|
|
777
|
-
private needsFlush = false;
|
|
778
808
|
private flushTrigger = false;
|
|
779
809
|
|
|
780
810
|
private _connected: boolean;
|
|
@@ -823,6 +853,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
823
853
|
private readonly scheduleManager: ScheduleManager;
|
|
824
854
|
private readonly blobManager: BlobManager;
|
|
825
855
|
private readonly pendingStateManager: PendingStateManager;
|
|
856
|
+
|
|
857
|
+
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
|
|
858
|
+
// but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
|
|
859
|
+
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
860
|
+
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
861
|
+
private readonly pendingAttachBatch = new BatchManager(64 * 1024);
|
|
862
|
+
private readonly pendingBatch = new BatchManager();
|
|
863
|
+
|
|
826
864
|
private readonly garbageCollector: IGarbageCollector;
|
|
827
865
|
|
|
828
866
|
// Local copy of incomplete received chunks.
|
|
@@ -833,6 +871,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
833
871
|
/** The last message processed at the time of the last summary. */
|
|
834
872
|
private messageAtLastSummary: ISummaryMetadataMessage | undefined;
|
|
835
873
|
|
|
874
|
+
private get emptyBatch() {
|
|
875
|
+
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
876
|
+
}
|
|
877
|
+
|
|
836
878
|
private get summarizer(): Summarizer {
|
|
837
879
|
assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
838
880
|
return this._summarizer;
|
|
@@ -868,11 +910,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
868
910
|
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
869
911
|
return true;
|
|
870
912
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
913
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
914
|
+
? this.summaryConfiguration.summarizerClientElection === true
|
|
915
|
+
: false;
|
|
876
916
|
}
|
|
877
917
|
private readonly maxOpsSinceLastSummary: number;
|
|
878
918
|
private getMaxOpsSinceLastSummary(): number {
|
|
@@ -881,11 +921,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
881
921
|
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
882
922
|
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
883
923
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
return 0;
|
|
888
|
-
}
|
|
924
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
925
|
+
? this.summaryConfiguration.maxOpsSinceLastSummary
|
|
926
|
+
: 0;
|
|
889
927
|
}
|
|
890
928
|
|
|
891
929
|
private readonly initialSummarizerDelayMs: number;
|
|
@@ -895,11 +933,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
895
933
|
if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
|
|
896
934
|
return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
|
|
897
935
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
return 0;
|
|
902
|
-
}
|
|
936
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
937
|
+
? this.summaryConfiguration.initialSummarizerDelayMs
|
|
938
|
+
: 0;
|
|
903
939
|
}
|
|
904
940
|
|
|
905
941
|
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
@@ -970,6 +1006,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
970
1006
|
getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
971
1007
|
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
972
1008
|
readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
1009
|
+
getContainerDiagnosticId: () => this.context.id,
|
|
1010
|
+
activeConnection: () => this.deltaManager.active,
|
|
973
1011
|
});
|
|
974
1012
|
|
|
975
1013
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
@@ -1044,6 +1082,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1044
1082
|
this.scheduleManager = new ScheduleManager(
|
|
1045
1083
|
context.deltaManager,
|
|
1046
1084
|
this,
|
|
1085
|
+
() => this.clientId,
|
|
1047
1086
|
ChildLogger.create(this.logger, "ScheduleManager"),
|
|
1048
1087
|
);
|
|
1049
1088
|
|
|
@@ -1058,7 +1097,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1058
1097
|
flush: this.flush.bind(this),
|
|
1059
1098
|
flushMode: () => this.flushMode,
|
|
1060
1099
|
reSubmit: this.reSubmit.bind(this),
|
|
1061
|
-
rollback: this.rollback.bind(this),
|
|
1062
1100
|
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
1063
1101
|
},
|
|
1064
1102
|
this._flushMode,
|
|
@@ -1293,15 +1331,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1293
1331
|
|
|
1294
1332
|
if (id === BlobManager.basePath && requestParser.isLeaf(2)) {
|
|
1295
1333
|
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
1296
|
-
|
|
1297
|
-
|
|
1334
|
+
return blob
|
|
1335
|
+
? {
|
|
1298
1336
|
status: 200,
|
|
1299
1337
|
mimeType: "fluid/object",
|
|
1300
1338
|
value: blob,
|
|
1301
|
-
};
|
|
1302
|
-
} else {
|
|
1303
|
-
return create404Response(request);
|
|
1304
|
-
}
|
|
1339
|
+
} : create404Response(request);
|
|
1305
1340
|
} else if (requestParser.pathParts.length > 0) {
|
|
1306
1341
|
const dataStore = await this.getDataStoreFromRequest(id, request);
|
|
1307
1342
|
const subRequest = requestParser.createSubRequest(1);
|
|
@@ -1427,7 +1462,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1427
1462
|
return true;
|
|
1428
1463
|
}
|
|
1429
1464
|
|
|
1430
|
-
if (!this.
|
|
1465
|
+
if (!this.hasPendingMessages()) {
|
|
1431
1466
|
// If there are no pending messages, we can always reconnect
|
|
1432
1467
|
this.resetReconnectCount();
|
|
1433
1468
|
return true;
|
|
@@ -1546,6 +1581,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1546
1581
|
this._perfSignalData.signalsLost = 0;
|
|
1547
1582
|
this._perfSignalData.signalTimestamp = 0;
|
|
1548
1583
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1584
|
+
} else {
|
|
1585
|
+
assert(this.attachState === AttachState.Attached,
|
|
1586
|
+
0x3cd /* Connection is possible only if container exists in storage */);
|
|
1549
1587
|
}
|
|
1550
1588
|
|
|
1551
1589
|
// Fail while disconnected
|
|
@@ -1554,9 +1592,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1554
1592
|
|
|
1555
1593
|
if (!this.shouldContinueReconnecting()) {
|
|
1556
1594
|
this.closeFn(
|
|
1557
|
-
// pre-0.58 error message: MaxReconnectsWithNoProgress
|
|
1558
1595
|
DataProcessingError.create(
|
|
1559
|
-
|
|
1596
|
+
// eslint-disable-next-line max-len
|
|
1597
|
+
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)",
|
|
1560
1598
|
"setConnectionState",
|
|
1561
1599
|
undefined,
|
|
1562
1600
|
{
|
|
@@ -1573,6 +1611,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1573
1611
|
}
|
|
1574
1612
|
|
|
1575
1613
|
this.dataStores.setConnectionState(connected, clientId);
|
|
1614
|
+
this.garbageCollector.setConnectionState(connected, clientId);
|
|
1576
1615
|
|
|
1577
1616
|
raiseConnectedEvent(this.mc.logger, this, connected, clientId);
|
|
1578
1617
|
}
|
|
@@ -1580,49 +1619,50 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1580
1619
|
public process(messageArg: ISequencedDocumentMessage, local: boolean) {
|
|
1581
1620
|
this.verifyNotClosed();
|
|
1582
1621
|
|
|
1583
|
-
// If it's not message for runtime, bail out right away.
|
|
1584
|
-
if (!isUnpackedRuntimeMessage(messageArg)) {
|
|
1585
|
-
return;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
|
|
1589
|
-
this.savedOps.push(messageArg);
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
1622
|
// Do shallow copy of message, as methods below will modify it.
|
|
1593
1623
|
// There might be multiple container instances receiving same message
|
|
1594
1624
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
1595
1625
|
// but would not modify contents details
|
|
1596
1626
|
let message = { ...messageArg };
|
|
1597
1627
|
|
|
1628
|
+
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
1629
|
+
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
1630
|
+
// Old ops may contain empty string (I assume noops).
|
|
1631
|
+
if (typeof message.contents === "string" && message.contents !== "") {
|
|
1632
|
+
message.contents = JSON.parse(message.contents);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
1636
|
+
// This format was not shipped to production workflows.
|
|
1637
|
+
const runtimeMessage = unpackRuntimeMessage(message);
|
|
1638
|
+
|
|
1639
|
+
if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
|
|
1640
|
+
this.savedOps.push(messageArg);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1598
1643
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1599
1644
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1600
1645
|
// messages once a batch has been fully processed.
|
|
1601
1646
|
this.scheduleManager.beforeOpProcessing(message);
|
|
1602
1647
|
|
|
1603
1648
|
try {
|
|
1604
|
-
message = unpackRuntimeMessage(message);
|
|
1605
|
-
|
|
1606
1649
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
1607
1650
|
// once all pieces are available
|
|
1608
1651
|
message = this.processRemoteChunkedMessage(message);
|
|
1609
1652
|
|
|
1610
1653
|
let localOpMetadata: unknown;
|
|
1611
|
-
if (local) {
|
|
1612
|
-
|
|
1613
|
-
// Do not process local chunked ops until all pieces are available.
|
|
1614
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
1615
|
-
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1616
|
-
}
|
|
1654
|
+
if (local && runtimeMessage) {
|
|
1655
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1617
1656
|
}
|
|
1618
1657
|
|
|
1619
1658
|
// If there are no more pending messages after processing a local message,
|
|
1620
1659
|
// the document is no longer dirty.
|
|
1621
|
-
if (!this.
|
|
1660
|
+
if (!this.hasPendingMessages()) {
|
|
1622
1661
|
this.updateDocumentDirtyState(false);
|
|
1623
1662
|
}
|
|
1624
1663
|
|
|
1625
|
-
|
|
1664
|
+
const type = message.type as ContainerMessageType;
|
|
1665
|
+
switch (type) {
|
|
1626
1666
|
case ContainerMessageType.Attach:
|
|
1627
1667
|
this.dataStores.processAttachMessage(message, local);
|
|
1628
1668
|
break;
|
|
@@ -1635,10 +1675,18 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1635
1675
|
case ContainerMessageType.BlobAttach:
|
|
1636
1676
|
this.blobManager.processBlobAttachOp(message, local);
|
|
1637
1677
|
break;
|
|
1678
|
+
case ContainerMessageType.ChunkedOp:
|
|
1679
|
+
case ContainerMessageType.Rejoin:
|
|
1680
|
+
break;
|
|
1638
1681
|
default:
|
|
1682
|
+
assert(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// For back-compat, notify only about runtime messages for now.
|
|
1686
|
+
if (runtimeMessage) {
|
|
1687
|
+
this.emit("op", message, runtimeMessage);
|
|
1639
1688
|
}
|
|
1640
1689
|
|
|
1641
|
-
this.emit("op", message);
|
|
1642
1690
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1643
1691
|
|
|
1644
1692
|
if (local) {
|
|
@@ -1752,30 +1800,76 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1752
1800
|
assert(this._orderSequentiallyCalls === 0,
|
|
1753
1801
|
0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1754
1802
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
}
|
|
1803
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1804
|
+
this.flushBatch(this.pendingBatch.popBatch());
|
|
1758
1805
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
// not connected, `this.needsFlush` will be false but the PendingStateManager might have pending messages and
|
|
1762
|
-
// hence needs to track this.
|
|
1763
|
-
this.pendingStateManager.onFlush();
|
|
1806
|
+
assert(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1807
|
+
}
|
|
1764
1808
|
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1809
|
+
protected flushBatch(batch: BatchMessage[]): void {
|
|
1810
|
+
const length = batch.length;
|
|
1811
|
+
|
|
1812
|
+
if (length > 1) {
|
|
1813
|
+
batch[0].metadata = { ...batch[0].metadata, batch: true };
|
|
1814
|
+
batch[length - 1].metadata = { ...batch[length - 1].metadata, batch: false };
|
|
1815
|
+
|
|
1816
|
+
// This assert fires for the following reason (there might be more cases like that):
|
|
1817
|
+
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1818
|
+
// i.e. in the middle of op processing!
|
|
1819
|
+
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1820
|
+
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1821
|
+
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1822
|
+
// Tracked via ADO #1834
|
|
1823
|
+
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1824
|
+
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1768
1825
|
}
|
|
1769
1826
|
|
|
1770
|
-
|
|
1827
|
+
let clientSequenceNumber: number = -1;
|
|
1771
1828
|
|
|
1772
1829
|
// Did we disconnect in the middle of turn-based batch?
|
|
1773
1830
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
1831
|
+
if (this.canSendOps()) {
|
|
1832
|
+
if (this.context.submitBatchFn !== undefined) {
|
|
1833
|
+
const batchToSend: IBatchMessage[] = [];
|
|
1834
|
+
for (const message of batch) {
|
|
1835
|
+
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1836
|
+
}
|
|
1837
|
+
// returns clientSequenceNumber of last message in a batch
|
|
1838
|
+
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1839
|
+
} else {
|
|
1840
|
+
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1841
|
+
// version that has support for batches (submitBatchFn)
|
|
1842
|
+
for (const message of batch) {
|
|
1843
|
+
clientSequenceNumber = this.context.submitFn(
|
|
1844
|
+
MessageType.Operation,
|
|
1845
|
+
message.deserializedContent,
|
|
1846
|
+
true, // batch
|
|
1847
|
+
message.metadata);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
this.deltaSender.flush();
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1854
|
+
clientSequenceNumber -= batch.length - 1;
|
|
1855
|
+
assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1776
1856
|
}
|
|
1777
1857
|
|
|
1778
|
-
|
|
1858
|
+
// Let the PendingStateManager know that a message was submitted.
|
|
1859
|
+
// In future, need to shift toward keeping batch as a whole!
|
|
1860
|
+
for (const message of batch) {
|
|
1861
|
+
this.pendingStateManager.onSubmitMessage(
|
|
1862
|
+
message.deserializedContent.type,
|
|
1863
|
+
clientSequenceNumber,
|
|
1864
|
+
message.referenceSequenceNumber,
|
|
1865
|
+
message.deserializedContent.contents,
|
|
1866
|
+
message.localOpMetadata,
|
|
1867
|
+
message.metadata,
|
|
1868
|
+
);
|
|
1869
|
+
clientSequenceNumber++;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
this.pendingStateManager.onFlush();
|
|
1779
1873
|
}
|
|
1780
1874
|
|
|
1781
1875
|
public orderSequentially(callback: () => void): void {
|
|
@@ -1801,9 +1895,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1801
1895
|
}
|
|
1802
1896
|
|
|
1803
1897
|
private trackOrderSequentiallyCalls(callback: () => void): void {
|
|
1804
|
-
let checkpoint: { rollback: () => void; } | undefined;
|
|
1898
|
+
let checkpoint: { rollback: (action: (message: BatchMessage) => void) => void; } | undefined;
|
|
1805
1899
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1806
|
-
|
|
1900
|
+
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1901
|
+
// 1. It would not help, as we flush attach ops as they become available.
|
|
1902
|
+
// 2. There is no way to undo process of data store creation.
|
|
1903
|
+
checkpoint = this.pendingBatch.checkpoint();
|
|
1807
1904
|
}
|
|
1808
1905
|
|
|
1809
1906
|
try {
|
|
@@ -1812,7 +1909,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1812
1909
|
} catch (error) {
|
|
1813
1910
|
if (checkpoint) {
|
|
1814
1911
|
// This will throw and close the container if rollback fails
|
|
1815
|
-
|
|
1912
|
+
try {
|
|
1913
|
+
checkpoint.rollback((message: BatchMessage) =>
|
|
1914
|
+
this.rollback(
|
|
1915
|
+
message.deserializedContent.type,
|
|
1916
|
+
message.deserializedContent.contents,
|
|
1917
|
+
message.localOpMetadata));
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
const error2 = wrapError(err, (message) => {
|
|
1920
|
+
return DataProcessingError.create(
|
|
1921
|
+
`RollbackError: ${message}`,
|
|
1922
|
+
"checkpointRollback",
|
|
1923
|
+
undefined) as DataProcessingError;
|
|
1924
|
+
});
|
|
1925
|
+
this.closeFn(error2);
|
|
1926
|
+
throw error2;
|
|
1927
|
+
}
|
|
1816
1928
|
} else {
|
|
1817
1929
|
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
1818
1930
|
this.closeFn(new GenericError("orderSequentially callback exception", error));
|
|
@@ -1948,7 +2060,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1948
2060
|
this.emit("attached");
|
|
1949
2061
|
}
|
|
1950
2062
|
|
|
1951
|
-
if (attachState === AttachState.Attached && !this.
|
|
2063
|
+
if (attachState === AttachState.Attached && !this.hasPendingMessages()) {
|
|
1952
2064
|
this.updateDocumentDirtyState(false);
|
|
1953
2065
|
}
|
|
1954
2066
|
this.dataStores.setAttachState(attachState);
|
|
@@ -2170,7 +2282,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2170
2282
|
|
|
2171
2283
|
/**
|
|
2172
2284
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
2173
|
-
* @returns the statistics of the garbage collection run.
|
|
2285
|
+
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
2174
2286
|
*/
|
|
2175
2287
|
public async collectGarbage(
|
|
2176
2288
|
options: {
|
|
@@ -2181,7 +2293,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2181
2293
|
/** True to generate full GC data */
|
|
2182
2294
|
fullGC?: boolean;
|
|
2183
2295
|
},
|
|
2184
|
-
): Promise<IGCStats> {
|
|
2296
|
+
): Promise<IGCStats | undefined> {
|
|
2185
2297
|
return this.garbageCollector.collectGarbage(options);
|
|
2186
2298
|
}
|
|
2187
2299
|
|
|
@@ -2216,6 +2328,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2216
2328
|
},
|
|
2217
2329
|
);
|
|
2218
2330
|
|
|
2331
|
+
assert(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
2332
|
+
|
|
2219
2333
|
let latestSnapshotVersionId: string | undefined;
|
|
2220
2334
|
if (refreshLatestAck) {
|
|
2221
2335
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(
|
|
@@ -2305,7 +2419,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2305
2419
|
const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
|
|
2306
2420
|
try {
|
|
2307
2421
|
summarizeResult = await this.summarize({
|
|
2308
|
-
fullTree: fullTree
|
|
2422
|
+
fullTree: fullTree ?? forcedFullTree,
|
|
2309
2423
|
trackState: true,
|
|
2310
2424
|
summaryLogger: summaryNumberLogger,
|
|
2311
2425
|
runGC: this.garbageCollector.shouldRunGC,
|
|
@@ -2413,7 +2527,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2413
2527
|
|
|
2414
2528
|
let clientSequenceNumber: number;
|
|
2415
2529
|
try {
|
|
2416
|
-
clientSequenceNumber = this.
|
|
2530
|
+
clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
|
|
2417
2531
|
} catch (error) {
|
|
2418
2532
|
return { stage: "upload", ...uploadData, error };
|
|
2419
2533
|
}
|
|
@@ -2472,7 +2586,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2472
2586
|
}
|
|
2473
2587
|
}
|
|
2474
2588
|
|
|
2589
|
+
private hasPendingMessages() {
|
|
2590
|
+
return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2475
2593
|
private updateDocumentDirtyState(dirty: boolean) {
|
|
2594
|
+
if (this.attachState !== AttachState.Attached) {
|
|
2595
|
+
assert(dirty, 0x3d2 /* Non-attached container is dirty */);
|
|
2596
|
+
} else {
|
|
2597
|
+
// Other way is not true = see this.isContainerMessageDirtyable()
|
|
2598
|
+
assert(!dirty || this.hasPendingMessages(),
|
|
2599
|
+
0x3d3 /* if doc is dirty, there has to be pending ops */);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2476
2602
|
if (this.dirtyContainer === dirty) {
|
|
2477
2603
|
return;
|
|
2478
2604
|
}
|
|
@@ -2511,104 +2637,117 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2511
2637
|
|
|
2512
2638
|
private submit(
|
|
2513
2639
|
type: ContainerMessageType,
|
|
2514
|
-
|
|
2640
|
+
contents: any,
|
|
2515
2641
|
localOpMetadata: unknown = undefined,
|
|
2516
|
-
|
|
2642
|
+
metadata: Record<string, unknown> | undefined = undefined,
|
|
2517
2643
|
): void {
|
|
2518
2644
|
this.verifyNotClosed();
|
|
2519
2645
|
|
|
2520
2646
|
// There should be no ops in detached container state!
|
|
2521
2647
|
assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
2522
2648
|
|
|
2523
|
-
|
|
2524
|
-
|
|
2649
|
+
const deserializedContent: ContainerRuntimeMessage = { type, contents };
|
|
2650
|
+
const serializedContent = JSON.stringify(deserializedContent);
|
|
2525
2651
|
|
|
2526
|
-
if (this.
|
|
2527
|
-
|
|
2652
|
+
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
2653
|
+
this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
|
|
2654
|
+
}
|
|
2528
2655
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2656
|
+
const message: BatchMessage = {
|
|
2657
|
+
contents: serializedContent,
|
|
2658
|
+
deserializedContent,
|
|
2659
|
+
metadata,
|
|
2660
|
+
localOpMetadata,
|
|
2661
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
try {
|
|
2665
|
+
// If this is attach message for new data store, and we are in a batch, send this op out of order
|
|
2666
|
+
// Is it safe:
|
|
2667
|
+
// Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
|
|
2668
|
+
// They become visible only when aliased, or handle to some sub-element of newly created datastore
|
|
2669
|
+
// is stored in some DDS, i.e. only after some other op.
|
|
2670
|
+
// Why:
|
|
2671
|
+
// Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
|
|
2672
|
+
// stores are created, causing issues like relay service throttling (too many ops) and catastrophic
|
|
2673
|
+
// failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
|
|
2674
|
+
// these issues.
|
|
2675
|
+
// Cons:
|
|
2676
|
+
// 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
|
|
2677
|
+
// This change creates new possibility of a lot of newly created data stores never being referenced
|
|
2678
|
+
// because client died before it had a change to submit the rest of the ops. This will create more
|
|
2679
|
+
// garbage that needs to be collected leveraging GC (Garbage Collection) feature.
|
|
2680
|
+
// 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
|
|
2681
|
+
// today as rollback can't undo creation of data store. To some extent not sending them is a bigger
|
|
2682
|
+
// issue than sending.
|
|
2683
|
+
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
2684
|
+
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
2685
|
+
if (type === ContainerMessageType.Attach &&
|
|
2686
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
2687
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
2688
|
+
// BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
|
|
2689
|
+
// when queue is not empty.
|
|
2690
|
+
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
2691
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
2692
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
2693
|
+
throw new GenericError(
|
|
2694
|
+
"BatchTooLarge",
|
|
2695
|
+
/* error */ undefined,
|
|
2696
|
+
{
|
|
2697
|
+
opSize: message.contents.length,
|
|
2698
|
+
count: this.pendingAttachBatch.length,
|
|
2699
|
+
limit: this.pendingAttachBatch.limit,
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
} else {
|
|
2704
|
+
if (!this.pendingBatch.push(message)) {
|
|
2705
|
+
throw new GenericError(
|
|
2706
|
+
"BatchTooLarge",
|
|
2707
|
+
/* error */ undefined,
|
|
2708
|
+
{
|
|
2709
|
+
opSize: message.contents.length,
|
|
2710
|
+
count: this.pendingBatch.length,
|
|
2711
|
+
limit: this.pendingBatch.limit,
|
|
2712
|
+
});
|
|
2544
2713
|
}
|
|
2545
2714
|
}
|
|
2546
2715
|
|
|
2547
|
-
if (
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
//
|
|
2552
|
-
//
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
length: serializedContent.length,
|
|
2558
|
-
limit: defaultMaxOpSizeInBytes,
|
|
2559
|
-
}));
|
|
2560
|
-
clientSequenceNumber = -1;
|
|
2716
|
+
if (this._flushMode !== FlushMode.TurnBased) {
|
|
2717
|
+
this.flush();
|
|
2718
|
+
} else if (!this.flushTrigger) {
|
|
2719
|
+
this.flushTrigger = true;
|
|
2720
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
2721
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
2722
|
+
Promise.resolve().then(() => {
|
|
2723
|
+
this.flushTrigger = false;
|
|
2724
|
+
this.flush();
|
|
2725
|
+
});
|
|
2561
2726
|
}
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
this.closeFn(error as GenericError);
|
|
2729
|
+
throw error;
|
|
2562
2730
|
}
|
|
2563
2731
|
|
|
2564
|
-
|
|
2565
|
-
this.pendingStateManager.onSubmitMessage(
|
|
2566
|
-
type,
|
|
2567
|
-
clientSequenceNumber,
|
|
2568
|
-
this.deltaManager.lastSequenceNumber,
|
|
2569
|
-
content,
|
|
2570
|
-
localOpMetadata,
|
|
2571
|
-
opMetadataInternal,
|
|
2572
|
-
);
|
|
2573
|
-
if (this.isContainerMessageDirtyable(type, content)) {
|
|
2732
|
+
if (this.isContainerMessageDirtyable(type, contents)) {
|
|
2574
2733
|
this.updateDocumentDirtyState(true);
|
|
2575
2734
|
}
|
|
2576
2735
|
}
|
|
2577
2736
|
|
|
2578
|
-
private
|
|
2579
|
-
type: MessageType,
|
|
2580
|
-
contents: any) {
|
|
2737
|
+
private submitSummaryMessage(contents: ISummaryContent) {
|
|
2581
2738
|
this.verifyNotClosed();
|
|
2582
2739
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
2583
2740
|
|
|
2584
2741
|
// System message should not be sent in the middle of the batch.
|
|
2585
|
-
|
|
2586
|
-
// That might be not what caller hopes to get, but we can look deeper if telemetry tells us it's a problem.
|
|
2587
|
-
const middleOfBatch = this.flushMode === FlushMode.TurnBased && this.needsFlush;
|
|
2588
|
-
if (middleOfBatch) {
|
|
2589
|
-
this.mc.logger.sendErrorEvent({ eventName: "submitSystemMessageError", type });
|
|
2590
|
-
}
|
|
2742
|
+
assert(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
|
|
2591
2743
|
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
contents
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
type: ContainerMessageType,
|
|
2600
|
-
contents: any,
|
|
2601
|
-
batch: boolean,
|
|
2602
|
-
appData?: any,
|
|
2603
|
-
) {
|
|
2604
|
-
this.verifyNotClosed();
|
|
2605
|
-
assert(this.connected, 0x259 /* "Container disconnected when trying to submit system message" */);
|
|
2606
|
-
const payload: ContainerRuntimeMessage = { type, contents };
|
|
2607
|
-
return this.context.submitFn(
|
|
2608
|
-
MessageType.Operation,
|
|
2609
|
-
payload,
|
|
2610
|
-
batch,
|
|
2611
|
-
appData);
|
|
2744
|
+
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
2745
|
+
return this.context.submitSummaryFn !== undefined
|
|
2746
|
+
? this.context.submitSummaryFn(contents)
|
|
2747
|
+
: this.context.submitFn(
|
|
2748
|
+
MessageType.Summarize,
|
|
2749
|
+
contents,
|
|
2750
|
+
false);
|
|
2612
2751
|
}
|
|
2613
2752
|
|
|
2614
2753
|
/**
|
|
@@ -2680,20 +2819,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2680
2819
|
summaryLogger: ITelemetryLogger,
|
|
2681
2820
|
) {
|
|
2682
2821
|
const readAndParseBlob = async <T>(id: string) => readAndParse<T>(this.storage, id);
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2822
|
+
// The call to fetch the snapshot is very expensive and not always needed.
|
|
2823
|
+
// It should only be done by the summarizerNode, if required.
|
|
2824
|
+
const snapshotTreeFetcher = async () => {
|
|
2825
|
+
const fetchResult = await this.fetchSnapshotFromStorage(
|
|
2688
2826
|
ackHandle,
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2827
|
+
summaryLogger,
|
|
2828
|
+
{
|
|
2829
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2830
|
+
ackHandle,
|
|
2831
|
+
summaryRefSeq,
|
|
2832
|
+
fetchLatest: false,
|
|
2833
|
+
});
|
|
2834
|
+
return fetchResult.snapshotTree;
|
|
2835
|
+
};
|
|
2693
2836
|
const result = await this.summarizerNode.refreshLatestSummary(
|
|
2694
2837
|
proposalHandle,
|
|
2695
2838
|
summaryRefSeq,
|
|
2696
|
-
|
|
2839
|
+
snapshotTreeFetcher,
|
|
2697
2840
|
readAndParseBlob,
|
|
2698
2841
|
summaryLogger,
|
|
2699
2842
|
);
|
|
@@ -2785,6 +2928,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2785
2928
|
throw new UsageError("can't get state when offline load disabled");
|
|
2786
2929
|
}
|
|
2787
2930
|
|
|
2931
|
+
// Flush pending batch.
|
|
2932
|
+
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
2933
|
+
// to close current batch.
|
|
2934
|
+
this.flush();
|
|
2935
|
+
|
|
2788
2936
|
const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
2789
2937
|
if (previousPendingState) {
|
|
2790
2938
|
return {
|
|
@@ -2874,6 +3022,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2874
3022
|
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
2875
3023
|
// don't have any more saved ops
|
|
2876
3024
|
await this.pendingStateManager.applyStashedOpsAt();
|
|
3025
|
+
|
|
3026
|
+
// If it's not the case, we should take it into account when calculating dirty state.
|
|
3027
|
+
assert(this.context.attachState === AttachState.Attached,
|
|
3028
|
+
0x3d5 /* this function is called for attached containers only */);
|
|
3029
|
+
if (!this.hasPendingMessages()) {
|
|
3030
|
+
this.updateDocumentDirtyState(false);
|
|
3031
|
+
}
|
|
2877
3032
|
}
|
|
2878
3033
|
|
|
2879
3034
|
private validateSummaryHeuristicConfiguration(configuration: ISummaryConfigurationHeuristics) {
|