@fluidframework/container-runtime 2.4.0-297385 → 2.4.0-299707
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/container-runtime.test-files.tar +0 -0
- package/dist/containerRuntime.d.ts +8 -0
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +94 -111
- package/dist/containerRuntime.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +2 -6
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +2 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +16 -10
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +8 -2
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +14 -7
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +8 -0
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +94 -111
- package/lib/containerRuntime.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +3 -7
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +2 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +16 -10
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +8 -2
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +14 -7
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +22 -21
- package/src/containerRuntime.ts +133 -147
- package/src/gc/garbageCollection.ts +5 -12
- package/src/opLifecycle/batchManager.ts +3 -0
- package/src/opLifecycle/outbox.ts +21 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +26 -8
package/src/containerRuntime.ts
CHANGED
|
@@ -102,7 +102,10 @@ import {
|
|
|
102
102
|
responseToException,
|
|
103
103
|
seqFromTree,
|
|
104
104
|
} from "@fluidframework/runtime-utils/internal";
|
|
105
|
-
import type {
|
|
105
|
+
import type {
|
|
106
|
+
IFluidErrorBase,
|
|
107
|
+
ITelemetryGenericEventExt,
|
|
108
|
+
} from "@fluidframework/telemetry-utils/internal";
|
|
106
109
|
import {
|
|
107
110
|
ITelemetryLoggerExt,
|
|
108
111
|
DataCorruptionError,
|
|
@@ -167,7 +170,7 @@ import {
|
|
|
167
170
|
type OutboundContainerRuntimeMessage,
|
|
168
171
|
type UnknownContainerRuntimeMessage,
|
|
169
172
|
} from "./messageTypes.js";
|
|
170
|
-
import {
|
|
173
|
+
import { ISavedOpMetadata } from "./metadata.js";
|
|
171
174
|
import {
|
|
172
175
|
BatchId,
|
|
173
176
|
BatchMessage,
|
|
@@ -236,19 +239,32 @@ import {
|
|
|
236
239
|
import { Throttler, formExponentialFn } from "./throttler.js";
|
|
237
240
|
|
|
238
241
|
/**
|
|
239
|
-
*
|
|
242
|
+
* Creates an error object to be thrown / passed to Container's close fn in case of an unknown message type.
|
|
240
243
|
* The parameters are typed to support compile-time enforcement of handling all known types/behaviors
|
|
241
244
|
*
|
|
242
|
-
* @param
|
|
245
|
+
* @param unknownContainerRuntimeMessageType - Typed as something unexpected, to ensure all known types have been
|
|
243
246
|
* handled before calling this function (e.g. in a switch statement).
|
|
244
|
-
*
|
|
247
|
+
*
|
|
248
|
+
* @param codePath - The code path where the unexpected message type was encountered.
|
|
249
|
+
*
|
|
250
|
+
* @param sequencedMessage - The sequenced message that contained the unexpected message type.
|
|
251
|
+
*
|
|
245
252
|
*/
|
|
246
|
-
function
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return
|
|
253
|
+
function getUnknownMessageTypeError(
|
|
254
|
+
unknownContainerRuntimeMessageType: UnknownContainerRuntimeMessage["type"],
|
|
255
|
+
codePath: string,
|
|
256
|
+
sequencedMessage?: ISequencedDocumentMessage,
|
|
257
|
+
): IFluidErrorBase {
|
|
258
|
+
return DataProcessingError.create(
|
|
259
|
+
"Runtime message of unknown type",
|
|
260
|
+
codePath,
|
|
261
|
+
sequencedMessage,
|
|
262
|
+
{
|
|
263
|
+
messageDetails: {
|
|
264
|
+
type: unknownContainerRuntimeMessageType,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
);
|
|
252
268
|
}
|
|
253
269
|
|
|
254
270
|
/**
|
|
@@ -1148,6 +1164,9 @@ export class ContainerRuntime
|
|
|
1148
1164
|
summaryOp: ISummaryContent,
|
|
1149
1165
|
referenceSequenceNumber?: number,
|
|
1150
1166
|
) => number;
|
|
1167
|
+
/**
|
|
1168
|
+
* Do not call directly - use submitAddressesSignal
|
|
1169
|
+
*/
|
|
1151
1170
|
private readonly submitSignalFn: (content: ISignalEnvelope, targetClientId?: string) => void;
|
|
1152
1171
|
public readonly disposeFn: (error?: ICriticalContainerError) => void;
|
|
1153
1172
|
public readonly closeFn: (error?: ICriticalContainerError) => void;
|
|
@@ -1755,7 +1774,7 @@ export class ContainerRuntime
|
|
|
1755
1774
|
type,
|
|
1756
1775
|
envelope1.contents,
|
|
1757
1776
|
);
|
|
1758
|
-
return this.
|
|
1777
|
+
return this.submitEnvelopedSignal(envelope2, targetClientId);
|
|
1759
1778
|
};
|
|
1760
1779
|
|
|
1761
1780
|
let snapshot: ISnapshot | ISnapshotTree | undefined = getSummaryForDatastores(
|
|
@@ -2524,27 +2543,12 @@ export class ContainerRuntime
|
|
|
2524
2543
|
// GC op is only sent in summarizer which should never have stashed ops.
|
|
2525
2544
|
throw new LoggingError("GC op not expected to be stashed in summarizer");
|
|
2526
2545
|
default: {
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
"Stashed runtime message of unexpected type",
|
|
2534
|
-
"applyStashedOp",
|
|
2535
|
-
undefined /* sequencedMessage */,
|
|
2536
|
-
{
|
|
2537
|
-
messageDetails: JSON.stringify({
|
|
2538
|
-
type: opContents.type,
|
|
2539
|
-
compatBehavior,
|
|
2540
|
-
}),
|
|
2541
|
-
},
|
|
2542
|
-
);
|
|
2543
|
-
this.closeFn(error);
|
|
2544
|
-
throw error;
|
|
2545
|
-
}
|
|
2546
|
-
// Note: Even if its compat behavior allows it, we don't know how to apply this stashed op.
|
|
2547
|
-
// All we can do is ignore it (similar to on process).
|
|
2546
|
+
const error = getUnknownMessageTypeError(
|
|
2547
|
+
opContents.type,
|
|
2548
|
+
"applyStashedOp" /* codePath */,
|
|
2549
|
+
);
|
|
2550
|
+
this.closeFn(error);
|
|
2551
|
+
throw error;
|
|
2548
2552
|
}
|
|
2549
2553
|
}
|
|
2550
2554
|
}
|
|
@@ -2975,27 +2979,13 @@ export class ContainerRuntime
|
|
|
2975
2979
|
);
|
|
2976
2980
|
break;
|
|
2977
2981
|
default: {
|
|
2978
|
-
const
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
{
|
|
2986
|
-
local,
|
|
2987
|
-
messageDetails: JSON.stringify({
|
|
2988
|
-
type: message.type,
|
|
2989
|
-
contentType: typeof message.contents,
|
|
2990
|
-
compatBehavior,
|
|
2991
|
-
batch: (message.metadata as IBatchMetadata | undefined)?.batch,
|
|
2992
|
-
compression: message.compression,
|
|
2993
|
-
}),
|
|
2994
|
-
},
|
|
2995
|
-
);
|
|
2996
|
-
this.closeFn(error);
|
|
2997
|
-
throw error;
|
|
2998
|
-
}
|
|
2982
|
+
const error = getUnknownMessageTypeError(
|
|
2983
|
+
message.type,
|
|
2984
|
+
"validateAndProcessRuntimeMessage" /* codePath */,
|
|
2985
|
+
message,
|
|
2986
|
+
);
|
|
2987
|
+
this.closeFn(error);
|
|
2988
|
+
throw error;
|
|
2999
2989
|
}
|
|
3000
2990
|
}
|
|
3001
2991
|
|
|
@@ -3021,6 +3011,72 @@ export class ContainerRuntime
|
|
|
3021
3011
|
this._signalTracking.totalSignalsSentInLatencyWindow = 0;
|
|
3022
3012
|
}
|
|
3023
3013
|
|
|
3014
|
+
/**
|
|
3015
|
+
* Updates signal telemetry including emitting telemetry events.
|
|
3016
|
+
*/
|
|
3017
|
+
private processSignalForTelemetry(envelope: ISignalEnvelope): void {
|
|
3018
|
+
const { clientBroadcastSignalSequenceNumber } = envelope;
|
|
3019
|
+
if (clientBroadcastSignalSequenceNumber === undefined) {
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
if (
|
|
3024
|
+
this._signalTracking.trackingSignalSequenceNumber === undefined ||
|
|
3025
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber === undefined
|
|
3026
|
+
) {
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
if (
|
|
3031
|
+
clientBroadcastSignalSequenceNumber >= this._signalTracking.trackingSignalSequenceNumber
|
|
3032
|
+
) {
|
|
3033
|
+
// Calculate the number of signals lost and log the event.
|
|
3034
|
+
const signalsLost =
|
|
3035
|
+
clientBroadcastSignalSequenceNumber -
|
|
3036
|
+
this._signalTracking.trackingSignalSequenceNumber;
|
|
3037
|
+
if (signalsLost > 0) {
|
|
3038
|
+
this._signalTracking.signalsLost += signalsLost;
|
|
3039
|
+
this.mc.logger.sendErrorEvent({
|
|
3040
|
+
eventName: "SignalLost",
|
|
3041
|
+
signalsLost, // Number of lost signals detected.
|
|
3042
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3043
|
+
clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
// Update the tracking signal sequence number to the next expected signal in the sequence.
|
|
3047
|
+
this._signalTracking.trackingSignalSequenceNumber =
|
|
3048
|
+
clientBroadcastSignalSequenceNumber + 1;
|
|
3049
|
+
} else if (
|
|
3050
|
+
// Check if this is a signal in range of interest.
|
|
3051
|
+
clientBroadcastSignalSequenceNumber >=
|
|
3052
|
+
this._signalTracking.minimumTrackingSignalSequenceNumber
|
|
3053
|
+
) {
|
|
3054
|
+
this._signalTracking.signalsOutOfOrder++;
|
|
3055
|
+
this.mc.logger.sendTelemetryEvent({
|
|
3056
|
+
eventName: "SignalOutOfOrder",
|
|
3057
|
+
type: envelope.contents.type, // Type of signal that was received out of order.
|
|
3058
|
+
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3059
|
+
clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
if (
|
|
3063
|
+
this._signalTracking.roundTripSignalSequenceNumber !== undefined &&
|
|
3064
|
+
clientBroadcastSignalSequenceNumber >= this._signalTracking.roundTripSignalSequenceNumber
|
|
3065
|
+
) {
|
|
3066
|
+
if (
|
|
3067
|
+
clientBroadcastSignalSequenceNumber ===
|
|
3068
|
+
this._signalTracking.roundTripSignalSequenceNumber
|
|
3069
|
+
) {
|
|
3070
|
+
// Latency tracked signal has been received.
|
|
3071
|
+
// We now log the roundtrip duration of the tracked signal.
|
|
3072
|
+
// This telemetry event also logs metrics for signals sent, signals lost, and out of order signals received.
|
|
3073
|
+
// These metrics are reset after logging the telemetry event.
|
|
3074
|
+
this.sendSignalTelemetryEvent();
|
|
3075
|
+
}
|
|
3076
|
+
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3024
3080
|
public processSignal(message: ISignalMessage, local: boolean) {
|
|
3025
3081
|
const envelope = message.content as ISignalEnvelope;
|
|
3026
3082
|
const transformed: IInboundSignalMessage = {
|
|
@@ -3033,64 +3089,15 @@ export class ContainerRuntime
|
|
|
3033
3089
|
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
3034
3090
|
if (
|
|
3035
3091
|
message.clientId === this.clientId &&
|
|
3036
|
-
|
|
3037
|
-
|
|
3092
|
+
// jason-ha: This `connected` check seems incorrect. Signals that come through
|
|
3093
|
+
// here must have been received while connected to service and there is no need
|
|
3094
|
+
// to avoid processing when connection has dropped. Because container runtime's
|
|
3095
|
+
// `connected` (and `_connected`) state also reflects some ops state, it may
|
|
3096
|
+
// easily be false when newly connected and signal tracking may very well
|
|
3097
|
+
// complain lost signals (that were simply skipped per this check).
|
|
3098
|
+
this.connected
|
|
3038
3099
|
) {
|
|
3039
|
-
|
|
3040
|
-
this._signalTracking.trackingSignalSequenceNumber !== undefined &&
|
|
3041
|
-
this._signalTracking.minimumTrackingSignalSequenceNumber !== undefined
|
|
3042
|
-
) {
|
|
3043
|
-
if (
|
|
3044
|
-
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3045
|
-
this._signalTracking.trackingSignalSequenceNumber
|
|
3046
|
-
) {
|
|
3047
|
-
// Calculate the number of signals lost and log the event.
|
|
3048
|
-
const signalsLost =
|
|
3049
|
-
envelope.clientBroadcastSignalSequenceNumber -
|
|
3050
|
-
this._signalTracking.trackingSignalSequenceNumber;
|
|
3051
|
-
if (signalsLost > 0) {
|
|
3052
|
-
this._signalTracking.signalsLost += signalsLost;
|
|
3053
|
-
this.mc.logger.sendErrorEvent({
|
|
3054
|
-
eventName: "SignalLost",
|
|
3055
|
-
signalsLost, // Number of lost signals detected.
|
|
3056
|
-
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3057
|
-
clientBroadcastSignalSequenceNumber:
|
|
3058
|
-
envelope.clientBroadcastSignalSequenceNumber, // Actual signal sequence number received.
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
// Update the tracking signal sequence number to the next expected signal in the sequence.
|
|
3062
|
-
this._signalTracking.trackingSignalSequenceNumber =
|
|
3063
|
-
envelope.clientBroadcastSignalSequenceNumber + 1;
|
|
3064
|
-
} else if (
|
|
3065
|
-
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3066
|
-
this._signalTracking.minimumTrackingSignalSequenceNumber
|
|
3067
|
-
) {
|
|
3068
|
-
this._signalTracking.signalsOutOfOrder++;
|
|
3069
|
-
this.mc.logger.sendTelemetryEvent({
|
|
3070
|
-
eventName: "SignalOutOfOrder",
|
|
3071
|
-
type: envelope.contents.type, // Type of signal that was received out of order.
|
|
3072
|
-
trackingSequenceNumber: this._signalTracking.trackingSignalSequenceNumber, // The next expected signal sequence number.
|
|
3073
|
-
clientBroadcastSignalSequenceNumber: envelope.clientBroadcastSignalSequenceNumber, // Sequence number of the out of order signal.
|
|
3074
|
-
});
|
|
3075
|
-
}
|
|
3076
|
-
if (
|
|
3077
|
-
this._signalTracking.roundTripSignalSequenceNumber !== undefined &&
|
|
3078
|
-
envelope.clientBroadcastSignalSequenceNumber >=
|
|
3079
|
-
this._signalTracking.roundTripSignalSequenceNumber
|
|
3080
|
-
) {
|
|
3081
|
-
if (
|
|
3082
|
-
envelope.clientBroadcastSignalSequenceNumber ===
|
|
3083
|
-
this._signalTracking.roundTripSignalSequenceNumber
|
|
3084
|
-
) {
|
|
3085
|
-
// Latency tracked signal has been received.
|
|
3086
|
-
// We now log the roundtrip duration of the tracked signal.
|
|
3087
|
-
// This telemetry event also logs metrics for signals sent, signals lost, and out of order signals received.
|
|
3088
|
-
// These metrics are reset after logging the telemetry event.
|
|
3089
|
-
this.sendSignalTelemetryEvent();
|
|
3090
|
-
}
|
|
3091
|
-
this._signalTracking.roundTripSignalSequenceNumber = undefined;
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3100
|
+
this.processSignalForTelemetry(envelope);
|
|
3094
3101
|
}
|
|
3095
3102
|
|
|
3096
3103
|
if (envelope.address === undefined) {
|
|
@@ -3337,19 +3344,24 @@ export class ContainerRuntime
|
|
|
3337
3344
|
address: string | undefined,
|
|
3338
3345
|
type: string,
|
|
3339
3346
|
content: any,
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
const newEnvelope: ISignalEnvelope = {
|
|
3347
|
+
): Omit<ISignalEnvelope, "broadcastSignalSequenceNumber"> {
|
|
3348
|
+
const newEnvelope: Omit<ISignalEnvelope, "broadcastSignalSequenceNumber"> = {
|
|
3343
3349
|
address,
|
|
3344
3350
|
contents: { type, content },
|
|
3345
3351
|
};
|
|
3346
3352
|
|
|
3353
|
+
return newEnvelope;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
private submitEnvelopedSignal(envelope: ISignalEnvelope, targetClientId?: string) {
|
|
3347
3357
|
const isBroadcastSignal = targetClientId === undefined;
|
|
3348
3358
|
|
|
3349
3359
|
if (isBroadcastSignal) {
|
|
3350
3360
|
const clientBroadcastSignalSequenceNumber = ++this._signalTracking
|
|
3351
3361
|
.broadcastSignalSequenceNumber;
|
|
3352
|
-
|
|
3362
|
+
// Stamp with the broadcast signal sequence number.
|
|
3363
|
+
envelope.clientBroadcastSignalSequenceNumber = clientBroadcastSignalSequenceNumber;
|
|
3364
|
+
|
|
3353
3365
|
this._signalTracking.signalsSentSinceLastLatencyMeasurement++;
|
|
3354
3366
|
|
|
3355
3367
|
if (
|
|
@@ -3378,7 +3390,7 @@ export class ContainerRuntime
|
|
|
3378
3390
|
}
|
|
3379
3391
|
}
|
|
3380
3392
|
|
|
3381
|
-
|
|
3393
|
+
this.submitSignalFn(envelope, targetClientId);
|
|
3382
3394
|
}
|
|
3383
3395
|
|
|
3384
3396
|
/**
|
|
@@ -3395,13 +3407,8 @@ export class ContainerRuntime
|
|
|
3395
3407
|
*/
|
|
3396
3408
|
public submitSignal(type: string, content: unknown, targetClientId?: string) {
|
|
3397
3409
|
this.verifyNotClosed();
|
|
3398
|
-
const envelope = this.createNewSignalEnvelope(
|
|
3399
|
-
|
|
3400
|
-
type,
|
|
3401
|
-
content,
|
|
3402
|
-
targetClientId,
|
|
3403
|
-
);
|
|
3404
|
-
return this.submitSignalFn(envelope, targetClientId);
|
|
3410
|
+
const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
|
|
3411
|
+
return this.submitEnvelopedSignal(envelope, targetClientId);
|
|
3405
3412
|
}
|
|
3406
3413
|
|
|
3407
3414
|
public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
|
|
@@ -4442,30 +4449,9 @@ export class ContainerRuntime
|
|
|
4442
4449
|
// send any ops, as some other client already changed schema.
|
|
4443
4450
|
break;
|
|
4444
4451
|
default: {
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
if (compatBehaviorAllowsMessageType(message.type, compatBehavior)) {
|
|
4449
|
-
// We do not ultimately resubmit it, to be consistent with this version of the code.
|
|
4450
|
-
this.logger.sendTelemetryEvent({
|
|
4451
|
-
eventName: "resubmitUnrecognizedMessageTypeAllowed",
|
|
4452
|
-
messageDetails: { type: message.type, compatBehavior },
|
|
4453
|
-
});
|
|
4454
|
-
} else {
|
|
4455
|
-
const error = DataProcessingError.create(
|
|
4456
|
-
"Resubmitting runtime message of unexpected type",
|
|
4457
|
-
"reSubmitCore",
|
|
4458
|
-
undefined /* sequencedMessage */,
|
|
4459
|
-
{
|
|
4460
|
-
messageDetails: JSON.stringify({
|
|
4461
|
-
type: message.type,
|
|
4462
|
-
compatBehavior,
|
|
4463
|
-
}),
|
|
4464
|
-
},
|
|
4465
|
-
);
|
|
4466
|
-
this.closeFn(error);
|
|
4467
|
-
throw error;
|
|
4468
|
-
}
|
|
4452
|
+
const error = getUnknownMessageTypeError(message.type, "reSubmitCore" /* codePath */);
|
|
4453
|
+
this.closeFn(error);
|
|
4454
|
+
throw error;
|
|
4469
4455
|
}
|
|
4470
4456
|
}
|
|
4471
4457
|
}
|
|
@@ -51,7 +51,6 @@ import {
|
|
|
51
51
|
} from "./gcDefinitions.js";
|
|
52
52
|
import {
|
|
53
53
|
cloneGCData,
|
|
54
|
-
compatBehaviorAllowsGCMessageType,
|
|
55
54
|
concatGarbageCollectionData,
|
|
56
55
|
dataStoreNodePathOnly,
|
|
57
56
|
getGCDataFromSnapshot,
|
|
@@ -896,16 +895,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
896
895
|
break;
|
|
897
896
|
}
|
|
898
897
|
default: {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
`Garbage collection message of unknown type ${gcMessageType}`,
|
|
904
|
-
"processMessage",
|
|
905
|
-
);
|
|
906
|
-
throw error;
|
|
907
|
-
}
|
|
908
|
-
break;
|
|
898
|
+
throw DataProcessingError.create(
|
|
899
|
+
`Garbage collection message of unknown type ${gcMessageType}`,
|
|
900
|
+
"processMessage",
|
|
901
|
+
);
|
|
909
902
|
}
|
|
910
903
|
}
|
|
911
904
|
}
|
|
@@ -1034,7 +1027,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1034
1027
|
*
|
|
1035
1028
|
* Submit a GC op indicating that the Tombstone with the given path has been loaded.
|
|
1036
1029
|
* Broadcasting this information in the op stream allows the Summarizer to reset unreferenced state
|
|
1037
|
-
* before
|
|
1030
|
+
* before running GC next.
|
|
1038
1031
|
*/
|
|
1039
1032
|
private triggerAutoRecovery(nodePath: string) {
|
|
1040
1033
|
// If sweep isn't enabled, auto-recovery isn't needed since its purpose is to prevent this object from being
|
|
@@ -20,6 +20,9 @@ export interface IBatchManagerOptions {
|
|
|
20
20
|
* If true, the outbox is allowed to rebase the batch during flushing.
|
|
21
21
|
*/
|
|
22
22
|
readonly canRebase: boolean;
|
|
23
|
+
|
|
24
|
+
/** If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored */
|
|
25
|
+
readonly ignoreBatchId?: boolean;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export interface BatchSequenceNumbers {
|
|
@@ -123,7 +123,11 @@ export class Outbox {
|
|
|
123
123
|
|
|
124
124
|
this.mainBatch = new BatchManager({ hardLimit, canRebase: true });
|
|
125
125
|
this.blobAttachBatch = new BatchManager({ hardLimit, canRebase: true });
|
|
126
|
-
this.idAllocationBatch = new BatchManager({
|
|
126
|
+
this.idAllocationBatch = new BatchManager({
|
|
127
|
+
hardLimit,
|
|
128
|
+
canRebase: false,
|
|
129
|
+
ignoreBatchId: true,
|
|
130
|
+
});
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
public get messageCount(): number {
|
|
@@ -251,17 +255,20 @@ export class Outbox {
|
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
private flushAll(resubmittingBatchId?: BatchId) {
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
this
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (resubmittingBatchId &&
|
|
258
|
+
// If we're resubmitting and all batches are empty, we need to flush an empty batch.
|
|
259
|
+
// Note that we currently resubmit one batch at a time, so on resubmit, 2 of the 3 batches will *always* be empty.
|
|
260
|
+
// It's theoretically possible that we don't *need* to resubmit this empty batch, and in those cases, it'll safely be ignored
|
|
261
|
+
// by the rest of the system, including remote clients.
|
|
262
|
+
// In some cases we *must* resubmit the empty batch (to match up with a non-empty version tracked locally by a container fork), so we do it always.
|
|
263
|
+
const allBatchesEmpty =
|
|
264
|
+
this.idAllocationBatch.empty && this.blobAttachBatch.empty && this.mainBatch.empty;
|
|
265
|
+
if (resubmittingBatchId && allBatchesEmpty) {
|
|
262
266
|
this.flushEmptyBatch(resubmittingBatchId);
|
|
263
267
|
return;
|
|
264
268
|
}
|
|
269
|
+
// Don't use resubmittingBatchId for idAllocationBatch.
|
|
270
|
+
// ID Allocation messages are not directly resubmitted so we don't want to reuse the batch ID.
|
|
271
|
+
this.flushInternal(this.idAllocationBatch);
|
|
265
272
|
this.flushInternal(
|
|
266
273
|
this.blobAttachBatch,
|
|
267
274
|
true /* disableGroupedBatching */,
|
|
@@ -332,7 +339,11 @@ export class Outbox {
|
|
|
332
339
|
);
|
|
333
340
|
}
|
|
334
341
|
|
|
335
|
-
this.params.pendingStateManager.onFlushBatch(
|
|
342
|
+
this.params.pendingStateManager.onFlushBatch(
|
|
343
|
+
rawBatch.messages,
|
|
344
|
+
clientSequenceNumber,
|
|
345
|
+
batchManager.options.ignoreBatchId,
|
|
346
|
+
);
|
|
336
347
|
}
|
|
337
348
|
|
|
338
349
|
/**
|
package/src/packageVersion.ts
CHANGED
|
@@ -41,7 +41,10 @@ export interface IPendingMessage {
|
|
|
41
41
|
localOpMetadata: unknown;
|
|
42
42
|
opMetadata: Record<string, unknown> | undefined;
|
|
43
43
|
sequenceNumber?: number;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect
|
|
46
|
+
* We don't include batchId itself to avoid redundancy, because that's stamped on opMetadata above
|
|
47
|
+
*/
|
|
45
48
|
batchInfo: {
|
|
46
49
|
/**
|
|
47
50
|
* The Batch's original clientId, from when it was first flushed to be submitted.
|
|
@@ -55,6 +58,8 @@ export interface IPendingMessage {
|
|
|
55
58
|
batchStartCsn: number;
|
|
56
59
|
/** length of the batch (how many runtime messages here) */
|
|
57
60
|
length: number;
|
|
61
|
+
/** If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored */
|
|
62
|
+
ignoreBatchId?: boolean;
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -239,8 +244,13 @@ export class PendingStateManager implements IDisposable {
|
|
|
239
244
|
* @param batch - The batch that was flushed
|
|
240
245
|
* @param clientSequenceNumber - The CSN of the first message in the batch,
|
|
241
246
|
* or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
|
|
247
|
+
* @param ignoreBatchId - Whether to ignore the batchId in the batchStartInfo
|
|
242
248
|
*/
|
|
243
|
-
public onFlushBatch(
|
|
249
|
+
public onFlushBatch(
|
|
250
|
+
batch: BatchMessage[],
|
|
251
|
+
clientSequenceNumber: number | undefined,
|
|
252
|
+
ignoreBatchId?: boolean,
|
|
253
|
+
) {
|
|
244
254
|
// clientId and batchStartCsn are used for generating the batchId so we can detect container forks
|
|
245
255
|
// where this batch was submitted by two different clients rehydrating from the same local state.
|
|
246
256
|
// In the typical case where the batch was actually sent, use the clientId and clientSequenceNumber.
|
|
@@ -269,7 +279,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
269
279
|
localOpMetadata,
|
|
270
280
|
opMetadata,
|
|
271
281
|
// Note: We only will read this off the first message, but put it on all for simplicity
|
|
272
|
-
batchInfo: { clientId, batchStartCsn, length: batch.length },
|
|
282
|
+
batchInfo: { clientId, batchStartCsn, length: batch.length, ignoreBatchId },
|
|
273
283
|
};
|
|
274
284
|
this.pendingMessages.push(pendingMessage);
|
|
275
285
|
}
|
|
@@ -328,15 +338,23 @@ export class PendingStateManager implements IDisposable {
|
|
|
328
338
|
* @returns whether the batch IDs match
|
|
329
339
|
*/
|
|
330
340
|
private remoteBatchMatchesPendingBatch(remoteBatchStart: BatchStartInfo): boolean {
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
341
|
+
// Find the first pending message that uses Batch ID, to compare to the incoming remote batch.
|
|
342
|
+
// If there is no such message, then the incoming remote batch doesn't have a match here and we can return.
|
|
343
|
+
const firstIndexUsingBatchId = Array.from({
|
|
344
|
+
length: this.pendingMessages.length,
|
|
345
|
+
}).findIndex((_, i) => this.pendingMessages.get(i)?.batchInfo.ignoreBatchId !== true);
|
|
346
|
+
const pendingMessageUsingBatchId =
|
|
347
|
+
firstIndexUsingBatchId === -1
|
|
348
|
+
? undefined
|
|
349
|
+
: this.pendingMessages.get(firstIndexUsingBatchId);
|
|
350
|
+
|
|
351
|
+
if (pendingMessageUsingBatchId === undefined) {
|
|
334
352
|
return false;
|
|
335
353
|
}
|
|
336
354
|
|
|
337
355
|
// We must compare the effective batch IDs, since one of these ops
|
|
338
356
|
// may have been the original, not resubmitted, so wouldn't have its batch ID stamped yet.
|
|
339
|
-
const pendingBatchId = getEffectiveBatchId(
|
|
357
|
+
const pendingBatchId = getEffectiveBatchId(pendingMessageUsingBatchId);
|
|
340
358
|
const inboundBatchId = getEffectiveBatchId(remoteBatchStart);
|
|
341
359
|
|
|
342
360
|
return pendingBatchId === inboundBatchId;
|
|
@@ -488,7 +506,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
488
506
|
0xa21 /* No pending message found as we start processing this remote batch */,
|
|
489
507
|
);
|
|
490
508
|
|
|
491
|
-
// If this batch became empty on resubmit, batch.messages will be empty (
|
|
509
|
+
// If this batch became empty on resubmit, batch.messages will be empty (but keyMessage is always set)
|
|
492
510
|
// and the next pending message should be an empty batch marker.
|
|
493
511
|
// More Info: We must submit empty batches and track them in case a different fork
|
|
494
512
|
// of this container also submitted the same batch (and it may not be empty for that fork).
|