@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.
Files changed (57) hide show
  1. package/dist/connectionTelemetry.d.ts +4 -0
  2. package/dist/connectionTelemetry.d.ts.map +1 -1
  3. package/dist/connectionTelemetry.js +6 -2
  4. package/dist/connectionTelemetry.js.map +1 -1
  5. package/dist/containerRuntime.d.ts +23 -11
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +173 -93
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStoreContext.d.ts +7 -7
  10. package/dist/dataStoreContext.d.ts.map +1 -1
  11. package/dist/dataStoreContext.js +1 -1
  12. package/dist/dataStoreContext.js.map +1 -1
  13. package/dist/dataStores.js +2 -1
  14. package/dist/dataStores.js.map +1 -1
  15. package/dist/packageVersion.d.ts +1 -1
  16. package/dist/packageVersion.js +1 -1
  17. package/dist/packageVersion.js.map +1 -1
  18. package/dist/summarizerTypes.d.ts +13 -2
  19. package/dist/summarizerTypes.d.ts.map +1 -1
  20. package/dist/summarizerTypes.js +3 -0
  21. package/dist/summarizerTypes.js.map +1 -1
  22. package/dist/summaryFormat.js +2 -1
  23. package/dist/summaryFormat.js.map +1 -1
  24. package/dist/summaryManager.d.ts.map +1 -1
  25. package/dist/summaryManager.js.map +1 -1
  26. package/lib/connectionTelemetry.d.ts +4 -0
  27. package/lib/connectionTelemetry.d.ts.map +1 -1
  28. package/lib/connectionTelemetry.js +5 -1
  29. package/lib/connectionTelemetry.js.map +1 -1
  30. package/lib/containerRuntime.d.ts +23 -11
  31. package/lib/containerRuntime.d.ts.map +1 -1
  32. package/lib/containerRuntime.js +177 -97
  33. package/lib/containerRuntime.js.map +1 -1
  34. package/lib/dataStoreContext.d.ts +7 -7
  35. package/lib/dataStoreContext.d.ts.map +1 -1
  36. package/lib/dataStoreContext.js +1 -1
  37. package/lib/dataStoreContext.js.map +1 -1
  38. package/lib/dataStores.js +2 -1
  39. package/lib/dataStores.js.map +1 -1
  40. package/lib/packageVersion.d.ts +1 -1
  41. package/lib/packageVersion.js +1 -1
  42. package/lib/packageVersion.js.map +1 -1
  43. package/lib/summarizerTypes.d.ts +13 -2
  44. package/lib/summarizerTypes.d.ts.map +1 -1
  45. package/lib/summarizerTypes.js +3 -0
  46. package/lib/summarizerTypes.js.map +1 -1
  47. package/lib/summaryFormat.js +2 -1
  48. package/lib/summaryFormat.js.map +1 -1
  49. package/lib/summaryManager.d.ts.map +1 -1
  50. package/lib/summaryManager.js.map +1 -1
  51. package/package.json +16 -16
  52. package/src/connectionTelemetry.ts +6 -1
  53. package/src/containerRuntime.ts +220 -110
  54. package/src/dataStoreContext.ts +7 -7
  55. package/src/packageVersion.ts +1 -1
  56. package/src/summarizerTypes.ts +16 -3
  57. package/src/summaryManager.ts +8 -3
@@ -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
- interface IRuntimeMessageMetadata {
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
- export class ScheduleManager {
315
- private readonly deltaScheduler: DeltaScheduler;
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 pauseClientId: string | undefined;
328
+ private currentBatchClientId: string | undefined;
318
329
  private localPaused = false;
319
- private batchClientId: string | undefined;
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 || !firstMessageMetadata.batch) {
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
- // Based on track pending update the pause state
368
- this.updatePauseState(this.deltaManager.lastSequenceNumber);
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
- public beginOperation(message: ISequencedDocumentMessage) {
372
- if (this.batchClientId !== message.clientId) {
373
- // As a back stop for any bugs marking the end of a batch - if the client ID flipped, we
374
- // consider the previous batch over.
375
- if (this.batchClientId) {
376
- this.emitter.emit("batchEnd", "Did not receive real batchEnd message", undefined);
377
- this.deltaScheduler.batchEnd();
378
-
379
- this.logger.sendTelemetryEvent({
380
- eventName: "BatchEndNotReceived",
381
- clientId: this.batchClientId,
382
- sequenceNumber: message.sequenceNumber,
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 endOperation(error: any | undefined, message: ISequencedDocumentMessage) {
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 (!this.batchClientId || batch === false) {
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: IFluidObject = context.scope,
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 = { generateSummaries: true },
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
- "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber",
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: IFluidObject,
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.beginOperation(message);
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
- error = e;
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.attachGraph();
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.attachGraph();
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
- // generateSummaries is turned off. We are throwing instead of returning a failure here,
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, generateSummaries: ${this.runtimeOptions.summaryOptions.generateSummaries}`,
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, generateSummaries: ${this.runtimeOptions.summaryOptions.generateSummaries}`,
2337
+ `Can't summarize, disableSummaries: ${this.summariesDisabled()}`,
2228
2338
  );
2229
2339
  }
2230
2340
  };
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import {
8
- IFluidObject,
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: IFluidObject,
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: IFluidObject,
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: IFluidObject,
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: IFluidObject & IFluidObject,
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: IFluidObject & IFluidObject,
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.attachGraph();
996
+ dataStoreRuntime.bindToContext();
997
997
  }
998
998
  }
999
999
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "0.51.0";
9
+ export const pkgVersion = "0.52.0";
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
26
- export interface IFluidObject extends Readonly<Partial<IProvideSummarizer>> { }
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 IEventProvider<ISummarizerEvents>, IFluidRouter, IFluidLoadable {
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
 
@@ -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 { ISummarizer, ISummarizerOptions, ISummarizingWarning, SummarizerStopReason } from "./summarizerTypes";
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) {