@fluidframework/container-runtime 0.51.0 → 0.52.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/dist/connectionTelemetry.d.ts +4 -0
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +6 -2
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +23 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +173 -93
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +7 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.js +2 -1
- package/dist/dataStores.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/summarizerTypes.d.ts +13 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +3 -0
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.js +2 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts +4 -0
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +5 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +23 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +177 -97
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +7 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.js +2 -1
- package/lib/dataStores.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/summarizerTypes.d.ts +13 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +3 -0
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.js +2 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js.map +1 -1
- package/package.json +16 -16
- package/src/connectionTelemetry.ts +6 -1
- package/src/containerRuntime.ts +220 -110
- package/src/dataStoreContext.ts +7 -7
- package/src/packageVersion.ts +1 -1
- package/src/summarizerTypes.ts +16 -3
- package/src/summaryManager.ts +8 -3
package/src/containerRuntime.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
IResponse,
|
|
15
15
|
IFluidHandle,
|
|
16
16
|
IFluidConfiguration,
|
|
17
|
+
FluidObject,
|
|
17
18
|
} from "@fluidframework/core-interfaces";
|
|
18
19
|
import {
|
|
19
20
|
IAudience,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
Trace,
|
|
37
38
|
TypedEventEmitter,
|
|
38
39
|
unreachableCase,
|
|
40
|
+
performance,
|
|
39
41
|
} from "@fluidframework/common-utils";
|
|
40
42
|
import {
|
|
41
43
|
ChildLogger,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
} from "@fluidframework/telemetry-utils";
|
|
47
49
|
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
48
50
|
import { readAndParse, BlobAggregationStorage } from "@fluidframework/driver-utils";
|
|
49
|
-
import { DataCorruptionError, GenericError } from "@fluidframework/container-utils";
|
|
51
|
+
import { DataCorruptionError, GenericError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
|
|
50
52
|
import {
|
|
51
53
|
BlobTreeEntry,
|
|
52
54
|
TreeTreeEntry,
|
|
@@ -102,7 +104,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
|
102
104
|
import { Summarizer } from "./summarizer";
|
|
103
105
|
import { formRequestSummarizerFn, ISummarizerRequestOptions, SummaryManager } from "./summaryManager";
|
|
104
106
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
105
|
-
import { ReportOpPerfTelemetry } from "./connectionTelemetry";
|
|
107
|
+
import { ReportOpPerfTelemetry, latencyThreshold } from "./connectionTelemetry";
|
|
106
108
|
import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
|
|
107
109
|
import { pkgVersion } from "./packageVersion";
|
|
108
110
|
import { BlobManager, IBlobManagerLoadInfo } from "./blobManager";
|
|
@@ -223,6 +225,12 @@ export interface IGCRuntimeOptions {
|
|
|
223
225
|
|
|
224
226
|
export interface ISummaryRuntimeOptions {
|
|
225
227
|
/**
|
|
228
|
+
* Flag that disables summaries if it is set to true.
|
|
229
|
+
*/
|
|
230
|
+
disableSummaries?: boolean;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @deprecated - To disable summaries, please set disableSummaries===true.
|
|
226
234
|
* Flag that will generate summaries if connected to a service that supports them.
|
|
227
235
|
* This defaults to true and must be explicitly set to false to disable.
|
|
228
236
|
*/
|
|
@@ -269,9 +277,9 @@ export interface IContainerRuntimeOptions {
|
|
|
269
277
|
loadSequenceNumberVerification?: "close" | "log" | "bypass";
|
|
270
278
|
}
|
|
271
279
|
|
|
272
|
-
|
|
280
|
+
type IRuntimeMessageMetadata = undefined | {
|
|
273
281
|
batch?: boolean;
|
|
274
|
-
}
|
|
282
|
+
};
|
|
275
283
|
|
|
276
284
|
// Local storage key to set the default flush mode to TurnBased
|
|
277
285
|
const turnBasedFlushModeKey = "FluidFlushModeTurnBased";
|
|
@@ -311,23 +319,20 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
|
311
319
|
return message;
|
|
312
320
|
}
|
|
313
321
|
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
/**
|
|
323
|
+
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
324
|
+
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
325
|
+
*/
|
|
326
|
+
class ScheduleManagerCore {
|
|
316
327
|
private pauseSequenceNumber: number | undefined;
|
|
317
|
-
private
|
|
328
|
+
private currentBatchClientId: string | undefined;
|
|
318
329
|
private localPaused = false;
|
|
319
|
-
private
|
|
330
|
+
private timePaused = 0;
|
|
320
331
|
|
|
321
332
|
constructor(
|
|
322
333
|
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
323
|
-
private readonly emitter: EventEmitter,
|
|
324
334
|
private readonly logger: ITelemetryLogger,
|
|
325
335
|
) {
|
|
326
|
-
this.deltaScheduler = new DeltaScheduler(
|
|
327
|
-
this.deltaManager,
|
|
328
|
-
ChildLogger.create(this.logger, "DeltaScheduler"),
|
|
329
|
-
);
|
|
330
|
-
|
|
331
336
|
// Listen for delta manager sends and add batch metadata to messages
|
|
332
337
|
this.deltaManager.on("prepareSend", (messages: IDocumentMessage[]) => {
|
|
333
338
|
if (messages.length === 0) {
|
|
@@ -336,7 +341,7 @@ export class ScheduleManager {
|
|
|
336
341
|
|
|
337
342
|
// First message will have the batch flag set to true if doing a batched send
|
|
338
343
|
const firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;
|
|
339
|
-
if (!firstMessageMetadata
|
|
344
|
+
if (!firstMessageMetadata?.batch) {
|
|
340
345
|
return;
|
|
341
346
|
}
|
|
342
347
|
|
|
@@ -356,32 +361,178 @@ export class ScheduleManager {
|
|
|
356
361
|
"push",
|
|
357
362
|
(message: ISequencedDocumentMessage) => {
|
|
358
363
|
this.trackPending(message);
|
|
359
|
-
this.updatePauseState(message.sequenceNumber);
|
|
360
364
|
});
|
|
361
365
|
|
|
366
|
+
// Start with baseline - empty inbound queue.
|
|
367
|
+
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
368
|
+
|
|
362
369
|
const allPending = this.deltaManager.inbound.toArray();
|
|
363
370
|
for (const pending of allPending) {
|
|
364
371
|
this.trackPending(pending);
|
|
365
372
|
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The only public function in this class - called when we processed an op,
|
|
377
|
+
* to make decision if op processing should be paused or not afer that.
|
|
378
|
+
*/
|
|
379
|
+
public afterOpProcessing(sequenceNumber: number) {
|
|
380
|
+
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
381
|
+
|
|
382
|
+
// If the inbound queue is ever empty, nothing to do!
|
|
383
|
+
if (this.deltaManager.inbound.length === 0) {
|
|
384
|
+
assert(this.pauseSequenceNumber === undefined,
|
|
385
|
+
0x295 /* "there should be no pending batch if we have no ops" */);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
366
388
|
|
|
367
|
-
//
|
|
368
|
-
|
|
389
|
+
// The queue is
|
|
390
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
391
|
+
// - here (processing ops until reaching start of incomplete batch)
|
|
392
|
+
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
393
|
+
// 2. resumed when batch end comes in (in trackPending())
|
|
394
|
+
|
|
395
|
+
// do we have incomplete batch to worry about?
|
|
396
|
+
if (this.pauseSequenceNumber !== undefined) {
|
|
397
|
+
assert(sequenceNumber < this.pauseSequenceNumber,
|
|
398
|
+
0x296 /* "we should never start processing incomplete batch!" */);
|
|
399
|
+
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
400
|
+
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
401
|
+
this.pauseQueue();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
369
404
|
}
|
|
370
405
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
406
|
+
private pauseQueue() {
|
|
407
|
+
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
408
|
+
this.localPaused = true;
|
|
409
|
+
this.timePaused = performance.now();
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
411
|
+
this.deltaManager.inbound.pause();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private resumeQueue(startBatch: number, endBatch: number) {
|
|
415
|
+
// Return early if no change in value
|
|
416
|
+
if (!this.localPaused) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.localPaused = false;
|
|
421
|
+
const duration = performance.now() - this.timePaused;
|
|
422
|
+
// Random round number - we want to know when batch waiting paused op processing.
|
|
423
|
+
if (duration > latencyThreshold) {
|
|
424
|
+
this.logger.sendErrorEvent({
|
|
425
|
+
eventName: "MaxBatchWaitTimeExceeded",
|
|
426
|
+
duration,
|
|
427
|
+
sequenceNumber: endBatch,
|
|
428
|
+
length: endBatch - startBatch,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
this.deltaManager.inbound.resume();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Called for each incoming op (i.e. inbound "push" notification)
|
|
436
|
+
*/
|
|
437
|
+
private trackPending(message: ISequencedDocumentMessage) {
|
|
438
|
+
assert(this.deltaManager.inbound.length !== 0,
|
|
439
|
+
0x298 /* "we have something in the queue that generates this event" */);
|
|
440
|
+
|
|
441
|
+
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),
|
|
442
|
+
0x299 /* "non-synchronized state" */);
|
|
443
|
+
|
|
444
|
+
const metadata = message.metadata as IRuntimeMessageMetadata;
|
|
445
|
+
const batchMetadata = metadata?.batch;
|
|
446
|
+
|
|
447
|
+
// Protocol messages are never part of a runtime batch of messages
|
|
448
|
+
if (!isRuntimeMessage(message)) {
|
|
449
|
+
// Protocol messages should never show up in the middle of the batch!
|
|
450
|
+
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
451
|
+
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
452
|
+
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
457
|
+
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
462
|
+
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
463
|
+
// the previous one
|
|
464
|
+
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
465
|
+
if (this.currentBatchClientId !== message.clientId) {
|
|
466
|
+
// "Batch not closed, yet message from another client!"
|
|
467
|
+
throw new DataCorruptionError(
|
|
468
|
+
"OpBatchIncomplete",
|
|
469
|
+
{
|
|
470
|
+
batchClientId: this.currentBatchClientId,
|
|
471
|
+
...extractSafePropertiesFromMessage(message),
|
|
472
|
+
});
|
|
384
473
|
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// The queue is
|
|
477
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
478
|
+
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
479
|
+
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
480
|
+
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
481
|
+
|
|
482
|
+
if (batchMetadata) {
|
|
483
|
+
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
484
|
+
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
485
|
+
this.pauseSequenceNumber = message.sequenceNumber;
|
|
486
|
+
this.currentBatchClientId = message.clientId;
|
|
487
|
+
// Start of the batch
|
|
488
|
+
// Only pause processing if queue has no other ops!
|
|
489
|
+
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
490
|
+
if (this.deltaManager.inbound.length === 1) {
|
|
491
|
+
this.pauseQueue();
|
|
492
|
+
}
|
|
493
|
+
} else if (batchMetadata === false) {
|
|
494
|
+
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
495
|
+
// Batch is complete, we can process it!
|
|
496
|
+
this.resumeQueue(this.pauseSequenceNumber, message.sequenceNumber);
|
|
497
|
+
this.pauseSequenceNumber = undefined;
|
|
498
|
+
this.currentBatchClientId = undefined;
|
|
499
|
+
} else {
|
|
500
|
+
// Continuation of current batch. Do nothing
|
|
501
|
+
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* This class has the following responsibilities:
|
|
508
|
+
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
509
|
+
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
510
|
+
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
511
|
+
* unless all ops of the batch are in.
|
|
512
|
+
*/
|
|
513
|
+
export class ScheduleManager {
|
|
514
|
+
private readonly deltaScheduler: DeltaScheduler;
|
|
515
|
+
private batchClientId: string | undefined;
|
|
516
|
+
private hitError = false;
|
|
517
|
+
|
|
518
|
+
private readonly scheduler: ScheduleManagerCore;
|
|
519
|
+
|
|
520
|
+
constructor(
|
|
521
|
+
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
522
|
+
private readonly emitter: EventEmitter,
|
|
523
|
+
private readonly logger: ITelemetryLogger,
|
|
524
|
+
) {
|
|
525
|
+
this.deltaScheduler = new DeltaScheduler(
|
|
526
|
+
this.deltaManager,
|
|
527
|
+
ChildLogger.create(this.logger, "DeltaScheduler"),
|
|
528
|
+
);
|
|
529
|
+
this.scheduler = new ScheduleManagerCore(deltaManager, logger);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
public beforeOpProcessing(message: ISequencedDocumentMessage) {
|
|
533
|
+
if (this.batchClientId !== message.clientId) {
|
|
534
|
+
assert(this.batchClientId === undefined,
|
|
535
|
+
0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
385
536
|
|
|
386
537
|
// This could be the beginning of a new batch or an individual message.
|
|
387
538
|
this.emitter.emit("batchBegin", message);
|
|
@@ -396,85 +547,34 @@ export class ScheduleManager {
|
|
|
396
547
|
}
|
|
397
548
|
}
|
|
398
549
|
|
|
399
|
-
public
|
|
550
|
+
public afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {
|
|
551
|
+
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
552
|
+
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
553
|
+
|
|
554
|
+
// Let the scheduler know how far we progressed, to decide if op processing
|
|
555
|
+
// should be paused or not.
|
|
556
|
+
this.scheduler.afterOpProcessing(message.sequenceNumber);
|
|
557
|
+
|
|
400
558
|
if (error) {
|
|
559
|
+
// We assume here that loader will close container and stop processing all future ops.
|
|
560
|
+
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
561
|
+
this.hitError = true;
|
|
401
562
|
this.batchClientId = undefined;
|
|
402
563
|
this.emitter.emit("batchEnd", error, message);
|
|
403
564
|
this.deltaScheduler.batchEnd();
|
|
404
565
|
return;
|
|
405
566
|
}
|
|
406
567
|
|
|
407
|
-
this.updatePauseState(message.sequenceNumber);
|
|
408
|
-
|
|
409
568
|
const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
|
|
410
569
|
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
411
570
|
// batch end metadata, this is end of the current batch.
|
|
412
|
-
if (
|
|
571
|
+
if (this.batchClientId === undefined || batch === false) {
|
|
413
572
|
this.batchClientId = undefined;
|
|
414
573
|
this.emitter.emit("batchEnd", undefined, message);
|
|
415
574
|
this.deltaScheduler.batchEnd();
|
|
416
575
|
return;
|
|
417
576
|
}
|
|
418
577
|
}
|
|
419
|
-
|
|
420
|
-
public setPaused(localPaused: boolean) {
|
|
421
|
-
// Return early if no change in value
|
|
422
|
-
if (this.localPaused === localPaused) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
this.localPaused = localPaused;
|
|
427
|
-
if (localPaused) {
|
|
428
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
429
|
-
this.deltaManager.inbound.pause();
|
|
430
|
-
} else {
|
|
431
|
-
this.deltaManager.inbound.resume();
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private updatePauseState(sequenceNumber: number) {
|
|
436
|
-
// If the inbound queue is ever empty we pause it and wait for new events
|
|
437
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
438
|
-
this.setPaused(true);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// If no message has caused the pause flag to be set, or the next message up is not the one we need to pause at
|
|
443
|
-
// then we simply continue processing
|
|
444
|
-
if (!this.pauseSequenceNumber || sequenceNumber + 1 < this.pauseSequenceNumber) {
|
|
445
|
-
this.setPaused(false);
|
|
446
|
-
} else {
|
|
447
|
-
// Otherwise the next message requires us to pause
|
|
448
|
-
this.setPaused(true);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private trackPending(message: ISequencedDocumentMessage) {
|
|
453
|
-
const metadata = message.metadata as IRuntimeMessageMetadata | undefined;
|
|
454
|
-
|
|
455
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
456
|
-
if (!isRuntimeMessage(message)) {
|
|
457
|
-
this.pauseSequenceNumber = undefined;
|
|
458
|
-
this.pauseClientId = undefined;
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const batchMetadata = metadata ? metadata.batch : undefined;
|
|
463
|
-
|
|
464
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
465
|
-
if (this.pauseClientId === message.clientId) {
|
|
466
|
-
if (batchMetadata !== undefined) {
|
|
467
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
468
|
-
// the previous one
|
|
469
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
470
|
-
this.pauseClientId = batchMetadata ? this.pauseClientId : undefined;
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
// We check the batch flag for the new clientID - if true we pause otherwise we reset the tracking data
|
|
474
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
475
|
-
this.pauseClientId = batchMetadata ? message.clientId : undefined;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
578
|
}
|
|
479
579
|
|
|
480
580
|
/**
|
|
@@ -519,7 +619,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
519
619
|
registryEntries: NamedFluidDataStoreRegistryEntries,
|
|
520
620
|
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>,
|
|
521
621
|
runtimeOptions: IContainerRuntimeOptions = {},
|
|
522
|
-
containerScope:
|
|
622
|
+
containerScope: FluidObject = context.scope,
|
|
523
623
|
existing?: boolean,
|
|
524
624
|
): Promise<ContainerRuntime> {
|
|
525
625
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
@@ -531,7 +631,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
531
631
|
});
|
|
532
632
|
|
|
533
633
|
const {
|
|
534
|
-
summaryOptions = {
|
|
634
|
+
summaryOptions = {},
|
|
535
635
|
gcOptions = {},
|
|
536
636
|
loadSequenceNumberVerification = "close",
|
|
537
637
|
} = runtimeOptions;
|
|
@@ -597,8 +697,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
597
697
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
598
698
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
599
699
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
700
|
+
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
600
701
|
const error = new DataCorruptionError(
|
|
601
|
-
"
|
|
702
|
+
"SummaryMetadataMismatch",
|
|
602
703
|
{ runtimeSequenceNumber, protocolSequenceNumber },
|
|
603
704
|
);
|
|
604
705
|
|
|
@@ -692,7 +793,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
692
793
|
return this._flushMode;
|
|
693
794
|
}
|
|
694
795
|
|
|
695
|
-
public get scope(): IFluidObject {
|
|
796
|
+
public get scope(): IFluidObject & FluidObject {
|
|
696
797
|
return this.containerScope;
|
|
697
798
|
}
|
|
698
799
|
|
|
@@ -798,7 +899,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
798
899
|
electedSummarizerData: ISerializedElection | undefined,
|
|
799
900
|
chunks: [string, string[]][],
|
|
800
901
|
private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptions>>,
|
|
801
|
-
private readonly containerScope:
|
|
902
|
+
private readonly containerScope: FluidObject,
|
|
802
903
|
public readonly logger: ITelemetryLogger,
|
|
803
904
|
existing: boolean,
|
|
804
905
|
blobManagerSnapshot: IBlobManagerLoadInfo,
|
|
@@ -910,7 +1011,11 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
910
1011
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
911
1012
|
|
|
912
1013
|
// Only create a SummaryManager if summaries are enabled and we are not the summarizer client
|
|
1014
|
+
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
913
1015
|
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
1016
|
+
this.runtimeOptions.summaryOptions.disableSummaries = true;
|
|
1017
|
+
}
|
|
1018
|
+
if (this.summariesDisabled()) {
|
|
914
1019
|
this._logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
915
1020
|
} else {
|
|
916
1021
|
const maxOpsSinceLastSummary = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary ?? 7000;
|
|
@@ -1326,12 +1431,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1326
1431
|
// but would not modify contents details
|
|
1327
1432
|
let message = { ...messageArg };
|
|
1328
1433
|
|
|
1329
|
-
let error: any | undefined;
|
|
1330
|
-
|
|
1331
1434
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1332
1435
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1333
1436
|
// messages once a batch has been fully processed.
|
|
1334
|
-
this.scheduleManager.
|
|
1437
|
+
this.scheduleManager.beforeOpProcessing(message);
|
|
1335
1438
|
|
|
1336
1439
|
try {
|
|
1337
1440
|
message = unpackRuntimeMessage(message);
|
|
@@ -1365,11 +1468,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1365
1468
|
}
|
|
1366
1469
|
|
|
1367
1470
|
this.emit("op", message);
|
|
1471
|
+
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1368
1472
|
} catch (e) {
|
|
1369
|
-
|
|
1473
|
+
this.scheduleManager.afterOpProcessing(e, message);
|
|
1370
1474
|
throw e;
|
|
1371
|
-
} finally {
|
|
1372
|
-
this.scheduleManager.endOperation(error, message);
|
|
1373
1475
|
}
|
|
1374
1476
|
}
|
|
1375
1477
|
|
|
@@ -1476,7 +1578,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1476
1578
|
|
|
1477
1579
|
public async createRootDataStore(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
|
|
1478
1580
|
const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
|
|
1479
|
-
fluidDataStore.
|
|
1581
|
+
fluidDataStore.bindToContext();
|
|
1480
1582
|
return fluidDataStore;
|
|
1481
1583
|
}
|
|
1482
1584
|
|
|
@@ -1500,7 +1602,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1500
1602
|
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
|
|
1501
1603
|
Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
1502
1604
|
if (isRoot) {
|
|
1503
|
-
fluidDataStore.
|
|
1605
|
+
fluidDataStore.bindToContext();
|
|
1504
1606
|
}
|
|
1505
1607
|
return fluidDataStore;
|
|
1506
1608
|
}
|
|
@@ -2199,6 +2301,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2199
2301
|
return this.pendingStateManager.getLocalState();
|
|
2200
2302
|
}
|
|
2201
2303
|
|
|
2304
|
+
/**
|
|
2305
|
+
* @returns true if summaries are explicitly disabled for this ContainerRuntime, false otherwise
|
|
2306
|
+
*/
|
|
2307
|
+
public summariesDisabled(): boolean {
|
|
2308
|
+
return this.runtimeOptions.summaryOptions.disableSummaries === true ||
|
|
2309
|
+
this.runtimeOptions.summaryOptions.summaryConfigOverrides?.disableSummaries === true;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2202
2312
|
public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
|
|
2203
2313
|
if (this.clientDetails.type === summarizerClientType) {
|
|
2204
2314
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -2206,10 +2316,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2206
2316
|
return this.summaryManager.summarizeOnDemand(...args);
|
|
2207
2317
|
} else {
|
|
2208
2318
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
2209
|
-
//
|
|
2319
|
+
// disableSummaries is turned on. We are throwing instead of returning a failure here,
|
|
2210
2320
|
// because it is a misuse of the API rather than an expected failure.
|
|
2211
2321
|
throw new Error(
|
|
2212
|
-
`Can't summarize,
|
|
2322
|
+
`Can't summarize, disableSummaries: ${this.summariesDisabled()}`,
|
|
2213
2323
|
);
|
|
2214
2324
|
}
|
|
2215
2325
|
};
|
|
@@ -2224,7 +2334,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2224
2334
|
// generateSummaries is turned off. We are throwing instead of returning a failure here,
|
|
2225
2335
|
// because it is a misuse of the API rather than an expected failure.
|
|
2226
2336
|
throw new Error(
|
|
2227
|
-
`Can't summarize,
|
|
2337
|
+
`Can't summarize, disableSummaries: ${this.summariesDisabled()}`,
|
|
2228
2338
|
);
|
|
2229
2339
|
}
|
|
2230
2340
|
};
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
FluidObject,
|
|
9
9
|
IRequest,
|
|
10
10
|
IResponse,
|
|
11
11
|
IFluidHandle,
|
|
@@ -212,7 +212,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
212
212
|
public readonly id: string,
|
|
213
213
|
private readonly existing: boolean,
|
|
214
214
|
public readonly storage: IDocumentStorageService,
|
|
215
|
-
public readonly scope:
|
|
215
|
+
public readonly scope: FluidObject | FluidObject,
|
|
216
216
|
createSummarizerNode: CreateChildSummarizerNodeFn,
|
|
217
217
|
private bindState: BindState,
|
|
218
218
|
public readonly isLocalDataStore: boolean,
|
|
@@ -677,7 +677,7 @@ export class RemotedFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
677
677
|
private readonly initSnapshotValue: ISnapshotTree | string | undefined,
|
|
678
678
|
runtime: ContainerRuntime,
|
|
679
679
|
storage: IDocumentStorageService,
|
|
680
|
-
scope:
|
|
680
|
+
scope: FluidObject,
|
|
681
681
|
createSummarizerNode: CreateChildSummarizerNodeFn,
|
|
682
682
|
pkg?: string[],
|
|
683
683
|
) {
|
|
@@ -794,7 +794,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
794
794
|
pkg: Readonly<string[]> | undefined,
|
|
795
795
|
runtime: ContainerRuntime,
|
|
796
796
|
storage: IDocumentStorageService,
|
|
797
|
-
scope:
|
|
797
|
+
scope: FluidObject,
|
|
798
798
|
createSummarizerNode: CreateChildSummarizerNodeFn,
|
|
799
799
|
bindChannel: (channel: IFluidDataStoreChannel) => void,
|
|
800
800
|
private readonly snapshotTree: ISnapshotTree | undefined,
|
|
@@ -914,7 +914,7 @@ export class LocalFluidDataStoreContext extends LocalFluidDataStoreContextBase {
|
|
|
914
914
|
pkg: string[] | undefined,
|
|
915
915
|
runtime: ContainerRuntime,
|
|
916
916
|
storage: IDocumentStorageService,
|
|
917
|
-
scope:
|
|
917
|
+
scope: FluidObject & FluidObject,
|
|
918
918
|
createSummarizerNode: CreateChildSummarizerNodeFn,
|
|
919
919
|
bindChannel: (channel: IFluidDataStoreChannel) => void,
|
|
920
920
|
snapshotTree: ISnapshotTree | undefined,
|
|
@@ -953,7 +953,7 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
953
953
|
pkg: Readonly<string[]>,
|
|
954
954
|
runtime: ContainerRuntime,
|
|
955
955
|
storage: IDocumentStorageService,
|
|
956
|
-
scope:
|
|
956
|
+
scope: FluidObject & FluidObject,
|
|
957
957
|
createSummarizerNode: CreateChildSummarizerNodeFn,
|
|
958
958
|
bindChannel: (channel: IFluidDataStoreChannel) => void,
|
|
959
959
|
isRootDataStore: boolean,
|
|
@@ -993,7 +993,7 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
993
993
|
super.bindRuntime(dataStoreRuntime);
|
|
994
994
|
|
|
995
995
|
if (this.isRootDataStore) {
|
|
996
|
-
dataStoreRuntime.
|
|
996
|
+
dataStoreRuntime.bindToContext();
|
|
997
997
|
}
|
|
998
998
|
}
|
|
999
999
|
|
package/src/packageVersion.ts
CHANGED
package/src/summarizerTypes.ts
CHANGED
|
@@ -22,13 +22,25 @@ import { ISummaryStats } from "@fluidframework/runtime-definitions";
|
|
|
22
22
|
import { ISummaryAckMessage, ISummaryNackMessage, ISummaryOpMessage } from "./summaryCollection";
|
|
23
23
|
|
|
24
24
|
declare module "@fluidframework/core-interfaces" {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export interface IFluidObject {
|
|
26
|
+
/** @deprecated - use `FluidObject<ISummarizer>` instead */
|
|
27
|
+
readonly ISummarizer?: ISummarizer;
|
|
28
|
+
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated - This will be removed in a later release.
|
|
34
|
+
*/
|
|
29
35
|
export const ISummarizer: keyof IProvideSummarizer = "ISummarizer";
|
|
30
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated - This will be removed in a later release.
|
|
39
|
+
*/
|
|
31
40
|
export interface IProvideSummarizer {
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated - This will be removed in a later release.
|
|
43
|
+
*/
|
|
32
44
|
readonly ISummarizer: ISummarizer;
|
|
33
45
|
}
|
|
34
46
|
|
|
@@ -283,7 +295,8 @@ export interface ISummarizerEvents extends IEvent {
|
|
|
283
295
|
(event: "summarizingError", listener: (error: ISummarizingWarning) => void);
|
|
284
296
|
}
|
|
285
297
|
|
|
286
|
-
export interface ISummarizer extends
|
|
298
|
+
export interface ISummarizer extends
|
|
299
|
+
IEventProvider<ISummarizerEvents>, IFluidRouter, IFluidLoadable, Partial<IProvideSummarizer>{
|
|
287
300
|
stop(reason: SummarizerStopReason): void;
|
|
288
301
|
run(onBehalfOf: string, options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason>;
|
|
289
302
|
|
package/src/summaryManager.ts
CHANGED
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
import { IDisposable, IEvent, IEventProvider, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { TypedEventEmitter, assert } from "@fluidframework/common-utils";
|
|
8
8
|
import { ChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
9
|
-
import { IFluidRouter, IRequest } from "@fluidframework/core-interfaces";
|
|
9
|
+
import { FluidObject, IFluidRouter, IRequest } from "@fluidframework/core-interfaces";
|
|
10
10
|
import { LoaderHeader } from "@fluidframework/container-definitions";
|
|
11
11
|
import { DriverHeader, DriverErrorType } from "@fluidframework/driver-definitions";
|
|
12
12
|
import { requestFluidObject } from "@fluidframework/runtime-utils";
|
|
13
13
|
import { createSummarizingWarning } from "./summarizer";
|
|
14
14
|
import { ISummarizerClientElection, summarizerClientType } from "./summarizerClientElection";
|
|
15
15
|
import { IThrottler } from "./throttler";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
ISummarizer,
|
|
18
|
+
ISummarizerOptions,
|
|
19
|
+
ISummarizingWarning,
|
|
20
|
+
SummarizerStopReason,
|
|
21
|
+
} from "./summarizerTypes";
|
|
17
22
|
import { SummaryCollection } from "./summaryCollection";
|
|
18
23
|
|
|
19
24
|
const defaultInitialDelayMs = 5000;
|
|
@@ -401,7 +406,7 @@ export const formRequestSummarizerFn = (
|
|
|
401
406
|
url: "/_summarizer",
|
|
402
407
|
};
|
|
403
408
|
|
|
404
|
-
const fluidObject = await requestFluidObject(loaderRouter, request);
|
|
409
|
+
const fluidObject = await requestFluidObject<FluidObject<ISummarizer>>(loaderRouter, request);
|
|
405
410
|
const summarizer = fluidObject.ISummarizer;
|
|
406
411
|
|
|
407
412
|
if (!summarizer) {
|