@fluidframework/container-runtime 0.57.0-51086 → 0.58.0-55561

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 (114) hide show
  1. package/dist/batchTracker.d.ts +26 -0
  2. package/dist/batchTracker.d.ts.map +1 -0
  3. package/dist/batchTracker.js +59 -0
  4. package/dist/batchTracker.js.map +1 -0
  5. package/dist/containerRuntime.d.ts +12 -7
  6. package/dist/containerRuntime.d.ts.map +1 -1
  7. package/dist/containerRuntime.js +125 -55
  8. package/dist/containerRuntime.js.map +1 -1
  9. package/dist/dataStore.d.ts +1 -36
  10. package/dist/dataStore.d.ts.map +1 -1
  11. package/dist/dataStore.js +5 -27
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +5 -7
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +12 -7
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +1 -1
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +3 -3
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +25 -11
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +100 -57
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/index.d.ts +0 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -3
  28. package/dist/index.js.map +1 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/dist/pendingStateManager.d.ts.map +1 -1
  33. package/dist/pendingStateManager.js +1 -6
  34. package/dist/pendingStateManager.js.map +1 -1
  35. package/dist/runningSummarizer.d.ts +1 -1
  36. package/dist/runningSummarizer.d.ts.map +1 -1
  37. package/dist/runningSummarizer.js +1 -1
  38. package/dist/runningSummarizer.js.map +1 -1
  39. package/dist/summarizer.d.ts +3 -4
  40. package/dist/summarizer.d.ts.map +1 -1
  41. package/dist/summarizer.js +8 -9
  42. package/dist/summarizer.js.map +1 -1
  43. package/dist/summaryGenerator.d.ts +1 -1
  44. package/dist/summaryGenerator.d.ts.map +1 -1
  45. package/dist/summaryGenerator.js +1 -1
  46. package/dist/summaryGenerator.js.map +1 -1
  47. package/dist/summaryManager.d.ts +2 -6
  48. package/dist/summaryManager.d.ts.map +1 -1
  49. package/dist/summaryManager.js +4 -10
  50. package/dist/summaryManager.js.map +1 -1
  51. package/lib/batchTracker.d.ts +26 -0
  52. package/lib/batchTracker.d.ts.map +1 -0
  53. package/lib/batchTracker.js +54 -0
  54. package/lib/batchTracker.js.map +1 -0
  55. package/lib/containerRuntime.d.ts +12 -7
  56. package/lib/containerRuntime.d.ts.map +1 -1
  57. package/lib/containerRuntime.js +126 -56
  58. package/lib/containerRuntime.js.map +1 -1
  59. package/lib/dataStore.d.ts +1 -36
  60. package/lib/dataStore.d.ts.map +1 -1
  61. package/lib/dataStore.js +4 -26
  62. package/lib/dataStore.js.map +1 -1
  63. package/lib/dataStoreContext.d.ts +5 -7
  64. package/lib/dataStoreContext.d.ts.map +1 -1
  65. package/lib/dataStoreContext.js +13 -8
  66. package/lib/dataStoreContext.js.map +1 -1
  67. package/lib/dataStores.d.ts +1 -1
  68. package/lib/dataStores.d.ts.map +1 -1
  69. package/lib/dataStores.js +3 -3
  70. package/lib/dataStores.js.map +1 -1
  71. package/lib/garbageCollection.d.ts +25 -11
  72. package/lib/garbageCollection.d.ts.map +1 -1
  73. package/lib/garbageCollection.js +98 -55
  74. package/lib/garbageCollection.js.map +1 -1
  75. package/lib/index.d.ts +0 -1
  76. package/lib/index.d.ts.map +1 -1
  77. package/lib/index.js +0 -1
  78. package/lib/index.js.map +1 -1
  79. package/lib/packageVersion.d.ts +1 -1
  80. package/lib/packageVersion.js +1 -1
  81. package/lib/packageVersion.js.map +1 -1
  82. package/lib/pendingStateManager.d.ts.map +1 -1
  83. package/lib/pendingStateManager.js +1 -6
  84. package/lib/pendingStateManager.js.map +1 -1
  85. package/lib/runningSummarizer.d.ts +1 -1
  86. package/lib/runningSummarizer.d.ts.map +1 -1
  87. package/lib/runningSummarizer.js +1 -1
  88. package/lib/runningSummarizer.js.map +1 -1
  89. package/lib/summarizer.d.ts +3 -4
  90. package/lib/summarizer.d.ts.map +1 -1
  91. package/lib/summarizer.js +8 -9
  92. package/lib/summarizer.js.map +1 -1
  93. package/lib/summaryGenerator.d.ts +1 -1
  94. package/lib/summaryGenerator.d.ts.map +1 -1
  95. package/lib/summaryGenerator.js +1 -1
  96. package/lib/summaryGenerator.js.map +1 -1
  97. package/lib/summaryManager.d.ts +2 -6
  98. package/lib/summaryManager.d.ts.map +1 -1
  99. package/lib/summaryManager.js +5 -11
  100. package/lib/summaryManager.js.map +1 -1
  101. package/package.json +12 -12
  102. package/src/batchTracker.ts +80 -0
  103. package/src/containerRuntime.ts +180 -63
  104. package/src/dataStore.ts +6 -42
  105. package/src/dataStoreContext.ts +17 -15
  106. package/src/dataStores.ts +9 -4
  107. package/src/garbageCollection.ts +125 -67
  108. package/src/index.ts +0 -1
  109. package/src/packageVersion.ts +1 -1
  110. package/src/pendingStateManager.ts +4 -8
  111. package/src/runningSummarizer.ts +3 -3
  112. package/src/summarizer.ts +8 -8
  113. package/src/summaryGenerator.ts +2 -2
  114. package/src/summaryManager.ts +5 -20
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
7
3
  exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.ContainerMessageType = void 0;
8
4
  const container_definitions_1 = require("@fluidframework/container-definitions");
@@ -33,6 +29,7 @@ const throttler_1 = require("./throttler");
33
29
  const runWhileConnectedCoordinator_1 = require("./runWhileConnectedCoordinator");
34
30
  const garbageCollection_1 = require("./garbageCollection");
35
31
  const dataStore_1 = require("./dataStore");
32
+ const batchTracker_1 = require("./batchTracker");
36
33
  var ContainerMessageType;
37
34
  (function (ContainerMessageType) {
38
35
  // An op to be delivered to store
@@ -75,9 +72,16 @@ var RuntimeHeaders;
75
72
  /** True if the request is coming from an IFluidHandle. */
76
73
  RuntimeHeaders["viaHandle"] = "viaHandle";
77
74
  })(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
78
- // Local storage key to set the default flush mode to TurnBased
79
- const turnBasedFlushModeKey = "Fluid.ContainerRuntime.FlushModeTurnBased";
80
75
  const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
76
+ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
77
+ // Feature gate for the max op size. If the value is negative, chunking is enabled
78
+ // and all ops over 16k would be chunked. If the value is positive, all ops with
79
+ // a size strictly larger will be rejected and the container closed with an error.
80
+ const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
81
+ // By default, we should reject any op larger than 768KB,
82
+ // in order to account for some extra overhead from serialization
83
+ // to not reach the 1MB limits in socket.io and Kafka.
84
+ const defaultMaxOpSizeInBytes = 768000;
81
85
  var RuntimeMessage;
82
86
  (function (RuntimeMessage) {
83
87
  RuntimeMessage["FluidDataStoreOp"] = "component";
@@ -355,7 +359,7 @@ exports.getDeviceSpec = getDeviceSpec;
355
359
  */
356
360
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
357
361
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, requestHandler, _storage) {
358
- var _a, _b, _c, _d, _e, _f, _g, _h;
362
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
359
363
  super();
360
364
  this.context = context;
361
365
  this.registry = registry;
@@ -364,13 +368,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
364
368
  this.logger = logger;
365
369
  this.requestHandler = requestHandler;
366
370
  this._storage = _storage;
371
+ this.defaultMaxConsecutiveReconnects = 15;
367
372
  this._orderSequentiallyCalls = 0;
373
+ this._flushMode = runtime_definitions_1.FlushMode.TurnBased;
368
374
  this.needsFlush = false;
369
375
  this.flushTrigger = false;
370
376
  this.paused = false;
377
+ this.consecutiveReconnects = 0;
371
378
  this._disposed = false;
372
379
  this.emitDirtyDocumentEvent = true;
373
- this.summarizerWarning = (warning) => this.mc.logger.sendTelemetryEvent({ eventName: "summarizerWarning" }, warning);
374
380
  /**
375
381
  * Used to apply stashed ops at their reference sequence number.
376
382
  * Normal op processing is synchronous, but applying stashed ops is async since the
@@ -420,7 +426,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
420
426
  throw new container_utils_1.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
421
427
  }
422
428
  };
423
- this.baseSummaryMessage = metadata === null || metadata === void 0 ? void 0 : metadata.message;
429
+ this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
424
430
  // If this is an existing container, we get values from metadata.
425
431
  // otherwise, we initialize them.
426
432
  if (existing) {
@@ -442,25 +448,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
442
448
  this.chunkMap = new Map(chunks);
443
449
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
444
450
  this.mc = telemetry_utils_1.loggerToMonitoringContext(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
445
- this._flushMode =
446
- ((_b = this.mc.config.getBoolean(turnBasedFlushModeKey)) !== null && _b !== void 0 ? _b : false) ? runtime_definitions_1.FlushMode.TurnBased : runtime_definitions_1.FlushMode.Immediate;
447
451
  this._aliasingEnabled =
448
- ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
449
- ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
452
+ ((_b = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _b !== void 0 ? _b : false) ||
453
+ ((_c = runtimeOptions.useDataStoreAliasing) !== null && _c !== void 0 ? _c : false);
454
+ this._maxOpSizeInBytes = ((_d = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _d !== void 0 ? _d : defaultMaxOpSizeInBytes);
455
+ this.maxConsecutiveReconnects = (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
456
+ this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), (nodePath) => this.dataStores.getNodePackagePath(nodePath),
450
457
  /**
451
- * Function that return the current server timestamp. This is used by the garbage collector to set the
452
- * time when a node becomes unreferenced.
453
- * We use the timestamp of the last op for current timestamp. However, there can be cases where
454
- * we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
455
- * of this client's connection.
458
+ * Returns the timestamp of the last message seen by this client. This is used by garbage collector as
459
+ * the current reference timestamp for tracking unreferenced objects.
456
460
  */
457
- const getCurrentTimestamp = () => {
458
- var _a, _b, _c;
459
- const client = this.clientId !== undefined ? this.getAudience().getMember(this.clientId) : undefined;
460
- const timestamp = client === null || client === void 0 ? void 0 : client.timestamp;
461
- return (_c = (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : timestamp) !== null && _c !== void 0 ? _c : Date.now();
462
- };
463
- this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), (nodePath) => this.dataStores.getNodePackagePath(nodePath), getCurrentTimestamp, this.closeFn, context.baseSnapshot, async (id) => driver_utils_1.readAndParse(this.storage, id), this.mc.logger, existing, metadata);
461
+ () => { var _a, _b, _c; return (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : (_c = this.messageAtLastSummary) === null || _c === void 0 ? void 0 : _c.timestamp; }, () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; }, context.baseSnapshot, async (id) => driver_utils_1.readAndParse(this.storage, id), this.mc.logger, existing, metadata);
464
462
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
465
463
  this.summarizerNode = runtime_utils_1.createRootSummarizerNodeWithGC(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
466
464
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
@@ -481,7 +479,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
481
479
  if (this.context.baseSnapshot) {
482
480
  this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
483
481
  }
484
- this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (dataStorePath, packagePath) => this.garbageCollector.nodeUpdated(dataStorePath, "Changed", packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
482
+ this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
485
483
  this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), this, this.logger);
486
484
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
487
485
  this.deltaSender = this.deltaManager;
@@ -492,7 +490,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
492
490
  this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
493
491
  const { attachState, pendingLocalState } = this.context;
494
492
  this.dirtyContainer = attachState !== container_definitions_1.AttachState.Attached
495
- || ((_e = pendingLocalState) === null || _e === void 0 ? void 0 : _e.pendingStates.length) > 0;
493
+ || ((_f = pendingLocalState) === null || _f === void 0 ? void 0 : _f.pendingStates.length) > 0;
496
494
  this.context.updateDirtyContainerState(this.dirtyContainer);
497
495
  // Map the deprecated generateSummaries flag to disableSummaries.
498
496
  if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
@@ -505,8 +503,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
505
503
  const orderedClientLogger = telemetry_utils_1.ChildLogger.create(this.logger, "OrderedClientElection");
506
504
  const orderedClientCollection = new orderedClientElection_1.OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
507
505
  const orderedClientElectionForSummarizer = new orderedClientElection_1.OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, summarizerClientElection_1.SummarizerClientElection.isClientEligible);
508
- const summarizerClientElectionEnabled = (_f = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _f !== void 0 ? _f : ((_g = this.runtimeOptions.summaryOptions) === null || _g === void 0 ? void 0 : _g.summarizerClientElection) === true;
509
- const maxOpsSinceLastSummary = (_h = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _h !== void 0 ? _h : 7000;
506
+ const summarizerClientElectionEnabled = (_g = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _g !== void 0 ? _g : ((_h = this.runtimeOptions.summaryOptions) === null || _h === void 0 ? void 0 : _h.summarizerClientElection) === true;
507
+ const maxOpsSinceLastSummary = (_j = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _j !== void 0 ? _j : 7000;
510
508
  this.summarizerClientElection = new summarizerClientElection_1.SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary, summarizerClientElectionEnabled);
511
509
  if (this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
512
510
  this._summarizer = new summarizer_1.Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => runWhileConnectedCoordinator_1.RunWhileConnectedCoordinator.create(runtime));
@@ -538,7 +536,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
538
536
  throttler_1.formExponentialFn({ coefficient: 20, initialDelay: 0 })), {
539
537
  initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
540
538
  }, this.runtimeOptions.summaryOptions.summarizerOptions);
541
- this.summaryManager.on("summarizerWarning", this.summarizerWarning);
542
539
  this.summaryManager.start();
543
540
  }
544
541
  }
@@ -569,6 +566,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
569
566
  // logging container load stats
570
567
  this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryCount: this.summaryCount, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
571
568
  connectionTelemetry_1.ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
569
+ batchTracker_1.BindBatchTracker(this, this.logger);
572
570
  }
573
571
  get IContainerRuntime() { return this; }
574
572
  get IFluidRouter() { return this; }
@@ -584,7 +582,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
584
582
  var _a, _b, _c;
585
583
  // If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
586
584
  // back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
587
- const passLogger = (_a = context.taggedLogger) !== null && _a !== void 0 ? _a : new telemetry_utils_1.TaggedLoggerAdapter(context.logger);
585
+ const backCompatContext = context;
586
+ const passLogger = (_a = backCompatContext.taggedLogger) !== null && _a !== void 0 ? _a : new telemetry_utils_1.TaggedLoggerAdapter(backCompatContext.logger);
588
587
  const logger = telemetry_utils_1.ChildLogger.create(passLogger, undefined, {
589
588
  all: {
590
589
  runtimeVersion: packageVersion_1.pkgVersion,
@@ -645,7 +644,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
645
644
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
646
645
  if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
647
646
  // "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
648
- const error = new container_utils_1.DataCorruptionError("SummaryMetadataMismatch", { runtimeSequenceNumber, protocolSequenceNumber });
647
+ const error = new container_utils_1.DataCorruptionError("Summary metadata mismatch", { runtimeSequenceNumber, protocolSequenceNumber });
649
648
  if (loadSequenceNumberVerification === "log") {
650
649
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
651
650
  }
@@ -747,7 +746,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
747
746
  attachState: this.attachState,
748
747
  }, error);
749
748
  if (this.summaryManager !== undefined) {
750
- this.summaryManager.off("summarizerWarning", this.summarizerWarning);
751
749
  this.summaryManager.dispose();
752
750
  }
753
751
  this.garbageCollector.dispose();
@@ -853,17 +851,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
853
851
  }
854
852
  }
855
853
  const dataStoreChannel = await dataStoreContext.realize();
856
- // Let the garbage collector know that a data store was requested / loaded. Realize the data store first so
857
- // that the package path is available.
858
- this.garbageCollector.nodeUpdated(`/${id}`, "Loaded", dataStoreContext.packagePath, request === null || request === void 0 ? void 0 : request.headers);
854
+ this.garbageCollector.nodeUpdated(`/${id}`, "Loaded", undefined /* timestampMs */, dataStoreContext.packagePath, request === null || request === void 0 ? void 0 : request.headers);
859
855
  return dataStoreChannel;
860
856
  }
861
857
  formMetadata() {
862
858
  var _a;
863
859
  return Object.assign(Object.assign({}, this.createContainerMetadata), { summaryCount: this.summaryCount, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined, gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
864
- // The last message processed at the time of summary. If there are no messages, nothing has changed from
865
- // the base summary we loaded from. So, use the message from its metadata blob.
866
- message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.baseSummaryMessage, sessionExpiryTimeoutMs: this.garbageCollector.sessionExpiryTimeoutMs });
860
+ // The last message processed at the time of summary. If there are no new messages, use the message from the
861
+ // last summary.
862
+ message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary, sessionExpiryTimeoutMs: this.garbageCollector.sessionExpiryTimeoutMs });
867
863
  }
868
864
  addContainerStateToSummary(summaryTree) {
869
865
  var _a;
@@ -894,6 +890,37 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
894
890
  }
895
891
  }
896
892
  }
893
+ // Track how many times the container tries to reconnect with pending messages.
894
+ // This happens when the connection state is changed and we reset the counter
895
+ // when we are able to process a local op or when there are no pending messages.
896
+ // If this counter reaches a max, it's a good indicator that the container
897
+ // is not making progress and it is stuck in a retry loop.
898
+ shouldContinueReconnecting() {
899
+ if (this.maxConsecutiveReconnects <= 0) {
900
+ // Feature disabled, we never stop reconnecting
901
+ return true;
902
+ }
903
+ if (!this.pendingStateManager.hasPendingMessages()) {
904
+ // If there are no pending messages, we can always reconnect
905
+ this.resetReconnectCount();
906
+ return true;
907
+ }
908
+ this.consecutiveReconnects++;
909
+ if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
910
+ // If we're halfway through the max reconnects, send an event in order
911
+ // to better identify false positives, if any. If the rate of this event
912
+ // matches Container Close count below, we can safely cut down
913
+ // maxConsecutiveReconnects to half.
914
+ this.mc.logger.sendTelemetryEvent({
915
+ eventName: "ReconnectsWithNoProgress",
916
+ attempts: this.consecutiveReconnects,
917
+ });
918
+ }
919
+ return this.consecutiveReconnects < this.maxConsecutiveReconnects;
920
+ }
921
+ resetReconnectCount() {
922
+ this.consecutiveReconnects = 0;
923
+ }
897
924
  replayPendingStates() {
898
925
  // We need to be able to send ops to replay states
899
926
  if (!this.canSendOps()) {
@@ -946,6 +973,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
946
973
  if (changeOfState) {
947
974
  this.deltaManager.off("op", this.onOp);
948
975
  this.context.pendingLocalState = undefined;
976
+ if (!this.shouldContinueReconnecting()) {
977
+ this.closeFn(new container_utils_1.GenericError("Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
978
+ { attempts: this.consecutiveReconnects }));
979
+ return;
980
+ }
949
981
  this.replayPendingStates();
950
982
  }
951
983
  this.dataStores.setConnectionState(connected, clientId);
@@ -998,6 +1030,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
998
1030
  }
999
1031
  this.emit("op", message);
1000
1032
  this.scheduleManager.afterOpProcessing(undefined, message);
1033
+ if (local) {
1034
+ // If we have processed a local op, this means that the container is
1035
+ // making progress and we can reset the counter for how many times
1036
+ // we have consecutively replayed the pending states
1037
+ this.resetReconnectCount();
1038
+ }
1001
1039
  }
1002
1040
  catch (e) {
1003
1041
  this.scheduleManager.afterOpProcessing(e, message);
@@ -1082,7 +1120,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1082
1120
  callback();
1083
1121
  }
1084
1122
  catch (error) {
1085
- this.closeFn(new container_utils_1.GenericError("orderSequentiallyCallbackException", error));
1123
+ this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
1086
1124
  throw error; // throw the original error for the consumer of the runtime
1087
1125
  }
1088
1126
  finally {
@@ -1125,7 +1163,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1125
1163
  const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
1126
1164
  const aliasedDataStore = dataStore_1.channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
1127
1165
  const result = await aliasedDataStore.trySetAlias(alias);
1128
- if (result !== dataStore_1.AliasResult.Success) {
1166
+ if (result !== "Success") {
1129
1167
  throw new container_utils_1.GenericError("dataStoreAliasFailure", undefined /* error */, {
1130
1168
  alias: {
1131
1169
  value: alias,
@@ -1157,7 +1195,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1157
1195
  if (isRoot) {
1158
1196
  fluidDataStore.bindToContext();
1159
1197
  }
1160
- return fluidDataStore;
1198
+ return dataStore_1.channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
1161
1199
  }
1162
1200
  async _createDataStoreWithProps(pkg, props, id = uuid_1.v4(), isRoot = false) {
1163
1201
  return this._aliasingEnabled === true && isRoot ?
@@ -1339,7 +1377,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1339
1377
  * @param options - options controlling how the summary is generated or submitted
1340
1378
  */
1341
1379
  async submitSummary(options) {
1342
- var _a, _b;
1380
+ var _a, _b, _c;
1343
1381
  const { fullTree, refreshLatestAck, summaryLogger } = options;
1344
1382
  if (refreshLatestAck) {
1345
1383
  const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, { all: { safeSummary: true } }));
@@ -1357,6 +1395,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1357
1395
  await this.deltaManager.inbound.pause();
1358
1396
  const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
1359
1397
  const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
1398
+ // We should be here is we haven't processed be here. If we are of if the last message's sequence number
1399
+ // doesn't match the last processed sequence number, log an error.
1400
+ if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
1401
+ summaryLogger.sendErrorEvent({
1402
+ eventName: "LastSequenceMismatch",
1403
+ message,
1404
+ });
1405
+ }
1360
1406
  this.summarizerNode.startSummary(summaryRefSeqNum, summaryLogger);
1361
1407
  // Helper function to check whether we should still continue between each async step.
1362
1408
  const checkContinue = () => {
@@ -1413,13 +1459,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1413
1459
  return { stage: "base", referenceSequenceNumber: summaryRefSeqNum, error };
1414
1460
  }
1415
1461
  const { summary: summaryTree, stats: partialStats } = summarizeResult;
1462
+ // Now that we have generated the summary, update the message at last summary to the last message processed.
1463
+ this.messageAtLastSummary = this.deltaManager.lastMessage;
1416
1464
  // Counting dataStores and handles
1417
1465
  // Because handles are unchanged dataStores in the current logic,
1418
1466
  // summarized dataStore count is total dataStore count minus handle count
1419
1467
  const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[runtime_definitions_1.channelsTreeName];
1420
1468
  common_utils_1.assert(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1421
1469
  const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
1422
- const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount }, partialStats);
1470
+ const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_b = summarizeResult.gcStats) === null || _b === void 0 ? void 0 : _b.updatedDataStoreCount }, partialStats);
1423
1471
  const generateSummaryData = {
1424
1472
  referenceSequenceNumber: summaryRefSeqNum,
1425
1473
  summaryTree,
@@ -1435,7 +1483,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1435
1483
  const summaryContext = lastAck === undefined
1436
1484
  ? {
1437
1485
  proposalHandle: undefined,
1438
- ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
1486
+ ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
1439
1487
  referenceSequenceNumber: summaryRefSeqNum,
1440
1488
  }
1441
1489
  : {
@@ -1566,16 +1614,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1566
1614
  });
1567
1615
  }
1568
1616
  }
1569
- // Note: Chunking will increase content beyond maxOpSize because we JSON'ing JSON payload -
1570
- // there will be a lot of escape characters that can make it up to 2x bigger!
1571
- // This is Ok, because DeltaManager.shouldSplit() will have 2 * maxMessageSize limit
1572
- if (!serializedContent || serializedContent.length <= maxOpSize) {
1573
- clientSequenceNumber = this.submitRuntimeMessage(type, content,
1574
- /* batch: */ this._flushMode === runtime_definitions_1.FlushMode.TurnBased, opMetadataInternal);
1575
- }
1576
- else {
1577
- clientSequenceNumber = this.submitChunkedMessage(type, serializedContent, maxOpSize);
1578
- }
1617
+ clientSequenceNumber = this.submitMaybeChunkedMessages(type, content, serializedContent, maxOpSize, this._flushMode === runtime_definitions_1.FlushMode.TurnBased, opMetadataInternal);
1579
1618
  }
1580
1619
  // Let the PendingStateManager know that a message was submitted.
1581
1620
  this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
@@ -1583,6 +1622,35 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1583
1622
  this.updateDocumentDirtyState(true);
1584
1623
  }
1585
1624
  }
1625
+ submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
1626
+ if (this._maxOpSizeInBytes >= 0) {
1627
+ // Chunking disabled
1628
+ if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
1629
+ return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1630
+ }
1631
+ // When chunking is disabled, we ignore the server max message size
1632
+ // and if the content length is larger than the client configured message size
1633
+ // instead of splitting the content, we will fail by explicitly close the container
1634
+ this.closeFn(new container_utils_1.GenericError("OpTooLarge",
1635
+ /* error */ undefined, {
1636
+ length: {
1637
+ value: serializedContent.length,
1638
+ tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1639
+ },
1640
+ limit: {
1641
+ value: this._maxOpSizeInBytes,
1642
+ tag: telemetry_utils_1.TelemetryDataTag.PackageData,
1643
+ },
1644
+ }));
1645
+ return -1;
1646
+ }
1647
+ // Chunking enabled, fallback on the server's max message size
1648
+ // and split the content accordingly
1649
+ if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
1650
+ return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
1651
+ }
1652
+ return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
1653
+ }
1586
1654
  submitChunkedMessage(type, content, maxOpSize) {
1587
1655
  const contentLength = content.length;
1588
1656
  const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
@@ -1661,6 +1729,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1661
1729
  const readAndParseBlob = async (id) => driver_utils_1.readAndParse(this.storage, id);
1662
1730
  const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
1663
1731
  eventName: "RefreshLatestSummaryGetSnapshot",
1732
+ ackHandle,
1733
+ summaryRefSeq,
1664
1734
  fetchLatest: false,
1665
1735
  }), readAndParseBlob, summaryLogger);
1666
1736
  // Notify the garbage collector so it can update its latest summary state.