@fluidframework/container-runtime 2.5.0-302463 → 2.10.0-304831
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 +145 -64
- 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 +144 -63
- 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 +21 -21
- package/src/blobManager/blobManager.ts +2 -2
- package/src/channelCollection.ts +234 -176
- package/src/containerRuntime.ts +189 -79
- 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,25 +2814,18 @@ 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
|
-
if (!runtimeBatch) {
|
|
2810
|
-
// The DeltaManager used to do this, but doesn't anymore as of Loader v2.4
|
|
2811
|
-
// Anyone listening to our "op" event would expect the contents to be parsed per this same logic
|
|
2812
|
-
if (
|
|
2813
|
-
typeof messageCopy.contents === "string" &&
|
|
2814
|
-
messageCopy.contents !== "" &&
|
|
2815
|
-
messageCopy.type !== MessageType.ClientLeave
|
|
2816
|
-
) {
|
|
2817
|
-
messageCopy.contents = JSON.parse(messageCopy.contents);
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
2822
|
this.processInboundMessages(
|
|
2821
2823
|
[{ message: messageCopy, localOpMetadata: undefined }],
|
|
2822
2824
|
{ batchStart: true, batchEnd: true }, // Single message
|
|
2823
2825
|
local,
|
|
2824
2826
|
savedOp,
|
|
2825
2827
|
runtimeBatch,
|
|
2828
|
+
false /* groupedBatch */,
|
|
2826
2829
|
);
|
|
2827
2830
|
}
|
|
2828
2831
|
|
|
@@ -2838,14 +2841,15 @@ export class ContainerRuntime
|
|
|
2838
2841
|
|
|
2839
2842
|
/**
|
|
2840
2843
|
* Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
|
|
2841
|
-
* @param
|
|
2844
|
+
* @param messagesWithMetadata - messages to process along with their metadata.
|
|
2842
2845
|
* @param locationInBatch - Are we processing the start and/or end of a batch?
|
|
2843
2846
|
* @param local - true if the messages were originally generated by the client receiving it.
|
|
2844
2847
|
* @param savedOp - true if the message is a replayed saved op.
|
|
2845
2848
|
* @param runtimeBatch - true if these are runtime messages.
|
|
2849
|
+
* @param groupedBatch - true if these messages are part of a grouped op batch.
|
|
2846
2850
|
*/
|
|
2847
2851
|
private processInboundMessages(
|
|
2848
|
-
|
|
2852
|
+
messagesWithMetadata: {
|
|
2849
2853
|
message: ISequencedDocumentMessage;
|
|
2850
2854
|
localOpMetadata?: unknown;
|
|
2851
2855
|
}[],
|
|
@@ -2853,35 +2857,110 @@ export class ContainerRuntime
|
|
|
2853
2857
|
local: boolean,
|
|
2854
2858
|
savedOp: boolean | undefined,
|
|
2855
2859
|
runtimeBatch: boolean,
|
|
2860
|
+
groupedBatch: boolean,
|
|
2856
2861
|
) {
|
|
2857
2862
|
if (locationInBatch.batchStart) {
|
|
2858
|
-
const firstMessage =
|
|
2863
|
+
const firstMessage = messagesWithMetadata[0]?.message;
|
|
2859
2864
|
assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
2860
2865
|
this.scheduleManager.batchBegin(firstMessage);
|
|
2861
2866
|
}
|
|
2862
2867
|
|
|
2863
2868
|
let error: unknown;
|
|
2864
2869
|
try {
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
this.
|
|
2869
|
-
|
|
2870
|
+
if (!runtimeBatch) {
|
|
2871
|
+
messagesWithMetadata.forEach(({ message }) => {
|
|
2872
|
+
this.ensureNoDataModelChanges(() => {
|
|
2873
|
+
this.observeNonRuntimeMessage(message);
|
|
2874
|
+
});
|
|
2875
|
+
});
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
// Helper that updates a message's minimum sequence number to the minimum sequence number that container
|
|
2880
|
+
// runtime is tracking and sets _processedClientSequenceNumber. It returns the updated message.
|
|
2881
|
+
const updateSequenceNumbers = (message: ISequencedDocumentMessage) => {
|
|
2882
|
+
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2883
|
+
message.minimumSequenceNumber =
|
|
2884
|
+
this.useDeltaManagerOpsProxy &&
|
|
2885
|
+
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
|
|
2886
|
+
? this.deltaManager.minimumSequenceNumber
|
|
2887
|
+
: message.minimumSequenceNumber;
|
|
2888
|
+
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2889
|
+
return message as InboundSequencedContainerRuntimeMessage;
|
|
2890
|
+
};
|
|
2891
|
+
|
|
2892
|
+
// Non-grouped batch messages are processed one at a time.
|
|
2893
|
+
if (!groupedBatch) {
|
|
2894
|
+
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2895
|
+
updateSequenceNumbers(message);
|
|
2896
|
+
this.ensureNoDataModelChanges(() => {
|
|
2897
|
+
this.validateAndProcessRuntimeMessages(
|
|
2898
|
+
message as InboundSequencedContainerRuntimeMessage,
|
|
2899
|
+
[
|
|
2900
|
+
{
|
|
2901
|
+
contents: message.contents,
|
|
2902
|
+
localOpMetadata,
|
|
2903
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
2904
|
+
},
|
|
2905
|
+
],
|
|
2870
2906
|
local,
|
|
2871
2907
|
savedOp,
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2908
|
+
);
|
|
2909
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
let bunchedMessagesContent: IRuntimeMessagesContent[] = [];
|
|
2916
|
+
let previousMessage: InboundSequencedContainerRuntimeMessage | undefined;
|
|
2917
|
+
|
|
2918
|
+
// Helper that processes the previous bunch of messages.
|
|
2919
|
+
const sendBunchedMessages = () => {
|
|
2920
|
+
assert(previousMessage !== undefined, 0xa67 /* previous message must exist */);
|
|
2921
|
+
this.ensureNoDataModelChanges(() => {
|
|
2922
|
+
this.validateAndProcessRuntimeMessages(
|
|
2923
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2924
|
+
previousMessage!,
|
|
2925
|
+
bunchedMessagesContent,
|
|
2926
|
+
local,
|
|
2927
|
+
savedOp,
|
|
2928
|
+
);
|
|
2877
2929
|
});
|
|
2878
|
-
|
|
2930
|
+
bunchedMessagesContent = [];
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
/**
|
|
2934
|
+
* For grouped batch messages, bunch contiguous messages of the same type and process them together.
|
|
2935
|
+
* This is an optimization mainly for DDSes, where it can process a bunch of ops together. DDSes
|
|
2936
|
+
* like merge tree or shared tree can process ops more efficiently when they are bunched together.
|
|
2937
|
+
*/
|
|
2938
|
+
for (const { message, localOpMetadata } of messagesWithMetadata) {
|
|
2939
|
+
const currentMessage = updateSequenceNumbers(message);
|
|
2940
|
+
if (previousMessage && previousMessage.type !== currentMessage.type) {
|
|
2941
|
+
sendBunchedMessages();
|
|
2942
|
+
}
|
|
2943
|
+
previousMessage = currentMessage;
|
|
2944
|
+
bunchedMessagesContent.push({
|
|
2945
|
+
contents: message.contents,
|
|
2946
|
+
localOpMetadata,
|
|
2947
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
// Process the last bunch of messages.
|
|
2952
|
+
sendBunchedMessages();
|
|
2953
|
+
|
|
2954
|
+
// Send the "op" events for the messages now that the ops have been processed.
|
|
2955
|
+
for (const { message } of messagesWithMetadata) {
|
|
2956
|
+
this.emit("op", message, true /* runtimeMessage */);
|
|
2957
|
+
}
|
|
2879
2958
|
} catch (e) {
|
|
2880
2959
|
error = e;
|
|
2881
2960
|
throw error;
|
|
2882
2961
|
} finally {
|
|
2883
2962
|
if (locationInBatch.batchEnd) {
|
|
2884
|
-
const lastMessage =
|
|
2963
|
+
const lastMessage = messagesWithMetadata[messagesWithMetadata.length - 1]?.message;
|
|
2885
2964
|
assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
|
|
2886
2965
|
this.scheduleManager.batchEnd(error, lastMessage);
|
|
2887
2966
|
}
|
|
@@ -2906,84 +2985,76 @@ export class ContainerRuntime
|
|
|
2906
2985
|
this.updateDocumentDirtyState(false);
|
|
2907
2986
|
}
|
|
2908
2987
|
|
|
2988
|
+
// The DeltaManager used to do this, but doesn't anymore as of Loader v2.4
|
|
2989
|
+
// Anyone listening to our "op" event would expect the contents to be parsed per this same logic
|
|
2990
|
+
if (
|
|
2991
|
+
typeof message.contents === "string" &&
|
|
2992
|
+
message.contents !== "" &&
|
|
2993
|
+
message.type !== MessageType.ClientLeave
|
|
2994
|
+
) {
|
|
2995
|
+
message.contents = JSON.parse(message.contents);
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2909
2998
|
this.emit("op", message, false /* runtimeMessage */);
|
|
2910
2999
|
}
|
|
2911
3000
|
|
|
2912
3001
|
/**
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
3002
|
+
* Process runtime messages. The messages here are contiguous messages in a batch.
|
|
3003
|
+
* Assuming the messages in the given bunch are also a TypedContainerRuntimeMessage, checks its type and dispatch
|
|
3004
|
+
* the messages to the appropriate handler in the runtime.
|
|
2915
3005
|
* Throws a DataProcessingError if the message looks like but doesn't conform to a known TypedContainerRuntimeMessage type.
|
|
3006
|
+
* @param message - The core message with common properties for all the messages.
|
|
3007
|
+
* @param messageContents - The contents, local metadata and clientSequenceNumbers of the messages.
|
|
3008
|
+
* @param local - true if the messages were originally generated by the client receiving it.
|
|
3009
|
+
* @param savedOp - true if the message is a replayed saved op.
|
|
3010
|
+
*
|
|
2916
3011
|
*/
|
|
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
|
-
|
|
3012
|
+
private validateAndProcessRuntimeMessages(
|
|
3013
|
+
message: Omit<InboundSequencedContainerRuntimeMessage, "contents">,
|
|
3014
|
+
messagesContent: IRuntimeMessagesContent[],
|
|
3015
|
+
local: boolean,
|
|
3016
|
+
savedOp?: boolean,
|
|
3017
|
+
): void {
|
|
2936
3018
|
// If there are no more pending messages after processing a local message,
|
|
2937
3019
|
// the document is no longer dirty.
|
|
2938
3020
|
if (!this.hasPendingMessages()) {
|
|
2939
3021
|
this.updateDocumentDirtyState(false);
|
|
2940
3022
|
}
|
|
2941
3023
|
|
|
3024
|
+
// Get the contents without the localOpMetadata because not all message types know about localOpMetadata.
|
|
3025
|
+
const contents = messagesContent.map((c) => c.contents);
|
|
3026
|
+
|
|
2942
3027
|
switch (message.type) {
|
|
3028
|
+
case ContainerMessageType.FluidDataStoreOp:
|
|
2943
3029
|
case ContainerMessageType.Attach:
|
|
2944
3030
|
case ContainerMessageType.Alias:
|
|
2945
|
-
|
|
2946
|
-
|
|
3031
|
+
// Remove the metadata from the message before sending it to the channel collection. The metadata
|
|
3032
|
+
// is added by the container runtime and is not part of the message that the channel collection and
|
|
3033
|
+
// layers below it expect.
|
|
3034
|
+
this.channelCollection.processMessages({ envelope: message, messagesContent, local });
|
|
2947
3035
|
break;
|
|
2948
3036
|
case ContainerMessageType.BlobAttach:
|
|
2949
|
-
this.blobManager.
|
|
3037
|
+
this.blobManager.processBlobAttachMessage(message, local);
|
|
2950
3038
|
break;
|
|
2951
3039
|
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
|
-
}
|
|
3040
|
+
this.processIdCompressorMessages(contents as IdCreationRange[], savedOp);
|
|
2974
3041
|
break;
|
|
2975
3042
|
case ContainerMessageType.GC:
|
|
2976
|
-
this.garbageCollector.
|
|
3043
|
+
this.garbageCollector.processMessages(
|
|
3044
|
+
contents as GarbageCollectionMessage[],
|
|
3045
|
+
message.timestamp,
|
|
3046
|
+
local,
|
|
3047
|
+
);
|
|
2977
3048
|
break;
|
|
2978
3049
|
case ContainerMessageType.ChunkedOp:
|
|
2979
|
-
// From observability POV, we should not
|
|
3050
|
+
// From observability POV, we should not expose the rest of the system (including "op" events on object) to these messages.
|
|
2980
3051
|
// Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
|
|
2981
3052
|
assert(false, 0x93d /* should not even get here */);
|
|
2982
3053
|
case ContainerMessageType.Rejoin:
|
|
2983
3054
|
break;
|
|
2984
3055
|
case ContainerMessageType.DocumentSchemaChange:
|
|
2985
|
-
this.documentsSchemaController.
|
|
2986
|
-
|
|
3056
|
+
this.documentsSchemaController.processDocumentSchemaMessages(
|
|
3057
|
+
contents as IDocumentSchemaChangeMessage[],
|
|
2987
3058
|
local,
|
|
2988
3059
|
message.sequenceNumber,
|
|
2989
3060
|
);
|
|
@@ -2992,14 +3063,38 @@ export class ContainerRuntime
|
|
|
2992
3063
|
const error = getUnknownMessageTypeError(
|
|
2993
3064
|
message.type,
|
|
2994
3065
|
"validateAndProcessRuntimeMessage" /* codePath */,
|
|
2995
|
-
message,
|
|
3066
|
+
message as ISequencedDocumentMessage,
|
|
2996
3067
|
);
|
|
2997
3068
|
this.closeFn(error);
|
|
2998
3069
|
throw error;
|
|
2999
3070
|
}
|
|
3000
3071
|
}
|
|
3072
|
+
}
|
|
3001
3073
|
|
|
3002
|
-
|
|
3074
|
+
private processIdCompressorMessages(messageContents: IdCreationRange[], savedOp?: boolean) {
|
|
3075
|
+
for (const range of messageContents) {
|
|
3076
|
+
// Don't re-finalize the range if we're processing a "savedOp" in
|
|
3077
|
+
// stashed ops flow. The compressor is stashed with these ops already processed.
|
|
3078
|
+
// That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
|
|
3079
|
+
// thus we need to process all the ops.
|
|
3080
|
+
if (!(this.skipSavedCompressorOps && savedOp === true)) {
|
|
3081
|
+
// Some other client turned on the id compressor. If we have not turned it on,
|
|
3082
|
+
// put it in a pending queue and delay finalization.
|
|
3083
|
+
if (this._idCompressor === undefined) {
|
|
3084
|
+
assert(
|
|
3085
|
+
this.idCompressorMode !== undefined,
|
|
3086
|
+
0x93c /* id compressor should be enabled */,
|
|
3087
|
+
);
|
|
3088
|
+
this.pendingIdCompressorOps.push(range);
|
|
3089
|
+
} else {
|
|
3090
|
+
assert(
|
|
3091
|
+
this.pendingIdCompressorOps.length === 0,
|
|
3092
|
+
0x979 /* there should be no pending ops! */,
|
|
3093
|
+
);
|
|
3094
|
+
this._idCompressor.finalizeCreationRange(range);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3003
3098
|
}
|
|
3004
3099
|
|
|
3005
3100
|
/**
|
|
@@ -3780,7 +3875,22 @@ export class ContainerRuntime
|
|
|
3780
3875
|
},
|
|
3781
3876
|
});
|
|
3782
3877
|
|
|
3783
|
-
assert
|
|
3878
|
+
// legacy: assert 0x3d1
|
|
3879
|
+
if (!this.outbox.isEmpty) {
|
|
3880
|
+
throw DataProcessingError.create(
|
|
3881
|
+
"Can't trigger summary in the middle of a batch",
|
|
3882
|
+
"submitSummary",
|
|
3883
|
+
undefined,
|
|
3884
|
+
{
|
|
3885
|
+
summaryNumber,
|
|
3886
|
+
pendingMessages: this.pendingMessagesCount,
|
|
3887
|
+
outboxLength: this.outbox.messageCount,
|
|
3888
|
+
mainBatchLength: this.outbox.mainBatchMessageCount,
|
|
3889
|
+
blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
|
|
3890
|
+
idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
|
|
3891
|
+
},
|
|
3892
|
+
);
|
|
3893
|
+
}
|
|
3784
3894
|
|
|
3785
3895
|
// If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
|
|
3786
3896
|
// 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
|
}
|