@fluidframework/container-runtime 0.56.5 → 0.56.8

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.
@@ -1,7 +1,3 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
1
  import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
6
2
  import { assert, Trace, TypedEventEmitter, unreachableCase, performance, } from "@fluidframework/common-utils";
7
3
  import { ChildLogger, raiseConnectedEvent, PerformanceEvent, normalizeError, TaggedLoggerAdapter, loggerToMonitoringContext, } from "@fluidframework/telemetry-utils";
@@ -59,6 +55,7 @@ const DefaultSummaryConfiguration = {
59
55
  ;
60
56
  // Local storage key to set the default flush mode to TurnBased
61
57
  const turnBasedFlushModeKey = "Fluid.ContainerRuntime.FlushModeTurnBased";
58
+ const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
62
59
  export var RuntimeMessage;
63
60
  (function (RuntimeMessage) {
64
61
  RuntimeMessage["FluidDataStoreOp"] = "component";
@@ -332,7 +329,7 @@ export function getDeviceSpec() {
332
329
  */
333
330
  export class ContainerRuntime extends TypedEventEmitter {
334
331
  constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, requestHandler, _storage) {
335
- var _a, _b, _c, _d, _e;
332
+ var _a, _b, _c, _d, _e, _f;
336
333
  super();
337
334
  this.context = context;
338
335
  this.registry = registry;
@@ -341,10 +338,12 @@ export class ContainerRuntime extends TypedEventEmitter {
341
338
  this.logger = logger;
342
339
  this.requestHandler = requestHandler;
343
340
  this._storage = _storage;
341
+ this.defaultMaxConsecutiveReconnects = 15;
344
342
  this._orderSequentiallyCalls = 0;
345
343
  this.needsFlush = false;
346
344
  this.flushTrigger = false;
347
345
  this.paused = false;
346
+ this.consecutiveReconnects = 0;
348
347
  this._disposed = false;
349
348
  this.dirtyContainer = false;
350
349
  this.emitDirtyDocumentEvent = true;
@@ -435,6 +434,7 @@ export class ContainerRuntime extends TypedEventEmitter {
435
434
  const timestamp = client === null || client === void 0 ? void 0 : client.timestamp;
436
435
  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();
437
436
  };
437
+ this.maxConsecutiveReconnects = (_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
438
438
  this.garbageCollector = GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), getCurrentTimestamp, this.closeFn, context.baseSnapshot, async (id) => readAndParse(this.storage, id), this.mc.logger, existing, metadata);
439
439
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
440
440
  this.summarizerNode = createRootSummarizerNodeWithGC(ChildLogger.create(this.logger, "SummarizerNode"),
@@ -476,8 +476,8 @@ export class ContainerRuntime extends TypedEventEmitter {
476
476
  const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
477
477
  const orderedClientCollection = new OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
478
478
  const orderedClientElectionForSummarizer = new OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, SummarizerClientElection.isClientEligible);
479
- const summarizerClientElectionEnabled = (_c = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _c !== void 0 ? _c : ((_d = this.runtimeOptions.summaryOptions) === null || _d === void 0 ? void 0 : _d.summarizerClientElection) === true;
480
- const maxOpsSinceLastSummary = (_e = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _e !== void 0 ? _e : 7000;
479
+ const summarizerClientElectionEnabled = (_d = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _d !== void 0 ? _d : ((_e = this.runtimeOptions.summaryOptions) === null || _e === void 0 ? void 0 : _e.summarizerClientElection) === true;
480
+ const maxOpsSinceLastSummary = (_f = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _f !== void 0 ? _f : 7000;
481
481
  this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary, summarizerClientElectionEnabled);
482
482
  if (this.context.clientDetails.type === summarizerClientType) {
483
483
  this._summarizer = new Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => RunWhileConnectedCoordinator.create(runtime));
@@ -883,6 +883,37 @@ export class ContainerRuntime extends TypedEventEmitter {
883
883
  }
884
884
  }
885
885
  }
886
+ // Track how many times the container tries to reconnect with pending messages.
887
+ // This happens when the connection state is changed and we reset the counter
888
+ // when we are able to process a local op or when there are no pending messages.
889
+ // If this counter reaches a max, it's a good indicator that the container
890
+ // is not making progress and it is stuck in a retry loop.
891
+ shouldContinueReconnecting() {
892
+ if (this.maxConsecutiveReconnects <= 0) {
893
+ // Feature disabled, we never stop reconnecting
894
+ return true;
895
+ }
896
+ if (!this.pendingStateManager.hasPendingMessages()) {
897
+ // If there are no pending messages, we can always reconnect
898
+ this.resetReconnectCount();
899
+ return true;
900
+ }
901
+ this.consecutiveReconnects++;
902
+ if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
903
+ // If we're halfway through the max reconnects, send an event in order
904
+ // to better identify false positives, if any. If the rate of this event
905
+ // matches `MaxReconnectsWithNoProgress`, we can safely cut down
906
+ // maxConsecutiveReconnects to half.
907
+ this.mc.logger.sendTelemetryEvent({
908
+ eventName: "ReconnectsWithNoProgress",
909
+ attempts: this.consecutiveReconnects,
910
+ });
911
+ }
912
+ return this.consecutiveReconnects < this.maxConsecutiveReconnects;
913
+ }
914
+ resetReconnectCount() {
915
+ this.consecutiveReconnects = 0;
916
+ }
886
917
  replayPendingStates() {
887
918
  // We need to be able to send ops to replay states
888
919
  if (!this.canSendOps()) {
@@ -935,6 +966,11 @@ export class ContainerRuntime extends TypedEventEmitter {
935
966
  if (changeOfState) {
936
967
  this.deltaManager.off("op", this.onOp);
937
968
  this.context.pendingLocalState = undefined;
969
+ if (!this.shouldContinueReconnecting()) {
970
+ this.closeFn(new GenericError("MaxReconnectsWithNoProgress", undefined, // error
971
+ { attempts: this.consecutiveReconnects }));
972
+ return;
973
+ }
938
974
  this.replayPendingStates();
939
975
  }
940
976
  this.dataStores.setConnectionState(connected, clientId);
@@ -987,6 +1023,12 @@ export class ContainerRuntime extends TypedEventEmitter {
987
1023
  }
988
1024
  this.emit("op", message);
989
1025
  this.scheduleManager.afterOpProcessing(undefined, message);
1026
+ if (local) {
1027
+ // If we have processed a local op, this means that the container is
1028
+ // making progress and we can reset the counter for how many times
1029
+ // we have consecutively replayed the pending states
1030
+ this.resetReconnectCount();
1031
+ }
990
1032
  }
991
1033
  catch (e) {
992
1034
  this.scheduleManager.afterOpProcessing(e, message);