@fluidframework/container-runtime 2.5.0-302463 → 2.5.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/CHANGELOG.md +18 -0
- package/api-report/container-runtime.legacy.alpha.api.md +3 -1
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +3 -3
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +1 -1
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +20 -5
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +185 -129
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +14 -4
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +138 -55
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +15 -3
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +48 -19
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +6 -14
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +5 -6
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +23 -22
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +2 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +3 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +9 -0
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +2 -0
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +11 -0
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +43 -28
- package/dist/summary/documentSchema.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +3 -3
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +1 -1
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +20 -5
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +186 -130
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +14 -4
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +137 -54
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +15 -3
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +48 -19
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +7 -15
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +5 -6
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +23 -22
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +2 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +3 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +9 -0
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +2 -0
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +11 -0
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +43 -28
- package/lib/summary/documentSchema.js.map +1 -1
- package/package.json +23 -19
- package/src/blobManager/blobManager.ts +2 -2
- package/src/channelCollection.ts +234 -176
- package/src/containerRuntime.ts +179 -68
- package/src/dataStoreContext.ts +66 -23
- package/src/dataStoreContexts.ts +7 -20
- package/src/gc/garbageCollection.ts +32 -32
- package/src/gc/gcDefinitions.ts +3 -3
- package/src/opLifecycle/outbox.ts +12 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +3 -0
- package/src/packageVersion.ts +1 -1
- package/src/summary/documentSchema.ts +56 -37
package/src/containerRuntime.ts
CHANGED
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
channelsTreeName,
|
|
90
90
|
gcTreeKey,
|
|
91
91
|
IInboundSignalMessage,
|
|
92
|
+
type IRuntimeMessagesContent,
|
|
92
93
|
} from "@fluidframework/runtime-definitions/internal";
|
|
93
94
|
import {
|
|
94
95
|
GCDataBuilder,
|
|
@@ -160,6 +161,7 @@ import {
|
|
|
160
161
|
IGCStats,
|
|
161
162
|
IGarbageCollector,
|
|
162
163
|
gcGenerationOptionName,
|
|
164
|
+
type GarbageCollectionMessage,
|
|
163
165
|
} from "./gc/index.js";
|
|
164
166
|
import {
|
|
165
167
|
ContainerMessageType,
|
|
@@ -202,6 +204,7 @@ import {
|
|
|
202
204
|
IConnectableRuntime,
|
|
203
205
|
IContainerRuntimeMetadata,
|
|
204
206
|
ICreateContainerMetadata,
|
|
207
|
+
type IDocumentSchemaChangeMessage,
|
|
205
208
|
type IDocumentSchemaCurrent,
|
|
206
209
|
IEnqueueSummarizeOptions,
|
|
207
210
|
IGenerateSummaryTreeResult,
|
|
@@ -558,6 +561,13 @@ export interface RuntimeHeaderData {
|
|
|
558
561
|
allowTombstone?: boolean;
|
|
559
562
|
}
|
|
560
563
|
|
|
564
|
+
/** Default values for Runtime Headers */
|
|
565
|
+
export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
|
|
566
|
+
wait: true,
|
|
567
|
+
viaHandle: false,
|
|
568
|
+
allowTombstone: false,
|
|
569
|
+
};
|
|
570
|
+
|
|
561
571
|
/**
|
|
562
572
|
* Available compression algorithms for op compression.
|
|
563
573
|
* @legacy
|
|
@@ -2804,6 +2814,9 @@ export class ContainerRuntime
|
|
|
2804
2814
|
local,
|
|
2805
2815
|
savedOp,
|
|
2806
2816
|
runtimeBatch,
|
|
2817
|
+
inboundResult.type === "fullBatch"
|
|
2818
|
+
? inboundResult.groupedBatch
|
|
2819
|
+
: false /* groupedBatch */,
|
|
2807
2820
|
);
|
|
2808
2821
|
} else {
|
|
2809
2822
|
if (!runtimeBatch) {
|
|
@@ -2823,6 +2836,7 @@ export class ContainerRuntime
|
|
|
2823
2836
|
local,
|
|
2824
2837
|
savedOp,
|
|
2825
2838
|
runtimeBatch,
|
|
2839
|
+
false /* groupedBatch */,
|
|
2826
2840
|
);
|
|
2827
2841
|
}
|
|
2828
2842
|
|
|
@@ -2838,14 +2852,15 @@ export class ContainerRuntime
|
|
|
2838
2852
|
|
|
2839
2853
|
/**
|
|
2840
2854
|
* Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
|
|
2841
|
-
* @param
|
|
2855
|
+
* @param messagesWithMetadata - messages to process along with their metadata.
|
|
2842
2856
|
* @param locationInBatch - Are we processing the start and/or end of a batch?
|
|
2843
2857
|
* @param local - true if the messages were originally generated by the client receiving it.
|
|
2844
2858
|
* @param savedOp - true if the message is a replayed saved op.
|
|
2845
2859
|
* @param runtimeBatch - true if these are runtime messages.
|
|
2860
|
+
* @param groupedBatch - true if these messages are part of a grouped op batch.
|
|
2846
2861
|
*/
|
|
2847
2862
|
private processInboundMessages(
|
|
2848
|
-
|
|
2863
|
+
messagesWithMetadata: {
|
|
2849
2864
|
message: ISequencedDocumentMessage;
|
|
2850
2865
|
localOpMetadata?: unknown;
|
|
2851
2866
|
}[],
|
|
@@ -2853,35 +2868,110 @@ export class ContainerRuntime
|
|
|
2853
2868
|
local: boolean,
|
|
2854
2869
|
savedOp: boolean | undefined,
|
|
2855
2870
|
runtimeBatch: boolean,
|
|
2871
|
+
groupedBatch: boolean,
|
|
2856
2872
|
) {
|
|
2857
2873
|
if (locationInBatch.batchStart) {
|
|
2858
|
-
const firstMessage =
|
|
2874
|
+
const firstMessage = messagesWithMetadata[0]?.message;
|
|
2859
2875
|
assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
2860
2876
|
this.scheduleManager.batchBegin(firstMessage);
|
|
2861
2877
|
}
|
|
2862
2878
|
|
|
2863
2879
|
let error: unknown;
|
|
2864
2880
|
try {
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
this.
|
|
2869
|
-
|
|
2881
|
+
if (!runtimeBatch) {
|
|
2882
|
+
messagesWithMetadata.forEach(({ message }) => {
|
|
2883
|
+
this.ensureNoDataModelChanges(() => {
|
|
2884
|
+
this.observeNonRuntimeMessage(message);
|
|
2885
|
+
});
|
|
2886
|
+
});
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// Helper that updates a message's minimum sequence number to the minimum sequence number that container
|
|
2891
|
+
// runtime is tracking and sets _processedClientSequenceNumber. It returns the updated message.
|
|
2892
|
+
const updateSequenceNumbers = (message: ISequencedDocumentMessage) => {
|
|
2893
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2894
|
+
message.minimumSequenceNumber =
|
|
2895
|
+
this.useDeltaManagerOpsProxy &&
|
|
2896
|
+
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
|
|
2897
|
+
? this.deltaManager.minimumSequenceNumber
|
|
2898
|
+
: message.minimumSequenceNumber;
|
|
2899
|
+
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2900
|
+
return message as InboundSequencedContainerRuntimeMessage;
|
|
2901
|
+
};
|
|
2902
|
+
|
|
2903
|
+
// Non-grouped batch messages are processed one at a time.
|
|
2904
|
+
if (!groupedBatch) {
|
|
2905
|
+
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2906
|
+
updateSequenceNumbers(message);
|
|
2907
|
+
this.ensureNoDataModelChanges(() => {
|
|
2908
|
+
this.validateAndProcessRuntimeMessages(
|
|
2909
|
+
message as InboundSequencedContainerRuntimeMessage,
|
|
2910
|
+
[
|
|
2911
|
+
{
|
|
2912
|
+
contents: message.contents,
|
|
2913
|
+
localOpMetadata,
|
|
2914
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
2915
|
+
},
|
|
2916
|
+
],
|
|
2870
2917
|
local,
|
|
2871
2918
|
savedOp,
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2919
|
+
);
|
|
2920
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
let bunchedMessagesContent: IRuntimeMessagesContent[] = [];
|
|
2927
|
+
let previousMessage: InboundSequencedContainerRuntimeMessage | undefined;
|
|
2928
|
+
|
|
2929
|
+
// Helper that processes the previous bunch of messages.
|
|
2930
|
+
const sendBunchedMessages = () => {
|
|
2931
|
+
assert(previousMessage !== undefined, 0xa67 /* previous message must exist */);
|
|
2932
|
+
this.ensureNoDataModelChanges(() => {
|
|
2933
|
+
this.validateAndProcessRuntimeMessages(
|
|
2934
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2935
|
+
previousMessage!,
|
|
2936
|
+
bunchedMessagesContent,
|
|
2937
|
+
local,
|
|
2938
|
+
savedOp,
|
|
2939
|
+
);
|
|
2877
2940
|
});
|
|
2878
|
-
|
|
2941
|
+
bunchedMessagesContent = [];
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2944
|
+
/**
|
|
2945
|
+
* For grouped batch messages, bunch contiguous messages of the same type and process them together.
|
|
2946
|
+
* This is an optimization mainly for DDSes, where it can process a bunch of ops together. DDSes
|
|
2947
|
+
* like merge tree or shared tree can process ops more efficiently when they are bunched together.
|
|
2948
|
+
*/
|
|
2949
|
+
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2950
|
+
const currentMessage = updateSequenceNumbers(message);
|
|
2951
|
+
if (previousMessage && previousMessage.type !== currentMessage.type) {
|
|
2952
|
+
sendBunchedMessages();
|
|
2953
|
+
}
|
|
2954
|
+
previousMessage = currentMessage;
|
|
2955
|
+
bunchedMessagesContent.push({
|
|
2956
|
+
contents: message.contents,
|
|
2957
|
+
localOpMetadata,
|
|
2958
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// Process the last bunch of messages.
|
|
2963
|
+
sendBunchedMessages();
|
|
2964
|
+
|
|
2965
|
+
// Send the "op" events for the messages now that the ops have been processed.
|
|
2966
|
+
for (const { message } of messagesWithMetadata) {
|
|
2967
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2968
|
+
}
|
|
2879
2969
|
} catch (e) {
|
|
2880
2970
|
error = e;
|
|
2881
2971
|
throw error;
|
|
2882
2972
|
} finally {
|
|
2883
2973
|
if (locationInBatch.batchEnd) {
|
|
2884
|
-
const lastMessage =
|
|
2974
|
+
const lastMessage = messagesWithMetadata[messagesWithMetadata.length - 1]?.message;
|
|
2885
2975
|
assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
|
|
2886
2976
|
this.scheduleManager.batchEnd(error, lastMessage);
|
|
2887
2977
|
}
|
|
@@ -2910,80 +3000,62 @@ export class ContainerRuntime
|
|
|
2910
3000
|
}
|
|
2911
3001
|
|
|
2912
3002
|
/**
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
3003
|
+
* Process runtime messages. The messages here are contiguous messages in a batch.
|
|
3004
|
+
* Assuming the messages in the given bunch are also a TypedContainerRuntimeMessage, checks its type and dispatch
|
|
3005
|
+
* the messages to the appropriate handler in the runtime.
|
|
2915
3006
|
* Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
|
|
3007
|
+
* @param message - The core message with common properties for all the messages.
|
|
3008
|
+
* @param messageContents - The contents, local metadata and clientSequenceNumbers of the messages.
|
|
3009
|
+
* @param local - true if the messages were originally generated by the client receiving it.
|
|
3010
|
+
* @param savedOp - true if the message is a replayed saved op.
|
|
3011
|
+
*
|
|
2916
3012
|
*/
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
local: boolean
|
|
2921
|
-
savedOp?: boolean
|
|
2922
|
-
|
|
2923
|
-
}): void {
|
|
2924
|
-
const { local, message, savedOp, localOpMetadata } = messageWithContext;
|
|
2925
|
-
|
|
2926
|
-
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2927
|
-
if (
|
|
2928
|
-
this.useDeltaManagerOpsProxy &&
|
|
2929
|
-
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
|
|
2930
|
-
) {
|
|
2931
|
-
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2935
|
-
|
|
3013
|
+
private validateAndProcessRuntimeMessages(
|
|
3014
|
+
message: Omit<InboundSequencedContainerRuntimeMessage, "contents">,
|
|
3015
|
+
messagesContent: IRuntimeMessagesContent[],
|
|
3016
|
+
local: boolean,
|
|
3017
|
+
savedOp?: boolean,
|
|
3018
|
+
): void {
|
|
2936
3019
|
// If there are no more pending messages after processing a local message,
|
|
2937
3020
|
// the document is no longer dirty.
|
|
2938
3021
|
if (!this.hasPendingMessages()) {
|
|
2939
3022
|
this.updateDocumentDirtyState(false);
|
|
2940
3023
|
}
|
|
2941
3024
|
|
|
3025
|
+
// Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
|
|
3026
|
+
const contents = messagesContent.map((c) => c.contents);
|
|
3027
|
+
|
|
2942
3028
|
switch (message.type) {
|
|
3029
|
+
case ContainerMessageType.FluidDataStoreOp:
|
|
2943
3030
|
case ContainerMessageType.Attach:
|
|
2944
3031
|
case ContainerMessageType.Alias:
|
|
2945
|
-
|
|
2946
|
-
|
|
3032
|
+
// Remove the metadata from the message before sending it to the channel collection. The metadata
|
|
3033
|
+
// is added by the container runtime and is not part of the message that the channel collection and
|
|
3034
|
+
// layers below it expect.
|
|
3035
|
+
this.channelCollection.processMessages({ envelope: message, messagesContent, local });
|
|
2947
3036
|
break;
|
|
2948
3037
|
case ContainerMessageType.BlobAttach:
|
|
2949
|
-
this.blobManager.
|
|
3038
|
+
this.blobManager.processBlobAttachMessage(message, local);
|
|
2950
3039
|
break;
|
|
2951
3040
|
case ContainerMessageType.IdAllocation:
|
|
2952
|
-
|
|
2953
|
-
// stashed ops flow. The compressor is stashed with these ops already processed.
|
|
2954
|
-
// That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
|
|
2955
|
-
// thus we need to process all the ops.
|
|
2956
|
-
if (!(this.skipSavedCompressorOps && savedOp === true)) {
|
|
2957
|
-
const range = message.contents;
|
|
2958
|
-
// Some other client turned on the id compressor. If we have not turned it on,
|
|
2959
|
-
// put it in a pending queue and delay finalization.
|
|
2960
|
-
if (this._idCompressor === undefined) {
|
|
2961
|
-
assert(
|
|
2962
|
-
this.idCompressorMode !== undefined,
|
|
2963
|
-
0x93c /* id compressor should be enabled */,
|
|
2964
|
-
);
|
|
2965
|
-
this.pendingIdCompressorOps.push(range);
|
|
2966
|
-
} else {
|
|
2967
|
-
assert(
|
|
2968
|
-
this.pendingIdCompressorOps.length === 0,
|
|
2969
|
-
0x979 /* there should be no pending ops! */,
|
|
2970
|
-
);
|
|
2971
|
-
this._idCompressor.finalizeCreationRange(range);
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
3041
|
+
this.processIdCompressorMessages(contents as IdCreationRange[], savedOp);
|
|
2974
3042
|
break;
|
|
2975
3043
|
case ContainerMessageType.GC:
|
|
2976
|
-
this.garbageCollector.
|
|
3044
|
+
this.garbageCollector.processMessages(
|
|
3045
|
+
contents as GarbageCollectionMessage[],
|
|
3046
|
+
message.timestamp,
|
|
3047
|
+
local,
|
|
3048
|
+
);
|
|
2977
3049
|
break;
|
|
2978
3050
|
case ContainerMessageType.ChunkedOp:
|
|
2979
|
-
// From observability POV, we should not
|
|
3051
|
+
// From observability POV, we should not expose the rest of the system (including "op" events on object) to these messages.
|
|
2980
3052
|
// Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
|
|
2981
3053
|
assert(false, 0x93d /* should not even get here */);
|
|
2982
3054
|
case ContainerMessageType.Rejoin:
|
|
2983
3055
|
break;
|
|
2984
3056
|
case ContainerMessageType.DocumentSchemaChange:
|
|
2985
|
-
this.documentsSchemaController.
|
|
2986
|
-
|
|
3057
|
+
this.documentsSchemaController.processDocumentSchemaMessages(
|
|
3058
|
+
contents as IDocumentSchemaChangeMessage[],
|
|
2987
3059
|
local,
|
|
2988
3060
|
message.sequenceNumber,
|
|
2989
3061
|
);
|
|
@@ -2992,14 +3064,38 @@ export class ContainerRuntime
|
|
|
2992
3064
|
const error = getUnknownMessageTypeError(
|
|
2993
3065
|
message.type,
|
|
2994
3066
|
"validateAndProcessRuntimeMessage" /* codePath */,
|
|
2995
|
-
message,
|
|
3067
|
+
message as ISequencedDocumentMessage,
|
|
2996
3068
|
);
|
|
2997
3069
|
this.closeFn(error);
|
|
2998
3070
|
throw error;
|
|
2999
3071
|
}
|
|
3000
3072
|
}
|
|
3073
|
+
}
|
|
3001
3074
|
|
|
3002
|
-
|
|
3075
|
+
private processIdCompressorMessages(messageContents: IdCreationRange[], savedOp?: boolean) {
|
|
3076
|
+
for (const range of messageContents) {
|
|
3077
|
+
// Don't re-finalize the range if we're processing a "savedOp" in
|
|
3078
|
+
// stashed ops flow. The compressor is stashed with these ops already processed.
|
|
3079
|
+
// That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
|
|
3080
|
+
// thus we need to process all the ops.
|
|
3081
|
+
if (!(this.skipSavedCompressorOps && savedOp === true)) {
|
|
3082
|
+
// Some other client turned on the id compressor. If we have not turned it on,
|
|
3083
|
+
// put it in a pending queue and delay finalization.
|
|
3084
|
+
if (this._idCompressor === undefined) {
|
|
3085
|
+
assert(
|
|
3086
|
+
this.idCompressorMode !== undefined,
|
|
3087
|
+
0x93c /* id compressor should be enabled */,
|
|
3088
|
+
);
|
|
3089
|
+
this.pendingIdCompressorOps.push(range);
|
|
3090
|
+
} else {
|
|
3091
|
+
assert(
|
|
3092
|
+
this.pendingIdCompressorOps.length === 0,
|
|
3093
|
+
0x979 /* there should be no pending ops! */,
|
|
3094
|
+
);
|
|
3095
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3003
3099
|
}
|
|
3004
3100
|
|
|
3005
3101
|
/**
|
|
@@ -3780,7 +3876,22 @@ export class ContainerRuntime
|
|
|
3780
3876
|
},
|
|
3781
3877
|
});
|
|
3782
3878
|
|
|
3783
|
-
assert
|
|
3879
|
+
// legacy: assert 0x3d1
|
|
3880
|
+
if (!this.outbox.isEmpty) {
|
|
3881
|
+
throw DataProcessingError.create(
|
|
3882
|
+
"Can't trigger summary in the middle of a batch",
|
|
3883
|
+
"submitSummary",
|
|
3884
|
+
undefined,
|
|
3885
|
+
{
|
|
3886
|
+
summaryNumber,
|
|
3887
|
+
pendingMessages: this.pendingMessagesCount,
|
|
3888
|
+
outboxLength: this.outbox.messageCount,
|
|
3889
|
+
mainBatchLength: this.outbox.mainBatchMessageCount,
|
|
3890
|
+
blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
|
|
3891
|
+
idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
|
|
3892
|
+
},
|
|
3893
|
+
);
|
|
3894
|
+
}
|
|
3784
3895
|
|
|
3785
3896
|
// If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
|
|
3786
3897
|
// and it may even be incorrect. So, wait for the container to be saved with a timeout. If the container is not
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
SummarizeInternalFn,
|
|
54
54
|
channelsTreeName,
|
|
55
55
|
IInboundSignalMessage,
|
|
56
|
+
type IPendingMessagesState,
|
|
57
|
+
type IRuntimeMessageCollection,
|
|
56
58
|
} from "@fluidframework/runtime-definitions/internal";
|
|
57
59
|
import {
|
|
58
60
|
addBlobToSummary,
|
|
@@ -313,7 +315,7 @@ export abstract class FluidDataStoreContext
|
|
|
313
315
|
* Returns the count of pending messages that are stored until the data store is realized.
|
|
314
316
|
*/
|
|
315
317
|
public get pendingCount(): number {
|
|
316
|
-
return this.
|
|
318
|
+
return this.pendingMessagesState?.pendingCount ?? 0;
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
protected registry: IFluidDataStoreRegistry | undefined;
|
|
@@ -321,7 +323,11 @@ export abstract class FluidDataStoreContext
|
|
|
321
323
|
protected detachedRuntimeCreation = false;
|
|
322
324
|
protected channel: IFluidDataStoreChannel | undefined;
|
|
323
325
|
private loaded = false;
|
|
324
|
-
|
|
326
|
+
/** Tracks the messages for this data store that are sent while it's not loaded */
|
|
327
|
+
private pendingMessagesState: IPendingMessagesState | undefined = {
|
|
328
|
+
messageCollections: [],
|
|
329
|
+
pendingCount: 0,
|
|
330
|
+
};
|
|
325
331
|
protected channelP: Promise<IFluidDataStoreChannel> | undefined;
|
|
326
332
|
protected _baseSnapshot: ISnapshotTree | undefined;
|
|
327
333
|
protected _attachState: AttachState;
|
|
@@ -561,25 +567,61 @@ export abstract class FluidDataStoreContext
|
|
|
561
567
|
this.channel!.setConnectionState(connected, clientId);
|
|
562
568
|
}
|
|
563
569
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
570
|
+
/**
|
|
571
|
+
* back-compat ADO 21575: This is temporary and will be removed once the compat requirement across Runtime and
|
|
572
|
+
* Datastore boundary is satisfied.
|
|
573
|
+
* Process the messages to maintain backwards compatibility. The `processMessages` function is added to
|
|
574
|
+
* IFluidDataStoreChannel in 2.5.0. For channels before that, call `process` for each message.
|
|
575
|
+
*/
|
|
576
|
+
private processMessagesCompat(
|
|
577
|
+
channel: IFluidDataStoreChannel,
|
|
578
|
+
messageCollection: IRuntimeMessageCollection,
|
|
579
|
+
) {
|
|
580
|
+
if (channel.processMessages !== undefined) {
|
|
581
|
+
channel.processMessages(messageCollection);
|
|
582
|
+
} else {
|
|
583
|
+
const { envelope, messagesContent, local } = messageCollection;
|
|
584
|
+
for (const { contents, localOpMetadata, clientSequenceNumber } of messagesContent) {
|
|
585
|
+
channel.process(
|
|
586
|
+
{ ...envelope, contents, clientSequenceNumber },
|
|
587
|
+
local,
|
|
588
|
+
localOpMetadata,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Process messages for this data store. The messages here are contiguous messages for this data store in a batch.
|
|
596
|
+
* @param messageCollection - The collection of messages to process.
|
|
597
|
+
*/
|
|
598
|
+
public processMessages(messageCollection: IRuntimeMessageCollection): void {
|
|
599
|
+
const { envelope, messagesContent, local } = messageCollection;
|
|
600
|
+
const safeTelemetryProps = extractSafePropertiesFromMessage(envelope);
|
|
601
|
+
// Tombstone error is logged in garbage collector. So, set "checkTombstone" to false when calling
|
|
571
602
|
// "verifyNotClosed" which logs tombstone errors.
|
|
572
603
|
this.verifyNotClosed("process", false /* checkTombstone */, safeTelemetryProps);
|
|
573
604
|
|
|
574
|
-
this.summarizerNode.recordChange(
|
|
605
|
+
this.summarizerNode.recordChange(envelope as ISequencedDocumentMessage);
|
|
575
606
|
|
|
576
607
|
if (this.loaded) {
|
|
577
|
-
|
|
608
|
+
assert(this.channel !== undefined, 0xa68 /* Channel is not loaded */);
|
|
609
|
+
this.processMessagesCompat(this.channel, messageCollection);
|
|
578
610
|
} else {
|
|
579
611
|
assert(!local, 0x142 /* "local store channel is not loaded" */);
|
|
580
|
-
assert(
|
|
581
|
-
|
|
582
|
-
|
|
612
|
+
assert(
|
|
613
|
+
this.pendingMessagesState !== undefined,
|
|
614
|
+
0xa69 /* pending messages queue is undefined */,
|
|
615
|
+
);
|
|
616
|
+
this.pendingMessagesState.messageCollections.push({
|
|
617
|
+
...messageCollection,
|
|
618
|
+
messagesContent: Array.from(messagesContent),
|
|
619
|
+
});
|
|
620
|
+
this.pendingMessagesState.pendingCount += messagesContent.length;
|
|
621
|
+
this.thresholdOpsCounter.sendIfMultiple(
|
|
622
|
+
"StorePendingOps",
|
|
623
|
+
this.pendingMessagesState.pendingCount,
|
|
624
|
+
);
|
|
583
625
|
}
|
|
584
626
|
}
|
|
585
627
|
|
|
@@ -788,20 +830,21 @@ export abstract class FluidDataStoreContext
|
|
|
788
830
|
}
|
|
789
831
|
|
|
790
832
|
protected processPendingOps(channel: IFluidDataStoreChannel) {
|
|
791
|
-
|
|
792
|
-
const pending = this.pending!;
|
|
833
|
+
const baseSequenceNumber = this.baseSnapshotSequenceNumber ?? -1;
|
|
793
834
|
|
|
794
|
-
|
|
795
|
-
|
|
835
|
+
assert(
|
|
836
|
+
this.pendingMessagesState !== undefined,
|
|
837
|
+
0xa6a /* pending messages queue is undefined */,
|
|
838
|
+
);
|
|
839
|
+
for (const messageCollection of this.pendingMessagesState.messageCollections) {
|
|
796
840
|
// Only process ops whose seq number is greater than snapshot sequence number from which it loaded.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
channel.process(op, false, undefined /* localOpMetadata */);
|
|
841
|
+
if (messageCollection.envelope.sequenceNumber > baseSequenceNumber) {
|
|
842
|
+
this.processMessagesCompat(channel, messageCollection);
|
|
800
843
|
}
|
|
801
844
|
}
|
|
802
|
-
this.pending = undefined;
|
|
803
845
|
|
|
804
|
-
this.thresholdOpsCounter.send("ProcessPendingOps",
|
|
846
|
+
this.thresholdOpsCounter.send("ProcessPendingOps", this.pendingMessagesState.pendingCount);
|
|
847
|
+
this.pendingMessagesState = undefined;
|
|
805
848
|
}
|
|
806
849
|
|
|
807
850
|
protected completeBindingRuntime(channel: IFluidDataStoreChannel) {
|
package/src/dataStoreContexts.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { IDisposable, ITelemetryBaseLogger } from "@fluidframework/core-interfac
|
|
|
7
7
|
import { assert, Deferred, Lazy } from "@fluidframework/core-utils/internal";
|
|
8
8
|
import {
|
|
9
9
|
ITelemetryLoggerExt,
|
|
10
|
-
PerformanceEvent,
|
|
11
10
|
createChildLogger,
|
|
12
11
|
} from "@fluidframework/telemetry-utils/internal";
|
|
13
12
|
|
|
@@ -41,7 +40,7 @@ export class DataStoreContexts
|
|
|
41
40
|
.catch((contextError) => {
|
|
42
41
|
this._logger.sendErrorEvent(
|
|
43
42
|
{
|
|
44
|
-
eventName: "
|
|
43
|
+
eventName: "FluidDataStoreContextDisposeError",
|
|
45
44
|
fluidDataStoreId,
|
|
46
45
|
},
|
|
47
46
|
contextError,
|
|
@@ -53,10 +52,7 @@ export class DataStoreContexts
|
|
|
53
52
|
private readonly _logger: ITelemetryLoggerExt;
|
|
54
53
|
|
|
55
54
|
constructor(baseLogger: ITelemetryBaseLogger) {
|
|
56
|
-
this._logger = createChildLogger({
|
|
57
|
-
namespace: "FluidDataStoreContexts",
|
|
58
|
-
logger: baseLogger,
|
|
59
|
-
});
|
|
55
|
+
this._logger = createChildLogger({ logger: baseLogger });
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
[Symbol.iterator](): Iterator<[string, FluidDataStoreContext]> {
|
|
@@ -144,20 +140,11 @@ export class DataStoreContexts
|
|
|
144
140
|
): Promise<FluidDataStoreContext | undefined> {
|
|
145
141
|
const deferredContext = this.ensureDeferred(id);
|
|
146
142
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
wait,
|
|
153
|
-
existing,
|
|
154
|
-
},
|
|
155
|
-
async () => (!wait && !existing ? undefined : deferredContext.promise),
|
|
156
|
-
{
|
|
157
|
-
start: true,
|
|
158
|
-
end: true,
|
|
159
|
-
},
|
|
160
|
-
);
|
|
143
|
+
if (!wait && !deferredContext.isCompleted) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return deferredContext.promise;
|
|
161
148
|
}
|
|
162
149
|
|
|
163
150
|
private ensureDeferred(id: string): Deferred<FluidDataStoreContext> {
|
|
@@ -861,44 +861,44 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
861
861
|
}
|
|
862
862
|
|
|
863
863
|
/**
|
|
864
|
-
* Process
|
|
865
|
-
* @param
|
|
866
|
-
* @param messageTimestampMs - The timestamp of the
|
|
864
|
+
* Process GC messages.
|
|
865
|
+
* @param messageContents - The contents of the messages.
|
|
866
|
+
* @param messageTimestampMs - The timestamp of the messages.
|
|
867
867
|
* @param local - Whether it was send by this client.
|
|
868
868
|
*/
|
|
869
|
-
public
|
|
870
|
-
|
|
869
|
+
public processMessages(
|
|
870
|
+
messageContents: GarbageCollectionMessage[],
|
|
871
871
|
messageTimestampMs: number,
|
|
872
872
|
local: boolean,
|
|
873
873
|
) {
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
// In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
|
|
892
|
-
// do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
|
|
893
|
-
this.summaryStateTracker.autoRecovery.requestFullGCOnNextRun();
|
|
874
|
+
for (const gcMessage of messageContents) {
|
|
875
|
+
const gcMessageType = gcMessage.type;
|
|
876
|
+
switch (gcMessageType) {
|
|
877
|
+
case GarbageCollectionMessageType.Sweep: {
|
|
878
|
+
// Delete the nodes whose ids are present in the contents.
|
|
879
|
+
this.deleteSweepReadyNodes(gcMessage.deletedNodeIds);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
case GarbageCollectionMessageType.TombstoneLoaded: {
|
|
883
|
+
// Mark the node as referenced to ensure it isn't Swept
|
|
884
|
+
const tombstonedNodePath = gcMessage.nodePath;
|
|
885
|
+
this.addedOutboundReference(
|
|
886
|
+
"/",
|
|
887
|
+
tombstonedNodePath,
|
|
888
|
+
messageTimestampMs,
|
|
889
|
+
true /* autorecovery */,
|
|
890
|
+
);
|
|
894
891
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
892
|
+
// In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
|
|
893
|
+
// do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
|
|
894
|
+
this.summaryStateTracker.autoRecovery.requestFullGCOnNextRun();
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
default:
|
|
898
|
+
throw DataProcessingError.create(
|
|
899
|
+
`Garbage collection message of unknown type ${gcMessageType}`,
|
|
900
|
+
"processMessage",
|
|
901
|
+
);
|
|
902
902
|
}
|
|
903
903
|
}
|
|
904
904
|
}
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -352,9 +352,9 @@ export interface IGarbageCollector {
|
|
|
352
352
|
timestampMs: number,
|
|
353
353
|
autorecovery?: true,
|
|
354
354
|
): void;
|
|
355
|
-
/** Called to process
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
/** Called to process garbage collection messages */
|
|
356
|
+
processMessages(
|
|
357
|
+
messageContents: GarbageCollectionMessage[],
|
|
358
358
|
messageTimestampMs: number,
|
|
359
359
|
local: boolean,
|
|
360
360
|
): void;
|
|
@@ -145,6 +145,18 @@ export class Outbox {
|
|
|
145
145
|
return this.mainBatch.length + this.blobAttachBatch.length + this.idAllocationBatch.length;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
public get mainBatchMessageCount(): number {
|
|
149
|
+
return this.mainBatch.length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public get blobAttachBatchMessageCount(): number {
|
|
153
|
+
return this.blobAttachBatch.length;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public get idAllocationBatchMessageCount(): number {
|
|
157
|
+
return this.idAllocationBatch.length;
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
public get isEmpty(): boolean {
|
|
149
161
|
return this.messageCount === 0;
|
|
150
162
|
}
|