@fluidframework/container-loader 2.110.0 → 2.112.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @fluidframework/container-loader
2
2
 
3
+ ## 2.112.0
4
+
5
+ Dependency updates only.
6
+
7
+ ## 2.111.0
8
+
9
+ ### Minor Changes
10
+
11
+ - Fix container crash when read-only mode is forced from a connected event handler ([#27637](https://github.com/microsoft/FluidFramework/pull/27637)) [32bbb06cf7f](https://github.com/microsoft/FluidFramework/commit/32bbb06cf7f7a59075624cc48c89c81b7687d890)
12
+
13
+ Forcing a container into read-only mode synchronously from within a "connected" event handler could leave an internal catch-up monitor in an inconsistent state, causing a later reconnection to fail with an assert ("catchUpMonitor should be gone", `0x3eb`).
14
+
15
+ This occurred when a client established an already-caught-up read connection and application code reacted to the resulting connection transition by disconnecting (for example, by forcing read-only mode). The catch-up monitor is now stored before it can synchronously notify listeners, so a re-entrant disconnect during the connection transition is handled correctly and subsequent reconnections proceed normally.
16
+
3
17
  ## 2.110.0
4
18
 
5
19
  ### Minor Changes
@@ -11,7 +11,13 @@ type CaughtUpListener = () => void;
11
11
  /**
12
12
  * Monitor that emits an event when a Container has caught up to a given point in the op stream
13
13
  */
14
- export type ICatchUpMonitor = IDisposable;
14
+ export interface ICatchUpMonitor extends IDisposable {
15
+ /**
16
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
17
+ * @remarks Must be called after the owner has stored its reference to the monitor.
18
+ */
19
+ start(): void;
20
+ }
15
21
  /**
16
22
  * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed
17
23
  * that were known at the time the monitor was created.
@@ -24,8 +30,23 @@ export declare class CatchUpMonitor implements ICatchUpMonitor {
24
30
  private readonly opHandler;
25
31
  /**
26
32
  * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
33
+ *
34
+ * @remarks
35
+ * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor
36
+ * reference has been stored to evaluate whether the container is already caught up. This ordering is
37
+ * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter
38
+ * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's
39
+ * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and
40
+ * leave a stale monitor behind (see assert 0x3eb).
27
41
  */
28
42
  constructor(deltaManager: IDeltaManager<unknown, unknown>, listener: CaughtUpListener);
43
+ /**
44
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
45
+ * @remarks
46
+ * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy
47
+ * triggered by the listener observes a fully-constructed, assigned monitor.
48
+ */
49
+ start(): void;
29
50
  private _disposed;
30
51
  /**
31
52
  * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}
@@ -1 +1 @@
1
- {"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAE1C;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAiBpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjB1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF;;OAEG;gBAEe,YAAY,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAC7C,QAAQ,EAAE,gBAAgB;IAe5C,OAAO,CAAC,SAAS,CAAkB;IAEnC;;OAEG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;OAEG;IACI,OAAO,IAAI,IAAI;CAQtB"}
1
+ {"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IACnD;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAyBpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAzB1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF;;;;;;;;;;OAUG;gBAEe,YAAY,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAC7C,QAAQ,EAAE,gBAAgB;IAY5C;;;;;OAKG;IACI,KAAK,IAAI,IAAI;IAKpB,OAAO,CAAC,SAAS,CAAkB;IAEnC;;OAEG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;OAEG;IACI,OAAO,IAAI,IAAI;CAQtB"}
@@ -13,6 +13,14 @@ const internal_1 = require("@fluidframework/core-utils/internal");
13
13
  class CatchUpMonitor {
14
14
  /**
15
15
  * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
16
+ *
17
+ * @remarks
18
+ * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor
19
+ * reference has been stored to evaluate whether the container is already caught up. This ordering is
20
+ * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter
21
+ * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's
22
+ * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and
23
+ * leave a stale monitor behind (see assert 0x3eb).
16
24
  */
17
25
  constructor(deltaManager, listener) {
18
26
  this.deltaManager = deltaManager;
@@ -28,6 +36,14 @@ class CatchUpMonitor {
28
36
  this.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;
29
37
  (0, internal_1.assert)(this.targetSeqNumber >= this.deltaManager.lastSequenceNumber, 0x37c /* Cannot wait for seqNumber below last processed sequence number */);
30
38
  this.deltaManager.on("op", this.opHandler);
39
+ }
40
+ /**
41
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
42
+ * @remarks
43
+ * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy
44
+ * triggered by the listener observes a fully-constructed, assigned monitor.
45
+ */
46
+ start() {
31
47
  // Simulate the last processed op to set caughtUp in case we already are
32
48
  this.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });
33
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAIH,kEAA6D;AAa7D;;;GAGG;AACH,MAAa,cAAc;IAa1B;;OAEG;IACH,YACkB,YAA6C,EAC7C,QAA0B;QAD1B,iBAAY,GAAZ,YAAY,CAAiC;QAC7C,aAAQ,GAAR,QAAQ,CAAkB;QAhBpC,aAAQ,GAAY,KAAK,CAAC;QAEjB,cAAS,GAAG,CAC5B,OAA0D,EACnD,EAAE;YACT,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,CAAC;QACF,CAAC,CAAC;QAsBM,cAAS,GAAY,KAAK,CAAC;QAblC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAE5D,IAAA,iBAAM,EACL,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAC5D,KAAK,CAAC,oEAAoE,CAC1E,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3C,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC1E,CAAC;IAID;;OAEG;IACH,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;OAEG;IACI,OAAO;QACb,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;CACD;AArDD,wCAqDC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\n\n/**\n * @see {@link CatchUpMonitor} for usage.\n */\ntype CaughtUpListener = () => void;\n\n/**\n * Monitor that emits an event when a Container has caught up to a given point in the op stream\n */\nexport type ICatchUpMonitor = IDisposable;\n\n/**\n * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed\n * that were known at the time the monitor was created.\n */\nexport class CatchUpMonitor implements ICatchUpMonitor {\n\tprivate readonly targetSeqNumber: number;\n\tprivate caughtUp: boolean = false;\n\n\tprivate readonly opHandler = (\n\t\tmessage: Pick<ISequencedDocumentMessage, \"sequenceNumber\">,\n\t): void => {\n\t\tif (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {\n\t\t\tthis.caughtUp = true;\n\t\t\tthis.listener();\n\t\t}\n\t};\n\n\t/**\n\t * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.\n\t */\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t\tprivate readonly listener: CaughtUpListener,\n\t) {\n\t\tthis.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;\n\n\t\tassert(\n\t\t\tthis.targetSeqNumber >= this.deltaManager.lastSequenceNumber,\n\t\t\t0x37c /* Cannot wait for seqNumber below last processed sequence number */,\n\t\t);\n\n\t\tthis.deltaManager.on(\"op\", this.opHandler);\n\n\t\t// Simulate the last processed op to set caughtUp in case we already are\n\t\tthis.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });\n\t}\n\n\tprivate _disposed: boolean = false;\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}\n\t */\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.dispose}\n\t */\n\tpublic dispose(): void {\n\t\tif (this._disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis._disposed = true;\n\n\t\tthis.deltaManager.off(\"op\", this.opHandler);\n\t}\n}\n"]}
1
+ {"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAIH,kEAA6D;AAmB7D;;;GAGG;AACH,MAAa,cAAc;IAa1B;;;;;;;;;;OAUG;IACH,YACkB,YAA6C,EAC7C,QAA0B;QAD1B,iBAAY,GAAZ,YAAY,CAAiC;QAC7C,aAAQ,GAAR,QAAQ,CAAkB;QAxBpC,aAAQ,GAAY,KAAK,CAAC;QAEjB,cAAS,GAAG,CAC5B,OAA0D,EACnD,EAAE;YACT,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,CAAC;QACF,CAAC,CAAC;QAsCM,cAAS,GAAY,KAAK,CAAC;QArBlC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAE5D,IAAA,iBAAM,EACL,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAC5D,KAAK,CAAC,oEAAoE,CAC1E,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACI,KAAK;QACX,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC1E,CAAC;IAID;;OAEG;IACH,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;OAEG;IACI,OAAO;QACb,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;CACD;AArED,wCAqEC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\n\n/**\n * @see {@link CatchUpMonitor} for usage.\n */\ntype CaughtUpListener = () => void;\n\n/**\n * Monitor that emits an event when a Container has caught up to a given point in the op stream\n */\nexport interface ICatchUpMonitor extends IDisposable {\n\t/**\n\t * Evaluate whether the container is already caught up, synchronously notifying the listener if so.\n\t * @remarks Must be called after the owner has stored its reference to the monitor.\n\t */\n\tstart(): void;\n}\n\n/**\n * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed\n * that were known at the time the monitor was created.\n */\nexport class CatchUpMonitor implements ICatchUpMonitor {\n\tprivate readonly targetSeqNumber: number;\n\tprivate caughtUp: boolean = false;\n\n\tprivate readonly opHandler = (\n\t\tmessage: Pick<ISequencedDocumentMessage, \"sequenceNumber\">,\n\t): void => {\n\t\tif (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {\n\t\t\tthis.caughtUp = true;\n\t\t\tthis.listener();\n\t\t}\n\t};\n\n\t/**\n\t * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.\n\t *\n\t * @remarks\n\t * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor\n\t * reference has been stored to evaluate whether the container is already caught up. This ordering is\n\t * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter\n\t * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's\n\t * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and\n\t * leave a stale monitor behind (see assert 0x3eb).\n\t */\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t\tprivate readonly listener: CaughtUpListener,\n\t) {\n\t\tthis.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;\n\n\t\tassert(\n\t\t\tthis.targetSeqNumber >= this.deltaManager.lastSequenceNumber,\n\t\t\t0x37c /* Cannot wait for seqNumber below last processed sequence number */,\n\t\t);\n\n\t\tthis.deltaManager.on(\"op\", this.opHandler);\n\t}\n\n\t/**\n\t * Evaluate whether the container is already caught up, synchronously notifying the listener if so.\n\t * @remarks\n\t * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy\n\t * triggered by the listener observes a fully-constructed, assigned monitor.\n\t */\n\tpublic start(): void {\n\t\t// Simulate the last processed op to set caughtUp in case we already are\n\t\tthis.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });\n\t}\n\n\tprivate _disposed: boolean = false;\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}\n\t */\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.dispose}\n\t */\n\tpublic dispose(): void {\n\t\tif (this._disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis._disposed = true;\n\n\t\tthis.deltaManager.off(\"op\", this.opHandler);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
@@ -131,7 +131,14 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
131
131
  // we might get callback right away, and it will screw up state transition (as code outside of switch
132
132
  // statement will overwrite current state).
133
133
  (0, internal_1.assert)(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);
134
+ // Assign the field BEFORE starting the monitor. `start()` may synchronously fire
135
+ // transitionToConnectedState (when already caught up), which can re-enter this handler
136
+ // (e.g. an app "connected" listener forcing readonly, triggering a disconnect). The
137
+ // Disconnected case clears this.catchUpMonitor, so the field must already reference the
138
+ // monitor for that cleanup to take effect; otherwise a stale monitor survives and the
139
+ // next Connected transition trips assert 0x3eb.
134
140
  this.catchUpMonitor = new catchUpMonitor_js_1.CatchUpMonitor(this.deltaManager, this.transitionToConnectedState);
141
+ this.catchUpMonitor.start();
135
142
  return;
136
143
  }
137
144
  case connectionState_js_1.ConnectionState.Disconnected: {
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.js","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { ITelemetryBaseProperties } from \"@fluidframework/core-interfaces\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport { assert, Timer } from \"@fluidframework/core-utils/internal\";\nimport type { IClient, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type { IAnyDriverError } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\ttype MonitoringContext,\n\tPerformanceEvent,\n\ttype TelemetryEventCategory,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { CatchUpMonitor, type ICatchUpMonitor } from \"./catchUpMonitor.js\";\nimport { ConnectionState } from \"./connectionState.js\";\nimport type { IConnectionDetailsInternal, IConnectionStateChangeReason } from \"./contracts.js\";\nimport type { IProtocolHandler } from \"./protocol.js\";\n\n// Based on recent data, it looks like majority of cases where we get stuck are due to really slow or\n// timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so\n// if retrying fixes the problem, we should not see these events.\nconst JoinOpTimeoutMs = 45000;\n\n// Timeout waiting for \"self\" join signal, before giving up\nconst JoinSignalTimeoutMs = 10000;\n\n/**\n * Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler\n */\nexport interface IConnectionStateHandlerInputs {\n\tlogger: TelemetryLoggerExt;\n\tmc: MonitoringContext;\n\t/**\n\t * Log to telemetry any change in state, included to Connecting\n\t */\n\tconnectionStateChanged: (\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t) => void;\n\t/**\n\t * Whether to expect the client to join in write mode on next connection\n\t */\n\tshouldClientJoinWrite: () => boolean;\n\t/**\n\t * (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again\n\t */\n\tmaxClientLeaveWaitTime: number | undefined;\n\t/**\n\t * Log an issue encountered while in the Connecting state. details will be logged as a JSON string\n\t */\n\tlogConnectionIssue: (\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t) => void;\n\t/**\n\t * Callback to note that an old local client ID is still present in the Quorum that should have left and should now be considered invalid\n\t */\n\tclientShouldHaveLeft: (clientId: string) => void;\n\n\t/**\n\t * Some critical error was hit. Container should be closed and error logged.\n\t */\n\tonCriticalError: (error: unknown) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n\treadonly connectionState: ConnectionState;\n\t/**\n\t * Pending clientID.\n\t * Changes whenever socket connection is established.\n\t * Resets to undefined when connection is lost\n\t */\n\treadonly pendingClientId: string | undefined;\n\t/**\n\t * clientId of a last established connection.\n\t * Does not reset on disconnect.\n\t * Changes only when new connection is established, client is fully caught up, and\n\t * there is no chance to ops from previous connection (i.e. if needed, we have waited and observed leave op from previous connection)\n\t */\n\treadonly clientId: string | undefined;\n\n\tcontainerSaved(): void;\n\tdispose(): void;\n\tinitProtocol(protocol: IProtocolHandler): void;\n\treceivedConnectEvent(details: IConnectionDetailsInternal): void;\n\treceivedDisconnectEvent(reason: IConnectionStateChangeReason): void;\n\testablishingConnection(reason: IConnectionStateChangeReason): void;\n\t/**\n\t * Switches state to disconnected when we are still establishing connection during container.load(),\n\t * container connect() or reconnect and the container gets closed or disposed or disconnect happens.\n\t * @param reason - reason for cancelling the connection.\n\t */\n\tcancelEstablishingConnection(reason: IConnectionStateChangeReason): void;\n}\n\nexport function createConnectionStateHandler(\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateHandler | ConnectionStateCatchup {\n\tconst config = inputs.mc.config;\n\treturn createConnectionStateHandlerCore(\n\t\tconfig.getBoolean(\"Fluid.Container.DisableCatchUpBeforeDeclaringConnected\") !== true, // connectedRaisedWhenCaughtUp\n\t\tconfig.getBoolean(\"Fluid.Container.DisableJoinSignalWait\") !== true, // readClientsWaitForJoinSignal\n\t\tinputs,\n\t\tdeltaManager,\n\t\tclientId,\n\t);\n}\n\nexport function createConnectionStateHandlerCore(\n\tconnectedRaisedWhenCaughtUp: boolean,\n\treadClientsWaitForJoinSignal: boolean,\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateCatchup | ConnectionStateHandler {\n\tconst factory = (handler: IConnectionStateHandlerInputs): ConnectionStateHandler =>\n\t\tnew ConnectionStateHandler(handler, readClientsWaitForJoinSignal, clientId);\n\n\treturn connectedRaisedWhenCaughtUp\n\t\t? new ConnectionStateCatchup(inputs, factory, deltaManager)\n\t\t: factory(inputs);\n}\n\n/**\n * Helper internal interface to abstract away Audience & Quorum\n */\ninterface IMembership {\n\ton(\n\t\teventName: \"addMember\" | \"removeMember\",\n\t\tlistener: (clientId: string, details: IClient | ISequencedClient) => void,\n\t);\n\tgetMember(clientId: string): undefined | unknown;\n}\n\n/**\n * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.\n * It implements both ends of communication interfaces and passes data back and forward\n */\nclass ConnectionStateHandlerPassThrough\n\timplements IConnectionStateHandler, IConnectionStateHandlerInputs\n{\n\tprotected readonly pimpl: IConnectionStateHandler;\n\n\tconstructor(\n\t\tprotected readonly inputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t) {\n\t\tthis.pimpl = pimplFactory(this);\n\t}\n\n\t// #region IConnectionStateHandler\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this.pimpl.connectionState;\n\t}\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this.pimpl.pendingClientId;\n\t}\n\tpublic get clientId(): string | undefined {\n\t\treturn this.pimpl.clientId;\n\t}\n\n\tpublic containerSaved(): void {\n\t\treturn this.pimpl.containerSaved();\n\t}\n\tpublic dispose(): void {\n\t\treturn this.pimpl.dispose();\n\t}\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\treturn this.pimpl.initProtocol(protocol);\n\t}\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\treturn this.pimpl.receivedDisconnectEvent(reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.establishingConnection(reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.cancelEstablishingConnection(reason);\n\t}\n\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\treturn this.pimpl.receivedConnectEvent(details);\n\t}\n\n\t// #endregion\n\n\t// #region IConnectionStateHandlerInputs\n\n\tpublic get logger(): TelemetryLoggerExt {\n\t\treturn this.inputs.logger;\n\t}\n\tpublic get mc(): MonitoringContext {\n\t\treturn this.inputs.mc;\n\t}\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\treturn this.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\tpublic shouldClientJoinWrite(): boolean {\n\t\treturn this.inputs.shouldClientJoinWrite();\n\t}\n\tpublic get maxClientLeaveWaitTime(): number | undefined {\n\t\treturn this.inputs.maxClientLeaveWaitTime;\n\t}\n\tpublic logConnectionIssue(\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t): void {\n\t\treturn this.inputs.logConnectionIssue(eventName, category, details);\n\t}\n\tpublic clientShouldHaveLeft(clientId: string): void {\n\t\treturn this.inputs.clientShouldHaveLeft(clientId);\n\t}\n\n\tpublic onCriticalError(error: unknown): void {\n\t\treturn this.inputs.onCriticalError(error);\n\t}\n\n\t// #endregion\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nexport class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {\n\tprivate catchUpMonitor: ICatchUpMonitor | undefined;\n\n\tconstructor(\n\t\tinputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t) {\n\t\tsuper(inputs, pimplFactory);\n\t\tthis._connectionState = this.pimpl.connectionState;\n\t}\n\n\tprivate _connectionState: ConnectionState;\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason<IAnyDriverError>,\n\t): void {\n\t\tswitch (value) {\n\t\t\tcase ConnectionState.Connected: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.CatchingUp,\n\t\t\t\t\t0x3e1 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\t// Create catch-up monitor here (not earlier), as we might get more exact info by now about how far\n\t\t\t\t// client is behind through join signal. This is only true if base layer uses signals (i.e. audience,\n\t\t\t\t// not quorum, including for \"rea\" connections) to make decisions about moving to \"connected\" state.\n\t\t\t\t// In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as\n\t\t\t\t// we might get callback right away, and it will screw up state transition (as code outside of switch\n\t\t\t\t// statement will overwrite current state).\n\t\t\t\tassert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);\n\t\t\t\tthis.catchUpMonitor = new CatchUpMonitor(\n\t\t\t\t\tthis.deltaManager,\n\t\t\t\t\tthis.transitionToConnectedState,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase ConnectionState.Disconnected: {\n\t\t\t\tthis.catchUpMonitor?.dispose();\n\t\t\t\tthis.catchUpMonitor = undefined;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// ConnectionState.EstablishingConnection state would be set when we start establishing connection\n\t\t\t// during container.connect() or reconnect because of an error.\n\t\t\tcase ConnectionState.EstablishingConnection: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.Disconnected,\n\t\t\t\t\t0x6d2 /* connectivity transition to establishing connection */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase ConnectionState.CatchingUp: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t\t\t0x3e3 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t}\n\t\tthis._connectionState = value;\n\t\tthis.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\n\tprivate readonly transitionToConnectedState = (): void => {\n\t\t// Defensive measure, we should always be in Connecting state when this is called.\n\t\tconst state = this.pimpl.connectionState;\n\t\tassert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);\n\t\tassert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);\n\t\tthis._connectionState = ConnectionState.Connected;\n\t\tthis.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {\n\t\t\ttext: \"caught up\",\n\t\t});\n\t};\n}\n\n/**\n * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.\n * This class ensures that any ops sent by this container instance on previous connection are either\n * sequenced or blocked by the server before emitting the new \"connected\" event and allowing runtime to resubmit ops.\n *\n * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op\n * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot\n * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could\n * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).\n *\n * The job of this class is to encapsulate the transition period during reconnect, which is identified by\n * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:\n *\n * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops\n * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other\n * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.\n *\n * b. We process the Join op for the new clientId (identified when the underlying connection was first established),\n * indicating the service is ready to sequence ops sent with the new clientId.\n *\n * c. We process all ops known at the time the underlying connection was established (so we are \"caught up\")\n *\n * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.\n *\n * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op/signal is\n * processed.\n *\n * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent\n */\nexport class ConnectionStateHandler implements IConnectionStateHandler {\n\tprivate _connectionState = ConnectionState.Disconnected;\n\tprivate _pendingClientId: string | undefined;\n\n\t/**\n\t * Tracks that we observe the \"leave\" op within the timeout for our previous clientId (see comment on ConnectionStateHandler class)\n\t * ! This ensures we do not switch to a new clientId until we process all potential messages from old clientId\n\t * ! i.e. We will always see the \"leave\" op for a client after we have seen all the ops it has sent\n\t * ! This check helps prevent the same op from being resubmitted by the PendingStateManager upon reconnecting\n\t */\n\tprivate readonly prevClientLeftTimer: Timer;\n\n\t/**\n\t * Tracks that we observe our own \"join\" op within the timeout after receiving a \"connected\" event from the DeltaManager\n\t */\n\tprivate readonly joinTimer: Timer;\n\n\tprivate protocol?: IProtocolHandler;\n\tprivate connection?: IConnectionDetailsInternal;\n\tprivate _clientId?: string;\n\n\t/**\n\t * Track how long we waited to see \"leave\" op for previous clientId\n\t */\n\tprivate waitEvent: PerformanceEvent | undefined;\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic get clientId(): string | undefined {\n\t\treturn this._clientId;\n\t}\n\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this._pendingClientId;\n\t}\n\n\tconstructor(\n\t\tprivate readonly handler: IConnectionStateHandlerInputs,\n\t\tprivate readonly readClientsWaitForJoinSignal: boolean,\n\t\tclientIdFromPausedSession?: string,\n\t) {\n\t\tthis._clientId = clientIdFromPausedSession;\n\t\t// eslint-disable-next-line unicorn/consistent-function-scoping\n\t\tconst errorHandler = (error): void => this.handler.onCriticalError(error);\n\t\tthis.prevClientLeftTimer = new Timer(\n\t\t\t// Default is 5 min for which we are going to wait for its own \"leave\" message. This is same as\n\t\t\t// the max time on server after which leave op is sent.\n\t\t\tthis.handler.maxClientLeaveWaitTime ?? 300000,\n\t\t\t() => {\n\t\t\t\tassert(\n\t\t\t\t\tthis.connectionState !== ConnectionState.Connected,\n\t\t\t\t\t0x2ac /* \"Connected when timeout waiting for leave from previous session fired!\" */,\n\t\t\t\t);\n\t\t\t\tthis.applyForConnectedState(\"timeout\");\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\n\t\tthis.joinTimer = new Timer(\n\t\t\t0, // default value is not used - startjoinTimer() explicitly provides timeout\n\t\t\t() => {\n\t\t\t\t// I've observed timer firing within couple ms from disconnect event, looks like\n\t\t\t\t// queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.\n\t\t\t\tif (this.connectionState !== ConnectionState.CatchingUp) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst details = {\n\t\t\t\t\tprotocolInitialized: this.protocol !== undefined,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t};\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"NoJoinSignal\" : \"NoJoinOp\", // eventName\n\t\t\t\t\t\"error\", // category\n\t\t\t\t\tdetails,\n\t\t\t\t);\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\t}\n\n\tprivate startjoinTimer(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x234 /* \"has joinTimer\" */);\n\t\tassert(this.connection !== undefined, 0x4b3 /* have connection */);\n\t\tthis.joinTimer.start(\n\t\t\tthis.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n\t\t);\n\t}\n\n\tprivate stopjoinTimer(): void {\n\t\tassert(this.joinTimer.hasTimer, 0x235 /* \"no joinTimer\" */);\n\t\tthis.joinTimer.clear();\n\t}\n\n\tprivate get waitingForLeaveOp(): boolean {\n\t\treturn this.prevClientLeftTimer.hasTimer;\n\t}\n\n\tpublic dispose(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n\t\tthis.prevClientLeftTimer.clear();\n\t}\n\n\tpublic containerSaved(): void {\n\t\t// If we were waiting for moving to Connected state, then only apply for state change. Since the container\n\t\t// is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.\n\t\tif (this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"containerSaved\");\n\t\t}\n\t}\n\n\tprivate receivedAddMemberEvent(clientId: string): void {\n\t\t// This is the only one that requires the pending client ID\n\t\tif (clientId === this.pendingClientId) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t} else if (this.shouldWaitForSelf()) {\n\t\t\t\t// timer has already fired, meaning it took too long to get join op/signal.\n\t\t\t\t// Record how long it actually took to recover.\n\t\t\t\t// This is generic event, as it by itself is not an error.\n\t\t\t\t// We also have a case where NoJoinOp happens during container boot (we do not report it as error in such case),\n\t\t\t\t// if this log statement happens after boot - we do not want to consider it error case.\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"ReceivedJoinSignal\" : \"ReceivedJoinOp\", // eventName\n\t\t\t\t\t\"generic\", // category\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Start the event in case we are waiting for leave or timeout.\n\t\t\tif (this.waitingForLeaveOp) {\n\t\t\t\tthis.waitEvent = PerformanceEvent.start(this.handler.logger, {\n\t\t\t\t\teventName: \"WaitBeforeClientLeave\",\n\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\twaitOnClientId: this._clientId,\n\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.applyForConnectedState(\"addMemberEvent\");\n\t\t}\n\t}\n\n\tprivate applyForConnectedState(\n\t\tsource: \"removeMemberEvent\" | \"addMemberEvent\" | \"timeout\" | \"containerSaved\",\n\t): void {\n\t\tassert(\n\t\t\tthis.protocol !== undefined,\n\t\t\t0x236 /* \"In all cases it should be already installed\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp || this.hasMember(this.clientId),\n\t\t\t0x2e2 /* \"Must only wait for leave message when clientId in quorum\" */,\n\t\t);\n\n\t\t// Move to connected state only if:\n\t\t// 1. We have seen our own \"join\" op (i.e. for this.pendingClientId)\n\t\t// 2. There is no \"leave\" timer running, meaning this is our first connection or the previous client has left (via this.prevClientLeftTimer)\n\t\tif (\n\t\t\tthis.pendingClientId !== this.clientId &&\n\t\t\tthis.hasMember(this.pendingClientId) &&\n\t\t\t!this.waitingForLeaveOp\n\t\t) {\n\t\t\tthis.waitEvent?.end({ source });\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t} else {\n\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t// We may not see any ops due to being disconnected all that time - that's not an error!\n\t\t\tconst error =\n\t\t\t\tsource === \"timeout\" && this.connectionState !== ConnectionState.Disconnected;\n\t\t\tthis.handler.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"connectedStateRejected\",\n\t\t\t\tcategory: error ? \"error\" : \"generic\",\n\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\tsource,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate receivedRemoveMemberEvent(clientId: string): void {\n\t\t// If the client which has left was us, then finish the timer.\n\t\tif (this.clientId === clientId && this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"removeMemberEvent\");\n\t\t}\n\t}\n\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\tthis.connection = undefined;\n\t\tthis.setConnectionState(ConnectionState.Disconnected, reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tassert(\n\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t0x6d3 /* Connection state should be EstablishingConnection */,\n\t\t);\n\t\tassert(this.connection === undefined, 0x6d4 /* No connection should be present */);\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.Disconnected;\n\t\tthis.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.EstablishingConnection;\n\t\tthis.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {\n\t\t\ttext: `Establishing Connection due to ${reason.text}`,\n\t\t\terror: reason.error,\n\t\t});\n\t}\n\n\t/**\n\t * Tells if need to wait for \"self\" to show up in audience.\n\t * @returns true if we should wait for \"self\" to appear in audience.\n\t * false is returned only for \"read\" connections, and only if this.readClientsWaitForJoinSignal is false.\n\t */\n\tprivate shouldWaitForSelf(): boolean {\n\t\tassert(\n\t\t\tthis.connection !== undefined,\n\t\t\t0x4b4 /* all callers call here with active connection */,\n\t\t);\n\t\treturn this.connection.mode === \"write\" || this.readClientsWaitForJoinSignal;\n\t}\n\n\t/**\n\t * The \"connect\" event indicates the connection to the Relay Service is live.\n\t * However, some additional conditions must be met before we can fully transition to\n\t * \"Connected\" state. This function handles that interim period, known as \"Connecting\" state.\n\t * @param details - Connection details returned from the Relay Service\n\t * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.\n\t * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for\n\t */\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\tthis.connection = details;\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.CatchingUp;\n\n\t\t// The following checks are wrong. They are only valid if user has write access to a file.\n\t\t// If user lost such access mid-session, user will not be able to get \"write\" connection.\n\t\t//\n\t\t// const writeConnection = details.mode === \"write\";\n\t\t// assert(!this.handler.shouldClientJoinWrite() || writeConnection,\n\t\t// 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);\n\t\t// assert(!this.waitingForLeaveOp || writeConnection,\n\t\t// 0x2a6 /* \"waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)\" */);\n\n\t\t// Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected\n\t\t// (have received the join message for the client ID)\n\t\t// This is especially important in the reconnect case. It's possible there could be outstanding\n\t\t// ops sent by this client, so we should keep the old client id until we see our own client's\n\t\t// join message. after we see the join message for our new connection with our new client id,\n\t\t// we know there can no longer be outstanding ops that we sent with the previous client id.\n\t\tthis._pendingClientId = details.clientId;\n\n\t\t// IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state\n\t\tthis.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState, details.reason);\n\n\t\t// Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.\n\t\t// Pending clientId could have joined already (i.e. join op/signal already processed):\n\t\t// We are fetching ops from storage in parallel to connecting to Relay Service,\n\t\t// and given async processes, it's possible that we have already processed our own join message before\n\t\t// connection was fully established.\n\t\tif (!this.hasMember(this._pendingClientId) && this.shouldWaitForSelf()) {\n\t\t\t// We are waiting for our own join op / signal. When it is processed\n\t\t\t// we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.\n\t\t\tthis.startjoinTimer();\n\t\t} else if (!this.waitingForLeaveOp) {\n\t\t\t// We're not waiting for Join or Leave op (if read-only connection those don't even apply),\n\t\t\t// go ahead and declare the state to be Connected!\n\t\t\t// If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t}\n\t\t// else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later\n\t}\n\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected,\n\t\treason: IConnectionStateChangeReason,\n\t): void;\n\tprivate setConnectionState(value: ConnectionState.Connected): void;\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected | ConnectionState.Connected,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\tif (this.connectionState === value) {\n\t\t\t// Already in the desired state - exit early\n\t\t\tthis.handler.logger.sendErrorEvent({ eventName: \"setConnectionStateSame\", value });\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = value;\n\n\t\t// This is the only place in code that deals with quorum. The rest works with audience\n\t\t// The code below ensures that we do not send ops until we know that old \"write\" client's disconnect\n\t\t// produced (and sequenced) leave op\n\t\tconst currentClientInQuorum =\n\t\t\tthis._clientId !== undefined &&\n\t\t\tthis.protocol?.quorum?.getMember(this._clientId) !== undefined;\n\t\tif (value === ConnectionState.Connected) {\n\t\t\tassert(\n\t\t\t\toldState === ConnectionState.CatchingUp,\n\t\t\t\t0x1d8 /* \"Should only transition from Connecting state\" */,\n\t\t\t);\n\t\t\t// Mark our old client should have left in the quorum if it's still there\n\t\t\tif (currentClientInQuorum) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tthis.handler.clientShouldHaveLeft(this._clientId!);\n\t\t\t}\n\t\t\tthis._clientId = this.pendingClientId;\n\t\t} else if (value === ConnectionState.Disconnected) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t}\n\n\t\t\t// Only wait for \"leave\" message if the connected client exists in the quorum and had some non-acked ops\n\t\t\t// Also check if the timer is not already running as\n\t\t\t// we could receive \"Disconnected\" event multiple times without getting connected and in that case we\n\t\t\t// don't want to reset the timer as we still want to wait on original client which started this timer.\n\t\t\tif (\n\t\t\t\tcurrentClientInQuorum &&\n\t\t\t\tthis.handler.shouldClientJoinWrite() &&\n\t\t\t\t!this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer\n\t\t\t) {\n\t\t\t\tthis.prevClientLeftTimer.restart();\n\t\t\t} else {\n\t\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t\tthis.handler.logger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"noWaitOnDisconnected\",\n\t\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\t\tclientId: this._clientId,\n\t\t\t\t\t\t\tinQuorum: currentClientInQuorum,\n\t\t\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tundefined, // error\n\t\t\t\t\tLogLevel.info,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Report transition\n\t\tthis.handler.connectionStateChanged(this._connectionState, oldState, reason);\n\n\t\t// Clear pending state immediately to prepare for reconnect\n\t\t// Do it after calling connectionStateChanged() above, such that our telemetry contains pendingClientId on disconnect events\n\t\t// and we can pair attempts to connect with disconnects (that's the only ID we have if connection did not move far enough before being disconnected)\n\t\tif (value === ConnectionState.Disconnected) {\n\t\t\tthis._pendingClientId = undefined;\n\t\t}\n\t}\n\n\tprotected get membership(): IMembership | undefined {\n\t\t// We could always use audience here.\n\t\t// This is true because Audience is a superset of quorum, i.e. when we filter Audience to \"write\" clients,\n\t\t// it is exactly the same as quorum! Please see asserts in Audience callbacks setup by initProtocol() enforcing that.\n\t\treturn this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n\t}\n\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\tthis.protocol = protocol;\n\n\t\tthis.membership?.on(\"addMember\", (clientId, details) => {\n\t\t\t// This is very important constrain. We rely on it when testing presence of \"self\" in Audience.\n\t\t\t// We do not want to move to \"connected\" state for \"write\" connections when JoinSignal shows up\n\t\t\t// for \"self\" - we want to move to \"connected\" state only when \"join\" op is received.\n\t\t\tassert(\n\t\t\t\t(details as IClient).mode === \"read\" ||\n\t\t\t\t\tprotocol.quorum.getMember(clientId) !== undefined,\n\t\t\t\t0x4b5 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedAddMemberEvent(clientId);\n\t\t});\n\n\t\tthis.membership?.on(\"removeMember\", (clientId) => {\n\t\t\tassert(\n\t\t\t\tprotocol.quorum.getMember(clientId) === undefined,\n\t\t\t\t0x4b6 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedRemoveMemberEvent(clientId);\n\t\t});\n\n\t\t/* There is a tiny tiny race possible, where these events happen in this order:\n 1. A connection is established (no \"cached\" mode is used, so it happens in parallel / faster than other steps)\n 2. Some other client produces a summary\n 3. We get \"lucky\" and load from that summary as our initial snapshot\n 4. ConnectionStateHandler.initProtocol is called, \"self\" is already in the quorum.\n We could avoid this sequence (and delete test case for it) if we move connection lower in Container.load()\n */\n\t\tif (this.hasMember(this.pendingClientId)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.receivedAddMemberEvent(this.pendingClientId!);\n\t\t}\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp,\n\t\t\t0x99d /* leave timer can't be set as we have not had access to quorum */,\n\t\t);\n\n\t\t// This check is required for scenario of loading container from pending state, and ensuring there is no way\n\t\t// old clientId is still in the quorum (very unlikely, but you never know)\n\t\t// if we have a clientId from a previous container we need to wait for its leave message\n\t\t// This mimicks check in setConnectionState()\n\t\t// Note that we are not consulting this.handler.shouldClientJoinWrite() here\n\t\t// It could produce wrong results for stashed ops were never sent to Loader yet, and if this check\n\t\t// makes determination only on that (and not uses \"dirty\" events), then it can produce wrong result.\n\t\t// In most cases it does not matter, as this client already left quorum. But in really unfortunate case,\n\t\t// we might wait even if we could avoid such wait.\n\t\tif (\n\t\t\tthis._clientId !== undefined &&\n\t\t\tprotocol.quorum?.getMember(this._clientId) !== undefined\n\t\t) {\n\t\t\tthis.prevClientLeftTimer.restart();\n\t\t}\n\t}\n\n\tprotected hasMember(clientId?: string): boolean {\n\t\treturn this.membership?.getMember(clientId ?? \"\") !== undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"connectionStateHandler.js","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { ITelemetryBaseProperties } from \"@fluidframework/core-interfaces\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport { assert, Timer } from \"@fluidframework/core-utils/internal\";\nimport type { IClient, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type { IAnyDriverError } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\ttype MonitoringContext,\n\tPerformanceEvent,\n\ttype TelemetryEventCategory,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { CatchUpMonitor, type ICatchUpMonitor } from \"./catchUpMonitor.js\";\nimport { ConnectionState } from \"./connectionState.js\";\nimport type { IConnectionDetailsInternal, IConnectionStateChangeReason } from \"./contracts.js\";\nimport type { IProtocolHandler } from \"./protocol.js\";\n\n// Based on recent data, it looks like majority of cases where we get stuck are due to really slow or\n// timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so\n// if retrying fixes the problem, we should not see these events.\nconst JoinOpTimeoutMs = 45000;\n\n// Timeout waiting for \"self\" join signal, before giving up\nconst JoinSignalTimeoutMs = 10000;\n\n/**\n * Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler\n */\nexport interface IConnectionStateHandlerInputs {\n\tlogger: TelemetryLoggerExt;\n\tmc: MonitoringContext;\n\t/**\n\t * Log to telemetry any change in state, included to Connecting\n\t */\n\tconnectionStateChanged: (\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t) => void;\n\t/**\n\t * Whether to expect the client to join in write mode on next connection\n\t */\n\tshouldClientJoinWrite: () => boolean;\n\t/**\n\t * (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again\n\t */\n\tmaxClientLeaveWaitTime: number | undefined;\n\t/**\n\t * Log an issue encountered while in the Connecting state. details will be logged as a JSON string\n\t */\n\tlogConnectionIssue: (\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t) => void;\n\t/**\n\t * Callback to note that an old local client ID is still present in the Quorum that should have left and should now be considered invalid\n\t */\n\tclientShouldHaveLeft: (clientId: string) => void;\n\n\t/**\n\t * Some critical error was hit. Container should be closed and error logged.\n\t */\n\tonCriticalError: (error: unknown) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n\treadonly connectionState: ConnectionState;\n\t/**\n\t * Pending clientID.\n\t * Changes whenever socket connection is established.\n\t * Resets to undefined when connection is lost\n\t */\n\treadonly pendingClientId: string | undefined;\n\t/**\n\t * clientId of a last established connection.\n\t * Does not reset on disconnect.\n\t * Changes only when new connection is established, client is fully caught up, and\n\t * there is no chance to ops from previous connection (i.e. if needed, we have waited and observed leave op from previous connection)\n\t */\n\treadonly clientId: string | undefined;\n\n\tcontainerSaved(): void;\n\tdispose(): void;\n\tinitProtocol(protocol: IProtocolHandler): void;\n\treceivedConnectEvent(details: IConnectionDetailsInternal): void;\n\treceivedDisconnectEvent(reason: IConnectionStateChangeReason): void;\n\testablishingConnection(reason: IConnectionStateChangeReason): void;\n\t/**\n\t * Switches state to disconnected when we are still establishing connection during container.load(),\n\t * container connect() or reconnect and the container gets closed or disposed or disconnect happens.\n\t * @param reason - reason for cancelling the connection.\n\t */\n\tcancelEstablishingConnection(reason: IConnectionStateChangeReason): void;\n}\n\nexport function createConnectionStateHandler(\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateHandler | ConnectionStateCatchup {\n\tconst config = inputs.mc.config;\n\treturn createConnectionStateHandlerCore(\n\t\tconfig.getBoolean(\"Fluid.Container.DisableCatchUpBeforeDeclaringConnected\") !== true, // connectedRaisedWhenCaughtUp\n\t\tconfig.getBoolean(\"Fluid.Container.DisableJoinSignalWait\") !== true, // readClientsWaitForJoinSignal\n\t\tinputs,\n\t\tdeltaManager,\n\t\tclientId,\n\t);\n}\n\nexport function createConnectionStateHandlerCore(\n\tconnectedRaisedWhenCaughtUp: boolean,\n\treadClientsWaitForJoinSignal: boolean,\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateCatchup | ConnectionStateHandler {\n\tconst factory = (handler: IConnectionStateHandlerInputs): ConnectionStateHandler =>\n\t\tnew ConnectionStateHandler(handler, readClientsWaitForJoinSignal, clientId);\n\n\treturn connectedRaisedWhenCaughtUp\n\t\t? new ConnectionStateCatchup(inputs, factory, deltaManager)\n\t\t: factory(inputs);\n}\n\n/**\n * Helper internal interface to abstract away Audience & Quorum\n */\ninterface IMembership {\n\ton(\n\t\teventName: \"addMember\" | \"removeMember\",\n\t\tlistener: (clientId: string, details: IClient | ISequencedClient) => void,\n\t);\n\tgetMember(clientId: string): undefined | unknown;\n}\n\n/**\n * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.\n * It implements both ends of communication interfaces and passes data back and forward\n */\nclass ConnectionStateHandlerPassThrough\n\timplements IConnectionStateHandler, IConnectionStateHandlerInputs\n{\n\tprotected readonly pimpl: IConnectionStateHandler;\n\n\tconstructor(\n\t\tprotected readonly inputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t) {\n\t\tthis.pimpl = pimplFactory(this);\n\t}\n\n\t// #region IConnectionStateHandler\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this.pimpl.connectionState;\n\t}\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this.pimpl.pendingClientId;\n\t}\n\tpublic get clientId(): string | undefined {\n\t\treturn this.pimpl.clientId;\n\t}\n\n\tpublic containerSaved(): void {\n\t\treturn this.pimpl.containerSaved();\n\t}\n\tpublic dispose(): void {\n\t\treturn this.pimpl.dispose();\n\t}\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\treturn this.pimpl.initProtocol(protocol);\n\t}\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\treturn this.pimpl.receivedDisconnectEvent(reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.establishingConnection(reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.cancelEstablishingConnection(reason);\n\t}\n\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\treturn this.pimpl.receivedConnectEvent(details);\n\t}\n\n\t// #endregion\n\n\t// #region IConnectionStateHandlerInputs\n\n\tpublic get logger(): TelemetryLoggerExt {\n\t\treturn this.inputs.logger;\n\t}\n\tpublic get mc(): MonitoringContext {\n\t\treturn this.inputs.mc;\n\t}\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\treturn this.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\tpublic shouldClientJoinWrite(): boolean {\n\t\treturn this.inputs.shouldClientJoinWrite();\n\t}\n\tpublic get maxClientLeaveWaitTime(): number | undefined {\n\t\treturn this.inputs.maxClientLeaveWaitTime;\n\t}\n\tpublic logConnectionIssue(\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t): void {\n\t\treturn this.inputs.logConnectionIssue(eventName, category, details);\n\t}\n\tpublic clientShouldHaveLeft(clientId: string): void {\n\t\treturn this.inputs.clientShouldHaveLeft(clientId);\n\t}\n\n\tpublic onCriticalError(error: unknown): void {\n\t\treturn this.inputs.onCriticalError(error);\n\t}\n\n\t// #endregion\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nexport class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {\n\tprivate catchUpMonitor: ICatchUpMonitor | undefined;\n\n\tconstructor(\n\t\tinputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t) {\n\t\tsuper(inputs, pimplFactory);\n\t\tthis._connectionState = this.pimpl.connectionState;\n\t}\n\n\tprivate _connectionState: ConnectionState;\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason<IAnyDriverError>,\n\t): void {\n\t\tswitch (value) {\n\t\t\tcase ConnectionState.Connected: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.CatchingUp,\n\t\t\t\t\t0x3e1 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\t// Create catch-up monitor here (not earlier), as we might get more exact info by now about how far\n\t\t\t\t// client is behind through join signal. This is only true if base layer uses signals (i.e. audience,\n\t\t\t\t// not quorum, including for \"rea\" connections) to make decisions about moving to \"connected\" state.\n\t\t\t\t// In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as\n\t\t\t\t// we might get callback right away, and it will screw up state transition (as code outside of switch\n\t\t\t\t// statement will overwrite current state).\n\t\t\t\tassert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);\n\t\t\t\t// Assign the field BEFORE starting the monitor. `start()` may synchronously fire\n\t\t\t\t// transitionToConnectedState (when already caught up), which can re-enter this handler\n\t\t\t\t// (e.g. an app \"connected\" listener forcing readonly, triggering a disconnect). The\n\t\t\t\t// Disconnected case clears this.catchUpMonitor, so the field must already reference the\n\t\t\t\t// monitor for that cleanup to take effect; otherwise a stale monitor survives and the\n\t\t\t\t// next Connected transition trips assert 0x3eb.\n\t\t\t\tthis.catchUpMonitor = new CatchUpMonitor(\n\t\t\t\t\tthis.deltaManager,\n\t\t\t\t\tthis.transitionToConnectedState,\n\t\t\t\t);\n\t\t\t\tthis.catchUpMonitor.start();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase ConnectionState.Disconnected: {\n\t\t\t\tthis.catchUpMonitor?.dispose();\n\t\t\t\tthis.catchUpMonitor = undefined;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// ConnectionState.EstablishingConnection state would be set when we start establishing connection\n\t\t\t// during container.connect() or reconnect because of an error.\n\t\t\tcase ConnectionState.EstablishingConnection: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.Disconnected,\n\t\t\t\t\t0x6d2 /* connectivity transition to establishing connection */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase ConnectionState.CatchingUp: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t\t\t0x3e3 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t}\n\t\tthis._connectionState = value;\n\t\tthis.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\n\tprivate readonly transitionToConnectedState = (): void => {\n\t\t// Defensive measure, we should always be in Connecting state when this is called.\n\t\tconst state = this.pimpl.connectionState;\n\t\tassert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);\n\t\tassert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);\n\t\tthis._connectionState = ConnectionState.Connected;\n\t\tthis.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {\n\t\t\ttext: \"caught up\",\n\t\t});\n\t};\n}\n\n/**\n * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.\n * This class ensures that any ops sent by this container instance on previous connection are either\n * sequenced or blocked by the server before emitting the new \"connected\" event and allowing runtime to resubmit ops.\n *\n * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op\n * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot\n * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could\n * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).\n *\n * The job of this class is to encapsulate the transition period during reconnect, which is identified by\n * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:\n *\n * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops\n * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other\n * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.\n *\n * b. We process the Join op for the new clientId (identified when the underlying connection was first established),\n * indicating the service is ready to sequence ops sent with the new clientId.\n *\n * c. We process all ops known at the time the underlying connection was established (so we are \"caught up\")\n *\n * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.\n *\n * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op/signal is\n * processed.\n *\n * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent\n */\nexport class ConnectionStateHandler implements IConnectionStateHandler {\n\tprivate _connectionState = ConnectionState.Disconnected;\n\tprivate _pendingClientId: string | undefined;\n\n\t/**\n\t * Tracks that we observe the \"leave\" op within the timeout for our previous clientId (see comment on ConnectionStateHandler class)\n\t * ! This ensures we do not switch to a new clientId until we process all potential messages from old clientId\n\t * ! i.e. We will always see the \"leave\" op for a client after we have seen all the ops it has sent\n\t * ! This check helps prevent the same op from being resubmitted by the PendingStateManager upon reconnecting\n\t */\n\tprivate readonly prevClientLeftTimer: Timer;\n\n\t/**\n\t * Tracks that we observe our own \"join\" op within the timeout after receiving a \"connected\" event from the DeltaManager\n\t */\n\tprivate readonly joinTimer: Timer;\n\n\tprivate protocol?: IProtocolHandler;\n\tprivate connection?: IConnectionDetailsInternal;\n\tprivate _clientId?: string;\n\n\t/**\n\t * Track how long we waited to see \"leave\" op for previous clientId\n\t */\n\tprivate waitEvent: PerformanceEvent | undefined;\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic get clientId(): string | undefined {\n\t\treturn this._clientId;\n\t}\n\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this._pendingClientId;\n\t}\n\n\tconstructor(\n\t\tprivate readonly handler: IConnectionStateHandlerInputs,\n\t\tprivate readonly readClientsWaitForJoinSignal: boolean,\n\t\tclientIdFromPausedSession?: string,\n\t) {\n\t\tthis._clientId = clientIdFromPausedSession;\n\t\t// eslint-disable-next-line unicorn/consistent-function-scoping\n\t\tconst errorHandler = (error): void => this.handler.onCriticalError(error);\n\t\tthis.prevClientLeftTimer = new Timer(\n\t\t\t// Default is 5 min for which we are going to wait for its own \"leave\" message. This is same as\n\t\t\t// the max time on server after which leave op is sent.\n\t\t\tthis.handler.maxClientLeaveWaitTime ?? 300000,\n\t\t\t() => {\n\t\t\t\tassert(\n\t\t\t\t\tthis.connectionState !== ConnectionState.Connected,\n\t\t\t\t\t0x2ac /* \"Connected when timeout waiting for leave from previous session fired!\" */,\n\t\t\t\t);\n\t\t\t\tthis.applyForConnectedState(\"timeout\");\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\n\t\tthis.joinTimer = new Timer(\n\t\t\t0, // default value is not used - startjoinTimer() explicitly provides timeout\n\t\t\t() => {\n\t\t\t\t// I've observed timer firing within couple ms from disconnect event, looks like\n\t\t\t\t// queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.\n\t\t\t\tif (this.connectionState !== ConnectionState.CatchingUp) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst details = {\n\t\t\t\t\tprotocolInitialized: this.protocol !== undefined,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t};\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"NoJoinSignal\" : \"NoJoinOp\", // eventName\n\t\t\t\t\t\"error\", // category\n\t\t\t\t\tdetails,\n\t\t\t\t);\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\t}\n\n\tprivate startjoinTimer(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x234 /* \"has joinTimer\" */);\n\t\tassert(this.connection !== undefined, 0x4b3 /* have connection */);\n\t\tthis.joinTimer.start(\n\t\t\tthis.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n\t\t);\n\t}\n\n\tprivate stopjoinTimer(): void {\n\t\tassert(this.joinTimer.hasTimer, 0x235 /* \"no joinTimer\" */);\n\t\tthis.joinTimer.clear();\n\t}\n\n\tprivate get waitingForLeaveOp(): boolean {\n\t\treturn this.prevClientLeftTimer.hasTimer;\n\t}\n\n\tpublic dispose(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n\t\tthis.prevClientLeftTimer.clear();\n\t}\n\n\tpublic containerSaved(): void {\n\t\t// If we were waiting for moving to Connected state, then only apply for state change. Since the container\n\t\t// is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.\n\t\tif (this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"containerSaved\");\n\t\t}\n\t}\n\n\tprivate receivedAddMemberEvent(clientId: string): void {\n\t\t// This is the only one that requires the pending client ID\n\t\tif (clientId === this.pendingClientId) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t} else if (this.shouldWaitForSelf()) {\n\t\t\t\t// timer has already fired, meaning it took too long to get join op/signal.\n\t\t\t\t// Record how long it actually took to recover.\n\t\t\t\t// This is generic event, as it by itself is not an error.\n\t\t\t\t// We also have a case where NoJoinOp happens during container boot (we do not report it as error in such case),\n\t\t\t\t// if this log statement happens after boot - we do not want to consider it error case.\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"ReceivedJoinSignal\" : \"ReceivedJoinOp\", // eventName\n\t\t\t\t\t\"generic\", // category\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Start the event in case we are waiting for leave or timeout.\n\t\t\tif (this.waitingForLeaveOp) {\n\t\t\t\tthis.waitEvent = PerformanceEvent.start(this.handler.logger, {\n\t\t\t\t\teventName: \"WaitBeforeClientLeave\",\n\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\twaitOnClientId: this._clientId,\n\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.applyForConnectedState(\"addMemberEvent\");\n\t\t}\n\t}\n\n\tprivate applyForConnectedState(\n\t\tsource: \"removeMemberEvent\" | \"addMemberEvent\" | \"timeout\" | \"containerSaved\",\n\t): void {\n\t\tassert(\n\t\t\tthis.protocol !== undefined,\n\t\t\t0x236 /* \"In all cases it should be already installed\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp || this.hasMember(this.clientId),\n\t\t\t0x2e2 /* \"Must only wait for leave message when clientId in quorum\" */,\n\t\t);\n\n\t\t// Move to connected state only if:\n\t\t// 1. We have seen our own \"join\" op (i.e. for this.pendingClientId)\n\t\t// 2. There is no \"leave\" timer running, meaning this is our first connection or the previous client has left (via this.prevClientLeftTimer)\n\t\tif (\n\t\t\tthis.pendingClientId !== this.clientId &&\n\t\t\tthis.hasMember(this.pendingClientId) &&\n\t\t\t!this.waitingForLeaveOp\n\t\t) {\n\t\t\tthis.waitEvent?.end({ source });\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t} else {\n\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t// We may not see any ops due to being disconnected all that time - that's not an error!\n\t\t\tconst error =\n\t\t\t\tsource === \"timeout\" && this.connectionState !== ConnectionState.Disconnected;\n\t\t\tthis.handler.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"connectedStateRejected\",\n\t\t\t\tcategory: error ? \"error\" : \"generic\",\n\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\tsource,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate receivedRemoveMemberEvent(clientId: string): void {\n\t\t// If the client which has left was us, then finish the timer.\n\t\tif (this.clientId === clientId && this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"removeMemberEvent\");\n\t\t}\n\t}\n\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\tthis.connection = undefined;\n\t\tthis.setConnectionState(ConnectionState.Disconnected, reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tassert(\n\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t0x6d3 /* Connection state should be EstablishingConnection */,\n\t\t);\n\t\tassert(this.connection === undefined, 0x6d4 /* No connection should be present */);\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.Disconnected;\n\t\tthis.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.EstablishingConnection;\n\t\tthis.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {\n\t\t\ttext: `Establishing Connection due to ${reason.text}`,\n\t\t\terror: reason.error,\n\t\t});\n\t}\n\n\t/**\n\t * Tells if need to wait for \"self\" to show up in audience.\n\t * @returns true if we should wait for \"self\" to appear in audience.\n\t * false is returned only for \"read\" connections, and only if this.readClientsWaitForJoinSignal is false.\n\t */\n\tprivate shouldWaitForSelf(): boolean {\n\t\tassert(\n\t\t\tthis.connection !== undefined,\n\t\t\t0x4b4 /* all callers call here with active connection */,\n\t\t);\n\t\treturn this.connection.mode === \"write\" || this.readClientsWaitForJoinSignal;\n\t}\n\n\t/**\n\t * The \"connect\" event indicates the connection to the Relay Service is live.\n\t * However, some additional conditions must be met before we can fully transition to\n\t * \"Connected\" state. This function handles that interim period, known as \"Connecting\" state.\n\t * @param details - Connection details returned from the Relay Service\n\t * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.\n\t * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for\n\t */\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\tthis.connection = details;\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.CatchingUp;\n\n\t\t// The following checks are wrong. They are only valid if user has write access to a file.\n\t\t// If user lost such access mid-session, user will not be able to get \"write\" connection.\n\t\t//\n\t\t// const writeConnection = details.mode === \"write\";\n\t\t// assert(!this.handler.shouldClientJoinWrite() || writeConnection,\n\t\t// 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);\n\t\t// assert(!this.waitingForLeaveOp || writeConnection,\n\t\t// 0x2a6 /* \"waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)\" */);\n\n\t\t// Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected\n\t\t// (have received the join message for the client ID)\n\t\t// This is especially important in the reconnect case. It's possible there could be outstanding\n\t\t// ops sent by this client, so we should keep the old client id until we see our own client's\n\t\t// join message. after we see the join message for our new connection with our new client id,\n\t\t// we know there can no longer be outstanding ops that we sent with the previous client id.\n\t\tthis._pendingClientId = details.clientId;\n\n\t\t// IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state\n\t\tthis.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState, details.reason);\n\n\t\t// Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.\n\t\t// Pending clientId could have joined already (i.e. join op/signal already processed):\n\t\t// We are fetching ops from storage in parallel to connecting to Relay Service,\n\t\t// and given async processes, it's possible that we have already processed our own join message before\n\t\t// connection was fully established.\n\t\tif (!this.hasMember(this._pendingClientId) && this.shouldWaitForSelf()) {\n\t\t\t// We are waiting for our own join op / signal. When it is processed\n\t\t\t// we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.\n\t\t\tthis.startjoinTimer();\n\t\t} else if (!this.waitingForLeaveOp) {\n\t\t\t// We're not waiting for Join or Leave op (if read-only connection those don't even apply),\n\t\t\t// go ahead and declare the state to be Connected!\n\t\t\t// If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t}\n\t\t// else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later\n\t}\n\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected,\n\t\treason: IConnectionStateChangeReason,\n\t): void;\n\tprivate setConnectionState(value: ConnectionState.Connected): void;\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected | ConnectionState.Connected,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\tif (this.connectionState === value) {\n\t\t\t// Already in the desired state - exit early\n\t\t\tthis.handler.logger.sendErrorEvent({ eventName: \"setConnectionStateSame\", value });\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = value;\n\n\t\t// This is the only place in code that deals with quorum. The rest works with audience\n\t\t// The code below ensures that we do not send ops until we know that old \"write\" client's disconnect\n\t\t// produced (and sequenced) leave op\n\t\tconst currentClientInQuorum =\n\t\t\tthis._clientId !== undefined &&\n\t\t\tthis.protocol?.quorum?.getMember(this._clientId) !== undefined;\n\t\tif (value === ConnectionState.Connected) {\n\t\t\tassert(\n\t\t\t\toldState === ConnectionState.CatchingUp,\n\t\t\t\t0x1d8 /* \"Should only transition from Connecting state\" */,\n\t\t\t);\n\t\t\t// Mark our old client should have left in the quorum if it's still there\n\t\t\tif (currentClientInQuorum) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tthis.handler.clientShouldHaveLeft(this._clientId!);\n\t\t\t}\n\t\t\tthis._clientId = this.pendingClientId;\n\t\t} else if (value === ConnectionState.Disconnected) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t}\n\n\t\t\t// Only wait for \"leave\" message if the connected client exists in the quorum and had some non-acked ops\n\t\t\t// Also check if the timer is not already running as\n\t\t\t// we could receive \"Disconnected\" event multiple times without getting connected and in that case we\n\t\t\t// don't want to reset the timer as we still want to wait on original client which started this timer.\n\t\t\tif (\n\t\t\t\tcurrentClientInQuorum &&\n\t\t\t\tthis.handler.shouldClientJoinWrite() &&\n\t\t\t\t!this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer\n\t\t\t) {\n\t\t\t\tthis.prevClientLeftTimer.restart();\n\t\t\t} else {\n\t\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t\tthis.handler.logger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"noWaitOnDisconnected\",\n\t\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\t\tclientId: this._clientId,\n\t\t\t\t\t\t\tinQuorum: currentClientInQuorum,\n\t\t\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tundefined, // error\n\t\t\t\t\tLogLevel.info,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Report transition\n\t\tthis.handler.connectionStateChanged(this._connectionState, oldState, reason);\n\n\t\t// Clear pending state immediately to prepare for reconnect\n\t\t// Do it after calling connectionStateChanged() above, such that our telemetry contains pendingClientId on disconnect events\n\t\t// and we can pair attempts to connect with disconnects (that's the only ID we have if connection did not move far enough before being disconnected)\n\t\tif (value === ConnectionState.Disconnected) {\n\t\t\tthis._pendingClientId = undefined;\n\t\t}\n\t}\n\n\tprotected get membership(): IMembership | undefined {\n\t\t// We could always use audience here.\n\t\t// This is true because Audience is a superset of quorum, i.e. when we filter Audience to \"write\" clients,\n\t\t// it is exactly the same as quorum! Please see asserts in Audience callbacks setup by initProtocol() enforcing that.\n\t\treturn this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n\t}\n\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\tthis.protocol = protocol;\n\n\t\tthis.membership?.on(\"addMember\", (clientId, details) => {\n\t\t\t// This is very important constrain. We rely on it when testing presence of \"self\" in Audience.\n\t\t\t// We do not want to move to \"connected\" state for \"write\" connections when JoinSignal shows up\n\t\t\t// for \"self\" - we want to move to \"connected\" state only when \"join\" op is received.\n\t\t\tassert(\n\t\t\t\t(details as IClient).mode === \"read\" ||\n\t\t\t\t\tprotocol.quorum.getMember(clientId) !== undefined,\n\t\t\t\t0x4b5 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedAddMemberEvent(clientId);\n\t\t});\n\n\t\tthis.membership?.on(\"removeMember\", (clientId) => {\n\t\t\tassert(\n\t\t\t\tprotocol.quorum.getMember(clientId) === undefined,\n\t\t\t\t0x4b6 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedRemoveMemberEvent(clientId);\n\t\t});\n\n\t\t/* There is a tiny tiny race possible, where these events happen in this order:\n 1. A connection is established (no \"cached\" mode is used, so it happens in parallel / faster than other steps)\n 2. Some other client produces a summary\n 3. We get \"lucky\" and load from that summary as our initial snapshot\n 4. ConnectionStateHandler.initProtocol is called, \"self\" is already in the quorum.\n We could avoid this sequence (and delete test case for it) if we move connection lower in Container.load()\n */\n\t\tif (this.hasMember(this.pendingClientId)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.receivedAddMemberEvent(this.pendingClientId!);\n\t\t}\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp,\n\t\t\t0x99d /* leave timer can't be set as we have not had access to quorum */,\n\t\t);\n\n\t\t// This check is required for scenario of loading container from pending state, and ensuring there is no way\n\t\t// old clientId is still in the quorum (very unlikely, but you never know)\n\t\t// if we have a clientId from a previous container we need to wait for its leave message\n\t\t// This mimicks check in setConnectionState()\n\t\t// Note that we are not consulting this.handler.shouldClientJoinWrite() here\n\t\t// It could produce wrong results for stashed ops were never sent to Loader yet, and if this check\n\t\t// makes determination only on that (and not uses \"dirty\" events), then it can produce wrong result.\n\t\t// In most cases it does not matter, as this client already left quorum. But in really unfortunate case,\n\t\t// we might wait even if we could avoid such wait.\n\t\tif (\n\t\t\tthis._clientId !== undefined &&\n\t\t\tprotocol.quorum?.getMember(this._clientId) !== undefined\n\t\t) {\n\t\t\tthis.prevClientLeftTimer.restart();\n\t\t}\n\t}\n\n\tprotected hasMember(clientId?: string): boolean {\n\t\treturn this.membership?.getMember(clientId ?? \"\") !== undefined;\n\t}\n}\n"]}
@@ -13,11 +13,11 @@ export declare const loaderCoreCompatDetails: {
13
13
  /**
14
14
  * The package version of the Loader layer.
15
15
  */
16
- readonly pkgVersion: "2.110.0";
16
+ readonly pkgVersion: "2.112.0";
17
17
  /**
18
18
  * The current generation of the Loader layer.
19
19
  */
20
- readonly generation: 7;
20
+ readonly generation: 8;
21
21
  };
22
22
  /**
23
23
  * Loader's compatibility details that is exposed to the Runtime layer.
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/container-loader";
8
- export declare const pkgVersion = "2.110.0";
8
+ export declare const pkgVersion = "2.112.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/container-loader";
11
- exports.pkgVersion = "2.110.0";
11
+ exports.pkgVersion = "2.112.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,kCAAkC,CAAC;AAC7C,QAAA,UAAU,GAAG,SAAS,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"2.110.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,kCAAkC,CAAC;AAC7C,QAAA,UAAU,GAAG,SAAS,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"2.112.0\";\n"]}
@@ -11,7 +11,13 @@ type CaughtUpListener = () => void;
11
11
  /**
12
12
  * Monitor that emits an event when a Container has caught up to a given point in the op stream
13
13
  */
14
- export type ICatchUpMonitor = IDisposable;
14
+ export interface ICatchUpMonitor extends IDisposable {
15
+ /**
16
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
17
+ * @remarks Must be called after the owner has stored its reference to the monitor.
18
+ */
19
+ start(): void;
20
+ }
15
21
  /**
16
22
  * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed
17
23
  * that were known at the time the monitor was created.
@@ -24,8 +30,23 @@ export declare class CatchUpMonitor implements ICatchUpMonitor {
24
30
  private readonly opHandler;
25
31
  /**
26
32
  * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
33
+ *
34
+ * @remarks
35
+ * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor
36
+ * reference has been stored to evaluate whether the container is already caught up. This ordering is
37
+ * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter
38
+ * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's
39
+ * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and
40
+ * leave a stale monitor behind (see assert 0x3eb).
27
41
  */
28
42
  constructor(deltaManager: IDeltaManager<unknown, unknown>, listener: CaughtUpListener);
43
+ /**
44
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
45
+ * @remarks
46
+ * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy
47
+ * triggered by the listener observes a fully-constructed, assigned monitor.
48
+ */
49
+ start(): void;
29
50
  private _disposed;
30
51
  /**
31
52
  * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}
@@ -1 +1 @@
1
- {"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAE1C;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAiBpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjB1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF;;OAEG;gBAEe,YAAY,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAC7C,QAAQ,EAAE,gBAAgB;IAe5C,OAAO,CAAC,SAAS,CAAkB;IAEnC;;OAEG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;OAEG;IACI,OAAO,IAAI,IAAI;CAQtB"}
1
+ {"version":3,"file":"catchUpMonitor.d.ts","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IACnD;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IAyBpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAzB1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF;;;;;;;;;;OAUG;gBAEe,YAAY,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAC7C,QAAQ,EAAE,gBAAgB;IAY5C;;;;;OAKG;IACI,KAAK,IAAI,IAAI;IAKpB,OAAO,CAAC,SAAS,CAAkB;IAEnC;;OAEG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;OAEG;IACI,OAAO,IAAI,IAAI;CAQtB"}
@@ -10,6 +10,14 @@ import { assert } from "@fluidframework/core-utils/internal";
10
10
  export class CatchUpMonitor {
11
11
  /**
12
12
  * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
13
+ *
14
+ * @remarks
15
+ * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor
16
+ * reference has been stored to evaluate whether the container is already caught up. This ordering is
17
+ * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter
18
+ * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's
19
+ * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and
20
+ * leave a stale monitor behind (see assert 0x3eb).
13
21
  */
14
22
  constructor(deltaManager, listener) {
15
23
  this.deltaManager = deltaManager;
@@ -25,6 +33,14 @@ export class CatchUpMonitor {
25
33
  this.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;
26
34
  assert(this.targetSeqNumber >= this.deltaManager.lastSequenceNumber, 0x37c /* Cannot wait for seqNumber below last processed sequence number */);
27
35
  this.deltaManager.on("op", this.opHandler);
36
+ }
37
+ /**
38
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
39
+ * @remarks
40
+ * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy
41
+ * triggered by the listener observes a fully-constructed, assigned monitor.
42
+ */
43
+ start() {
28
44
  // Simulate the last processed op to set caughtUp in case we already are
29
45
  this.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });
30
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAa7D;;;GAGG;AACH,MAAM,OAAO,cAAc;IAa1B;;OAEG;IACH,YACkB,YAA6C,EAC7C,QAA0B;QAD1B,iBAAY,GAAZ,YAAY,CAAiC;QAC7C,aAAQ,GAAR,QAAQ,CAAkB;QAhBpC,aAAQ,GAAY,KAAK,CAAC;QAEjB,cAAS,GAAG,CAC5B,OAA0D,EACnD,EAAE;YACT,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,CAAC;QACF,CAAC,CAAC;QAsBM,cAAS,GAAY,KAAK,CAAC;QAblC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAE5D,MAAM,CACL,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAC5D,KAAK,CAAC,oEAAoE,CAC1E,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3C,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC1E,CAAC;IAID;;OAEG;IACH,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;OAEG;IACI,OAAO;QACb,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\n\n/**\n * @see {@link CatchUpMonitor} for usage.\n */\ntype CaughtUpListener = () => void;\n\n/**\n * Monitor that emits an event when a Container has caught up to a given point in the op stream\n */\nexport type ICatchUpMonitor = IDisposable;\n\n/**\n * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed\n * that were known at the time the monitor was created.\n */\nexport class CatchUpMonitor implements ICatchUpMonitor {\n\tprivate readonly targetSeqNumber: number;\n\tprivate caughtUp: boolean = false;\n\n\tprivate readonly opHandler = (\n\t\tmessage: Pick<ISequencedDocumentMessage, \"sequenceNumber\">,\n\t): void => {\n\t\tif (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {\n\t\t\tthis.caughtUp = true;\n\t\t\tthis.listener();\n\t\t}\n\t};\n\n\t/**\n\t * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.\n\t */\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t\tprivate readonly listener: CaughtUpListener,\n\t) {\n\t\tthis.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;\n\n\t\tassert(\n\t\t\tthis.targetSeqNumber >= this.deltaManager.lastSequenceNumber,\n\t\t\t0x37c /* Cannot wait for seqNumber below last processed sequence number */,\n\t\t);\n\n\t\tthis.deltaManager.on(\"op\", this.opHandler);\n\n\t\t// Simulate the last processed op to set caughtUp in case we already are\n\t\tthis.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });\n\t}\n\n\tprivate _disposed: boolean = false;\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}\n\t */\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.dispose}\n\t */\n\tpublic dispose(): void {\n\t\tif (this._disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis._disposed = true;\n\n\t\tthis.deltaManager.off(\"op\", this.opHandler);\n\t}\n}\n"]}
1
+ {"version":3,"file":"catchUpMonitor.js","sourceRoot":"","sources":["../src/catchUpMonitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAmB7D;;;GAGG;AACH,MAAM,OAAO,cAAc;IAa1B;;;;;;;;;;OAUG;IACH,YACkB,YAA6C,EAC7C,QAA0B;QAD1B,iBAAY,GAAZ,YAAY,CAAiC;QAC7C,aAAQ,GAAR,QAAQ,CAAkB;QAxBpC,aAAQ,GAAY,KAAK,CAAC;QAEjB,cAAS,GAAG,CAC5B,OAA0D,EACnD,EAAE;YACT,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,CAAC;QACF,CAAC,CAAC;QAsCM,cAAS,GAAY,KAAK,CAAC;QArBlC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;QAE5D,MAAM,CACL,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAC5D,KAAK,CAAC,oEAAoE,CAC1E,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACI,KAAK;QACX,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC1E,CAAC;IAID;;OAEG;IACH,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;OAEG;IACI,OAAO;QACb,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\n\n/**\n * @see {@link CatchUpMonitor} for usage.\n */\ntype CaughtUpListener = () => void;\n\n/**\n * Monitor that emits an event when a Container has caught up to a given point in the op stream\n */\nexport interface ICatchUpMonitor extends IDisposable {\n\t/**\n\t * Evaluate whether the container is already caught up, synchronously notifying the listener if so.\n\t * @remarks Must be called after the owner has stored its reference to the monitor.\n\t */\n\tstart(): void;\n}\n\n/**\n * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed\n * that were known at the time the monitor was created.\n */\nexport class CatchUpMonitor implements ICatchUpMonitor {\n\tprivate readonly targetSeqNumber: number;\n\tprivate caughtUp: boolean = false;\n\n\tprivate readonly opHandler = (\n\t\tmessage: Pick<ISequencedDocumentMessage, \"sequenceNumber\">,\n\t): void => {\n\t\tif (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {\n\t\t\tthis.caughtUp = true;\n\t\t\tthis.listener();\n\t\t}\n\t};\n\n\t/**\n\t * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.\n\t *\n\t * @remarks\n\t * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor\n\t * reference has been stored to evaluate whether the container is already caught up. This ordering is\n\t * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter\n\t * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's\n\t * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and\n\t * leave a stale monitor behind (see assert 0x3eb).\n\t */\n\tconstructor(\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t\tprivate readonly listener: CaughtUpListener,\n\t) {\n\t\tthis.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;\n\n\t\tassert(\n\t\t\tthis.targetSeqNumber >= this.deltaManager.lastSequenceNumber,\n\t\t\t0x37c /* Cannot wait for seqNumber below last processed sequence number */,\n\t\t);\n\n\t\tthis.deltaManager.on(\"op\", this.opHandler);\n\t}\n\n\t/**\n\t * Evaluate whether the container is already caught up, synchronously notifying the listener if so.\n\t * @remarks\n\t * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy\n\t * triggered by the listener observes a fully-constructed, assigned monitor.\n\t */\n\tpublic start(): void {\n\t\t// Simulate the last processed op to set caughtUp in case we already are\n\t\tthis.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });\n\t}\n\n\tprivate _disposed: boolean = false;\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}\n\t */\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#IDisposable.dispose}\n\t */\n\tpublic dispose(): void {\n\t\tif (this._disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis._disposed = true;\n\n\t\tthis.deltaManager.off(\"op\", this.opHandler);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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"}
@@ -126,7 +126,14 @@ export class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
126
126
  // we might get callback right away, and it will screw up state transition (as code outside of switch
127
127
  // statement will overwrite current state).
128
128
  assert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);
129
+ // Assign the field BEFORE starting the monitor. `start()` may synchronously fire
130
+ // transitionToConnectedState (when already caught up), which can re-enter this handler
131
+ // (e.g. an app "connected" listener forcing readonly, triggering a disconnect). The
132
+ // Disconnected case clears this.catchUpMonitor, so the field must already reference the
133
+ // monitor for that cleanup to take effect; otherwise a stale monitor survives and the
134
+ // next Connected transition trips assert 0x3eb.
129
135
  this.catchUpMonitor = new CatchUpMonitor(this.deltaManager, this.transitionToConnectedState);
136
+ this.catchUpMonitor.start();
130
137
  return;
131
138
  }
132
139
  case ConnectionState.Disconnected: {
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.js","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { ITelemetryBaseProperties } from \"@fluidframework/core-interfaces\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport { assert, Timer } from \"@fluidframework/core-utils/internal\";\nimport type { IClient, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type { IAnyDriverError } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\ttype MonitoringContext,\n\tPerformanceEvent,\n\ttype TelemetryEventCategory,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { CatchUpMonitor, type ICatchUpMonitor } from \"./catchUpMonitor.js\";\nimport { ConnectionState } from \"./connectionState.js\";\nimport type { IConnectionDetailsInternal, IConnectionStateChangeReason } from \"./contracts.js\";\nimport type { IProtocolHandler } from \"./protocol.js\";\n\n// Based on recent data, it looks like majority of cases where we get stuck are due to really slow or\n// timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so\n// if retrying fixes the problem, we should not see these events.\nconst JoinOpTimeoutMs = 45000;\n\n// Timeout waiting for \"self\" join signal, before giving up\nconst JoinSignalTimeoutMs = 10000;\n\n/**\n * Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler\n */\nexport interface IConnectionStateHandlerInputs {\n\tlogger: TelemetryLoggerExt;\n\tmc: MonitoringContext;\n\t/**\n\t * Log to telemetry any change in state, included to Connecting\n\t */\n\tconnectionStateChanged: (\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t) => void;\n\t/**\n\t * Whether to expect the client to join in write mode on next connection\n\t */\n\tshouldClientJoinWrite: () => boolean;\n\t/**\n\t * (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again\n\t */\n\tmaxClientLeaveWaitTime: number | undefined;\n\t/**\n\t * Log an issue encountered while in the Connecting state. details will be logged as a JSON string\n\t */\n\tlogConnectionIssue: (\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t) => void;\n\t/**\n\t * Callback to note that an old local client ID is still present in the Quorum that should have left and should now be considered invalid\n\t */\n\tclientShouldHaveLeft: (clientId: string) => void;\n\n\t/**\n\t * Some critical error was hit. Container should be closed and error logged.\n\t */\n\tonCriticalError: (error: unknown) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n\treadonly connectionState: ConnectionState;\n\t/**\n\t * Pending clientID.\n\t * Changes whenever socket connection is established.\n\t * Resets to undefined when connection is lost\n\t */\n\treadonly pendingClientId: string | undefined;\n\t/**\n\t * clientId of a last established connection.\n\t * Does not reset on disconnect.\n\t * Changes only when new connection is established, client is fully caught up, and\n\t * there is no chance to ops from previous connection (i.e. if needed, we have waited and observed leave op from previous connection)\n\t */\n\treadonly clientId: string | undefined;\n\n\tcontainerSaved(): void;\n\tdispose(): void;\n\tinitProtocol(protocol: IProtocolHandler): void;\n\treceivedConnectEvent(details: IConnectionDetailsInternal): void;\n\treceivedDisconnectEvent(reason: IConnectionStateChangeReason): void;\n\testablishingConnection(reason: IConnectionStateChangeReason): void;\n\t/**\n\t * Switches state to disconnected when we are still establishing connection during container.load(),\n\t * container connect() or reconnect and the container gets closed or disposed or disconnect happens.\n\t * @param reason - reason for cancelling the connection.\n\t */\n\tcancelEstablishingConnection(reason: IConnectionStateChangeReason): void;\n}\n\nexport function createConnectionStateHandler(\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateHandler | ConnectionStateCatchup {\n\tconst config = inputs.mc.config;\n\treturn createConnectionStateHandlerCore(\n\t\tconfig.getBoolean(\"Fluid.Container.DisableCatchUpBeforeDeclaringConnected\") !== true, // connectedRaisedWhenCaughtUp\n\t\tconfig.getBoolean(\"Fluid.Container.DisableJoinSignalWait\") !== true, // readClientsWaitForJoinSignal\n\t\tinputs,\n\t\tdeltaManager,\n\t\tclientId,\n\t);\n}\n\nexport function createConnectionStateHandlerCore(\n\tconnectedRaisedWhenCaughtUp: boolean,\n\treadClientsWaitForJoinSignal: boolean,\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateCatchup | ConnectionStateHandler {\n\tconst factory = (handler: IConnectionStateHandlerInputs): ConnectionStateHandler =>\n\t\tnew ConnectionStateHandler(handler, readClientsWaitForJoinSignal, clientId);\n\n\treturn connectedRaisedWhenCaughtUp\n\t\t? new ConnectionStateCatchup(inputs, factory, deltaManager)\n\t\t: factory(inputs);\n}\n\n/**\n * Helper internal interface to abstract away Audience & Quorum\n */\ninterface IMembership {\n\ton(\n\t\teventName: \"addMember\" | \"removeMember\",\n\t\tlistener: (clientId: string, details: IClient | ISequencedClient) => void,\n\t);\n\tgetMember(clientId: string): undefined | unknown;\n}\n\n/**\n * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.\n * It implements both ends of communication interfaces and passes data back and forward\n */\nclass ConnectionStateHandlerPassThrough\n\timplements IConnectionStateHandler, IConnectionStateHandlerInputs\n{\n\tprotected readonly pimpl: IConnectionStateHandler;\n\n\tconstructor(\n\t\tprotected readonly inputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t) {\n\t\tthis.pimpl = pimplFactory(this);\n\t}\n\n\t// #region IConnectionStateHandler\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this.pimpl.connectionState;\n\t}\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this.pimpl.pendingClientId;\n\t}\n\tpublic get clientId(): string | undefined {\n\t\treturn this.pimpl.clientId;\n\t}\n\n\tpublic containerSaved(): void {\n\t\treturn this.pimpl.containerSaved();\n\t}\n\tpublic dispose(): void {\n\t\treturn this.pimpl.dispose();\n\t}\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\treturn this.pimpl.initProtocol(protocol);\n\t}\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\treturn this.pimpl.receivedDisconnectEvent(reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.establishingConnection(reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.cancelEstablishingConnection(reason);\n\t}\n\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\treturn this.pimpl.receivedConnectEvent(details);\n\t}\n\n\t// #endregion\n\n\t// #region IConnectionStateHandlerInputs\n\n\tpublic get logger(): TelemetryLoggerExt {\n\t\treturn this.inputs.logger;\n\t}\n\tpublic get mc(): MonitoringContext {\n\t\treturn this.inputs.mc;\n\t}\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\treturn this.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\tpublic shouldClientJoinWrite(): boolean {\n\t\treturn this.inputs.shouldClientJoinWrite();\n\t}\n\tpublic get maxClientLeaveWaitTime(): number | undefined {\n\t\treturn this.inputs.maxClientLeaveWaitTime;\n\t}\n\tpublic logConnectionIssue(\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t): void {\n\t\treturn this.inputs.logConnectionIssue(eventName, category, details);\n\t}\n\tpublic clientShouldHaveLeft(clientId: string): void {\n\t\treturn this.inputs.clientShouldHaveLeft(clientId);\n\t}\n\n\tpublic onCriticalError(error: unknown): void {\n\t\treturn this.inputs.onCriticalError(error);\n\t}\n\n\t// #endregion\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nexport class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {\n\tprivate catchUpMonitor: ICatchUpMonitor | undefined;\n\n\tconstructor(\n\t\tinputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t) {\n\t\tsuper(inputs, pimplFactory);\n\t\tthis._connectionState = this.pimpl.connectionState;\n\t}\n\n\tprivate _connectionState: ConnectionState;\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason<IAnyDriverError>,\n\t): void {\n\t\tswitch (value) {\n\t\t\tcase ConnectionState.Connected: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.CatchingUp,\n\t\t\t\t\t0x3e1 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\t// Create catch-up monitor here (not earlier), as we might get more exact info by now about how far\n\t\t\t\t// client is behind through join signal. This is only true if base layer uses signals (i.e. audience,\n\t\t\t\t// not quorum, including for \"rea\" connections) to make decisions about moving to \"connected\" state.\n\t\t\t\t// In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as\n\t\t\t\t// we might get callback right away, and it will screw up state transition (as code outside of switch\n\t\t\t\t// statement will overwrite current state).\n\t\t\t\tassert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);\n\t\t\t\tthis.catchUpMonitor = new CatchUpMonitor(\n\t\t\t\t\tthis.deltaManager,\n\t\t\t\t\tthis.transitionToConnectedState,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase ConnectionState.Disconnected: {\n\t\t\t\tthis.catchUpMonitor?.dispose();\n\t\t\t\tthis.catchUpMonitor = undefined;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// ConnectionState.EstablishingConnection state would be set when we start establishing connection\n\t\t\t// during container.connect() or reconnect because of an error.\n\t\t\tcase ConnectionState.EstablishingConnection: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.Disconnected,\n\t\t\t\t\t0x6d2 /* connectivity transition to establishing connection */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase ConnectionState.CatchingUp: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t\t\t0x3e3 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t}\n\t\tthis._connectionState = value;\n\t\tthis.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\n\tprivate readonly transitionToConnectedState = (): void => {\n\t\t// Defensive measure, we should always be in Connecting state when this is called.\n\t\tconst state = this.pimpl.connectionState;\n\t\tassert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);\n\t\tassert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);\n\t\tthis._connectionState = ConnectionState.Connected;\n\t\tthis.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {\n\t\t\ttext: \"caught up\",\n\t\t});\n\t};\n}\n\n/**\n * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.\n * This class ensures that any ops sent by this container instance on previous connection are either\n * sequenced or blocked by the server before emitting the new \"connected\" event and allowing runtime to resubmit ops.\n *\n * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op\n * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot\n * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could\n * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).\n *\n * The job of this class is to encapsulate the transition period during reconnect, which is identified by\n * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:\n *\n * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops\n * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other\n * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.\n *\n * b. We process the Join op for the new clientId (identified when the underlying connection was first established),\n * indicating the service is ready to sequence ops sent with the new clientId.\n *\n * c. We process all ops known at the time the underlying connection was established (so we are \"caught up\")\n *\n * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.\n *\n * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op/signal is\n * processed.\n *\n * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent\n */\nexport class ConnectionStateHandler implements IConnectionStateHandler {\n\tprivate _connectionState = ConnectionState.Disconnected;\n\tprivate _pendingClientId: string | undefined;\n\n\t/**\n\t * Tracks that we observe the \"leave\" op within the timeout for our previous clientId (see comment on ConnectionStateHandler class)\n\t * ! This ensures we do not switch to a new clientId until we process all potential messages from old clientId\n\t * ! i.e. We will always see the \"leave\" op for a client after we have seen all the ops it has sent\n\t * ! This check helps prevent the same op from being resubmitted by the PendingStateManager upon reconnecting\n\t */\n\tprivate readonly prevClientLeftTimer: Timer;\n\n\t/**\n\t * Tracks that we observe our own \"join\" op within the timeout after receiving a \"connected\" event from the DeltaManager\n\t */\n\tprivate readonly joinTimer: Timer;\n\n\tprivate protocol?: IProtocolHandler;\n\tprivate connection?: IConnectionDetailsInternal;\n\tprivate _clientId?: string;\n\n\t/**\n\t * Track how long we waited to see \"leave\" op for previous clientId\n\t */\n\tprivate waitEvent: PerformanceEvent | undefined;\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic get clientId(): string | undefined {\n\t\treturn this._clientId;\n\t}\n\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this._pendingClientId;\n\t}\n\n\tconstructor(\n\t\tprivate readonly handler: IConnectionStateHandlerInputs,\n\t\tprivate readonly readClientsWaitForJoinSignal: boolean,\n\t\tclientIdFromPausedSession?: string,\n\t) {\n\t\tthis._clientId = clientIdFromPausedSession;\n\t\t// eslint-disable-next-line unicorn/consistent-function-scoping\n\t\tconst errorHandler = (error): void => this.handler.onCriticalError(error);\n\t\tthis.prevClientLeftTimer = new Timer(\n\t\t\t// Default is 5 min for which we are going to wait for its own \"leave\" message. This is same as\n\t\t\t// the max time on server after which leave op is sent.\n\t\t\tthis.handler.maxClientLeaveWaitTime ?? 300000,\n\t\t\t() => {\n\t\t\t\tassert(\n\t\t\t\t\tthis.connectionState !== ConnectionState.Connected,\n\t\t\t\t\t0x2ac /* \"Connected when timeout waiting for leave from previous session fired!\" */,\n\t\t\t\t);\n\t\t\t\tthis.applyForConnectedState(\"timeout\");\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\n\t\tthis.joinTimer = new Timer(\n\t\t\t0, // default value is not used - startjoinTimer() explicitly provides timeout\n\t\t\t() => {\n\t\t\t\t// I've observed timer firing within couple ms from disconnect event, looks like\n\t\t\t\t// queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.\n\t\t\t\tif (this.connectionState !== ConnectionState.CatchingUp) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst details = {\n\t\t\t\t\tprotocolInitialized: this.protocol !== undefined,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t};\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"NoJoinSignal\" : \"NoJoinOp\", // eventName\n\t\t\t\t\t\"error\", // category\n\t\t\t\t\tdetails,\n\t\t\t\t);\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\t}\n\n\tprivate startjoinTimer(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x234 /* \"has joinTimer\" */);\n\t\tassert(this.connection !== undefined, 0x4b3 /* have connection */);\n\t\tthis.joinTimer.start(\n\t\t\tthis.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n\t\t);\n\t}\n\n\tprivate stopjoinTimer(): void {\n\t\tassert(this.joinTimer.hasTimer, 0x235 /* \"no joinTimer\" */);\n\t\tthis.joinTimer.clear();\n\t}\n\n\tprivate get waitingForLeaveOp(): boolean {\n\t\treturn this.prevClientLeftTimer.hasTimer;\n\t}\n\n\tpublic dispose(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n\t\tthis.prevClientLeftTimer.clear();\n\t}\n\n\tpublic containerSaved(): void {\n\t\t// If we were waiting for moving to Connected state, then only apply for state change. Since the container\n\t\t// is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.\n\t\tif (this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"containerSaved\");\n\t\t}\n\t}\n\n\tprivate receivedAddMemberEvent(clientId: string): void {\n\t\t// This is the only one that requires the pending client ID\n\t\tif (clientId === this.pendingClientId) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t} else if (this.shouldWaitForSelf()) {\n\t\t\t\t// timer has already fired, meaning it took too long to get join op/signal.\n\t\t\t\t// Record how long it actually took to recover.\n\t\t\t\t// This is generic event, as it by itself is not an error.\n\t\t\t\t// We also have a case where NoJoinOp happens during container boot (we do not report it as error in such case),\n\t\t\t\t// if this log statement happens after boot - we do not want to consider it error case.\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"ReceivedJoinSignal\" : \"ReceivedJoinOp\", // eventName\n\t\t\t\t\t\"generic\", // category\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Start the event in case we are waiting for leave or timeout.\n\t\t\tif (this.waitingForLeaveOp) {\n\t\t\t\tthis.waitEvent = PerformanceEvent.start(this.handler.logger, {\n\t\t\t\t\teventName: \"WaitBeforeClientLeave\",\n\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\twaitOnClientId: this._clientId,\n\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.applyForConnectedState(\"addMemberEvent\");\n\t\t}\n\t}\n\n\tprivate applyForConnectedState(\n\t\tsource: \"removeMemberEvent\" | \"addMemberEvent\" | \"timeout\" | \"containerSaved\",\n\t): void {\n\t\tassert(\n\t\t\tthis.protocol !== undefined,\n\t\t\t0x236 /* \"In all cases it should be already installed\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp || this.hasMember(this.clientId),\n\t\t\t0x2e2 /* \"Must only wait for leave message when clientId in quorum\" */,\n\t\t);\n\n\t\t// Move to connected state only if:\n\t\t// 1. We have seen our own \"join\" op (i.e. for this.pendingClientId)\n\t\t// 2. There is no \"leave\" timer running, meaning this is our first connection or the previous client has left (via this.prevClientLeftTimer)\n\t\tif (\n\t\t\tthis.pendingClientId !== this.clientId &&\n\t\t\tthis.hasMember(this.pendingClientId) &&\n\t\t\t!this.waitingForLeaveOp\n\t\t) {\n\t\t\tthis.waitEvent?.end({ source });\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t} else {\n\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t// We may not see any ops due to being disconnected all that time - that's not an error!\n\t\t\tconst error =\n\t\t\t\tsource === \"timeout\" && this.connectionState !== ConnectionState.Disconnected;\n\t\t\tthis.handler.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"connectedStateRejected\",\n\t\t\t\tcategory: error ? \"error\" : \"generic\",\n\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\tsource,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate receivedRemoveMemberEvent(clientId: string): void {\n\t\t// If the client which has left was us, then finish the timer.\n\t\tif (this.clientId === clientId && this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"removeMemberEvent\");\n\t\t}\n\t}\n\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\tthis.connection = undefined;\n\t\tthis.setConnectionState(ConnectionState.Disconnected, reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tassert(\n\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t0x6d3 /* Connection state should be EstablishingConnection */,\n\t\t);\n\t\tassert(this.connection === undefined, 0x6d4 /* No connection should be present */);\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.Disconnected;\n\t\tthis.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.EstablishingConnection;\n\t\tthis.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {\n\t\t\ttext: `Establishing Connection due to ${reason.text}`,\n\t\t\terror: reason.error,\n\t\t});\n\t}\n\n\t/**\n\t * Tells if need to wait for \"self\" to show up in audience.\n\t * @returns true if we should wait for \"self\" to appear in audience.\n\t * false is returned only for \"read\" connections, and only if this.readClientsWaitForJoinSignal is false.\n\t */\n\tprivate shouldWaitForSelf(): boolean {\n\t\tassert(\n\t\t\tthis.connection !== undefined,\n\t\t\t0x4b4 /* all callers call here with active connection */,\n\t\t);\n\t\treturn this.connection.mode === \"write\" || this.readClientsWaitForJoinSignal;\n\t}\n\n\t/**\n\t * The \"connect\" event indicates the connection to the Relay Service is live.\n\t * However, some additional conditions must be met before we can fully transition to\n\t * \"Connected\" state. This function handles that interim period, known as \"Connecting\" state.\n\t * @param details - Connection details returned from the Relay Service\n\t * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.\n\t * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for\n\t */\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\tthis.connection = details;\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.CatchingUp;\n\n\t\t// The following checks are wrong. They are only valid if user has write access to a file.\n\t\t// If user lost such access mid-session, user will not be able to get \"write\" connection.\n\t\t//\n\t\t// const writeConnection = details.mode === \"write\";\n\t\t// assert(!this.handler.shouldClientJoinWrite() || writeConnection,\n\t\t// 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);\n\t\t// assert(!this.waitingForLeaveOp || writeConnection,\n\t\t// 0x2a6 /* \"waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)\" */);\n\n\t\t// Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected\n\t\t// (have received the join message for the client ID)\n\t\t// This is especially important in the reconnect case. It's possible there could be outstanding\n\t\t// ops sent by this client, so we should keep the old client id until we see our own client's\n\t\t// join message. after we see the join message for our new connection with our new client id,\n\t\t// we know there can no longer be outstanding ops that we sent with the previous client id.\n\t\tthis._pendingClientId = details.clientId;\n\n\t\t// IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state\n\t\tthis.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState, details.reason);\n\n\t\t// Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.\n\t\t// Pending clientId could have joined already (i.e. join op/signal already processed):\n\t\t// We are fetching ops from storage in parallel to connecting to Relay Service,\n\t\t// and given async processes, it's possible that we have already processed our own join message before\n\t\t// connection was fully established.\n\t\tif (!this.hasMember(this._pendingClientId) && this.shouldWaitForSelf()) {\n\t\t\t// We are waiting for our own join op / signal. When it is processed\n\t\t\t// we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.\n\t\t\tthis.startjoinTimer();\n\t\t} else if (!this.waitingForLeaveOp) {\n\t\t\t// We're not waiting for Join or Leave op (if read-only connection those don't even apply),\n\t\t\t// go ahead and declare the state to be Connected!\n\t\t\t// If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t}\n\t\t// else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later\n\t}\n\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected,\n\t\treason: IConnectionStateChangeReason,\n\t): void;\n\tprivate setConnectionState(value: ConnectionState.Connected): void;\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected | ConnectionState.Connected,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\tif (this.connectionState === value) {\n\t\t\t// Already in the desired state - exit early\n\t\t\tthis.handler.logger.sendErrorEvent({ eventName: \"setConnectionStateSame\", value });\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = value;\n\n\t\t// This is the only place in code that deals with quorum. The rest works with audience\n\t\t// The code below ensures that we do not send ops until we know that old \"write\" client's disconnect\n\t\t// produced (and sequenced) leave op\n\t\tconst currentClientInQuorum =\n\t\t\tthis._clientId !== undefined &&\n\t\t\tthis.protocol?.quorum?.getMember(this._clientId) !== undefined;\n\t\tif (value === ConnectionState.Connected) {\n\t\t\tassert(\n\t\t\t\toldState === ConnectionState.CatchingUp,\n\t\t\t\t0x1d8 /* \"Should only transition from Connecting state\" */,\n\t\t\t);\n\t\t\t// Mark our old client should have left in the quorum if it's still there\n\t\t\tif (currentClientInQuorum) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tthis.handler.clientShouldHaveLeft(this._clientId!);\n\t\t\t}\n\t\t\tthis._clientId = this.pendingClientId;\n\t\t} else if (value === ConnectionState.Disconnected) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t}\n\n\t\t\t// Only wait for \"leave\" message if the connected client exists in the quorum and had some non-acked ops\n\t\t\t// Also check if the timer is not already running as\n\t\t\t// we could receive \"Disconnected\" event multiple times without getting connected and in that case we\n\t\t\t// don't want to reset the timer as we still want to wait on original client which started this timer.\n\t\t\tif (\n\t\t\t\tcurrentClientInQuorum &&\n\t\t\t\tthis.handler.shouldClientJoinWrite() &&\n\t\t\t\t!this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer\n\t\t\t) {\n\t\t\t\tthis.prevClientLeftTimer.restart();\n\t\t\t} else {\n\t\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t\tthis.handler.logger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"noWaitOnDisconnected\",\n\t\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\t\tclientId: this._clientId,\n\t\t\t\t\t\t\tinQuorum: currentClientInQuorum,\n\t\t\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tundefined, // error\n\t\t\t\t\tLogLevel.info,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Report transition\n\t\tthis.handler.connectionStateChanged(this._connectionState, oldState, reason);\n\n\t\t// Clear pending state immediately to prepare for reconnect\n\t\t// Do it after calling connectionStateChanged() above, such that our telemetry contains pendingClientId on disconnect events\n\t\t// and we can pair attempts to connect with disconnects (that's the only ID we have if connection did not move far enough before being disconnected)\n\t\tif (value === ConnectionState.Disconnected) {\n\t\t\tthis._pendingClientId = undefined;\n\t\t}\n\t}\n\n\tprotected get membership(): IMembership | undefined {\n\t\t// We could always use audience here.\n\t\t// This is true because Audience is a superset of quorum, i.e. when we filter Audience to \"write\" clients,\n\t\t// it is exactly the same as quorum! Please see asserts in Audience callbacks setup by initProtocol() enforcing that.\n\t\treturn this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n\t}\n\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\tthis.protocol = protocol;\n\n\t\tthis.membership?.on(\"addMember\", (clientId, details) => {\n\t\t\t// This is very important constrain. We rely on it when testing presence of \"self\" in Audience.\n\t\t\t// We do not want to move to \"connected\" state for \"write\" connections when JoinSignal shows up\n\t\t\t// for \"self\" - we want to move to \"connected\" state only when \"join\" op is received.\n\t\t\tassert(\n\t\t\t\t(details as IClient).mode === \"read\" ||\n\t\t\t\t\tprotocol.quorum.getMember(clientId) !== undefined,\n\t\t\t\t0x4b5 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedAddMemberEvent(clientId);\n\t\t});\n\n\t\tthis.membership?.on(\"removeMember\", (clientId) => {\n\t\t\tassert(\n\t\t\t\tprotocol.quorum.getMember(clientId) === undefined,\n\t\t\t\t0x4b6 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedRemoveMemberEvent(clientId);\n\t\t});\n\n\t\t/* There is a tiny tiny race possible, where these events happen in this order:\n 1. A connection is established (no \"cached\" mode is used, so it happens in parallel / faster than other steps)\n 2. Some other client produces a summary\n 3. We get \"lucky\" and load from that summary as our initial snapshot\n 4. ConnectionStateHandler.initProtocol is called, \"self\" is already in the quorum.\n We could avoid this sequence (and delete test case for it) if we move connection lower in Container.load()\n */\n\t\tif (this.hasMember(this.pendingClientId)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.receivedAddMemberEvent(this.pendingClientId!);\n\t\t}\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp,\n\t\t\t0x99d /* leave timer can't be set as we have not had access to quorum */,\n\t\t);\n\n\t\t// This check is required for scenario of loading container from pending state, and ensuring there is no way\n\t\t// old clientId is still in the quorum (very unlikely, but you never know)\n\t\t// if we have a clientId from a previous container we need to wait for its leave message\n\t\t// This mimicks check in setConnectionState()\n\t\t// Note that we are not consulting this.handler.shouldClientJoinWrite() here\n\t\t// It could produce wrong results for stashed ops were never sent to Loader yet, and if this check\n\t\t// makes determination only on that (and not uses \"dirty\" events), then it can produce wrong result.\n\t\t// In most cases it does not matter, as this client already left quorum. But in really unfortunate case,\n\t\t// we might wait even if we could avoid such wait.\n\t\tif (\n\t\t\tthis._clientId !== undefined &&\n\t\t\tprotocol.quorum?.getMember(this._clientId) !== undefined\n\t\t) {\n\t\t\tthis.prevClientLeftTimer.restart();\n\t\t}\n\t}\n\n\tprotected hasMember(clientId?: string): boolean {\n\t\treturn this.membership?.getMember(clientId ?? \"\") !== undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"connectionStateHandler.js","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IDeltaManager } from \"@fluidframework/container-definitions/internal\";\nimport type { ITelemetryBaseProperties } from \"@fluidframework/core-interfaces\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport { assert, Timer } from \"@fluidframework/core-utils/internal\";\nimport type { IClient, ISequencedClient } from \"@fluidframework/driver-definitions\";\nimport type { IAnyDriverError } from \"@fluidframework/driver-definitions/internal\";\nimport {\n\ttype MonitoringContext,\n\tPerformanceEvent,\n\ttype TelemetryEventCategory,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { CatchUpMonitor, type ICatchUpMonitor } from \"./catchUpMonitor.js\";\nimport { ConnectionState } from \"./connectionState.js\";\nimport type { IConnectionDetailsInternal, IConnectionStateChangeReason } from \"./contracts.js\";\nimport type { IProtocolHandler } from \"./protocol.js\";\n\n// Based on recent data, it looks like majority of cases where we get stuck are due to really slow or\n// timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so\n// if retrying fixes the problem, we should not see these events.\nconst JoinOpTimeoutMs = 45000;\n\n// Timeout waiting for \"self\" join signal, before giving up\nconst JoinSignalTimeoutMs = 10000;\n\n/**\n * Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler\n */\nexport interface IConnectionStateHandlerInputs {\n\tlogger: TelemetryLoggerExt;\n\tmc: MonitoringContext;\n\t/**\n\t * Log to telemetry any change in state, included to Connecting\n\t */\n\tconnectionStateChanged: (\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t) => void;\n\t/**\n\t * Whether to expect the client to join in write mode on next connection\n\t */\n\tshouldClientJoinWrite: () => boolean;\n\t/**\n\t * (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again\n\t */\n\tmaxClientLeaveWaitTime: number | undefined;\n\t/**\n\t * Log an issue encountered while in the Connecting state. details will be logged as a JSON string\n\t */\n\tlogConnectionIssue: (\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t) => void;\n\t/**\n\t * Callback to note that an old local client ID is still present in the Quorum that should have left and should now be considered invalid\n\t */\n\tclientShouldHaveLeft: (clientId: string) => void;\n\n\t/**\n\t * Some critical error was hit. Container should be closed and error logged.\n\t */\n\tonCriticalError: (error: unknown) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n\treadonly connectionState: ConnectionState;\n\t/**\n\t * Pending clientID.\n\t * Changes whenever socket connection is established.\n\t * Resets to undefined when connection is lost\n\t */\n\treadonly pendingClientId: string | undefined;\n\t/**\n\t * clientId of a last established connection.\n\t * Does not reset on disconnect.\n\t * Changes only when new connection is established, client is fully caught up, and\n\t * there is no chance to ops from previous connection (i.e. if needed, we have waited and observed leave op from previous connection)\n\t */\n\treadonly clientId: string | undefined;\n\n\tcontainerSaved(): void;\n\tdispose(): void;\n\tinitProtocol(protocol: IProtocolHandler): void;\n\treceivedConnectEvent(details: IConnectionDetailsInternal): void;\n\treceivedDisconnectEvent(reason: IConnectionStateChangeReason): void;\n\testablishingConnection(reason: IConnectionStateChangeReason): void;\n\t/**\n\t * Switches state to disconnected when we are still establishing connection during container.load(),\n\t * container connect() or reconnect and the container gets closed or disposed or disconnect happens.\n\t * @param reason - reason for cancelling the connection.\n\t */\n\tcancelEstablishingConnection(reason: IConnectionStateChangeReason): void;\n}\n\nexport function createConnectionStateHandler(\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateHandler | ConnectionStateCatchup {\n\tconst config = inputs.mc.config;\n\treturn createConnectionStateHandlerCore(\n\t\tconfig.getBoolean(\"Fluid.Container.DisableCatchUpBeforeDeclaringConnected\") !== true, // connectedRaisedWhenCaughtUp\n\t\tconfig.getBoolean(\"Fluid.Container.DisableJoinSignalWait\") !== true, // readClientsWaitForJoinSignal\n\t\tinputs,\n\t\tdeltaManager,\n\t\tclientId,\n\t);\n}\n\nexport function createConnectionStateHandlerCore(\n\tconnectedRaisedWhenCaughtUp: boolean,\n\treadClientsWaitForJoinSignal: boolean,\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<unknown, unknown>,\n\tclientId?: string,\n): ConnectionStateCatchup | ConnectionStateHandler {\n\tconst factory = (handler: IConnectionStateHandlerInputs): ConnectionStateHandler =>\n\t\tnew ConnectionStateHandler(handler, readClientsWaitForJoinSignal, clientId);\n\n\treturn connectedRaisedWhenCaughtUp\n\t\t? new ConnectionStateCatchup(inputs, factory, deltaManager)\n\t\t: factory(inputs);\n}\n\n/**\n * Helper internal interface to abstract away Audience & Quorum\n */\ninterface IMembership {\n\ton(\n\t\teventName: \"addMember\" | \"removeMember\",\n\t\tlistener: (clientId: string, details: IClient | ISequencedClient) => void,\n\t);\n\tgetMember(clientId: string): undefined | unknown;\n}\n\n/**\n * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.\n * It implements both ends of communication interfaces and passes data back and forward\n */\nclass ConnectionStateHandlerPassThrough\n\timplements IConnectionStateHandler, IConnectionStateHandlerInputs\n{\n\tprotected readonly pimpl: IConnectionStateHandler;\n\n\tconstructor(\n\t\tprotected readonly inputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t) {\n\t\tthis.pimpl = pimplFactory(this);\n\t}\n\n\t// #region IConnectionStateHandler\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this.pimpl.connectionState;\n\t}\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this.pimpl.pendingClientId;\n\t}\n\tpublic get clientId(): string | undefined {\n\t\treturn this.pimpl.clientId;\n\t}\n\n\tpublic containerSaved(): void {\n\t\treturn this.pimpl.containerSaved();\n\t}\n\tpublic dispose(): void {\n\t\treturn this.pimpl.dispose();\n\t}\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\treturn this.pimpl.initProtocol(protocol);\n\t}\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\treturn this.pimpl.receivedDisconnectEvent(reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.establishingConnection(reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\treturn this.pimpl.cancelEstablishingConnection(reason);\n\t}\n\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\treturn this.pimpl.receivedConnectEvent(details);\n\t}\n\n\t// #endregion\n\n\t// #region IConnectionStateHandlerInputs\n\n\tpublic get logger(): TelemetryLoggerExt {\n\t\treturn this.inputs.logger;\n\t}\n\tpublic get mc(): MonitoringContext {\n\t\treturn this.inputs.mc;\n\t}\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\treturn this.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\tpublic shouldClientJoinWrite(): boolean {\n\t\treturn this.inputs.shouldClientJoinWrite();\n\t}\n\tpublic get maxClientLeaveWaitTime(): number | undefined {\n\t\treturn this.inputs.maxClientLeaveWaitTime;\n\t}\n\tpublic logConnectionIssue(\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryBaseProperties,\n\t): void {\n\t\treturn this.inputs.logConnectionIssue(eventName, category, details);\n\t}\n\tpublic clientShouldHaveLeft(clientId: string): void {\n\t\treturn this.inputs.clientShouldHaveLeft(clientId);\n\t}\n\n\tpublic onCriticalError(error: unknown): void {\n\t\treturn this.inputs.onCriticalError(error);\n\t}\n\n\t// #endregion\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nexport class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {\n\tprivate catchUpMonitor: ICatchUpMonitor | undefined;\n\n\tconstructor(\n\t\tinputs: IConnectionStateHandlerInputs,\n\t\tpimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n\t\tprivate readonly deltaManager: IDeltaManager<unknown, unknown>,\n\t) {\n\t\tsuper(inputs, pimplFactory);\n\t\tthis._connectionState = this.pimpl.connectionState;\n\t}\n\n\tprivate _connectionState: ConnectionState;\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: IConnectionStateChangeReason<IAnyDriverError>,\n\t): void {\n\t\tswitch (value) {\n\t\t\tcase ConnectionState.Connected: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.CatchingUp,\n\t\t\t\t\t0x3e1 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\t// Create catch-up monitor here (not earlier), as we might get more exact info by now about how far\n\t\t\t\t// client is behind through join signal. This is only true if base layer uses signals (i.e. audience,\n\t\t\t\t// not quorum, including for \"rea\" connections) to make decisions about moving to \"connected\" state.\n\t\t\t\t// In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as\n\t\t\t\t// we might get callback right away, and it will screw up state transition (as code outside of switch\n\t\t\t\t// statement will overwrite current state).\n\t\t\t\tassert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);\n\t\t\t\t// Assign the field BEFORE starting the monitor. `start()` may synchronously fire\n\t\t\t\t// transitionToConnectedState (when already caught up), which can re-enter this handler\n\t\t\t\t// (e.g. an app \"connected\" listener forcing readonly, triggering a disconnect). The\n\t\t\t\t// Disconnected case clears this.catchUpMonitor, so the field must already reference the\n\t\t\t\t// monitor for that cleanup to take effect; otherwise a stale monitor survives and the\n\t\t\t\t// next Connected transition trips assert 0x3eb.\n\t\t\t\tthis.catchUpMonitor = new CatchUpMonitor(\n\t\t\t\t\tthis.deltaManager,\n\t\t\t\t\tthis.transitionToConnectedState,\n\t\t\t\t);\n\t\t\t\tthis.catchUpMonitor.start();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase ConnectionState.Disconnected: {\n\t\t\t\tthis.catchUpMonitor?.dispose();\n\t\t\t\tthis.catchUpMonitor = undefined;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// ConnectionState.EstablishingConnection state would be set when we start establishing connection\n\t\t\t// during container.connect() or reconnect because of an error.\n\t\t\tcase ConnectionState.EstablishingConnection: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.Disconnected,\n\t\t\t\t\t0x6d2 /* connectivity transition to establishing connection */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase ConnectionState.CatchingUp: {\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t\t\t0x3e3 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t}\n\t\tthis._connectionState = value;\n\t\tthis.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\n\tprivate readonly transitionToConnectedState = (): void => {\n\t\t// Defensive measure, we should always be in Connecting state when this is called.\n\t\tconst state = this.pimpl.connectionState;\n\t\tassert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);\n\t\tassert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);\n\t\tthis._connectionState = ConnectionState.Connected;\n\t\tthis.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {\n\t\t\ttext: \"caught up\",\n\t\t});\n\t};\n}\n\n/**\n * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.\n * This class ensures that any ops sent by this container instance on previous connection are either\n * sequenced or blocked by the server before emitting the new \"connected\" event and allowing runtime to resubmit ops.\n *\n * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op\n * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot\n * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could\n * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).\n *\n * The job of this class is to encapsulate the transition period during reconnect, which is identified by\n * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:\n *\n * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops\n * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other\n * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.\n *\n * b. We process the Join op for the new clientId (identified when the underlying connection was first established),\n * indicating the service is ready to sequence ops sent with the new clientId.\n *\n * c. We process all ops known at the time the underlying connection was established (so we are \"caught up\")\n *\n * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.\n *\n * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op/signal is\n * processed.\n *\n * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent\n */\nexport class ConnectionStateHandler implements IConnectionStateHandler {\n\tprivate _connectionState = ConnectionState.Disconnected;\n\tprivate _pendingClientId: string | undefined;\n\n\t/**\n\t * Tracks that we observe the \"leave\" op within the timeout for our previous clientId (see comment on ConnectionStateHandler class)\n\t * ! This ensures we do not switch to a new clientId until we process all potential messages from old clientId\n\t * ! i.e. We will always see the \"leave\" op for a client after we have seen all the ops it has sent\n\t * ! This check helps prevent the same op from being resubmitted by the PendingStateManager upon reconnecting\n\t */\n\tprivate readonly prevClientLeftTimer: Timer;\n\n\t/**\n\t * Tracks that we observe our own \"join\" op within the timeout after receiving a \"connected\" event from the DeltaManager\n\t */\n\tprivate readonly joinTimer: Timer;\n\n\tprivate protocol?: IProtocolHandler;\n\tprivate connection?: IConnectionDetailsInternal;\n\tprivate _clientId?: string;\n\n\t/**\n\t * Track how long we waited to see \"leave\" op for previous clientId\n\t */\n\tprivate waitEvent: PerformanceEvent | undefined;\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic get clientId(): string | undefined {\n\t\treturn this._clientId;\n\t}\n\n\tpublic get pendingClientId(): string | undefined {\n\t\treturn this._pendingClientId;\n\t}\n\n\tconstructor(\n\t\tprivate readonly handler: IConnectionStateHandlerInputs,\n\t\tprivate readonly readClientsWaitForJoinSignal: boolean,\n\t\tclientIdFromPausedSession?: string,\n\t) {\n\t\tthis._clientId = clientIdFromPausedSession;\n\t\t// eslint-disable-next-line unicorn/consistent-function-scoping\n\t\tconst errorHandler = (error): void => this.handler.onCriticalError(error);\n\t\tthis.prevClientLeftTimer = new Timer(\n\t\t\t// Default is 5 min for which we are going to wait for its own \"leave\" message. This is same as\n\t\t\t// the max time on server after which leave op is sent.\n\t\t\tthis.handler.maxClientLeaveWaitTime ?? 300000,\n\t\t\t() => {\n\t\t\t\tassert(\n\t\t\t\t\tthis.connectionState !== ConnectionState.Connected,\n\t\t\t\t\t0x2ac /* \"Connected when timeout waiting for leave from previous session fired!\" */,\n\t\t\t\t);\n\t\t\t\tthis.applyForConnectedState(\"timeout\");\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\n\t\tthis.joinTimer = new Timer(\n\t\t\t0, // default value is not used - startjoinTimer() explicitly provides timeout\n\t\t\t() => {\n\t\t\t\t// I've observed timer firing within couple ms from disconnect event, looks like\n\t\t\t\t// queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.\n\t\t\t\tif (this.connectionState !== ConnectionState.CatchingUp) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst details = {\n\t\t\t\t\tprotocolInitialized: this.protocol !== undefined,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t};\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"NoJoinSignal\" : \"NoJoinOp\", // eventName\n\t\t\t\t\t\"error\", // category\n\t\t\t\t\tdetails,\n\t\t\t\t);\n\t\t\t},\n\t\t\terrorHandler,\n\t\t);\n\t}\n\n\tprivate startjoinTimer(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x234 /* \"has joinTimer\" */);\n\t\tassert(this.connection !== undefined, 0x4b3 /* have connection */);\n\t\tthis.joinTimer.start(\n\t\t\tthis.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n\t\t);\n\t}\n\n\tprivate stopjoinTimer(): void {\n\t\tassert(this.joinTimer.hasTimer, 0x235 /* \"no joinTimer\" */);\n\t\tthis.joinTimer.clear();\n\t}\n\n\tprivate get waitingForLeaveOp(): boolean {\n\t\treturn this.prevClientLeftTimer.hasTimer;\n\t}\n\n\tpublic dispose(): void {\n\t\tassert(!this.joinTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n\t\tthis.prevClientLeftTimer.clear();\n\t}\n\n\tpublic containerSaved(): void {\n\t\t// If we were waiting for moving to Connected state, then only apply for state change. Since the container\n\t\t// is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.\n\t\tif (this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"containerSaved\");\n\t\t}\n\t}\n\n\tprivate receivedAddMemberEvent(clientId: string): void {\n\t\t// This is the only one that requires the pending client ID\n\t\tif (clientId === this.pendingClientId) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t} else if (this.shouldWaitForSelf()) {\n\t\t\t\t// timer has already fired, meaning it took too long to get join op/signal.\n\t\t\t\t// Record how long it actually took to recover.\n\t\t\t\t// This is generic event, as it by itself is not an error.\n\t\t\t\t// We also have a case where NoJoinOp happens during container boot (we do not report it as error in such case),\n\t\t\t\t// if this log statement happens after boot - we do not want to consider it error case.\n\t\t\t\tthis.handler.logConnectionIssue(\n\t\t\t\t\tthis.connection?.mode === \"read\" ? \"ReceivedJoinSignal\" : \"ReceivedJoinOp\", // eventName\n\t\t\t\t\t\"generic\", // category\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Start the event in case we are waiting for leave or timeout.\n\t\t\tif (this.waitingForLeaveOp) {\n\t\t\t\tthis.waitEvent = PerformanceEvent.start(this.handler.logger, {\n\t\t\t\t\teventName: \"WaitBeforeClientLeave\",\n\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\twaitOnClientId: this._clientId,\n\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.applyForConnectedState(\"addMemberEvent\");\n\t\t}\n\t}\n\n\tprivate applyForConnectedState(\n\t\tsource: \"removeMemberEvent\" | \"addMemberEvent\" | \"timeout\" | \"containerSaved\",\n\t): void {\n\t\tassert(\n\t\t\tthis.protocol !== undefined,\n\t\t\t0x236 /* \"In all cases it should be already installed\" */,\n\t\t);\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp || this.hasMember(this.clientId),\n\t\t\t0x2e2 /* \"Must only wait for leave message when clientId in quorum\" */,\n\t\t);\n\n\t\t// Move to connected state only if:\n\t\t// 1. We have seen our own \"join\" op (i.e. for this.pendingClientId)\n\t\t// 2. There is no \"leave\" timer running, meaning this is our first connection or the previous client has left (via this.prevClientLeftTimer)\n\t\tif (\n\t\t\tthis.pendingClientId !== this.clientId &&\n\t\t\tthis.hasMember(this.pendingClientId) &&\n\t\t\t!this.waitingForLeaveOp\n\t\t) {\n\t\t\tthis.waitEvent?.end({ source });\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t} else {\n\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t// We may not see any ops due to being disconnected all that time - that's not an error!\n\t\t\tconst error =\n\t\t\t\tsource === \"timeout\" && this.connectionState !== ConnectionState.Disconnected;\n\t\t\tthis.handler.logger.sendTelemetryEvent({\n\t\t\t\teventName: \"connectedStateRejected\",\n\t\t\t\tcategory: error ? \"error\" : \"generic\",\n\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\tsource,\n\t\t\t\t\tpendingClientId: this.pendingClientId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\tclientJoined: this.hasMember(this.pendingClientId),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate receivedRemoveMemberEvent(clientId: string): void {\n\t\t// If the client which has left was us, then finish the timer.\n\t\tif (this.clientId === clientId && this.waitingForLeaveOp) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"removeMemberEvent\");\n\t\t}\n\t}\n\n\tpublic receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>): void {\n\t\tthis.connection = undefined;\n\t\tthis.setConnectionState(ConnectionState.Disconnected, reason);\n\t}\n\n\tpublic cancelEstablishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tassert(\n\t\t\tthis._connectionState === ConnectionState.EstablishingConnection,\n\t\t\t0x6d3 /* Connection state should be EstablishingConnection */,\n\t\t);\n\t\tassert(this.connection === undefined, 0x6d4 /* No connection should be present */);\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.Disconnected;\n\t\tthis.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);\n\t}\n\n\tpublic establishingConnection(reason: IConnectionStateChangeReason): void {\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.EstablishingConnection;\n\t\tthis.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {\n\t\t\ttext: `Establishing Connection due to ${reason.text}`,\n\t\t\terror: reason.error,\n\t\t});\n\t}\n\n\t/**\n\t * Tells if need to wait for \"self\" to show up in audience.\n\t * @returns true if we should wait for \"self\" to appear in audience.\n\t * false is returned only for \"read\" connections, and only if this.readClientsWaitForJoinSignal is false.\n\t */\n\tprivate shouldWaitForSelf(): boolean {\n\t\tassert(\n\t\t\tthis.connection !== undefined,\n\t\t\t0x4b4 /* all callers call here with active connection */,\n\t\t);\n\t\treturn this.connection.mode === \"write\" || this.readClientsWaitForJoinSignal;\n\t}\n\n\t/**\n\t * The \"connect\" event indicates the connection to the Relay Service is live.\n\t * However, some additional conditions must be met before we can fully transition to\n\t * \"Connected\" state. This function handles that interim period, known as \"Connecting\" state.\n\t * @param details - Connection details returned from the Relay Service\n\t * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.\n\t * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for\n\t */\n\tpublic receivedConnectEvent(details: IConnectionDetailsInternal): void {\n\t\tthis.connection = details;\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = ConnectionState.CatchingUp;\n\n\t\t// The following checks are wrong. They are only valid if user has write access to a file.\n\t\t// If user lost such access mid-session, user will not be able to get \"write\" connection.\n\t\t//\n\t\t// const writeConnection = details.mode === \"write\";\n\t\t// assert(!this.handler.shouldClientJoinWrite() || writeConnection,\n\t\t// 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);\n\t\t// assert(!this.waitingForLeaveOp || writeConnection,\n\t\t// 0x2a6 /* \"waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)\" */);\n\n\t\t// Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected\n\t\t// (have received the join message for the client ID)\n\t\t// This is especially important in the reconnect case. It's possible there could be outstanding\n\t\t// ops sent by this client, so we should keep the old client id until we see our own client's\n\t\t// join message. after we see the join message for our new connection with our new client id,\n\t\t// we know there can no longer be outstanding ops that we sent with the previous client id.\n\t\tthis._pendingClientId = details.clientId;\n\n\t\t// IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state\n\t\tthis.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState, details.reason);\n\n\t\t// Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.\n\t\t// Pending clientId could have joined already (i.e. join op/signal already processed):\n\t\t// We are fetching ops from storage in parallel to connecting to Relay Service,\n\t\t// and given async processes, it's possible that we have already processed our own join message before\n\t\t// connection was fully established.\n\t\tif (!this.hasMember(this._pendingClientId) && this.shouldWaitForSelf()) {\n\t\t\t// We are waiting for our own join op / signal. When it is processed\n\t\t\t// we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.\n\t\t\tthis.startjoinTimer();\n\t\t} else if (!this.waitingForLeaveOp) {\n\t\t\t// We're not waiting for Join or Leave op (if read-only connection those don't even apply),\n\t\t\t// go ahead and declare the state to be Connected!\n\t\t\t// If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.\n\t\t\tthis.setConnectionState(ConnectionState.Connected);\n\t\t}\n\t\t// else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later\n\t}\n\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected,\n\t\treason: IConnectionStateChangeReason,\n\t): void;\n\tprivate setConnectionState(value: ConnectionState.Connected): void;\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected | ConnectionState.Connected,\n\t\treason?: IConnectionStateChangeReason,\n\t): void {\n\t\tif (this.connectionState === value) {\n\t\t\t// Already in the desired state - exit early\n\t\t\tthis.handler.logger.sendErrorEvent({ eventName: \"setConnectionStateSame\", value });\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldState = this._connectionState;\n\t\tthis._connectionState = value;\n\n\t\t// This is the only place in code that deals with quorum. The rest works with audience\n\t\t// The code below ensures that we do not send ops until we know that old \"write\" client's disconnect\n\t\t// produced (and sequenced) leave op\n\t\tconst currentClientInQuorum =\n\t\t\tthis._clientId !== undefined &&\n\t\t\tthis.protocol?.quorum?.getMember(this._clientId) !== undefined;\n\t\tif (value === ConnectionState.Connected) {\n\t\t\tassert(\n\t\t\t\toldState === ConnectionState.CatchingUp,\n\t\t\t\t0x1d8 /* \"Should only transition from Connecting state\" */,\n\t\t\t);\n\t\t\t// Mark our old client should have left in the quorum if it's still there\n\t\t\tif (currentClientInQuorum) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tthis.handler.clientShouldHaveLeft(this._clientId!);\n\t\t\t}\n\t\t\tthis._clientId = this.pendingClientId;\n\t\t} else if (value === ConnectionState.Disconnected) {\n\t\t\tif (this.joinTimer.hasTimer) {\n\t\t\t\tthis.stopjoinTimer();\n\t\t\t}\n\n\t\t\t// Only wait for \"leave\" message if the connected client exists in the quorum and had some non-acked ops\n\t\t\t// Also check if the timer is not already running as\n\t\t\t// we could receive \"Disconnected\" event multiple times without getting connected and in that case we\n\t\t\t// don't want to reset the timer as we still want to wait on original client which started this timer.\n\t\t\tif (\n\t\t\t\tcurrentClientInQuorum &&\n\t\t\t\tthis.handler.shouldClientJoinWrite() &&\n\t\t\t\t!this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer\n\t\t\t) {\n\t\t\t\tthis.prevClientLeftTimer.restart();\n\t\t\t} else {\n\t\t\t\t// Adding this event temporarily so that we can get help debugging if something goes wrong.\n\t\t\t\tthis.handler.logger.sendTelemetryEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: \"noWaitOnDisconnected\",\n\t\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\t\tclientId: this._clientId,\n\t\t\t\t\t\t\tinQuorum: currentClientInQuorum,\n\t\t\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\n\t\t\t\t\t\t\thadOutstandingOps: this.handler.shouldClientJoinWrite(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tundefined, // error\n\t\t\t\t\tLogLevel.info,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Report transition\n\t\tthis.handler.connectionStateChanged(this._connectionState, oldState, reason);\n\n\t\t// Clear pending state immediately to prepare for reconnect\n\t\t// Do it after calling connectionStateChanged() above, such that our telemetry contains pendingClientId on disconnect events\n\t\t// and we can pair attempts to connect with disconnects (that's the only ID we have if connection did not move far enough before being disconnected)\n\t\tif (value === ConnectionState.Disconnected) {\n\t\t\tthis._pendingClientId = undefined;\n\t\t}\n\t}\n\n\tprotected get membership(): IMembership | undefined {\n\t\t// We could always use audience here.\n\t\t// This is true because Audience is a superset of quorum, i.e. when we filter Audience to \"write\" clients,\n\t\t// it is exactly the same as quorum! Please see asserts in Audience callbacks setup by initProtocol() enforcing that.\n\t\treturn this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n\t}\n\n\tpublic initProtocol(protocol: IProtocolHandler): void {\n\t\tthis.protocol = protocol;\n\n\t\tthis.membership?.on(\"addMember\", (clientId, details) => {\n\t\t\t// This is very important constrain. We rely on it when testing presence of \"self\" in Audience.\n\t\t\t// We do not want to move to \"connected\" state for \"write\" connections when JoinSignal shows up\n\t\t\t// for \"self\" - we want to move to \"connected\" state only when \"join\" op is received.\n\t\t\tassert(\n\t\t\t\t(details as IClient).mode === \"read\" ||\n\t\t\t\t\tprotocol.quorum.getMember(clientId) !== undefined,\n\t\t\t\t0x4b5 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedAddMemberEvent(clientId);\n\t\t});\n\n\t\tthis.membership?.on(\"removeMember\", (clientId) => {\n\t\t\tassert(\n\t\t\t\tprotocol.quorum.getMember(clientId) === undefined,\n\t\t\t\t0x4b6 /* Audience is subset of quorum */,\n\t\t\t);\n\t\t\tthis.receivedRemoveMemberEvent(clientId);\n\t\t});\n\n\t\t/* There is a tiny tiny race possible, where these events happen in this order:\n 1. A connection is established (no \"cached\" mode is used, so it happens in parallel / faster than other steps)\n 2. Some other client produces a summary\n 3. We get \"lucky\" and load from that summary as our initial snapshot\n 4. ConnectionStateHandler.initProtocol is called, \"self\" is already in the quorum.\n We could avoid this sequence (and delete test case for it) if we move connection lower in Container.load()\n */\n\t\tif (this.hasMember(this.pendingClientId)) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.receivedAddMemberEvent(this.pendingClientId!);\n\t\t}\n\n\t\tassert(\n\t\t\t!this.waitingForLeaveOp,\n\t\t\t0x99d /* leave timer can't be set as we have not had access to quorum */,\n\t\t);\n\n\t\t// This check is required for scenario of loading container from pending state, and ensuring there is no way\n\t\t// old clientId is still in the quorum (very unlikely, but you never know)\n\t\t// if we have a clientId from a previous container we need to wait for its leave message\n\t\t// This mimicks check in setConnectionState()\n\t\t// Note that we are not consulting this.handler.shouldClientJoinWrite() here\n\t\t// It could produce wrong results for stashed ops were never sent to Loader yet, and if this check\n\t\t// makes determination only on that (and not uses \"dirty\" events), then it can produce wrong result.\n\t\t// In most cases it does not matter, as this client already left quorum. But in really unfortunate case,\n\t\t// we might wait even if we could avoid such wait.\n\t\tif (\n\t\t\tthis._clientId !== undefined &&\n\t\t\tprotocol.quorum?.getMember(this._clientId) !== undefined\n\t\t) {\n\t\t\tthis.prevClientLeftTimer.restart();\n\t\t}\n\t}\n\n\tprotected hasMember(clientId?: string): boolean {\n\t\treturn this.membership?.getMember(clientId ?? \"\") !== undefined;\n\t}\n}\n"]}
@@ -13,11 +13,11 @@ export declare const loaderCoreCompatDetails: {
13
13
  /**
14
14
  * The package version of the Loader layer.
15
15
  */
16
- readonly pkgVersion: "2.110.0";
16
+ readonly pkgVersion: "2.112.0";
17
17
  /**
18
18
  * The current generation of the Loader layer.
19
19
  */
20
- readonly generation: 7;
20
+ readonly generation: 8;
21
21
  };
22
22
  /**
23
23
  * Loader's compatibility details that is exposed to the Runtime layer.
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/container-loader";
8
- export declare const pkgVersion = "2.110.0";
8
+ export declare const pkgVersion = "2.112.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/container-loader";
8
- export const pkgVersion = "2.110.0";
8
+ export const pkgVersion = "2.112.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"2.110.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"2.112.0\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.110.0",
3
+ "version": "2.112.0",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -129,13 +129,13 @@
129
129
  "temp-directory": "nyc/.nyc_output"
130
130
  },
131
131
  "dependencies": {
132
- "@fluid-internal/client-utils": "~2.110.0",
133
- "@fluidframework/container-definitions": "~2.110.0",
134
- "@fluidframework/core-interfaces": "~2.110.0",
135
- "@fluidframework/core-utils": "~2.110.0",
136
- "@fluidframework/driver-definitions": "~2.110.0",
137
- "@fluidframework/driver-utils": "~2.110.0",
138
- "@fluidframework/telemetry-utils": "~2.110.0",
132
+ "@fluid-internal/client-utils": "~2.112.0",
133
+ "@fluidframework/container-definitions": "~2.112.0",
134
+ "@fluidframework/core-interfaces": "~2.112.0",
135
+ "@fluidframework/core-utils": "~2.112.0",
136
+ "@fluidframework/driver-definitions": "~2.112.0",
137
+ "@fluidframework/driver-utils": "~2.112.0",
138
+ "@fluidframework/telemetry-utils": "~2.112.0",
139
139
  "debug": "^4.3.4",
140
140
  "double-ended-queue": "^2.1.0-0",
141
141
  "events_pkg": "npm:events@^3.1.0",
@@ -144,13 +144,13 @@
144
144
  "devDependencies": {
145
145
  "@arethetypeswrong/cli": "^0.18.2",
146
146
  "@biomejs/biome": "~2.4.5",
147
- "@fluid-internal/client-utils": "~2.110.0",
148
- "@fluid-internal/mocha-test-setup": "~2.110.0",
149
- "@fluid-private/test-loader-utils": "~2.110.0",
147
+ "@fluid-internal/client-utils": "~2.112.0",
148
+ "@fluid-internal/mocha-test-setup": "~2.112.0",
149
+ "@fluid-private/test-loader-utils": "~2.112.0",
150
150
  "@fluid-tools/build-cli": "^0.65.0",
151
151
  "@fluidframework/build-common": "^2.0.3",
152
152
  "@fluidframework/build-tools": "^0.65.0",
153
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.103.0",
153
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.111.0",
154
154
  "@fluidframework/eslint-config-fluid": "^13.0.0",
155
155
  "@microsoft/api-extractor": "7.58.1",
156
156
  "@types/debug": "^4.1.5",
@@ -160,7 +160,7 @@
160
160
  "@types/node": "~22.19.17",
161
161
  "@types/sinon": "^17.0.3",
162
162
  "c8": "^10.1.3",
163
- "concurrently": "^9.2.1",
163
+ "concurrently": "^10.0.3",
164
164
  "copyfiles": "^2.4.1",
165
165
  "cross-env": "^10.1.0",
166
166
  "eslint": "~9.39.1",
@@ -172,35 +172,7 @@
172
172
  "typescript": "~5.4.5"
173
173
  },
174
174
  "typeValidation": {
175
- "broken": {
176
- "Class_Loader": {
177
- "backCompat": false
178
- },
179
- "ClassStatics_Loader": {
180
- "backCompat": false
181
- },
182
- "Interface_IContainerHostProps": {
183
- "backCompat": false
184
- },
185
- "Interface_ICreateAndLoadContainerProps": {
186
- "backCompat": false
187
- },
188
- "Interface_ICreateDetachedContainerProps": {
189
- "backCompat": false
190
- },
191
- "Interface_ILoaderProps": {
192
- "backCompat": false
193
- },
194
- "Interface_ILoaderServices": {
195
- "backCompat": false
196
- },
197
- "Interface_ILoadExistingContainerProps": {
198
- "backCompat": false
199
- },
200
- "Interface_IRehydrateDetachedContainerProps": {
201
- "backCompat": false
202
- }
203
- },
175
+ "broken": {},
204
176
  "entrypoint": "legacy"
205
177
  },
206
178
  "scripts": {
@@ -16,7 +16,13 @@ type CaughtUpListener = () => void;
16
16
  /**
17
17
  * Monitor that emits an event when a Container has caught up to a given point in the op stream
18
18
  */
19
- export type ICatchUpMonitor = IDisposable;
19
+ export interface ICatchUpMonitor extends IDisposable {
20
+ /**
21
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
22
+ * @remarks Must be called after the owner has stored its reference to the monitor.
23
+ */
24
+ start(): void;
25
+ }
20
26
 
21
27
  /**
22
28
  * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed
@@ -37,6 +43,14 @@ export class CatchUpMonitor implements ICatchUpMonitor {
37
43
 
38
44
  /**
39
45
  * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
46
+ *
47
+ * @remarks
48
+ * The constructor only wires up the op listener. Call {@link CatchUpMonitor.start} once the monitor
49
+ * reference has been stored to evaluate whether the container is already caught up. This ordering is
50
+ * important: `start` can synchronously invoke the listener (when already caught up), which may re-enter
51
+ * the owner and observe the monitor. If that synchronous check ran from the constructor, the owner's
52
+ * field would not yet be assigned, so re-entrant cleanup (e.g. a disconnect) would silently no-op and
53
+ * leave a stale monitor behind (see assert 0x3eb).
40
54
  */
41
55
  constructor(
42
56
  private readonly deltaManager: IDeltaManager<unknown, unknown>,
@@ -50,7 +64,15 @@ export class CatchUpMonitor implements ICatchUpMonitor {
50
64
  );
51
65
 
52
66
  this.deltaManager.on("op", this.opHandler);
67
+ }
53
68
 
69
+ /**
70
+ * Evaluate whether the container is already caught up, synchronously notifying the listener if so.
71
+ * @remarks
72
+ * Must be called by the owner after storing its reference to this monitor, so that any re-entrancy
73
+ * triggered by the listener observes a fully-constructed, assigned monitor.
74
+ */
75
+ public start(): void {
54
76
  // Simulate the last processed op to set caughtUp in case we already are
55
77
  this.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });
56
78
  }
@@ -277,10 +277,17 @@ export class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
277
277
  // we might get callback right away, and it will screw up state transition (as code outside of switch
278
278
  // statement will overwrite current state).
279
279
  assert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);
280
+ // Assign the field BEFORE starting the monitor. `start()` may synchronously fire
281
+ // transitionToConnectedState (when already caught up), which can re-enter this handler
282
+ // (e.g. an app "connected" listener forcing readonly, triggering a disconnect). The
283
+ // Disconnected case clears this.catchUpMonitor, so the field must already reference the
284
+ // monitor for that cleanup to take effect; otherwise a stale monitor survives and the
285
+ // next Connected transition trips assert 0x3eb.
280
286
  this.catchUpMonitor = new CatchUpMonitor(
281
287
  this.deltaManager,
282
288
  this.transitionToConnectedState,
283
289
  );
290
+ this.catchUpMonitor.start();
284
291
  return;
285
292
  }
286
293
  case ConnectionState.Disconnected: {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.110.0";
9
+ export const pkgVersion = "2.112.0";