@fluidframework/container-loader 2.0.0-internal.3.0.5 → 2.0.0-internal.3.1.1
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/.eslintrc.js +18 -21
- package/.mocharc.js +2 -2
- package/README.md +45 -43
- package/api-extractor.json +2 -2
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +2 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +51 -24
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +35 -16
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +1 -10
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +89 -44
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +6 -2
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +2 -4
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -3
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +56 -27
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +4 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +3 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +18 -15
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +6 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +53 -26
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +35 -16
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +1 -10
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +93 -48
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +6 -2
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -4
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -3
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +58 -29
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +4 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +3 -3
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +18 -15
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +6 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +6 -4
- package/lib/utils.js.map +1 -1
- package/package.json +115 -114
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +51 -46
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1006 -944
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +544 -465
- package/src/container.ts +2056 -1909
- package/src/containerContext.ts +350 -340
- package/src/containerStorageAdapter.ts +163 -153
- package/src/contracts.ts +155 -153
- package/src/deltaManager.ts +1069 -992
- package/src/deltaManagerProxy.ts +143 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +14 -17
- package/src/loader.ts +428 -430
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +93 -87
- package/src/protocolTreeDocumentStorageService.ts +30 -33
- package/src/quorum.ts +34 -34
- package/src/retriableDocumentStorageService.ts +118 -102
- package/src/utils.ts +89 -82
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
|
@@ -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 { ITelemetryLogger, ITelemetryProperties, TelemetryEventCategory } from \"@fluidframework/common-definitions\";\nimport { assert, Timer } from \"@fluidframework/common-utils\";\nimport { IConnectionDetails, IDeltaManager } from \"@fluidframework/container-definitions\";\nimport { ILocalSequencedClient } from \"@fluidframework/protocol-base\";\nimport { ISequencedClient, IClient } from \"@fluidframework/protocol-definitions\";\nimport { PerformanceEvent, loggerToMonitoringContext } from \"@fluidframework/telemetry-utils\";\nimport { ConnectionState } from \"./connectionState\";\nimport { CatchUpMonitor, ICatchUpMonitor } from \"./catchUpMonitor\";\nimport { IProtocolHandler } from \"./protocol\";\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 = 5000;\n\n/** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */\nexport interface IConnectionStateHandlerInputs {\n logger: ITelemetryLogger;\n /** Log to telemetry any change in state, included to Connecting */\n connectionStateChanged:\n (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;\n /** Whether to expect the client to join in write mode on next connection */\n shouldClientJoinWrite: () => boolean;\n /** (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again */\n maxClientLeaveWaitTime: number | undefined;\n /** Log an issue encountered while in the Connecting state. details will be logged as a JSON string */\n logConnectionIssue: (eventName: string, category: TelemetryEventCategory, details?: ITelemetryProperties) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n readonly connectionState: ConnectionState;\n readonly pendingClientId: string | undefined;\n\n containerSaved(): void;\n dispose(): void;\n initProtocol(protocol: IProtocolHandler): void;\n receivedConnectEvent(details: IConnectionDetails): void;\n receivedDisconnectEvent(reason: string): void;\n}\n\nexport function createConnectionStateHandler(\n inputs: IConnectionStateHandlerInputs,\n deltaManager: IDeltaManager<any, any>,\n clientId?: string,\n) {\n const mc = loggerToMonitoringContext(inputs.logger);\n return createConnectionStateHandlerCore(\n mc.config.getBoolean(\"Fluid.Container.CatchUpBeforeDeclaringConnected\") === true, // connectedRaisedWhenCaughtUp\n mc.config.getBoolean(\"Fluid.Container.EnableJoinSignalWait\") === true, // readClientsWaitForJoinSignal\n inputs,\n deltaManager,\n clientId,\n );\n}\n\nexport function createConnectionStateHandlerCore(\n connectedRaisedWhenCaughtUp: boolean,\n readClientsWaitForJoinSignal: boolean,\n inputs: IConnectionStateHandlerInputs,\n deltaManager: IDeltaManager<any, any>,\n clientId?: string,\n) {\n if (!connectedRaisedWhenCaughtUp) {\n return new ConnectionStateHandler(inputs, readClientsWaitForJoinSignal, clientId);\n }\n return new ConnectionStateCatchup(\n inputs,\n (handler: IConnectionStateHandlerInputs) => new ConnectionStateHandler(\n handler,\n readClientsWaitForJoinSignal,\n clientId),\n deltaManager);\n}\n\n/**\n * Helper internal interface to abstract away Audience & Quorum\n */\ninterface IMembership {\n on(\n eventName: \"addMember\" | \"removeMember\",\n listener: (clientId: string, details: IClient | ISequencedClient) => void);\n getMember(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 implements IConnectionStateHandler, IConnectionStateHandlerInputs {\n protected readonly pimpl: IConnectionStateHandler;\n\n constructor(\n protected readonly inputs: IConnectionStateHandlerInputs,\n pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n ) {\n this.pimpl = pimplFactory(this);\n }\n\n /**\n * IConnectionStateHandler\n */\n public get connectionState() { return this.pimpl.connectionState; }\n public get pendingClientId() { return this.pimpl.pendingClientId; }\n\n public containerSaved() { return this.pimpl.containerSaved(); }\n public dispose() { return this.pimpl.dispose(); }\n public initProtocol(protocol: IProtocolHandler) { return this.pimpl.initProtocol(protocol); }\n public receivedDisconnectEvent(reason: string) { return this.pimpl.receivedDisconnectEvent(reason); }\n\n public receivedConnectEvent(details: IConnectionDetails) {\n return this.pimpl.receivedConnectEvent(details);\n }\n\n /**\n * IConnectionStateHandlerInputs\n */\n\n public get logger() { return this.inputs.logger; }\n public connectionStateChanged(\n value: ConnectionState,\n oldState: ConnectionState,\n reason?: string | undefined,\n ) {\n return this.inputs.connectionStateChanged(value, oldState, reason);\n }\n public shouldClientJoinWrite() { return this.inputs.shouldClientJoinWrite(); }\n public get maxClientLeaveWaitTime() { return this.inputs.maxClientLeaveWaitTime; }\n public logConnectionIssue(eventName: string, category: TelemetryEventCategory, details?: ITelemetryProperties) {\n return this.inputs.logConnectionIssue(eventName, category, details);\n }\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nclass ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {\n private catchUpMonitor: ICatchUpMonitor | undefined;\n\n constructor(\n inputs: IConnectionStateHandlerInputs,\n pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler,\n private readonly deltaManager: IDeltaManager<any, any>,\n ) {\n super(inputs, pimplFactory);\n this._connectionState = this.pimpl.connectionState;\n }\n\n private _connectionState: ConnectionState;\n public get connectionState() {\n return this._connectionState;\n }\n\n public connectionStateChanged(value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) {\n switch (value) {\n case ConnectionState.Connected:\n assert(this._connectionState === ConnectionState.CatchingUp, 0x3e1 /* connectivity transitions */);\n // Create catch-up monitor here (not earlier), as we might get more exact info by now about how far\n // client is behind through join signal. This is only true if base layer uses signals (i.e. audience,\n // not quorum, including for \"rea\" connections) to make decisions about moving to \"connected\" state.\n // In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as\n // we might get callback right away, and it will screw up state transition (as code outside of switch\n // statement will overwrite current state).\n assert(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);\n this.catchUpMonitor = new CatchUpMonitor(this.deltaManager, this.transitionToConnectedState);\n return;\n case ConnectionState.Disconnected:\n this.catchUpMonitor?.dispose();\n this.catchUpMonitor = undefined;\n break;\n case ConnectionState.CatchingUp:\n assert(this._connectionState === ConnectionState.Disconnected, 0x3e3 /* connectivity transitions */);\n break;\n default:\n }\n this._connectionState = value;\n this.inputs.connectionStateChanged(value, oldState, reason);\n }\n\n private readonly transitionToConnectedState = () => {\n // Defensive measure, we should always be in Connecting state when this is called.\n const state = this.pimpl.connectionState;\n assert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);\n assert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);\n this._connectionState = ConnectionState.Connected;\n this.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, \"caught up\");\n };\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 */\nclass ConnectionStateHandler implements IConnectionStateHandler {\n private _connectionState = ConnectionState.Disconnected;\n private _pendingClientId: string | undefined;\n private readonly prevClientLeftTimer: Timer;\n private readonly joinOpTimer: Timer;\n private protocol?: IProtocolHandler;\n private connection?: IConnectionDetails;\n private _clientId?: string;\n\n private waitEvent: PerformanceEvent | undefined;\n\n public get connectionState(): ConnectionState {\n return this._connectionState;\n }\n\n private get clientId(): string | undefined {\n return this._clientId;\n }\n\n public get pendingClientId(): string | undefined {\n return this._pendingClientId;\n }\n\n constructor(\n private readonly handler: IConnectionStateHandlerInputs,\n private readonly readClientsWaitForJoinSignal: boolean,\n clientIdFromPausedSession?: string,\n ) {\n this._clientId = clientIdFromPausedSession;\n this.prevClientLeftTimer = new Timer(\n // Default is 5 min for which we are going to wait for its own \"leave\" message. This is same as\n // the max time on server after which leave op is sent.\n this.handler.maxClientLeaveWaitTime ?? 300000,\n () => {\n assert(this.connectionState !== ConnectionState.Connected,\n 0x2ac /* \"Connected when timeout waiting for leave from previous session fired!\" */);\n this.applyForConnectedState(\"timeout\");\n },\n );\n\n this.joinOpTimer = new Timer(\n 0, // default value is not used - startJoinOpTimer() explicitly provides timeout\n () => {\n // I've observed timer firing within couple ms from disconnect event, looks like\n // queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.\n if (this.connectionState !== ConnectionState.CatchingUp) {\n return;\n }\n const details = {\n protocolInitialized: this.protocol !== undefined,\n pendingClientId: this.pendingClientId,\n clientJoined: this.hasMember(this.pendingClientId),\n waitingForLeaveOp: this.waitingForLeaveOp,\n };\n this.handler.logConnectionIssue(\"NoJoinOp\", \"error\", details);\n },\n );\n }\n\n private startJoinOpTimer() {\n assert(!this.joinOpTimer.hasTimer, 0x234 /* \"has joinOpTimer\" */);\n assert(this.connection !== undefined, 0x4b3 /* have connection */);\n this.joinOpTimer.start(\n this.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n );\n }\n\n private stopJoinOpTimer() {\n assert(this.joinOpTimer.hasTimer, 0x235 /* \"no joinOpTimer\" */);\n this.joinOpTimer.clear();\n }\n\n private get waitingForLeaveOp() {\n return this.prevClientLeftTimer.hasTimer;\n }\n\n public dispose() {\n assert(!this.joinOpTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n this.prevClientLeftTimer.clear();\n }\n\n public containerSaved() {\n // If we were waiting for moving to Connected state, then only apply for state change. Since the container\n // is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.\n if (this.waitingForLeaveOp) {\n this.prevClientLeftTimer.clear();\n this.applyForConnectedState(\"containerSaved\");\n }\n }\n\n private receivedAddMemberEvent(clientId: string) {\n // This is the only one that requires the pending client ID\n if (clientId === this.pendingClientId) {\n if (this.joinOpTimer.hasTimer) {\n this.stopJoinOpTimer();\n } else if (this.shouldWaitForJoinSignal()) {\n // timer has already fired, meaning it took too long to get join op/signal.\n // Record how long it actually took to recover.\n // This is generic event, as it by itself is not an error.\n // We also have a case where NoJoinOp happens during container boot (we do not report it as error in such case),\n // if this log statement happens after boot - we do not want to consider it error case.\n this.handler.logConnectionIssue(\"ReceivedJoinOp\", \"generic\");\n }\n // Start the event in case we are waiting for leave or timeout.\n if (this.waitingForLeaveOp) {\n this.waitEvent = PerformanceEvent.start(this.handler.logger, {\n eventName: \"WaitBeforeClientLeave\",\n details: JSON.stringify({\n waitOnClientId: this._clientId,\n hadOutstandingOps: this.handler.shouldClientJoinWrite(),\n }),\n });\n }\n this.applyForConnectedState(\"addMemberEvent\");\n }\n }\n\n private applyForConnectedState(source: \"removeMemberEvent\" | \"addMemberEvent\" | \"timeout\" | \"containerSaved\") {\n assert(this.protocol !== undefined, 0x236 /* \"In all cases it should be already installed\" */);\n\n assert(!this.waitingForLeaveOp || this.hasMember(this.clientId),\n 0x2e2 /* \"Must only wait for leave message when clientId in quorum\" */);\n\n // Move to connected state only if we are in Connecting state, we have seen our join op\n // and there is no timer running which means we are not waiting for previous client to leave\n // or timeout has occurred while doing so.\n if (this.pendingClientId !== this.clientId\n && this.hasMember(this.pendingClientId)\n && !this.waitingForLeaveOp\n ) {\n this.waitEvent?.end({ source });\n this.setConnectionState(ConnectionState.Connected);\n } else {\n // Adding this event temporarily so that we can get help debugging if something goes wrong.\n // We may not see any ops due to being disconnected all that time - that's not an error!\n const error = source === \"timeout\" && this.connectionState !== ConnectionState.Disconnected;\n this.handler.logger.sendTelemetryEvent({\n eventName: \"connectedStateRejected\",\n category: error ? \"error\" : \"generic\",\n details: JSON.stringify({\n source,\n pendingClientId: this.pendingClientId,\n clientId: this.clientId,\n waitingForLeaveOp: this.waitingForLeaveOp,\n clientJoined: this.hasMember(this.pendingClientId),\n }),\n });\n }\n }\n\n private receivedRemoveMemberEvent(clientId: string) {\n // If the client which has left was us, then finish the timer.\n if (this.clientId === clientId) {\n this.prevClientLeftTimer.clear();\n this.applyForConnectedState(\"removeMemberEvent\");\n }\n }\n\n public receivedDisconnectEvent(reason: string) {\n this.connection = undefined;\n this.setConnectionState(ConnectionState.Disconnected, reason);\n }\n\n private shouldWaitForJoinSignal() {\n assert(this.connection !== undefined, 0x4b4 /* all callers call here with active connection */);\n return this.connection.mode === \"write\" || this.readClientsWaitForJoinSignal;\n }\n\n /**\n * The \"connect\" event indicates the connection to the Relay Service is live.\n * However, some additional conditions must be met before we can fully transition to\n * \"Connected\" state. This function handles that interim period, known as \"Connecting\" state.\n * @param details - Connection details returned from the Relay Service\n * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.\n * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for\n */\n public receivedConnectEvent(\n details: IConnectionDetails,\n ) {\n this.connection = details;\n\n const oldState = this._connectionState;\n this._connectionState = ConnectionState.CatchingUp;\n\n // The following checks are wrong. They are only valid if user has write access to a file.\n // If user lost such access mid-session, user will not be able to get \"write\" connection.\n //\n // const writeConnection = details.mode === \"write\";\n // assert(!this.handler.shouldClientJoinWrite() || writeConnection,\n // 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);\n // assert(!this.waitingForLeaveOp || writeConnection,\n // 0x2a6 /* \"waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)\" */);\n\n // Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected\n // (have received the join message for the client ID)\n // This is especially important in the reconnect case. It's possible there could be outstanding\n // ops sent by this client, so we should keep the old client id until we see our own client's\n // join message. after we see the join message for our new connection with our new client id,\n // we know there can no longer be outstanding ops that we sent with the previous client id.\n this._pendingClientId = details.clientId;\n\n // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state\n this.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState);\n\n // Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.\n // Pending clientId could have joined already (i.e. join op/signal already processed):\n // We are fetching ops from storage in parallel to connecting to Relay Service,\n // and given async processes, it's possible that we have already processed our own join message before\n // connection was fully established.\n if (!this.hasMember(this._pendingClientId) && this.shouldWaitForJoinSignal()) {\n // We are waiting for our own join op / signal. When it is processed\n // we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.\n this.startJoinOpTimer();\n } else if (!this.waitingForLeaveOp) {\n // We're not waiting for Join or Leave op (if read-only connection those don't even apply),\n // go ahead and declare the state to be Connected!\n // If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.\n this.setConnectionState(ConnectionState.Connected);\n }\n // else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later\n }\n\n private setConnectionState(value: ConnectionState.Disconnected, reason: string): void;\n private setConnectionState(value: ConnectionState.Connected): void;\n private setConnectionState(value: ConnectionState.Disconnected | ConnectionState.Connected, reason?: string): void {\n if (this.connectionState === value) {\n // Already in the desired state - exit early\n this.handler.logger.sendErrorEvent({ eventName: \"setConnectionStateSame\", value });\n return;\n }\n\n const oldState = this._connectionState;\n this._connectionState = value;\n\n // This is the only place in code that deals with quorum. The rest works with audience\n // The code below ensures that we do not send ops until we know that old \"write\" client's disconnect\n // produced (and sequenced) leave op\n let client: ILocalSequencedClient | undefined;\n if (this._clientId !== undefined) {\n client = this.protocol?.quorum?.getMember(this._clientId);\n }\n if (value === ConnectionState.Connected) {\n assert(oldState === ConnectionState.CatchingUp,\n 0x1d8 /* \"Should only transition from Connecting state\" */);\n // Mark our old client should have left in the quorum if it's still there\n if (client !== undefined) {\n client.shouldHaveLeft = true;\n }\n this._clientId = this.pendingClientId;\n } else if (value === ConnectionState.Disconnected) {\n // Clear pending state immediately to prepare for reconnect\n this._pendingClientId = undefined;\n\n if (this.joinOpTimer.hasTimer) {\n this.stopJoinOpTimer();\n }\n\n // Only wait for \"leave\" message if the connected client exists in the quorum and had some non-acked ops\n // Also check if the timer is not already running as\n // we could receive \"Disconnected\" event multiple times without getting connected and in that case we\n // don't want to reset the timer as we still want to wait on original client which started this timer.\n if (client !== undefined\n && this.handler.shouldClientJoinWrite()\n && !this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer\n ) {\n this.prevClientLeftTimer.restart();\n } else {\n // Adding this event temporarily so that we can get help debugging if something goes wrong.\n this.handler.logger.sendTelemetryEvent({\n eventName: \"noWaitOnDisconnected\",\n details: JSON.stringify({\n clientId: this._clientId,\n inQuorum: client !== undefined,\n waitingForLeaveOp: this.waitingForLeaveOp,\n hadOutstandingOps: this.handler.shouldClientJoinWrite(),\n }),\n });\n }\n }\n\n // Report transition before we propagate event across layers\n this.handler.connectionStateChanged(this._connectionState, oldState, reason);\n }\n\n // Helper method to switch between quorum and audience.\n // Old design was checking only quorum for \"write\" clients.\n // Latest change checks audience for all types of connections.\n protected get membership(): IMembership | undefined {\n // We could always use audience here, and in practice it will probably be correct.\n // (including case when this.readClientsWaitForJoinSignal === false).\n // But only if it's superset of quorum, i.e. when filtered to \"write\" clients, they are always identical!\n // It's safer to assume that we have bugs and engaging kill-bit switch should bring us back to well-known\n // and tested state!\n return this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n }\n\n public initProtocol(protocol: IProtocolHandler) {\n this.protocol = protocol;\n\n this.membership?.on(\"addMember\", (clientId, details) => {\n assert((details as IClient).mode === \"read\" || protocol.quorum.getMember(clientId) !== undefined,\n 0x4b5 /* Audience is subset of quorum */);\n this.receivedAddMemberEvent(clientId);\n });\n\n this.membership?.on(\"removeMember\", (clientId) => {\n assert(protocol.quorum.getMember(clientId) === undefined, 0x4b6 /* Audience is subset of quorum */);\n this.receivedRemoveMemberEvent(clientId);\n });\n\n /* 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 if (this.hasMember(this.pendingClientId)) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.receivedAddMemberEvent(this.pendingClientId!);\n }\n\n // if we have a clientId from a previous container we need to wait for its leave message\n if (this.clientId !== undefined && this.hasMember(this.clientId)) {\n this.prevClientLeftTimer.restart();\n }\n }\n\n protected hasMember(clientId?: string) {\n return this.membership?.getMember(clientId ?? \"\") !== undefined;\n }\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 {\n\tITelemetryLogger,\n\tITelemetryProperties,\n\tTelemetryEventCategory,\n} from \"@fluidframework/common-definitions\";\nimport { assert, Timer } from \"@fluidframework/common-utils\";\nimport { IConnectionDetails, IDeltaManager } from \"@fluidframework/container-definitions\";\nimport { ILocalSequencedClient } from \"@fluidframework/protocol-base\";\nimport { ISequencedClient, IClient } from \"@fluidframework/protocol-definitions\";\nimport { PerformanceEvent, loggerToMonitoringContext } from \"@fluidframework/telemetry-utils\";\nimport { ConnectionState } from \"./connectionState\";\nimport { CatchUpMonitor, ICatchUpMonitor } from \"./catchUpMonitor\";\nimport { IProtocolHandler } from \"./protocol\";\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 = 5000;\n\n/** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */\nexport interface IConnectionStateHandlerInputs {\n\tlogger: ITelemetryLogger;\n\t/** Log to telemetry any change in state, included to Connecting */\n\tconnectionStateChanged: (\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: string | undefined,\n\t) => void;\n\t/** Whether to expect the client to join in write mode on next connection */\n\tshouldClientJoinWrite: () => boolean;\n\t/** (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again */\n\tmaxClientLeaveWaitTime: number | undefined;\n\t/** Log an issue encountered while in the Connecting state. details will be logged as a JSON string */\n\tlogConnectionIssue: (\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryProperties,\n\t) => void;\n}\n\n/**\n * interface that connection state handler implements\n */\nexport interface IConnectionStateHandler {\n\treadonly connectionState: ConnectionState;\n\treadonly pendingClientId: string | undefined;\n\n\tcontainerSaved(): void;\n\tdispose(): void;\n\tinitProtocol(protocol: IProtocolHandler): void;\n\treceivedConnectEvent(details: IConnectionDetails): void;\n\treceivedDisconnectEvent(reason: string): void;\n}\n\nexport function createConnectionStateHandler(\n\tinputs: IConnectionStateHandlerInputs,\n\tdeltaManager: IDeltaManager<any, any>,\n\tclientId?: string,\n) {\n\tconst mc = loggerToMonitoringContext(inputs.logger);\n\treturn createConnectionStateHandlerCore(\n\t\tmc.config.getBoolean(\"Fluid.Container.CatchUpBeforeDeclaringConnected\") === true, // connectedRaisedWhenCaughtUp\n\t\tmc.config.getBoolean(\"Fluid.Container.EnableJoinSignalWait\") === 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<any, any>,\n\tclientId?: string,\n) {\n\tif (!connectedRaisedWhenCaughtUp) {\n\t\treturn new ConnectionStateHandler(inputs, readClientsWaitForJoinSignal, clientId);\n\t}\n\treturn new ConnectionStateCatchup(\n\t\tinputs,\n\t\t(handler: IConnectionStateHandlerInputs) =>\n\t\t\tnew ConnectionStateHandler(handler, readClientsWaitForJoinSignal, clientId),\n\t\tdeltaManager,\n\t);\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/**\n\t * IConnectionStateHandler\n\t */\n\tpublic get connectionState() {\n\t\treturn this.pimpl.connectionState;\n\t}\n\tpublic get pendingClientId() {\n\t\treturn this.pimpl.pendingClientId;\n\t}\n\n\tpublic containerSaved() {\n\t\treturn this.pimpl.containerSaved();\n\t}\n\tpublic dispose() {\n\t\treturn this.pimpl.dispose();\n\t}\n\tpublic initProtocol(protocol: IProtocolHandler) {\n\t\treturn this.pimpl.initProtocol(protocol);\n\t}\n\tpublic receivedDisconnectEvent(reason: string) {\n\t\treturn this.pimpl.receivedDisconnectEvent(reason);\n\t}\n\n\tpublic receivedConnectEvent(details: IConnectionDetails) {\n\t\treturn this.pimpl.receivedConnectEvent(details);\n\t}\n\n\t/**\n\t * IConnectionStateHandlerInputs\n\t */\n\n\tpublic get logger() {\n\t\treturn this.inputs.logger;\n\t}\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: string | undefined,\n\t) {\n\t\treturn this.inputs.connectionStateChanged(value, oldState, reason);\n\t}\n\tpublic shouldClientJoinWrite() {\n\t\treturn this.inputs.shouldClientJoinWrite();\n\t}\n\tpublic get maxClientLeaveWaitTime() {\n\t\treturn this.inputs.maxClientLeaveWaitTime;\n\t}\n\tpublic logConnectionIssue(\n\t\teventName: string,\n\t\tcategory: TelemetryEventCategory,\n\t\tdetails?: ITelemetryProperties,\n\t) {\n\t\treturn this.inputs.logConnectionIssue(eventName, category, details);\n\t}\n}\n\n/**\n * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number\n * before raising connected event\n */\nclass 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<any, any>,\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() {\n\t\treturn this._connectionState;\n\t}\n\n\tpublic connectionStateChanged(\n\t\tvalue: ConnectionState,\n\t\toldState: ConnectionState,\n\t\treason?: string | undefined,\n\t) {\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(\n\t\t\t\t\tthis.catchUpMonitor === undefined,\n\t\t\t\t\t0x3eb /* catchUpMonitor should be gone */,\n\t\t\t\t);\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\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\tcase ConnectionState.CatchingUp:\n\t\t\t\tassert(\n\t\t\t\t\tthis._connectionState === ConnectionState.Disconnected,\n\t\t\t\t\t0x3e3 /* connectivity transitions */,\n\t\t\t\t);\n\t\t\t\tbreak;\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 = () => {\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(\n\t\t\tConnectionState.Connected,\n\t\t\tConnectionState.CatchingUp,\n\t\t\t\"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 */\nclass ConnectionStateHandler implements IConnectionStateHandler {\n\tprivate _connectionState = ConnectionState.Disconnected;\n\tprivate _pendingClientId: string | undefined;\n\tprivate readonly prevClientLeftTimer: Timer;\n\tprivate readonly joinOpTimer: Timer;\n\tprivate protocol?: IProtocolHandler;\n\tprivate connection?: IConnectionDetails;\n\tprivate _clientId?: string;\n\n\tprivate waitEvent: PerformanceEvent | undefined;\n\n\tpublic get connectionState(): ConnectionState {\n\t\treturn this._connectionState;\n\t}\n\n\tprivate 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\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);\n\n\t\tthis.joinOpTimer = new Timer(\n\t\t\t0, // default value is not used - startJoinOpTimer() 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(\"NoJoinOp\", \"error\", details);\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate startJoinOpTimer() {\n\t\tassert(!this.joinOpTimer.hasTimer, 0x234 /* \"has joinOpTimer\" */);\n\t\tassert(this.connection !== undefined, 0x4b3 /* have connection */);\n\t\tthis.joinOpTimer.start(\n\t\t\tthis.connection.mode === \"write\" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,\n\t\t);\n\t}\n\n\tprivate stopJoinOpTimer() {\n\t\tassert(this.joinOpTimer.hasTimer, 0x235 /* \"no joinOpTimer\" */);\n\t\tthis.joinOpTimer.clear();\n\t}\n\n\tprivate get waitingForLeaveOp() {\n\t\treturn this.prevClientLeftTimer.hasTimer;\n\t}\n\n\tpublic dispose() {\n\t\tassert(!this.joinOpTimer.hasTimer, 0x2a5 /* \"join timer\" */);\n\t\tthis.prevClientLeftTimer.clear();\n\t}\n\n\tpublic containerSaved() {\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) {\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.joinOpTimer.hasTimer) {\n\t\t\t\tthis.stopJoinOpTimer();\n\t\t\t} else if (this.shouldWaitForJoinSignal()) {\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(\"ReceivedJoinOp\", \"generic\");\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) {\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 we are in Connecting state, we have seen our join op\n\t\t// and there is no timer running which means we are not waiting for previous client to leave\n\t\t// or timeout has occurred while doing so.\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) {\n\t\t// If the client which has left was us, then finish the timer.\n\t\tif (this.clientId === clientId) {\n\t\t\tthis.prevClientLeftTimer.clear();\n\t\t\tthis.applyForConnectedState(\"removeMemberEvent\");\n\t\t}\n\t}\n\n\tpublic receivedDisconnectEvent(reason: string) {\n\t\tthis.connection = undefined;\n\t\tthis.setConnectionState(ConnectionState.Disconnected, reason);\n\t}\n\n\tprivate shouldWaitForJoinSignal() {\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: IConnectionDetails) {\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);\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.shouldWaitForJoinSignal()) {\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.startJoinOpTimer();\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(value: ConnectionState.Disconnected, reason: string): void;\n\tprivate setConnectionState(value: ConnectionState.Connected): void;\n\tprivate setConnectionState(\n\t\tvalue: ConnectionState.Disconnected | ConnectionState.Connected,\n\t\treason?: string,\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\tlet client: ILocalSequencedClient | undefined;\n\t\tif (this._clientId !== undefined) {\n\t\t\tclient = this.protocol?.quorum?.getMember(this._clientId);\n\t\t}\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 (client !== undefined) {\n\t\t\t\tclient.shouldHaveLeft = true;\n\t\t\t}\n\t\t\tthis._clientId = this.pendingClientId;\n\t\t} else if (value === ConnectionState.Disconnected) {\n\t\t\t// Clear pending state immediately to prepare for reconnect\n\t\t\tthis._pendingClientId = undefined;\n\n\t\t\tif (this.joinOpTimer.hasTimer) {\n\t\t\t\tthis.stopJoinOpTimer();\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\tclient !== undefined &&\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\teventName: \"noWaitOnDisconnected\",\n\t\t\t\t\tdetails: JSON.stringify({\n\t\t\t\t\t\tclientId: this._clientId,\n\t\t\t\t\t\tinQuorum: client !== undefined,\n\t\t\t\t\t\twaitingForLeaveOp: this.waitingForLeaveOp,\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}\n\n\t\t// Report transition before we propagate event across layers\n\t\tthis.handler.connectionStateChanged(this._connectionState, oldState, reason);\n\t}\n\n\t// Helper method to switch between quorum and audience.\n\t// Old design was checking only quorum for \"write\" clients.\n\t// Latest change checks audience for all types of connections.\n\tprotected get membership(): IMembership | undefined {\n\t\t// We could always use audience here, and in practice it will probably be correct.\n\t\t// (including case when this.readClientsWaitForJoinSignal === false).\n\t\t// But only if it's superset of quorum, i.e. when filtered to \"write\" clients, they are always identical!\n\t\t// It's safer to assume that we have bugs and engaging kill-bit switch should bring us back to well-known\n\t\t// and tested state!\n\t\treturn this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;\n\t}\n\n\tpublic initProtocol(protocol: IProtocolHandler) {\n\t\tthis.protocol = protocol;\n\n\t\tthis.membership?.on(\"addMember\", (clientId, details) => {\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\t// if we have a clientId from a previous container we need to wait for its leave message\n\t\tif (this.clientId !== undefined && this.hasMember(this.clientId)) {\n\t\t\tthis.prevClientLeftTimer.restart();\n\t\t}\n\t}\n\n\tprotected hasMember(clientId?: string) {\n\t\treturn this.membership?.getMember(clientId ?? \"\") !== undefined;\n\t}\n}\n"]}
|
package/lib/container.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { ITelemetryBaseLogger, ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
6
|
-
import { IRequest, IResponse, IFluidRouter
|
|
6
|
+
import { IRequest, IResponse, IFluidRouter } from "@fluidframework/core-interfaces";
|
|
7
7
|
import { IAudience, IContainer, IContainerEvents, IDeltaManager, ICriticalContainerError, AttachState, ReadOnlyInfo, IContainerLoadMode, IFluidCodeDetails } from "@fluidframework/container-definitions";
|
|
8
8
|
import { IDocumentStorageService, IFluidResolvedUrl, IResolvedUrl } from "@fluidframework/driver-definitions";
|
|
9
9
|
import { IClientConfiguration, IClientDetails, IDocumentMessage, IProtocolState, IQuorumClients, ISequencedDocumentMessage, IVersion } from "@fluidframework/protocol-definitions";
|
|
@@ -34,10 +34,6 @@ export interface IContainerLoadOptions {
|
|
|
34
34
|
* use the loader's logger, `Loader.services.subLogger`.
|
|
35
35
|
*/
|
|
36
36
|
baseLogger?: ITelemetryBaseLogger;
|
|
37
|
-
/**
|
|
38
|
-
* A scope object to replace the one provided on the Loader.
|
|
39
|
-
*/
|
|
40
|
-
scopeOverride?: FluidObject;
|
|
41
37
|
}
|
|
42
38
|
export interface IContainerConfig {
|
|
43
39
|
resolvedUrl?: IFluidResolvedUrl;
|
|
@@ -55,10 +51,6 @@ export interface IContainerConfig {
|
|
|
55
51
|
* use the loader's logger, `Loader.services.subLogger`.
|
|
56
52
|
*/
|
|
57
53
|
baseLogger?: ITelemetryBaseLogger;
|
|
58
|
-
/**
|
|
59
|
-
* A scope object to replace the one provided on the Loader.
|
|
60
|
-
*/
|
|
61
|
-
scopeOverride?: FluidObject;
|
|
62
54
|
}
|
|
63
55
|
/**
|
|
64
56
|
* Waits until container connects to delta storage and gets up-to-date.
|
|
@@ -137,7 +129,6 @@ export declare class Container extends EventEmitterWithErrorHandling<IContainerE
|
|
|
137
129
|
private readonly storageService;
|
|
138
130
|
get storage(): IDocumentStorageService;
|
|
139
131
|
private readonly clientDetailsOverride;
|
|
140
|
-
private readonly _scopeOverride;
|
|
141
132
|
private readonly _deltaManager;
|
|
142
133
|
private service;
|
|
143
134
|
private _context;
|
package/lib/container.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
|
package/lib/container.js
CHANGED
|
@@ -7,7 +7,7 @@ import merge from "lodash/merge";
|
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
8
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
|
-
import { GenericError, UsageError
|
|
10
|
+
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
11
11
|
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
13
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
@@ -19,13 +19,13 @@ import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
|
19
19
|
import { RelativeLoader } from "./loader";
|
|
20
20
|
import { pkgVersion } from "./packageVersion";
|
|
21
21
|
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
22
|
-
import { createConnectionStateHandler
|
|
22
|
+
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
23
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
24
|
-
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
24
|
+
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
|
|
25
25
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
26
26
|
import { ConnectionManager } from "./connectionManager";
|
|
27
27
|
import { ConnectionState } from "./connectionState";
|
|
28
|
-
import { ProtocolHandler
|
|
28
|
+
import { ProtocolHandler } from "./protocol";
|
|
29
29
|
const detachedContainerRefSeqNumber = 0;
|
|
30
30
|
const dirtyContainerEvent = "dirty";
|
|
31
31
|
const savedContainerEvent = "saved";
|
|
@@ -65,8 +65,8 @@ export async function waitContainerToCatchUp(container) {
|
|
|
65
65
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
66
66
|
// which is a reasonable approximation of "caught up"
|
|
67
67
|
const waitForOps = () => {
|
|
68
|
-
assert(container.connectionState === ConnectionState.CatchingUp
|
|
69
|
-
|
|
68
|
+
assert(container.connectionState === ConnectionState.CatchingUp ||
|
|
69
|
+
container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
70
70
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
71
71
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
72
72
|
assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -160,7 +160,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
160
160
|
this.setAutoReconnectTime = performance.now();
|
|
161
161
|
this._disposed = false;
|
|
162
162
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
163
|
-
this._scopeOverride = config.scopeOverride;
|
|
164
163
|
this._resolvedUrl = config.resolvedUrl;
|
|
165
164
|
if (config.canReconnect !== undefined) {
|
|
166
165
|
this._canReconnect = config.canReconnect;
|
|
@@ -198,6 +197,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
198
197
|
dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
|
|
199
198
|
dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
|
|
200
199
|
dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
|
|
200
|
+
dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
|
|
201
201
|
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
202
202
|
},
|
|
203
203
|
});
|
|
@@ -215,7 +215,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
215
215
|
}
|
|
216
216
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
217
217
|
if (this._lifecycleState === "loaded") {
|
|
218
|
-
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
218
|
+
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
219
|
+
? reason
|
|
220
|
+
: undefined /* disconnectedReason */);
|
|
219
221
|
}
|
|
220
222
|
},
|
|
221
223
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
@@ -228,7 +230,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
228
230
|
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
229
231
|
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
230
232
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName,
|
|
231
|
-
mode, category:
|
|
233
|
+
mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: performance.now() -
|
|
234
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
232
235
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
233
236
|
// to very slow op fetches and we will eventually get there.
|
|
234
237
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -262,7 +265,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
262
265
|
}
|
|
263
266
|
else {
|
|
264
267
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
265
|
-
setTimeout(() => {
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
this.lastVisible = undefined;
|
|
270
|
+
}, 0);
|
|
266
271
|
}
|
|
267
272
|
};
|
|
268
273
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
@@ -273,7 +278,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
273
278
|
// if we are in connecting stage.
|
|
274
279
|
this.on("newListener", (event, listener) => {
|
|
275
280
|
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
276
|
-
Promise.resolve()
|
|
281
|
+
Promise.resolve()
|
|
282
|
+
.then(() => {
|
|
277
283
|
switch (event) {
|
|
278
284
|
case dirtyContainerEvent:
|
|
279
285
|
if (this._dirtyContainer) {
|
|
@@ -297,7 +303,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
297
303
|
break;
|
|
298
304
|
default:
|
|
299
305
|
}
|
|
300
|
-
})
|
|
306
|
+
})
|
|
307
|
+
.catch((error) => {
|
|
301
308
|
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
302
309
|
});
|
|
303
310
|
});
|
|
@@ -312,7 +319,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
312
319
|
canReconnect: loadOptions.canReconnect,
|
|
313
320
|
serializedContainerState: pendingLocalState,
|
|
314
321
|
baseLogger: loadOptions.baseLogger,
|
|
315
|
-
scopeOverride: loadOptions.scopeOverride,
|
|
316
322
|
}, protocolHandlerBuilder);
|
|
317
323
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
318
324
|
var _a, _b;
|
|
@@ -327,7 +333,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
327
333
|
reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
|
|
328
334
|
};
|
|
329
335
|
container.on("closed", onClosed);
|
|
330
|
-
container
|
|
336
|
+
container
|
|
337
|
+
.load(version, mode, pendingLocalState)
|
|
331
338
|
.finally(() => {
|
|
332
339
|
container.removeListener("closed", onClosed);
|
|
333
340
|
})
|
|
@@ -376,8 +383,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
376
383
|
}
|
|
377
384
|
}
|
|
378
385
|
get closed() {
|
|
379
|
-
return (this._lifecycleState === "closing" ||
|
|
380
|
-
|
|
386
|
+
return (this._lifecycleState === "closing" ||
|
|
387
|
+
this._lifecycleState === "closed" ||
|
|
388
|
+
this._lifecycleState === "disposing" ||
|
|
389
|
+
this._lifecycleState === "disposed");
|
|
381
390
|
}
|
|
382
391
|
get storage() {
|
|
383
392
|
return this.storageService;
|
|
@@ -394,8 +403,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
394
403
|
}
|
|
395
404
|
return this._protocolHandler;
|
|
396
405
|
}
|
|
397
|
-
get connectionMode() {
|
|
398
|
-
|
|
406
|
+
get connectionMode() {
|
|
407
|
+
return this._deltaManager.connectionManager.connectionMode;
|
|
408
|
+
}
|
|
409
|
+
get IFluidRouter() {
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
399
412
|
get resolvedUrl() {
|
|
400
413
|
return this._resolvedUrl;
|
|
401
414
|
}
|
|
@@ -477,10 +490,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
477
490
|
get isDirty() {
|
|
478
491
|
return this._dirtyContainer;
|
|
479
492
|
}
|
|
480
|
-
get serviceFactory() {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
get
|
|
493
|
+
get serviceFactory() {
|
|
494
|
+
return this.loader.services.documentServiceFactory;
|
|
495
|
+
}
|
|
496
|
+
get urlResolver() {
|
|
497
|
+
return this.loader.services.urlResolver;
|
|
498
|
+
}
|
|
499
|
+
get scope() {
|
|
500
|
+
return this.loader.services.scope;
|
|
501
|
+
}
|
|
502
|
+
get codeLoader() {
|
|
503
|
+
return this.loader.services.codeLoader;
|
|
504
|
+
}
|
|
484
505
|
/**
|
|
485
506
|
* Retrieves the quorum associated with the document
|
|
486
507
|
*/
|
|
@@ -548,7 +569,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
548
569
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
549
570
|
this.mc.logger.sendTelemetryEvent({
|
|
550
571
|
eventName: "ContainerDispose",
|
|
551
|
-
category:
|
|
572
|
+
category: "generic",
|
|
552
573
|
}, error);
|
|
553
574
|
// ! Progressing from "closed" to "disposing" is not allowed
|
|
554
575
|
if (this._lifecycleState !== "closed") {
|
|
@@ -604,8 +625,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
604
625
|
const appSummary = this.context.createSummary();
|
|
605
626
|
const protocolSummary = this.captureProtocolSummary();
|
|
606
627
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
607
|
-
if (this.loader.services.detachedBlobStorage &&
|
|
608
|
-
|
|
628
|
+
if (this.loader.services.detachedBlobStorage &&
|
|
629
|
+
this.loader.services.detachedBlobStorage.size > 0) {
|
|
630
|
+
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
631
|
+
type: SummaryType.Blob,
|
|
632
|
+
content: "true",
|
|
633
|
+
};
|
|
609
634
|
}
|
|
610
635
|
return JSON.stringify(combinedSummary);
|
|
611
636
|
}
|
|
@@ -620,8 +645,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
620
645
|
assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
621
646
|
this.attachStarted = true;
|
|
622
647
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
623
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
624
|
-
|
|
648
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
|
|
649
|
+
this.loader.services.detachedBlobStorage.size > 0;
|
|
625
650
|
try {
|
|
626
651
|
assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
627
652
|
let summary;
|
|
@@ -659,7 +684,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
659
684
|
const redirectTable = new Map();
|
|
660
685
|
// if new blobs are added while uploading, upload them too
|
|
661
686
|
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
662
|
-
const newIds = this.loader.services.detachedBlobStorage
|
|
687
|
+
const newIds = this.loader.services.detachedBlobStorage
|
|
688
|
+
.getBlobIds()
|
|
689
|
+
.filter((id) => !redirectTable.has(id));
|
|
663
690
|
for (const id of newIds) {
|
|
664
691
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
665
692
|
const response = await this.storageService.createBlob(blob);
|
|
@@ -681,7 +708,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
681
708
|
this._attachState = AttachState.Attached;
|
|
682
709
|
this.emit("attached");
|
|
683
710
|
if (!this.closed) {
|
|
684
|
-
this.resumeInternal({
|
|
711
|
+
this.resumeInternal({
|
|
712
|
+
fetchOpsFromStorage: false,
|
|
713
|
+
reason: "createDetached",
|
|
714
|
+
});
|
|
685
715
|
}
|
|
686
716
|
}
|
|
687
717
|
catch (error) {
|
|
@@ -781,7 +811,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
781
811
|
throw new Error("Proposed code details should be greater than the current");
|
|
782
812
|
}
|
|
783
813
|
}
|
|
784
|
-
return this.protocolHandler.quorum
|
|
814
|
+
return this.protocolHandler.quorum
|
|
815
|
+
.propose("code", codeDetails)
|
|
785
816
|
.then(() => true)
|
|
786
817
|
.catch(() => false);
|
|
787
818
|
}
|
|
@@ -790,9 +821,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
790
821
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
791
822
|
await Promise.all([
|
|
792
823
|
this.deltaManager.inbound.pause(),
|
|
793
|
-
this.deltaManager.inboundSignal.pause()
|
|
824
|
+
this.deltaManager.inboundSignal.pause(),
|
|
794
825
|
]);
|
|
795
|
-
if ((await this.context.satisfies(codeDetails) === true)
|
|
826
|
+
if ((await this.context.satisfies(codeDetails)) === true) {
|
|
796
827
|
this.deltaManager.inbound.resume();
|
|
797
828
|
this.deltaManager.inboundSignal.resume();
|
|
798
829
|
return;
|
|
@@ -838,7 +869,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
838
869
|
// connections to same file) in two ways:
|
|
839
870
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
840
871
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
841
|
-
const connectionArgs = {
|
|
872
|
+
const connectionArgs = {
|
|
873
|
+
reason: "DocumentOpen",
|
|
874
|
+
mode: "write",
|
|
875
|
+
fetchOpsFromStorage: false,
|
|
876
|
+
};
|
|
842
877
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
843
878
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
844
879
|
if (loadMode.deltaConnection === undefined) {
|
|
@@ -962,7 +997,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
962
997
|
}
|
|
963
998
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
964
999
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
965
|
-
assert(!!this.loader.services.detachedBlobStorage &&
|
|
1000
|
+
assert(!!this.loader.services.detachedBlobStorage &&
|
|
1001
|
+
this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
966
1002
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
967
1003
|
}
|
|
968
1004
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
@@ -1009,11 +1045,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1009
1045
|
};
|
|
1010
1046
|
if (snapshot !== undefined) {
|
|
1011
1047
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
1012
|
-
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1048
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1049
|
+
await Promise.all([
|
|
1050
|
+
readAndParse(storage, baseTree.blobs.quorumMembers),
|
|
1051
|
+
readAndParse(storage, baseTree.blobs.quorumProposals),
|
|
1052
|
+
readAndParse(storage, baseTree.blobs.quorumValues),
|
|
1053
|
+
]);
|
|
1017
1054
|
}
|
|
1018
1055
|
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
1019
1056
|
}
|
|
@@ -1100,7 +1137,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1100
1137
|
if (this.clientDetailsOverride !== undefined) {
|
|
1101
1138
|
merge(client.details, this.clientDetailsOverride);
|
|
1102
1139
|
}
|
|
1103
|
-
client.details.environment = [
|
|
1140
|
+
client.details.environment = [
|
|
1141
|
+
client.details.environment,
|
|
1142
|
+
` loaderVersion:${pkgVersion}`,
|
|
1143
|
+
].join(";");
|
|
1104
1144
|
return client;
|
|
1105
1145
|
}
|
|
1106
1146
|
/**
|
|
@@ -1110,8 +1150,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1110
1150
|
* If it's not true, runtime is not in position to send ops.
|
|
1111
1151
|
*/
|
|
1112
1152
|
activeConnection() {
|
|
1113
|
-
return this.connectionState === ConnectionState.Connected &&
|
|
1114
|
-
this.connectionMode === "write";
|
|
1153
|
+
return (this.connectionState === ConnectionState.Connected && this.connectionMode === "write");
|
|
1115
1154
|
}
|
|
1116
1155
|
createDeltaManager() {
|
|
1117
1156
|
const serviceProvider = () => this.service;
|
|
@@ -1178,7 +1217,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1178
1217
|
}
|
|
1179
1218
|
else {
|
|
1180
1219
|
if (value === ConnectionState.Connected) {
|
|
1181
|
-
durationFromDisconnected =
|
|
1220
|
+
durationFromDisconnected =
|
|
1221
|
+
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1182
1222
|
durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1183
1223
|
}
|
|
1184
1224
|
else {
|
|
@@ -1221,7 +1261,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1221
1261
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1222
1262
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1223
1263
|
if (logOpsOnReconnect) {
|
|
1224
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1264
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1265
|
+
eventName: "OpsSentOnReconnect",
|
|
1266
|
+
count: this.messageCountAfterDisconnection,
|
|
1267
|
+
});
|
|
1225
1268
|
}
|
|
1226
1269
|
}
|
|
1227
1270
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
@@ -1258,8 +1301,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1258
1301
|
if (summary.details === undefined) {
|
|
1259
1302
|
summary.details = {};
|
|
1260
1303
|
}
|
|
1261
|
-
summary.details.includesProtocolTree =
|
|
1262
|
-
this.options.summarizeProtocolTree === true;
|
|
1304
|
+
summary.details.includesProtocolTree = this.options.summarizeProtocolTree === true;
|
|
1263
1305
|
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1264
1306
|
}
|
|
1265
1307
|
submitMessage(type, contents, batch, metadata, compression) {
|
|
@@ -1318,10 +1360,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1318
1360
|
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1319
1361
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1320
1362
|
// We should have a defined version to load from if specified version requested
|
|
1321
|
-
this.mc.logger.sendErrorEvent({
|
|
1363
|
+
this.mc.logger.sendErrorEvent({
|
|
1364
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
1365
|
+
id: specifiedVersion,
|
|
1366
|
+
});
|
|
1322
1367
|
}
|
|
1323
1368
|
this._loadedFromVersion = version;
|
|
1324
|
-
const snapshot = (_a = await this.storageService.getSnapshotTree(version)) !== null && _a !== void 0 ? _a : undefined;
|
|
1369
|
+
const snapshot = (_a = (await this.storageService.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1325
1370
|
if (snapshot === undefined && version !== undefined) {
|
|
1326
1371
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1327
1372
|
}
|