@fluidframework/container-runtime 0.58.1000 → 0.58.1001
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/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +1 -1
- package/dist/containerRuntime.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 +15 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +78 -72
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +1 -1
- package/lib/containerRuntime.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 +15 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +78 -72
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +11 -11
- package/src/containerRuntime.ts +1 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +102 -86
|
@@ -37,8 +37,8 @@ export interface IPendingFlushMode {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* This represents
|
|
41
|
-
* flush
|
|
40
|
+
* This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime
|
|
41
|
+
* to flush pending messages.
|
|
42
42
|
*/
|
|
43
43
|
export interface IPendingFlush {
|
|
44
44
|
type: "flush";
|
|
@@ -86,6 +86,13 @@ export class PendingStateManager implements IDisposable {
|
|
|
86
86
|
// the correct batch metadata.
|
|
87
87
|
private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* This tracks the flush mode for the next message in the pending state queue. When replaying messages, we need to
|
|
91
|
+
* first set the flush mode to this value and then send ops. It is important to do this info because the flush
|
|
92
|
+
* mode could have been updated.
|
|
93
|
+
*/
|
|
94
|
+
private flushModeForNextMessage: FlushMode;
|
|
95
|
+
|
|
89
96
|
private clientId: string | undefined;
|
|
90
97
|
|
|
91
98
|
private get connected(): boolean {
|
|
@@ -115,19 +122,23 @@ export class PendingStateManager implements IDisposable {
|
|
|
115
122
|
constructor(
|
|
116
123
|
private readonly containerRuntime: ContainerRuntime,
|
|
117
124
|
private readonly applyStashedOp: (type, content) => Promise<unknown>,
|
|
118
|
-
|
|
125
|
+
initialFlushMode: FlushMode,
|
|
126
|
+
initialLocalState: IPendingLocalState | undefined,
|
|
119
127
|
) {
|
|
120
|
-
this.initialStates = new Deque<IPendingState>(
|
|
128
|
+
this.initialStates = new Deque<IPendingState>(initialLocalState?.pendingStates ?? []);
|
|
121
129
|
|
|
122
|
-
if (
|
|
123
|
-
if (
|
|
124
|
-
this.previousClientIds.add(
|
|
130
|
+
if (initialLocalState) {
|
|
131
|
+
if (initialLocalState?.clientId) {
|
|
132
|
+
this.previousClientIds.add(initialLocalState.clientId);
|
|
125
133
|
}
|
|
126
134
|
// get stashed op count and client sequence number of first op
|
|
127
|
-
const messages =
|
|
135
|
+
const messages = initialLocalState.pendingStates
|
|
128
136
|
.filter((state) => state.type === "message") as IPendingMessage[];
|
|
129
137
|
this.firstStashedCSN = messages[0].clientSequenceNumber;
|
|
130
138
|
}
|
|
139
|
+
|
|
140
|
+
this.flushModeForNextMessage = initialFlushMode;
|
|
141
|
+
this.onFlushModeUpdated(initialFlushMode);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
public get disposed() { return this.disposeOnce.evaluated; }
|
|
@@ -169,54 +180,27 @@ export class PendingStateManager implements IDisposable {
|
|
|
169
180
|
* @param flushMode - The flushMode that was updated.
|
|
170
181
|
*/
|
|
171
182
|
public onFlushModeUpdated(flushMode: FlushMode) {
|
|
172
|
-
|
|
173
|
-
const previousState = this.pendingStates.peekBack();
|
|
174
|
-
|
|
175
|
-
// We don't have to track a previous "flush" state because FlushMode.Immediate flushes the messages. So,
|
|
176
|
-
// just tracking this FlushMode.Immediate is enough.
|
|
177
|
-
if (previousState?.type === "flush") {
|
|
178
|
-
this.pendingStates.removeBack();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// If no messages were sent between FlushMode.TurnBased and FlushMode.Immediate,
|
|
182
|
-
// then we do not have to track both these states.
|
|
183
|
-
// Remove FlushMode.TurnBased from the pending queue and return.
|
|
184
|
-
if (previousState?.type === "flushMode" && previousState.flushMode === FlushMode.TurnBased) {
|
|
185
|
-
this.pendingStates.removeBack();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const pendingFlushMode: IPendingFlushMode = {
|
|
191
|
-
type: "flushMode",
|
|
192
|
-
flushMode,
|
|
193
|
-
};
|
|
194
|
-
this.pendingStates.push(pendingFlushMode);
|
|
183
|
+
this.pendingStates.push({ type: "flushMode", flushMode });
|
|
195
184
|
}
|
|
196
185
|
|
|
197
186
|
/**
|
|
198
187
|
* Called when flush() is called on the ContainerRuntime to manually flush messages.
|
|
199
188
|
*/
|
|
200
189
|
public onFlush() {
|
|
201
|
-
// If the FlushMode is Immediate, we
|
|
202
|
-
// is
|
|
190
|
+
// If the FlushMode is Immediate, we don't need to track an explicit flush call because every message is
|
|
191
|
+
// automatically flushed. So, flush is a no-op.
|
|
203
192
|
if (this.containerRuntime.flushMode === FlushMode.Immediate) {
|
|
204
193
|
return;
|
|
205
194
|
}
|
|
206
195
|
|
|
207
|
-
// If the previous state is not a message,
|
|
196
|
+
// If the previous state is not a message, flush is a no-op.
|
|
208
197
|
const previousState = this.pendingStates.peekBack();
|
|
209
198
|
if (previousState?.type !== "message") {
|
|
210
199
|
return;
|
|
211
200
|
}
|
|
212
201
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
// new one.
|
|
216
|
-
const pendingFlush: IPendingFlush = {
|
|
217
|
-
type: "flush",
|
|
218
|
-
};
|
|
219
|
-
this.pendingStates.push(pendingFlush);
|
|
202
|
+
// An explicit flush is interesting and is tracked only if there are messages sent in TurnBased mode.
|
|
203
|
+
this.pendingStates.push({ type: "flush" });
|
|
220
204
|
}
|
|
221
205
|
|
|
222
206
|
/**
|
|
@@ -249,7 +233,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
249
233
|
* Processes a local message once it's ack'd by the server to verify that there was no data corruption and that
|
|
250
234
|
* the batch information was preserved for batch messages. Also process remote messages that might have been
|
|
251
235
|
* sent from a previous container.
|
|
252
|
-
* @param message - The
|
|
236
|
+
* @param message - The message that got ack'd and needs to be processed.
|
|
253
237
|
*/
|
|
254
238
|
public processMessage(message: ISequencedDocumentMessage, local: boolean) {
|
|
255
239
|
// Do not process chunked ops until all pieces are available.
|
|
@@ -324,7 +308,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
324
308
|
/**
|
|
325
309
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
326
310
|
* the batch information was preserved for batch messages.
|
|
327
|
-
* @param message - The
|
|
311
|
+
* @param message - The message that got ack'd and needs to be processed.
|
|
328
312
|
*/
|
|
329
313
|
private processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {
|
|
330
314
|
// Pre-processing part - This may be the start of a batch.
|
|
@@ -353,9 +337,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
353
337
|
this.pendingMessagesCount--;
|
|
354
338
|
|
|
355
339
|
// Post-processing part - If we are processing a batch then this could be the last message in the batch.
|
|
356
|
-
|
|
357
|
-
this.maybeProcessBatchEnd(message);
|
|
358
|
-
}
|
|
340
|
+
this.maybeProcessBatchEnd(message);
|
|
359
341
|
|
|
360
342
|
return pendingState.localOpMetadata;
|
|
361
343
|
}
|
|
@@ -365,46 +347,82 @@ export class PendingStateManager implements IDisposable {
|
|
|
365
347
|
* @param message - The message that is being processed.
|
|
366
348
|
*/
|
|
367
349
|
private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
350
|
+
// Tracks the last FlushMode that was set before this message was sent.
|
|
351
|
+
let pendingFlushMode: FlushMode | undefined;
|
|
352
|
+
// Tracks whether a flush was called before this message was sent.
|
|
353
|
+
let pendingFlush: boolean = false;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* We are checking if the next message is the start of a batch. It can happen in the following scenarios:
|
|
357
|
+
* 1. The FlushMode was set to TurnBased before this message was sent.
|
|
358
|
+
* 2. The FlushMode was already TurnBased and a flush was called before this message was sent. This essentially
|
|
359
|
+
* means that the flush marked the end of a previous batch and beginning of a new batch.
|
|
360
|
+
*
|
|
361
|
+
* Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was
|
|
362
|
+
* updated a bunch of times without sending any messages.
|
|
363
|
+
*/
|
|
364
|
+
let nextPendingState = this.peekNextPendingState();
|
|
365
|
+
while (nextPendingState.type !== "message") {
|
|
366
|
+
if (nextPendingState.type === "flushMode") {
|
|
367
|
+
pendingFlushMode = nextPendingState.flushMode;
|
|
368
|
+
}
|
|
369
|
+
if (nextPendingState.type === "flush") {
|
|
370
|
+
pendingFlush = true;
|
|
371
|
+
}
|
|
372
|
+
this.pendingStates.shift();
|
|
373
|
+
nextPendingState = this.peekNextPendingState();
|
|
371
374
|
}
|
|
372
375
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (pendingState.type === "flushMode") {
|
|
376
|
-
assert(pendingState.flushMode === FlushMode.TurnBased,
|
|
377
|
-
0x16a /* "Flush mode should be manual when processing batch begin" */);
|
|
376
|
+
if (pendingFlushMode !== undefined) {
|
|
377
|
+
this.flushModeForNextMessage = pendingFlushMode;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.pendingBatchBeginMessage = message;
|
|
386
|
-
this.isProcessingBatch = true;
|
|
380
|
+
// If the FlushMode was set to Immediate before this message was sent, this message won't be a batch message
|
|
381
|
+
// because in Immediate mode, every message is flushed individually.
|
|
382
|
+
if (pendingFlushMode === FlushMode.Immediate) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
387
385
|
|
|
388
|
-
|
|
389
|
-
|
|
386
|
+
/**
|
|
387
|
+
* This message is the first in a batch if before it was sent either the FlushMode was set to TurnBased or there
|
|
388
|
+
* was an explicit flush call. Note that a flush call is tracked only in TurnBased mode and it indicates the end
|
|
389
|
+
* of one batch and beginning of another.
|
|
390
|
+
*/
|
|
391
|
+
if (pendingFlushMode === FlushMode.TurnBased || pendingFlush) {
|
|
392
|
+
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
393
|
+
assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,
|
|
394
|
+
0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
395
|
+
|
|
396
|
+
// Set the pending batch state indicating we have started processing a batch.
|
|
397
|
+
this.pendingBatchBeginMessage = message;
|
|
398
|
+
this.isProcessingBatch = true;
|
|
399
|
+
}
|
|
390
400
|
}
|
|
391
401
|
|
|
402
|
+
/**
|
|
403
|
+
* This message could be the last message in batch. If so, clear batch state since the batch is complete.
|
|
404
|
+
* @param message - The message that is being processed.
|
|
405
|
+
*/
|
|
392
406
|
private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {
|
|
393
|
-
|
|
394
|
-
if (nextPendingState.type !== "flush" && nextPendingState.type !== "flushMode") {
|
|
407
|
+
if (!this.isProcessingBatch) {
|
|
395
408
|
return;
|
|
396
409
|
}
|
|
397
410
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// beginning of a new one. So, it will removed when the next batch begin is processed.
|
|
402
|
-
if (nextPendingState.type === "flushMode") {
|
|
403
|
-
assert(nextPendingState.flushMode === FlushMode.Immediate,
|
|
404
|
-
0x16c /* "Flush mode is set to TurnBased in the middle of processing a batch" */);
|
|
405
|
-
this.pendingStates.shift();
|
|
411
|
+
const nextPendingState = this.peekNextPendingState();
|
|
412
|
+
if (nextPendingState.type === "message") {
|
|
413
|
+
return;
|
|
406
414
|
}
|
|
407
415
|
|
|
416
|
+
/**
|
|
417
|
+
* We are in the middle of processing a batch. The batch ends when we see an explicit flush. We should never see
|
|
418
|
+
* a FlushMode before flush. This is true because we track batches only when FlushMode is TurnBased and in this
|
|
419
|
+
* mode, a batch ends either by calling flush or by changing the mode to Immediate which also triggers a flush.
|
|
420
|
+
*/
|
|
421
|
+
assert(
|
|
422
|
+
nextPendingState.type !== "flushMode",
|
|
423
|
+
0x2bd /* "We should not see a pending FlushMode until we see a flush when processing a batch" */,
|
|
424
|
+
);
|
|
425
|
+
|
|
408
426
|
// There should be a pending batch begin message.
|
|
409
427
|
assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
|
|
410
428
|
|
|
@@ -462,6 +480,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
462
480
|
// Save the current FlushMode so that we can revert it back after replaying the states.
|
|
463
481
|
const savedFlushMode = this.containerRuntime.flushMode;
|
|
464
482
|
|
|
483
|
+
// Set the flush mode for the next message. This step is important because the flush mode may have been changed
|
|
484
|
+
// after the next pending message was sent.
|
|
485
|
+
this.containerRuntime.setFlushMode(this.flushModeForNextMessage);
|
|
486
|
+
|
|
465
487
|
// Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were
|
|
466
488
|
// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue
|
|
467
489
|
// which must not be replayed.
|
|
@@ -470,23 +492,17 @@ export class PendingStateManager implements IDisposable {
|
|
|
470
492
|
const pendingState = this.pendingStates.shift()!;
|
|
471
493
|
switch (pendingState.type) {
|
|
472
494
|
case "message":
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
pendingState.opMetadata);
|
|
479
|
-
}
|
|
495
|
+
this.containerRuntime.reSubmitFn(
|
|
496
|
+
pendingState.messageType,
|
|
497
|
+
pendingState.content,
|
|
498
|
+
pendingState.localOpMetadata,
|
|
499
|
+
pendingState.opMetadata);
|
|
480
500
|
break;
|
|
481
501
|
case "flushMode":
|
|
482
|
-
|
|
483
|
-
this.containerRuntime.setFlushMode(pendingState.flushMode);
|
|
484
|
-
}
|
|
502
|
+
this.containerRuntime.setFlushMode(pendingState.flushMode);
|
|
485
503
|
break;
|
|
486
504
|
case "flush":
|
|
487
|
-
|
|
488
|
-
this.containerRuntime.flush();
|
|
489
|
-
}
|
|
505
|
+
this.containerRuntime.flush();
|
|
490
506
|
break;
|
|
491
507
|
default:
|
|
492
508
|
break;
|