@fluidframework/container-runtime 2.4.0 → 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 +183 -119
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +12 -4
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +155 -66
- 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/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/opProperties.js +1 -1
- package/dist/opProperties.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/summary/documentSchema.d.ts +11 -0
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +45 -30
- 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 +183 -119
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +12 -4
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +155 -66
- 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/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/opProperties.js +1 -1
- package/lib/opProperties.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/summary/documentSchema.d.ts +11 -0
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +45 -30
- package/lib/summary/documentSchema.js.map +1 -1
- package/package.json +24 -24
- package/src/blobManager/blobManager.ts +2 -2
- package/src/channelCollection.ts +227 -160
- package/src/containerRuntime.ts +197 -80
- package/src/dataStoreContext.ts +66 -23
- 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/opProperties.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/summary/documentSchema.ts +58 -39
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,
|
|
@@ -105,6 +106,7 @@ import {
|
|
|
105
106
|
import type {
|
|
106
107
|
IFluidErrorBase,
|
|
107
108
|
ITelemetryGenericEventExt,
|
|
109
|
+
TelemetryEventPropertyTypeExt,
|
|
108
110
|
} from "@fluidframework/telemetry-utils/internal";
|
|
109
111
|
import {
|
|
110
112
|
ITelemetryLoggerExt,
|
|
@@ -159,6 +161,7 @@ import {
|
|
|
159
161
|
IGCStats,
|
|
160
162
|
IGarbageCollector,
|
|
161
163
|
gcGenerationOptionName,
|
|
164
|
+
type GarbageCollectionMessage,
|
|
162
165
|
} from "./gc/index.js";
|
|
163
166
|
import {
|
|
164
167
|
ContainerMessageType,
|
|
@@ -201,6 +204,7 @@ import {
|
|
|
201
204
|
IConnectableRuntime,
|
|
202
205
|
IContainerRuntimeMetadata,
|
|
203
206
|
ICreateContainerMetadata,
|
|
207
|
+
type IDocumentSchemaChangeMessage,
|
|
204
208
|
type IDocumentSchemaCurrent,
|
|
205
209
|
IEnqueueSummarizeOptions,
|
|
206
210
|
IGenerateSummaryTreeResult,
|
|
@@ -2810,6 +2814,9 @@ export class ContainerRuntime
|
|
|
2810
2814
|
local,
|
|
2811
2815
|
savedOp,
|
|
2812
2816
|
runtimeBatch,
|
|
2817
|
+
inboundResult.type === "fullBatch"
|
|
2818
|
+
? inboundResult.groupedBatch
|
|
2819
|
+
: false /* groupedBatch */,
|
|
2813
2820
|
);
|
|
2814
2821
|
} else {
|
|
2815
2822
|
if (!runtimeBatch) {
|
|
@@ -2829,6 +2836,7 @@ export class ContainerRuntime
|
|
|
2829
2836
|
local,
|
|
2830
2837
|
savedOp,
|
|
2831
2838
|
runtimeBatch,
|
|
2839
|
+
false /* groupedBatch */,
|
|
2832
2840
|
);
|
|
2833
2841
|
}
|
|
2834
2842
|
|
|
@@ -2844,14 +2852,15 @@ export class ContainerRuntime
|
|
|
2844
2852
|
|
|
2845
2853
|
/**
|
|
2846
2854
|
* Processes inbound message(s). It calls schedule manager according to the messages' location in the batch.
|
|
2847
|
-
* @param
|
|
2855
|
+
* @param messagesWithMetadata - messages to process along with their metadata.
|
|
2848
2856
|
* @param locationInBatch - Are we processing the start and/or end of a batch?
|
|
2849
2857
|
* @param local - true if the messages were originally generated by the client receiving it.
|
|
2850
2858
|
* @param savedOp - true if the message is a replayed saved op.
|
|
2851
2859
|
* @param runtimeBatch - true if these are runtime messages.
|
|
2860
|
+
* @param groupedBatch - true if these messages are part of a grouped op batch.
|
|
2852
2861
|
*/
|
|
2853
2862
|
private processInboundMessages(
|
|
2854
|
-
|
|
2863
|
+
messagesWithMetadata: {
|
|
2855
2864
|
message: ISequencedDocumentMessage;
|
|
2856
2865
|
localOpMetadata?: unknown;
|
|
2857
2866
|
}[],
|
|
@@ -2859,35 +2868,110 @@ export class ContainerRuntime
|
|
|
2859
2868
|
local: boolean,
|
|
2860
2869
|
savedOp: boolean | undefined,
|
|
2861
2870
|
runtimeBatch: boolean,
|
|
2871
|
+
groupedBatch: boolean,
|
|
2862
2872
|
) {
|
|
2863
2873
|
if (locationInBatch.batchStart) {
|
|
2864
|
-
const firstMessage =
|
|
2874
|
+
const firstMessage = messagesWithMetadata[0]?.message;
|
|
2865
2875
|
assert(firstMessage !== undefined, 0xa31 /* Batch must have at least one message */);
|
|
2866
2876
|
this.scheduleManager.batchBegin(firstMessage);
|
|
2867
2877
|
}
|
|
2868
2878
|
|
|
2869
2879
|
let error: unknown;
|
|
2870
2880
|
try {
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
this.
|
|
2875
|
-
|
|
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
|
+
],
|
|
2876
2917
|
local,
|
|
2877
2918
|
savedOp,
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
|
|
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
|
+
);
|
|
2883
2940
|
});
|
|
2884
|
-
|
|
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
|
+
}
|
|
2885
2969
|
} catch (e) {
|
|
2886
2970
|
error = e;
|
|
2887
2971
|
throw error;
|
|
2888
2972
|
} finally {
|
|
2889
2973
|
if (locationInBatch.batchEnd) {
|
|
2890
|
-
const lastMessage =
|
|
2974
|
+
const lastMessage = messagesWithMetadata[messagesWithMetadata.length - 1]?.message;
|
|
2891
2975
|
assert(lastMessage !== undefined, 0xa32 /* Batch must have at least one message */);
|
|
2892
2976
|
this.scheduleManager.batchEnd(error, lastMessage);
|
|
2893
2977
|
}
|
|
@@ -2916,80 +3000,62 @@ export class ContainerRuntime
|
|
|
2916
3000
|
}
|
|
2917
3001
|
|
|
2918
3002
|
/**
|
|
2919
|
-
*
|
|
2920
|
-
*
|
|
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.
|
|
2921
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
|
+
*
|
|
2922
3012
|
*/
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
local: boolean
|
|
2927
|
-
savedOp?: boolean
|
|
2928
|
-
|
|
2929
|
-
}): void {
|
|
2930
|
-
const { local, message, savedOp, localOpMetadata } = messageWithContext;
|
|
2931
|
-
|
|
2932
|
-
// Set the minimum sequence number to the containerRuntime's understanding of minimum sequence number.
|
|
2933
|
-
if (
|
|
2934
|
-
this.useDeltaManagerOpsProxy &&
|
|
2935
|
-
this.deltaManager.minimumSequenceNumber < message.minimumSequenceNumber
|
|
2936
|
-
) {
|
|
2937
|
-
message.minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
this._processedClientSequenceNumber = message.clientSequenceNumber;
|
|
2941
|
-
|
|
3013
|
+
private validateAndProcessRuntimeMessages(
|
|
3014
|
+
message: Omit<InboundSequencedContainerRuntimeMessage, "contents">,
|
|
3015
|
+
messagesContent: IRuntimeMessagesContent[],
|
|
3016
|
+
local: boolean,
|
|
3017
|
+
savedOp?: boolean,
|
|
3018
|
+
): void {
|
|
2942
3019
|
// If there are no more pending messages after processing a local message,
|
|
2943
3020
|
// the document is no longer dirty.
|
|
2944
3021
|
if (!this.hasPendingMessages()) {
|
|
2945
3022
|
this.updateDocumentDirtyState(false);
|
|
2946
3023
|
}
|
|
2947
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
|
+
|
|
2948
3028
|
switch (message.type) {
|
|
3029
|
+
case ContainerMessageType.FluidDataStoreOp:
|
|
2949
3030
|
case ContainerMessageType.Attach:
|
|
2950
3031
|
case ContainerMessageType.Alias:
|
|
2951
|
-
|
|
2952
|
-
|
|
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 });
|
|
2953
3036
|
break;
|
|
2954
3037
|
case ContainerMessageType.BlobAttach:
|
|
2955
|
-
this.blobManager.
|
|
3038
|
+
this.blobManager.processBlobAttachMessage(message, local);
|
|
2956
3039
|
break;
|
|
2957
3040
|
case ContainerMessageType.IdAllocation:
|
|
2958
|
-
|
|
2959
|
-
// stashed ops flow. The compressor is stashed with these ops already processed.
|
|
2960
|
-
// That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and
|
|
2961
|
-
// thus we need to process all the ops.
|
|
2962
|
-
if (!(this.skipSavedCompressorOps && savedOp === true)) {
|
|
2963
|
-
const range = message.contents;
|
|
2964
|
-
// Some other client turned on the id compressor. If we have not turned it on,
|
|
2965
|
-
// put it in a pending queue and delay finalization.
|
|
2966
|
-
if (this._idCompressor === undefined) {
|
|
2967
|
-
assert(
|
|
2968
|
-
this.idCompressorMode !== undefined,
|
|
2969
|
-
0x93c /* id compressor should be enabled */,
|
|
2970
|
-
);
|
|
2971
|
-
this.pendingIdCompressorOps.push(range);
|
|
2972
|
-
} else {
|
|
2973
|
-
assert(
|
|
2974
|
-
this.pendingIdCompressorOps.length === 0,
|
|
2975
|
-
0x979 /* there should be no pending ops! */,
|
|
2976
|
-
);
|
|
2977
|
-
this._idCompressor.finalizeCreationRange(range);
|
|
2978
|
-
}
|
|
2979
|
-
}
|
|
3041
|
+
this.processIdCompressorMessages(contents as IdCreationRange[], savedOp);
|
|
2980
3042
|
break;
|
|
2981
3043
|
case ContainerMessageType.GC:
|
|
2982
|
-
this.garbageCollector.
|
|
3044
|
+
this.garbageCollector.processMessages(
|
|
3045
|
+
contents as GarbageCollectionMessage[],
|
|
3046
|
+
message.timestamp,
|
|
3047
|
+
local,
|
|
3048
|
+
);
|
|
2983
3049
|
break;
|
|
2984
3050
|
case ContainerMessageType.ChunkedOp:
|
|
2985
|
-
// 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.
|
|
2986
3052
|
// Also resetReconnectCount() would be wrong - see comment that was there before this change was made.
|
|
2987
3053
|
assert(false, 0x93d /* should not even get here */);
|
|
2988
3054
|
case ContainerMessageType.Rejoin:
|
|
2989
3055
|
break;
|
|
2990
3056
|
case ContainerMessageType.DocumentSchemaChange:
|
|
2991
|
-
this.documentsSchemaController.
|
|
2992
|
-
|
|
3057
|
+
this.documentsSchemaController.processDocumentSchemaMessages(
|
|
3058
|
+
contents as IDocumentSchemaChangeMessage[],
|
|
2993
3059
|
local,
|
|
2994
3060
|
message.sequenceNumber,
|
|
2995
3061
|
);
|
|
@@ -2998,14 +3064,38 @@ export class ContainerRuntime
|
|
|
2998
3064
|
const error = getUnknownMessageTypeError(
|
|
2999
3065
|
message.type,
|
|
3000
3066
|
"validateAndProcessRuntimeMessage" /* codePath */,
|
|
3001
|
-
message,
|
|
3067
|
+
message as ISequencedDocumentMessage,
|
|
3002
3068
|
);
|
|
3003
3069
|
this.closeFn(error);
|
|
3004
3070
|
throw error;
|
|
3005
3071
|
}
|
|
3006
3072
|
}
|
|
3073
|
+
}
|
|
3007
3074
|
|
|
3008
|
-
|
|
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
|
+
}
|
|
3009
3099
|
}
|
|
3010
3100
|
|
|
3011
3101
|
/**
|
|
@@ -3015,11 +3105,13 @@ export class ContainerRuntime
|
|
|
3015
3105
|
const duration = Date.now() - this._signalTracking.signalTimestamp;
|
|
3016
3106
|
this.mc.logger.sendPerformanceEvent({
|
|
3017
3107
|
eventName: "SignalLatency",
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3108
|
+
details: {
|
|
3109
|
+
duration, // Roundtrip duration of the tracked signal in milliseconds.
|
|
3110
|
+
sent: this._signalTracking.totalSignalsSentInLatencyWindow, // Signals sent since the last logged SignalLatency event.
|
|
3111
|
+
lost: this._signalTracking.signalsLost, // Signals lost since the last logged SignalLatency event.
|
|
3112
|
+
outOfOrder: this._signalTracking.signalsOutOfOrder, // Out of order signals since the last logged SignalLatency event.
|
|
3113
|
+
reconnectCount: this.consecutiveReconnects, // Container reconnect count.
|
|
3114
|
+
},
|
|
3023
3115
|
});
|
|
3024
3116
|
this._signalTracking.signalsLost = 0;
|
|
3025
3117
|
this._signalTracking.signalsOutOfOrder = 0;
|
|
@@ -3054,9 +3146,11 @@ export class ContainerRuntime
|
|
|
3054
3146
|
this._signalTracking.signalsLost += signalsLost;
|
|
3055
3147
|
this.mc.logger.sendErrorEvent({
|
|
3056
3148
|
eventName: "SignalLost",
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3149
|
+
details: {
|
|
3150
|
+
signalsLost, // Number of lost signals detected.
|
|
3151
|
+
expectedSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3152
|
+
clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
|
|
3153
|
+
},
|
|
3060
3154
|
});
|
|
3061
3155
|
}
|
|
3062
3156
|
// Update the tracking signal sequence number to the next expected signal in the sequence.
|
|
@@ -3068,11 +3162,18 @@ export class ContainerRuntime
|
|
|
3068
3162
|
this._signalTracking.minimumTrackingSignalSequenceNumber
|
|
3069
3163
|
) {
|
|
3070
3164
|
this._signalTracking.signalsOutOfOrder++;
|
|
3165
|
+
const details: TelemetryEventPropertyTypeExt = {
|
|
3166
|
+
expectedSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3167
|
+
clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
3168
|
+
};
|
|
3169
|
+
// Only log `contents.type` when address is for container to avoid
|
|
3170
|
+
// chance that contents type is customer data.
|
|
3171
|
+
if (envelope.address === undefined) {
|
|
3172
|
+
details.contentsType = envelope.contents.type; // Type of signal that was received out of order.
|
|
3173
|
+
}
|
|
3071
3174
|
this.mc.logger.sendTelemetryEvent({
|
|
3072
3175
|
eventName: "SignalOutOfOrder",
|
|
3073
|
-
|
|
3074
|
-
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3075
|
-
clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
3176
|
+
details,
|
|
3076
3177
|
});
|
|
3077
3178
|
}
|
|
3078
3179
|
if (
|
|
@@ -3085,7 +3186,8 @@ export class ContainerRuntime
|
|
|
3085
3186
|
) {
|
|
3086
3187
|
// Latency tracked signal has been received.
|
|
3087
3188
|
// We now log the roundtrip duration of the tracked signal.
|
|
3088
|
-
// This telemetry event also logs metrics for
|
|
3189
|
+
// This telemetry event also logs metrics for broadcast signals
|
|
3190
|
+
// sent, lost, and out of order.
|
|
3089
3191
|
// These metrics are reset after logging the telemetry event.
|
|
3090
3192
|
this.sendSignalTelemetryEvent();
|
|
3091
3193
|
}
|
|
@@ -3774,7 +3876,22 @@ export class ContainerRuntime
|
|
|
3774
3876
|
},
|
|
3775
3877
|
});
|
|
3776
3878
|
|
|
3777
|
-
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
|
+
}
|
|
3778
3895
|
|
|
3779
3896
|
// If the container is dirty, i.e., there are pending unacked ops, the summary will not be eventual consistent
|
|
3780
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) {
|
|
@@ -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;
|