@fluidframework/container-runtime 2.92.0 → 2.93.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 +4 -0
- package/README.md +1 -1
- package/container-runtime.test-files.tar +0 -0
- package/dist/containerRuntime.d.ts +2 -2
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +25 -36
- package/dist/containerRuntime.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +3 -9
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +3 -2
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +8 -5
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +40 -57
- 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 +1 -6
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +6 -16
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runtimeLayerCompatState.d.ts +2 -2
- package/eslint.config.mts +1 -1
- package/lib/containerRuntime.d.ts +2 -2
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +25 -36
- package/lib/containerRuntime.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +3 -9
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +3 -2
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +8 -5
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +41 -58
- 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 +1 -6
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +6 -16
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runtimeLayerCompatState.d.ts +2 -2
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +24 -29
- package/src/containerRuntime.ts +25 -46
- package/src/opLifecycle/batchManager.ts +4 -12
- package/src/opLifecycle/outbox.ts +51 -69
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +5 -23
package/src/containerRuntime.ts
CHANGED
|
@@ -2069,6 +2069,24 @@ export class ContainerRuntime
|
|
|
2069
2069
|
}),
|
|
2070
2070
|
reSubmit: this.reSubmit.bind(this),
|
|
2071
2071
|
opReentrancy: () => this.dataModelChangeRunner.running,
|
|
2072
|
+
generateIdAllocationOp: (): LocalBatchMessage | undefined => {
|
|
2073
|
+
if (this._idCompressor === undefined) {
|
|
2074
|
+
return undefined;
|
|
2075
|
+
}
|
|
2076
|
+
const idRange = this._idCompressor.takeNextCreationRange();
|
|
2077
|
+
if (idRange.ids === undefined) {
|
|
2078
|
+
return undefined;
|
|
2079
|
+
}
|
|
2080
|
+
const idAllocationMessage: ContainerRuntimeIdAllocationMessage = {
|
|
2081
|
+
type: ContainerMessageType.IdAllocation,
|
|
2082
|
+
contents: idRange,
|
|
2083
|
+
};
|
|
2084
|
+
return {
|
|
2085
|
+
runtimeOp: idAllocationMessage,
|
|
2086
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
2087
|
+
staged: false,
|
|
2088
|
+
};
|
|
2089
|
+
},
|
|
2072
2090
|
});
|
|
2073
2091
|
|
|
2074
2092
|
this._quorum = quorum;
|
|
@@ -3687,9 +3705,6 @@ export class ContainerRuntime
|
|
|
3687
3705
|
|
|
3688
3706
|
this.stageControls = undefined;
|
|
3689
3707
|
|
|
3690
|
-
// During Staging Mode, we avoid submitting any ID Allocation ops (apart from resubmitting pre-staging ops).
|
|
3691
|
-
// Now that we've exited, we need to submit an ID Allocation op for any IDs that were generated while in Staging Mode.
|
|
3692
|
-
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
3693
3708
|
const batchInfos = discardOrCommit();
|
|
3694
3709
|
event.reportProgress({
|
|
3695
3710
|
details: {
|
|
@@ -4272,7 +4287,6 @@ export class ContainerRuntime
|
|
|
4272
4287
|
outboxLength: this.outbox.messageCount,
|
|
4273
4288
|
mainBatchLength: this.outbox.mainBatchMessageCount,
|
|
4274
4289
|
blobAttachBatchLength: this.outbox.blobAttachBatchMessageCount,
|
|
4275
|
-
idAllocationBatchLength: this.outbox.idAllocationBatchMessageCount,
|
|
4276
4290
|
},
|
|
4277
4291
|
);
|
|
4278
4292
|
}
|
|
@@ -4605,7 +4619,8 @@ export class ContainerRuntime
|
|
|
4605
4619
|
|
|
4606
4620
|
/**
|
|
4607
4621
|
* This helper is called during summarization. If the container is dirty, it will return a failed summarize result
|
|
4608
|
-
* (IBaseSummarizeResult) unless this is the final summarize attempt
|
|
4622
|
+
* (IBaseSummarizeResult) unless this is the final summarize attempt, in which case the summary is allowed to
|
|
4623
|
+
* proceed to make progress in documents where there are consistently pending ops in the summarizer.
|
|
4609
4624
|
* @param logger - The logger to be used for sending telemetry.
|
|
4610
4625
|
* @param referenceSequenceNumber - The reference sequence number of the summary attempt.
|
|
4611
4626
|
* @param minimumSequenceNumber - The minimum sequence number of the summary attempt.
|
|
@@ -4624,13 +4639,9 @@ export class ContainerRuntime
|
|
|
4624
4639
|
return;
|
|
4625
4640
|
}
|
|
4626
4641
|
|
|
4627
|
-
//
|
|
4628
|
-
//
|
|
4629
|
-
|
|
4630
|
-
if (
|
|
4631
|
-
finalAttempt &&
|
|
4632
|
-
this.mc.config.getBoolean("Fluid.Summarizer.SkipFailingIncorrectSummary") === true
|
|
4633
|
-
) {
|
|
4642
|
+
// Don't fail the summary in the last attempt. This is a fallback to make progress in
|
|
4643
|
+
// documents where there are consistently pending ops in the summarizer.
|
|
4644
|
+
if (finalAttempt) {
|
|
4634
4645
|
const error = DataProcessingError.create(
|
|
4635
4646
|
"Pending ops during summarization",
|
|
4636
4647
|
"submitSummary",
|
|
@@ -4639,7 +4650,7 @@ export class ContainerRuntime
|
|
|
4639
4650
|
);
|
|
4640
4651
|
logger.sendErrorEvent(
|
|
4641
4652
|
{
|
|
4642
|
-
eventName: "
|
|
4653
|
+
eventName: "PendingOpsDuringSummaryFinalAttempt",
|
|
4643
4654
|
referenceSequenceNumber,
|
|
4644
4655
|
minimumSequenceNumber,
|
|
4645
4656
|
beforeGenerate: beforeSummaryGeneration,
|
|
@@ -4733,33 +4744,6 @@ export class ContainerRuntime
|
|
|
4733
4744
|
return this.blobManager.lookupTemporaryBlobStorageId(localId);
|
|
4734
4745
|
}
|
|
4735
4746
|
|
|
4736
|
-
private submitIdAllocationOpIfNeeded({
|
|
4737
|
-
resubmitOutstandingRanges = false,
|
|
4738
|
-
staged,
|
|
4739
|
-
}: {
|
|
4740
|
-
resubmitOutstandingRanges?: boolean;
|
|
4741
|
-
staged: boolean;
|
|
4742
|
-
}): void {
|
|
4743
|
-
if (this._idCompressor) {
|
|
4744
|
-
const idRange = resubmitOutstandingRanges
|
|
4745
|
-
? this._idCompressor.takeUnfinalizedCreationRange()
|
|
4746
|
-
: this._idCompressor.takeNextCreationRange();
|
|
4747
|
-
// Don't include the idRange if there weren't any Ids allocated
|
|
4748
|
-
if (idRange.ids !== undefined) {
|
|
4749
|
-
const idAllocationMessage: ContainerRuntimeIdAllocationMessage = {
|
|
4750
|
-
type: ContainerMessageType.IdAllocation,
|
|
4751
|
-
contents: idRange,
|
|
4752
|
-
};
|
|
4753
|
-
const idAllocationBatchMessage: LocalBatchMessage = {
|
|
4754
|
-
runtimeOp: idAllocationMessage,
|
|
4755
|
-
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
4756
|
-
staged,
|
|
4757
|
-
};
|
|
4758
|
-
this.outbox.submitIdAllocation(idAllocationBatchMessage);
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
}
|
|
4762
|
-
|
|
4763
4747
|
private submit(
|
|
4764
4748
|
containerRuntimeMessage: LocalContainerRuntimeMessage,
|
|
4765
4749
|
localOpMetadata: unknown = undefined,
|
|
@@ -4803,11 +4787,6 @@ export class ContainerRuntime
|
|
|
4803
4787
|
0xbba /* Unexpected message type submitted in Staging Mode */,
|
|
4804
4788
|
);
|
|
4805
4789
|
|
|
4806
|
-
// Before submitting any non-staged change, submit the ID Allocation op to cover any compressed IDs included in the op.
|
|
4807
|
-
if (!staged) {
|
|
4808
|
-
this.submitIdAllocationOpIfNeeded({ staged: false });
|
|
4809
|
-
}
|
|
4810
|
-
|
|
4811
4790
|
// Allow document schema controller to send a message if it needs to propose change in document schema.
|
|
4812
4791
|
// If it needs to send a message, it will call provided callback with payload of such message and rely
|
|
4813
4792
|
// on this callback to do actual sending.
|
|
@@ -4867,7 +4846,7 @@ export class ContainerRuntime
|
|
|
4867
4846
|
// Incoming ops still break the batch via direct this.flush() calls elsewhere
|
|
4868
4847
|
// (deltaManager "op" handler, process(), connection changes, getPendingLocalState,
|
|
4869
4848
|
// exitStagingMode). Those all bypass scheduleFlush(), so they're unaffected by this check.
|
|
4870
|
-
// Additionally, outbox.
|
|
4849
|
+
// Additionally, outbox.outboxSequenceNumberCoherencyCheck() (called on every submit) detects
|
|
4871
4850
|
// sequence number changes and throws if unexpected changes are detected.
|
|
4872
4851
|
if (
|
|
4873
4852
|
this.inStagingMode &&
|
|
@@ -20,17 +20,8 @@ import { serializeOp } from "./opSerialization.js";
|
|
|
20
20
|
import type { BatchStartInfo } from "./remoteMessageProcessor.js";
|
|
21
21
|
|
|
22
22
|
export interface IBatchManagerOptions {
|
|
23
|
+
readonly disableGroupedBatching: boolean;
|
|
23
24
|
readonly compressionOptions?: ICompressionRuntimeOptions;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* If true, the outbox is allowed to rebase the batch during flushing.
|
|
27
|
-
*/
|
|
28
|
-
readonly canRebase: boolean;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored
|
|
32
|
-
*/
|
|
33
|
-
readonly ignoreBatchId?: boolean;
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
export interface BatchSequenceNumbers {
|
|
@@ -127,8 +118,9 @@ export class BatchManager {
|
|
|
127
118
|
|
|
128
119
|
/**
|
|
129
120
|
* Gets the pending batch and clears state for the next batch.
|
|
121
|
+
* The caller is responsible for calling {@link addBatchMetadata} after any modifications (e.g. prepending messages).
|
|
130
122
|
*/
|
|
131
|
-
public popBatch(
|
|
123
|
+
public popBatch(): LocalBatch {
|
|
132
124
|
assert(this.pendingBatch[0] !== undefined, 0xb8a /* expected non-empty batch */);
|
|
133
125
|
const batch: LocalBatch = {
|
|
134
126
|
messages: this.pendingBatch,
|
|
@@ -141,7 +133,7 @@ export class BatchManager {
|
|
|
141
133
|
this.clientSequenceNumber = undefined;
|
|
142
134
|
this.hasReentrantOps = false;
|
|
143
135
|
|
|
144
|
-
return
|
|
136
|
+
return batch;
|
|
145
137
|
}
|
|
146
138
|
|
|
147
139
|
/**
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type BatchSequenceNumbers,
|
|
29
29
|
sequenceNumbersMatch,
|
|
30
30
|
type BatchId,
|
|
31
|
+
addBatchMetadata,
|
|
31
32
|
} from "./batchManager.js";
|
|
32
33
|
import type {
|
|
33
34
|
LocalBatchMessage,
|
|
@@ -65,6 +66,13 @@ export interface IOutboxParameters {
|
|
|
65
66
|
readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
|
|
66
67
|
readonly reSubmit: (message: PendingMessageResubmitData, squash: boolean) => void;
|
|
67
68
|
readonly opReentrancy: () => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* JIT callback to generate an ID allocation op at flush time.
|
|
71
|
+
* Called after rebase (if any), so the returned message has the correct refSeq.
|
|
72
|
+
*
|
|
73
|
+
* @returns A LocalBatchMessage for the ID allocation op, or undefined if no IDs need allocating.
|
|
74
|
+
*/
|
|
75
|
+
readonly generateIdAllocationOp: () => LocalBatchMessage | undefined;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
/**
|
|
@@ -189,7 +197,6 @@ export class Outbox {
|
|
|
189
197
|
private readonly logger: ITelemetryLoggerExt;
|
|
190
198
|
private readonly mainBatch: BatchManager;
|
|
191
199
|
private readonly blobAttachBatch: BatchManager;
|
|
192
|
-
private readonly idAllocationBatch: BatchManager;
|
|
193
200
|
private batchRebasesToReport = 5;
|
|
194
201
|
private rebasing = false;
|
|
195
202
|
|
|
@@ -205,16 +212,12 @@ export class Outbox {
|
|
|
205
212
|
constructor(private readonly params: IOutboxParameters) {
|
|
206
213
|
this.logger = createChildLogger({ logger: params.logger, namespace: "Outbox" });
|
|
207
214
|
|
|
208
|
-
this.mainBatch = new BatchManager({
|
|
209
|
-
this.blobAttachBatch = new BatchManager({
|
|
210
|
-
this.idAllocationBatch = new BatchManager({
|
|
211
|
-
canRebase: false,
|
|
212
|
-
ignoreBatchId: true,
|
|
213
|
-
});
|
|
215
|
+
this.mainBatch = new BatchManager({ disableGroupedBatching: false });
|
|
216
|
+
this.blobAttachBatch = new BatchManager({ disableGroupedBatching: true });
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
public get messageCount(): number {
|
|
217
|
-
return this.mainBatch.length + this.blobAttachBatch.length
|
|
220
|
+
return this.mainBatch.length + this.blobAttachBatch.length;
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
public get mainBatchMessageCount(): number {
|
|
@@ -225,19 +228,12 @@ export class Outbox {
|
|
|
225
228
|
return this.blobAttachBatch.length;
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
public get idAllocationBatchMessageCount(): number {
|
|
229
|
-
return this.idAllocationBatch.length;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
231
|
public get isEmpty(): boolean {
|
|
233
232
|
return this.messageCount === 0;
|
|
234
233
|
}
|
|
235
234
|
|
|
236
235
|
public containsUserChanges(): boolean {
|
|
237
|
-
return (
|
|
238
|
-
this.mainBatch.containsUserChanges() || this.blobAttachBatch.containsUserChanges()
|
|
239
|
-
// ID Allocation ops are not user changes
|
|
240
|
-
);
|
|
236
|
+
return this.mainBatch.containsUserChanges() || this.blobAttachBatch.containsUserChanges();
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
/**
|
|
@@ -251,13 +247,11 @@ export class Outbox {
|
|
|
251
247
|
* last message processed by the ContainerRuntime. In the absence of op reentrancy, this
|
|
252
248
|
* pair will remain stable during a single JS turn during which the batch is being built up.
|
|
253
249
|
*/
|
|
254
|
-
private
|
|
250
|
+
private outboxSequenceNumberCoherencyCheck(): void {
|
|
255
251
|
const mainBatchSeqNums = this.mainBatch.sequenceNumbers;
|
|
256
252
|
const blobAttachSeqNums = this.blobAttachBatch.sequenceNumbers;
|
|
257
|
-
const idAllocSeqNums = this.idAllocationBatch.sequenceNumbers;
|
|
258
253
|
assert(
|
|
259
|
-
sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums)
|
|
260
|
-
sequenceNumbersMatch(mainBatchSeqNums, idAllocSeqNums),
|
|
254
|
+
sequenceNumbersMatch(mainBatchSeqNums, blobAttachSeqNums),
|
|
261
255
|
0x58d /* Reference sequence numbers from both batches must be in sync */,
|
|
262
256
|
);
|
|
263
257
|
|
|
@@ -265,8 +259,7 @@ export class Outbox {
|
|
|
265
259
|
|
|
266
260
|
if (
|
|
267
261
|
sequenceNumbersMatch(mainBatchSeqNums, currentSequenceNumbers) &&
|
|
268
|
-
sequenceNumbersMatch(blobAttachSeqNums, currentSequenceNumbers)
|
|
269
|
-
sequenceNumbersMatch(idAllocSeqNums, currentSequenceNumbers)
|
|
262
|
+
sequenceNumbersMatch(blobAttachSeqNums, currentSequenceNumbers)
|
|
270
263
|
) {
|
|
271
264
|
// The reference sequence numbers are stable, there is nothing to do
|
|
272
265
|
return;
|
|
@@ -314,23 +307,17 @@ export class Outbox {
|
|
|
314
307
|
}
|
|
315
308
|
|
|
316
309
|
public submit(message: LocalBatchMessage): void {
|
|
317
|
-
this.
|
|
310
|
+
this.outboxSequenceNumberCoherencyCheck();
|
|
318
311
|
|
|
319
312
|
this.addMessageToBatchManager(this.mainBatch, message);
|
|
320
313
|
}
|
|
321
314
|
|
|
322
315
|
public submitBlobAttach(message: LocalBatchMessage): void {
|
|
323
|
-
this.
|
|
316
|
+
this.outboxSequenceNumberCoherencyCheck();
|
|
324
317
|
|
|
325
318
|
this.addMessageToBatchManager(this.blobAttachBatch, message);
|
|
326
319
|
}
|
|
327
320
|
|
|
328
|
-
public submitIdAllocation(message: LocalBatchMessage): void {
|
|
329
|
-
this.maybeFlushPartialBatch();
|
|
330
|
-
|
|
331
|
-
this.addMessageToBatchManager(this.idAllocationBatch, message);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
321
|
private addMessageToBatchManager(
|
|
335
322
|
batchManager: BatchManager,
|
|
336
323
|
message: LocalBatchMessage,
|
|
@@ -352,11 +339,12 @@ export class Outbox {
|
|
|
352
339
|
public flush(resubmitInfo?: BatchResubmitInfo): void {
|
|
353
340
|
// We have nothing to flush if all batchManagers are empty, and we we're not needing to resubmit an empty batch placeholder
|
|
354
341
|
if (
|
|
355
|
-
this.idAllocationBatch.empty &&
|
|
356
342
|
this.blobAttachBatch.empty &&
|
|
357
343
|
this.mainBatch.empty &&
|
|
358
344
|
resubmitInfo?.batchId === undefined
|
|
359
345
|
) {
|
|
346
|
+
// Note that it's possible that there are unfinalized ranges in the ID Compressor,
|
|
347
|
+
// but there's no urgency to flush those if they're not referenced in any messages.
|
|
360
348
|
return;
|
|
361
349
|
}
|
|
362
350
|
|
|
@@ -368,8 +356,7 @@ export class Outbox {
|
|
|
368
356
|
}
|
|
369
357
|
|
|
370
358
|
private flushAll(resubmitInfo?: BatchResubmitInfo): void {
|
|
371
|
-
const allBatchesEmpty =
|
|
372
|
-
this.idAllocationBatch.empty && this.blobAttachBatch.empty && this.mainBatch.empty;
|
|
359
|
+
const allBatchesEmpty = this.blobAttachBatch.empty && this.mainBatch.empty;
|
|
373
360
|
if (allBatchesEmpty) {
|
|
374
361
|
// If we're resubmitting with a batchId and all batches are empty, we need to flush an empty batch.
|
|
375
362
|
// Note that we currently resubmit one batch at a time, so on resubmit, 1 of the 2 batches will *always* be empty.
|
|
@@ -382,22 +369,8 @@ export class Outbox {
|
|
|
382
369
|
return;
|
|
383
370
|
}
|
|
384
371
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.flushInternal({
|
|
388
|
-
batchManager: this.idAllocationBatch,
|
|
389
|
-
// Note: For now, we will never stage ID Allocation messages.
|
|
390
|
-
// They won't contain personal info and no harm in extra allocations in case of discarding the staged changes
|
|
391
|
-
});
|
|
392
|
-
this.flushInternal({
|
|
393
|
-
batchManager: this.blobAttachBatch,
|
|
394
|
-
disableGroupedBatching: true,
|
|
395
|
-
resubmitInfo,
|
|
396
|
-
});
|
|
397
|
-
this.flushInternal({
|
|
398
|
-
batchManager: this.mainBatch,
|
|
399
|
-
resubmitInfo,
|
|
400
|
-
});
|
|
372
|
+
this.flushInternal(this.blobAttachBatch, resubmitInfo);
|
|
373
|
+
this.flushInternal(this.mainBatch, resubmitInfo);
|
|
401
374
|
}
|
|
402
375
|
|
|
403
376
|
private flushEmptyBatch(
|
|
@@ -429,17 +402,15 @@ export class Outbox {
|
|
|
429
402
|
return;
|
|
430
403
|
}
|
|
431
404
|
|
|
432
|
-
private flushInternal(
|
|
433
|
-
batchManager: BatchManager
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}): void {
|
|
437
|
-
const { batchManager, disableGroupedBatching = false, resubmitInfo } = params;
|
|
405
|
+
private flushInternal(
|
|
406
|
+
batchManager: BatchManager,
|
|
407
|
+
resubmitInfo?: BatchResubmitInfo, // undefined if not resubmitting
|
|
408
|
+
): void {
|
|
438
409
|
if (batchManager.empty) {
|
|
439
410
|
return;
|
|
440
411
|
}
|
|
441
412
|
|
|
442
|
-
|
|
413
|
+
let rawBatch = batchManager.popBatch();
|
|
443
414
|
|
|
444
415
|
// On resubmit we use the original batch's staged state, so these should match as well.
|
|
445
416
|
const staged = rawBatch.staged === true;
|
|
@@ -449,13 +420,15 @@ export class Outbox {
|
|
|
449
420
|
);
|
|
450
421
|
|
|
451
422
|
const groupingEnabled =
|
|
452
|
-
!disableGroupedBatching &&
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
423
|
+
!batchManager.options.disableGroupedBatching &&
|
|
424
|
+
this.params.groupingManager.groupedBatchingEnabled();
|
|
425
|
+
if (rawBatch.hasReentrantOps === true) {
|
|
426
|
+
assert(
|
|
427
|
+
resubmitInfo === undefined,
|
|
428
|
+
0xcf2 /* Re-submitting a batch with reentrant ops is not supported */,
|
|
429
|
+
);
|
|
458
430
|
assert(!this.rebasing, 0x6fa /* A rebased batch should never have reentrant ops */);
|
|
431
|
+
// Rebase the current batch (resubmit the ops one-by-one) and then reinvoke flushInternal.
|
|
459
432
|
// If a batch contains reentrant ops (ops created as a result from processing another op)
|
|
460
433
|
// it needs to be rebased so that we can ensure consistent reference sequence numbers
|
|
461
434
|
// and eventual consistency at the DDS level.
|
|
@@ -466,11 +439,22 @@ export class Outbox {
|
|
|
466
439
|
return;
|
|
467
440
|
}
|
|
468
441
|
|
|
469
|
-
let clientSequenceNumber: number | undefined;
|
|
470
442
|
// Did we disconnect? (i.e. is shouldSend false?)
|
|
471
443
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
472
444
|
// Because flush() is a task that executes async (on clean stack), we can get here in disconnected state.
|
|
473
|
-
|
|
445
|
+
const shouldSendNow = this.params.shouldSend() && !staged;
|
|
446
|
+
let clientSequenceNumber: number | undefined;
|
|
447
|
+
if (shouldSendNow) {
|
|
448
|
+
// Generate ID Allocation op just-in-time, after rebase (if any), and before addBatchMetadata,
|
|
449
|
+
// so that the prepended idAllocMsg is correctly marked as the first op in the batch.
|
|
450
|
+
// This ensures the refSeq is correct (matching the rest of the batch) and that
|
|
451
|
+
// ID ranges aren't lost during rebase (since reSubmit drops IdAllocation ops).
|
|
452
|
+
// Only generate for non-staged batches — ID alloc ops are always non-staged.
|
|
453
|
+
const idAllocMsg = this.params.generateIdAllocationOp();
|
|
454
|
+
if (idAllocMsg !== undefined) {
|
|
455
|
+
rawBatch = { ...rawBatch, messages: [idAllocMsg, ...rawBatch.messages] };
|
|
456
|
+
}
|
|
457
|
+
addBatchMetadata(rawBatch, resubmitInfo?.batchId);
|
|
474
458
|
const virtualizedBatch = this.virtualizeBatch(rawBatch, groupingEnabled);
|
|
475
459
|
|
|
476
460
|
clientSequenceNumber = this.sendBatch(virtualizedBatch);
|
|
@@ -478,13 +462,14 @@ export class Outbox {
|
|
|
478
462
|
clientSequenceNumber === undefined || clientSequenceNumber >= 0,
|
|
479
463
|
0x9d2 /* unexpected negative clientSequenceNumber (empty batch should yield undefined) */,
|
|
480
464
|
);
|
|
465
|
+
} else {
|
|
466
|
+
addBatchMetadata(rawBatch, resubmitInfo?.batchId);
|
|
481
467
|
}
|
|
482
468
|
|
|
483
469
|
this.params.pendingStateManager.onFlushBatch(
|
|
484
470
|
rawBatch.messages,
|
|
485
471
|
clientSequenceNumber,
|
|
486
472
|
staged,
|
|
487
|
-
batchManager.options.ignoreBatchId,
|
|
488
473
|
);
|
|
489
474
|
}
|
|
490
475
|
|
|
@@ -496,7 +481,6 @@ export class Outbox {
|
|
|
496
481
|
*/
|
|
497
482
|
private rebase(rawBatch: LocalBatch, batchManager: BatchManager): void {
|
|
498
483
|
assert(!this.rebasing, 0x6fb /* Reentrancy */);
|
|
499
|
-
assert(batchManager.options.canRebase, 0x9a7 /* BatchManager does not support rebase */);
|
|
500
484
|
|
|
501
485
|
this.rebasing = true;
|
|
502
486
|
const squash = false;
|
|
@@ -523,7 +507,7 @@ export class Outbox {
|
|
|
523
507
|
this.batchRebasesToReport--;
|
|
524
508
|
}
|
|
525
509
|
|
|
526
|
-
this.flushInternal(
|
|
510
|
+
this.flushInternal(batchManager);
|
|
527
511
|
this.rebasing = false;
|
|
528
512
|
}
|
|
529
513
|
|
|
@@ -664,7 +648,6 @@ export class Outbox {
|
|
|
664
648
|
*/
|
|
665
649
|
public getBatchCheckpoints(): {
|
|
666
650
|
mainBatch: IBatchCheckpoint;
|
|
667
|
-
idAllocationBatch: IBatchCheckpoint;
|
|
668
651
|
blobAttachBatch: IBatchCheckpoint;
|
|
669
652
|
} {
|
|
670
653
|
// This variable is declared with a specific type so that we have a standard import of the IBatchCheckpoint type.
|
|
@@ -672,7 +655,6 @@ export class Outbox {
|
|
|
672
655
|
const mainBatch: IBatchCheckpoint = this.mainBatch.checkpoint();
|
|
673
656
|
return {
|
|
674
657
|
mainBatch,
|
|
675
|
-
idAllocationBatch: this.idAllocationBatch.checkpoint(),
|
|
676
658
|
blobAttachBatch: this.blobAttachBatch.checkpoint(),
|
|
677
659
|
};
|
|
678
660
|
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -84,10 +84,6 @@ export interface IPendingMessage {
|
|
|
84
84
|
* length of the batch (how many runtime messages here)
|
|
85
85
|
*/
|
|
86
86
|
length: number;
|
|
87
|
-
/**
|
|
88
|
-
* If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored
|
|
89
|
-
*/
|
|
90
|
-
ignoreBatchId?: boolean;
|
|
91
87
|
/**
|
|
92
88
|
* If true, this batch is staged and should not actually be submitted on replayPendingStates.
|
|
93
89
|
*/
|
|
@@ -407,13 +403,11 @@ export class PendingStateManager implements IDisposable {
|
|
|
407
403
|
* @param clientSequenceNumber - The CSN of the first message in the batch,
|
|
408
404
|
* or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
|
|
409
405
|
* @param staged - Indicates whether batch is staged (not to be submitted while runtime is in Staging Mode)
|
|
410
|
-
* @param ignoreBatchId - Whether to ignore the batchId in the batchStartInfo
|
|
411
406
|
*/
|
|
412
407
|
public onFlushBatch(
|
|
413
408
|
batch: LocalBatchMessage[] | [LocalEmptyBatchPlaceholder],
|
|
414
409
|
clientSequenceNumber: number | undefined,
|
|
415
410
|
staged: boolean,
|
|
416
|
-
ignoreBatchId?: boolean,
|
|
417
411
|
): void {
|
|
418
412
|
// clientId and batchStartCsn are used for generating the batchId so we can detect container forks
|
|
419
413
|
// where this batch was submitted by two different clients rehydrating from the same local state.
|
|
@@ -431,7 +425,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
431
425
|
clientId !== undefined,
|
|
432
426
|
0xa33 /* clientId (from stateHandler) could only be undefined if we've never connected, but we have a CSN so we know that's not the case */,
|
|
433
427
|
);
|
|
434
|
-
const batchInfo = { clientId, batchStartCsn, length: batch.length,
|
|
428
|
+
const batchInfo = { clientId, batchStartCsn, length: batch.length, staged };
|
|
435
429
|
for (const message of batch) {
|
|
436
430
|
const {
|
|
437
431
|
runtimeOp,
|
|
@@ -512,23 +506,14 @@ export class PendingStateManager implements IDisposable {
|
|
|
512
506
|
* @returns whether the batch IDs match
|
|
513
507
|
*/
|
|
514
508
|
private remoteBatchMatchesPendingBatch(remoteBatchStart: BatchStartInfo): boolean {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const firstIndexUsingBatchId = Array.from({
|
|
518
|
-
length: this.pendingMessages.length,
|
|
519
|
-
}).findIndex((_, i) => this.pendingMessages.get(i)?.batchInfo.ignoreBatchId !== true);
|
|
520
|
-
const pendingMessageUsingBatchId =
|
|
521
|
-
firstIndexUsingBatchId === -1
|
|
522
|
-
? undefined
|
|
523
|
-
: this.pendingMessages.get(firstIndexUsingBatchId);
|
|
524
|
-
|
|
525
|
-
if (pendingMessageUsingBatchId === undefined) {
|
|
509
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
510
|
+
if (pendingMessage === undefined) {
|
|
526
511
|
return false;
|
|
527
512
|
}
|
|
528
513
|
|
|
529
514
|
// We must compare the effective batch IDs, since one of these ops
|
|
530
515
|
// may have been the original, not resubmitted, so wouldn't have its batch ID stamped yet.
|
|
531
|
-
const pendingBatchId = getEffectiveBatchId(
|
|
516
|
+
const pendingBatchId = getEffectiveBatchId(pendingMessage);
|
|
532
517
|
const inboundBatchId = getEffectiveBatchId(remoteBatchStart);
|
|
533
518
|
|
|
534
519
|
return pendingBatchId === inboundBatchId;
|
|
@@ -810,10 +795,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
810
795
|
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
811
796
|
|
|
812
797
|
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
813
|
-
const batchId =
|
|
814
|
-
pendingMessage.batchInfo.ignoreBatchId === true
|
|
815
|
-
? undefined
|
|
816
|
-
: getEffectiveBatchId(pendingMessage);
|
|
798
|
+
const batchId = getEffectiveBatchId(pendingMessage);
|
|
817
799
|
|
|
818
800
|
const staged = pendingMessage.batchInfo.staged;
|
|
819
801
|
|