Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Unhandled "WebSocket connection closed" when CDP connection is unstable #29830

Merged
merged 22 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
71a93aa
unit and integration tests that reproduce websocket disconnected unha…
cacieprins Jun 13, 2024
42be544
WIP: command queue
cacieprins Jul 3, 2024
d2fc2d1
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 8, 2024
dd0da67
complete command queue and retry refactor
cacieprins Jul 9, 2024
e637979
cri-client changes pass tests; modify certain tests for readability a…
cacieprins Jul 9, 2024
a6cb8a6
removes unnecessary logic from command queue, adds unit tests for com…
cacieprins Jul 10, 2024
927902a
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 10, 2024
b24d6a0
rm unused cdp state - this should be reserved for future refactor
cacieprins Jul 10, 2024
8dde990
small edits to cri-client: better error handling, more comprehensive …
cacieprins Jul 10, 2024
b763ddb
comment re: queue property
cacieprins Jul 10, 2024
f2450fb
rearrange cri client member methods for readability
cacieprins Jul 10, 2024
78e82a4
further edits
cacieprins Jul 10, 2024
0b19fe9
Changelog
cacieprins Jul 10, 2024
673d86b
Update cli/CHANGELOG.md
cacieprins Jul 11, 2024
ac82a83
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 11, 2024
422fe2f
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
jennifer-shehane Jul 12, 2024
1e3440e
fix continuous retry on close
cacieprins Jul 12, 2024
98fae10
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
jennifer-shehane Jul 12, 2024
bf06572
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 15, 2024
4c5d899
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 15, 2024
fc52033
split heavier debugs to verbose
cacieprins Jul 15, 2024
6a4031b
Merge branch 'develop' into cacie/fix/ws-disconnected-trampoline
cacieprins Jul 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ _Released 7/16/2024 (PENDING)_

**Bugfixes:**

- Fixed an issue where unhandled `WebSocket connection closed` exceptions would be thrown when CDP connections rapidly connect, disconnect, and connect again while there are pending commands. Fixes [#29572](https://github.com/cypress-io/cypress/issues/29572).
- CLI output properly displays non-JSON response bodies when a Test Replay upload attempt returns a non-JSON response body for a non-200 status code. Addressed in [#29801](https://github.com/cypress-io/cypress/pull/29801).
- Fixed an issue where the ReadStream used to upload a Test Replay recording could erroneously be re-used when retrying in cases of retryable upload failures. Fixes [#29227](https://github.com/cypress-io/cypress/issues/29227).
- Fixed an issue where command snapshots were not being captured within the `cy.origin()` command within Test Replay. Addressed in [#29828](https://github.com/cypress-io/cypress/pull/29828).
Expand Down
86 changes: 86 additions & 0 deletions packages/server/lib/browsers/cdp-command-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping'
import pDefer, { DeferredPromise } from 'p-defer'
import type { CdpCommand } from './cdp_automation'
import Debug from 'debug'

const debug = Debug('cypress:server:browsers:cdp-command-queue')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How chatty will these logs be? Do we want to make some of them verbose and some of them non-verbose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very chatty - commands are only pushed into the command queue when they come in while the websocket connection is unexpectedly disconnected. params might be a larger payload, though, and could benefit from a verbose (or removal, as it's not pertinent to the command queue operations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in fc52033

const debugVerbose = Debug('cypress:server:browsers:cd-command-queue')

type CommandReturn<T extends CdpCommand> = ProtocolMapping.Commands[T]['returnType']

export type Command<T extends CdpCommand> = {
command: T
params?: object
deferred: DeferredPromise<CommandReturn<T>>
sessionId?: string
}

export class CDPCommandQueue {
private queue: Command<any>[] = []

public get entries () {
return [...this.queue]
}

public add <TCmd extends CdpCommand> (
command: TCmd,
params: ProtocolMapping.Commands[TCmd]['paramsType'][0],
sessionId?: string,
): Promise<CommandReturn<TCmd>> {
debug('enqueing command %s', command)
debugVerbose('enqueing command %s with params %o', command, params)

const deferred = pDefer<CommandReturn<TCmd>>()

const commandPackage: Command<TCmd> = {
command,
params,
deferred,
sessionId,
}

this.queue.push(commandPackage)

debug('Command enqueued; new length: %d', this.queue.length)
debugVerbose('Queue Contents: %O', this.queue)

return deferred.promise
}

public clear () {
debug('clearing command queue')
this.queue = []
}

public extract<T extends CdpCommand> (search: Partial<Command<T>>): Command<T> | undefined {
// this should find, remove, and return if found a given command

const index = this.queue.findIndex((enqueued) => {
for (let k of Object.keys(search)) {
if (search[k] !== enqueued[k]) {
return false
}
}

return true
})

debug('extracting %o from commands at index %d', search, index)

if (index === -1) {
return undefined
}

const [extracted] = this.queue.splice(index, 1)

return extracted
}

public shift () {
return this.queue.shift()
}

public unshift (value: Command<any>) {
return this.queue.unshift(value)
}
}
Loading
Loading