@fluidframework/container-runtime 2.4.0-299374 → 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.map +1 -1
- package/dist/containerRuntime.js +22 -59
- 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.map +1 -1
- package/lib/containerRuntime.js +22 -59
- 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 +20 -18
- package/src/containerRuntime.ts +43 -77
- 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
|
/**
|
|
@@ -2527,27 +2543,12 @@ export class ContainerRuntime
|
|
|
2527
2543
|
// GC op is only sent in summarizer which should never have stashed ops.
|
|
2528
2544
|
throw new LoggingError("GC op not expected to be stashed in summarizer");
|
|
2529
2545
|
default: {
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
"Stashed runtime message of unexpected type",
|
|
2537
|
-
"applyStashedOp",
|
|
2538
|
-
undefined /* sequencedMessage */,
|
|
2539
|
-
{
|
|
2540
|
-
messageDetails: JSON.stringify({
|
|
2541
|
-
type: opContents.type,
|
|
2542
|
-
compatBehavior,
|
|
2543
|
-
}),
|
|
2544
|
-
},
|
|
2545
|
-
);
|
|
2546
|
-
this.closeFn(error);
|
|
2547
|
-
throw error;
|
|
2548
|
-
}
|
|
2549
|
-
// Note: Even if its compat behavior allows it, we don't know how to apply this stashed op.
|
|
2550
|
-
// 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;
|
|
2551
2552
|
}
|
|
2552
2553
|
}
|
|
2553
2554
|
}
|
|
@@ -2978,27 +2979,13 @@ export class ContainerRuntime
|
|
|
2978
2979
|
);
|
|
2979
2980
|
break;
|
|
2980
2981
|
default: {
|
|
2981
|
-
const
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
{
|
|
2989
|
-
local,
|
|
2990
|
-
messageDetails: JSON.stringify({
|
|
2991
|
-
type: message.type,
|
|
2992
|
-
contentType: typeof message.contents,
|
|
2993
|
-
compatBehavior,
|
|
2994
|
-
batch: (message.metadata as IBatchMetadata | undefined)?.batch,
|
|
2995
|
-
compression: message.compression,
|
|
2996
|
-
}),
|
|
2997
|
-
},
|
|
2998
|
-
);
|
|
2999
|
-
this.closeFn(error);
|
|
3000
|
-
throw error;
|
|
3001
|
-
}
|
|
2982
|
+
const error = getUnknownMessageTypeError(
|
|
2983
|
+
message.type,
|
|
2984
|
+
"validateAndProcessRuntimeMessage" /* codePath */,
|
|
2985
|
+
message,
|
|
2986
|
+
);
|
|
2987
|
+
this.closeFn(error);
|
|
2988
|
+
throw error;
|
|
3002
2989
|
}
|
|
3003
2990
|
}
|
|
3004
2991
|
|
|
@@ -4462,30 +4449,9 @@ export class ContainerRuntime
|
|
|
4462
4449
|
// send any ops, as some other client already changed schema.
|
|
4463
4450
|
break;
|
|
4464
4451
|
default: {
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
if (compatBehaviorAllowsMessageType(message.type, compatBehavior)) {
|
|
4469
|
-
// We do not ultimately resubmit it, to be consistent with this version of the code.
|
|
4470
|
-
this.logger.sendTelemetryEvent({
|
|
4471
|
-
eventName: "resubmitUnrecognizedMessageTypeAllowed",
|
|
4472
|
-
messageDetails: { type: message.type, compatBehavior },
|
|
4473
|
-
});
|
|
4474
|
-
} else {
|
|
4475
|
-
const error = DataProcessingError.create(
|
|
4476
|
-
"Resubmitting runtime message of unexpected type",
|
|
4477
|
-
"reSubmitCore",
|
|
4478
|
-
undefined /* sequencedMessage */,
|
|
4479
|
-
{
|
|
4480
|
-
messageDetails: JSON.stringify({
|
|
4481
|
-
type: message.type,
|
|
4482
|
-
compatBehavior,
|
|
4483
|
-
}),
|
|
4484
|
-
},
|
|
4485
|
-
);
|
|
4486
|
-
this.closeFn(error);
|
|
4487
|
-
throw error;
|
|
4488
|
-
}
|
|
4452
|
+
const error = getUnknownMessageTypeError(message.type, "reSubmitCore" /* codePath */);
|
|
4453
|
+
this.closeFn(error);
|
|
4454
|
+
throw error;
|
|
4489
4455
|
}
|
|
4490
4456
|
}
|
|
4491
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).
|