mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-24 07:59:12 -05:00
perf: reduce the initial timeout for proxy connections, make configurable (#31283)
* use cached preflight response on subsequent cloud requests that require a preflight * introduce env var to skip initial proxy-api request * rename env to CYPRESS_INTERNAL prefix * change noproxy timeout default to 5 seconds * fix * rm internal * changelog * Update packages/server/test/unit/cloud/api/api_spec.js Co-authored-by: Jennifer Shehane <jennifer@cypress.io> * Update packages/server/lib/cloud/api/index.ts Co-authored-by: Bill Glesias <bglesias@gmail.com> * fix ts --------- Co-authored-by: Jennifer Shehane <jennifer@cypress.io> Co-authored-by: Bill Glesias <bglesias@gmail.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
|
||||
_Released 4/22/2025 (PENDING)_
|
||||
|
||||
**Performance:**
|
||||
|
||||
- Reduced the initial timeout for the preflight API request to determine proxy conditions from sixty seconds to five, and made this timeout duration configurable with the `CYPRESS_INITIAL_PREFLIGHT_TIMEOUT` environment variable. Addresses [#28423](https://github.com/cypress-io/cypress/issues/28423). Addressed in [#31283](https://github.com/cypress-io/cypress/pull/31283).
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- The [`cy.press()`](http://on.cypress.io/api/press) command no longer errors when used in specs subsequent to the first spec in run mode. Fixes [#31466](https://github.com/cypress-io/cypress/issues/31466).
|
||||
|
||||
@@ -6,14 +6,19 @@ const request = require('@cypress/request-promise')
|
||||
const humanInterval = require('human-interval')
|
||||
|
||||
const RequestErrors = require('@cypress/request-promise/errors')
|
||||
const { agent } = require('@packages/network')
|
||||
|
||||
const pkg = require('@packages/root')
|
||||
|
||||
const machineId = require('../machine_id')
|
||||
const errors = require('../../errors')
|
||||
const { apiUrl, apiRoutes, makeRoutes } = require('../routes')
|
||||
|
||||
import Bluebird from 'bluebird'
|
||||
|
||||
import type { AfterSpecDurations } from '@packages/types'
|
||||
import { agent } from '@packages/network'
|
||||
import type { CombinedAgent } from '@packages/network/lib/agent'
|
||||
|
||||
import { apiUrl, apiRoutes, makeRoutes } from '../routes'
|
||||
import { getText } from '../../util/status_code'
|
||||
import * as enc from '../encryption'
|
||||
import getEnvInformationForProjectRoot from '../environment'
|
||||
@@ -22,7 +27,7 @@ import type { OptionsWithUrl } from 'request-promise'
|
||||
import { fs } from '../../util/fs'
|
||||
import ProtocolManager from '../protocol'
|
||||
import type { ProjectBase } from '../../project-base'
|
||||
import type { AfterSpecDurations } from '@packages/types'
|
||||
|
||||
import { PUBLIC_KEY_VERSION } from '../constants'
|
||||
|
||||
// axios implementation disabled until proxy issues can be diagnosed/fixed
|
||||
@@ -235,6 +240,16 @@ const isRetriableError = (err) => {
|
||||
(err.statusCode == null)
|
||||
}
|
||||
|
||||
function noProxyPreflightTimeout (): number {
|
||||
try {
|
||||
const timeoutFromEnv = Number(process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT)
|
||||
|
||||
return isNaN(timeoutFromEnv) ? 5000 : timeoutFromEnv
|
||||
} catch (e: unknown) {
|
||||
return 5000
|
||||
}
|
||||
}
|
||||
|
||||
export type CreateRunOptions = {
|
||||
projectRoot: string
|
||||
ci: {
|
||||
@@ -307,8 +322,21 @@ type UpdateInstanceArtifactsOptions = {
|
||||
instanceId: string
|
||||
timeout?: number
|
||||
}
|
||||
interface DefaultPreflightResult {
|
||||
encrypt: true
|
||||
}
|
||||
|
||||
let preflightResult = {
|
||||
interface PreflightWarning {
|
||||
message: string
|
||||
}
|
||||
|
||||
interface CachedPreflightResult {
|
||||
encrypt: boolean
|
||||
apiUrl: string
|
||||
warnings?: PreflightWarning[]
|
||||
}
|
||||
|
||||
let preflightResult: DefaultPreflightResult | CachedPreflightResult = {
|
||||
encrypt: true,
|
||||
}
|
||||
|
||||
@@ -598,14 +626,13 @@ export default {
|
||||
|
||||
sendPreflight (preflightInfo) {
|
||||
return retryWithBackoff(async (attemptIndex) => {
|
||||
const { timeout, projectRoot } = preflightInfo
|
||||
|
||||
preflightInfo = _.omit(preflightInfo, 'timeout', 'projectRoot')
|
||||
const { projectRoot, timeout, ...preflightRequestBody } = preflightInfo
|
||||
|
||||
const preflightBaseProxy = apiUrl.replace('api', 'api-proxy')
|
||||
|
||||
const envInformation = await getEnvInformationForProjectRoot(projectRoot, process.pid.toString())
|
||||
const makeReq = ({ baseUrl, agent }) => {
|
||||
|
||||
const makeReq = (baseUrl: string, agent: CombinedAgent | null, timeout: number) => {
|
||||
return rp.post({
|
||||
url: `${baseUrl}preflight`,
|
||||
body: {
|
||||
@@ -613,13 +640,13 @@ export default {
|
||||
envUrl: envInformation.envUrl,
|
||||
dependencies: envInformation.dependencies,
|
||||
errors: envInformation.errors,
|
||||
...preflightInfo,
|
||||
...preflightRequestBody,
|
||||
},
|
||||
headers: {
|
||||
'x-route-version': '1',
|
||||
'x-cypress-request-attempt': attemptIndex,
|
||||
},
|
||||
timeout: timeout ?? SIXTY_SECONDS,
|
||||
timeout,
|
||||
json: true,
|
||||
encrypt: 'always',
|
||||
agent,
|
||||
@@ -631,14 +658,19 @@ export default {
|
||||
}
|
||||
|
||||
const postReqs = async () => {
|
||||
return makeReq({ baseUrl: preflightBaseProxy, agent: null })
|
||||
.catch((err) => {
|
||||
if (err.statusCode === 412) {
|
||||
throw err
|
||||
}
|
||||
const initialPreflightTimeout = noProxyPreflightTimeout()
|
||||
|
||||
return makeReq({ baseUrl: apiUrl, agent })
|
||||
})
|
||||
if (initialPreflightTimeout >= 0) {
|
||||
try {
|
||||
return await makeReq(preflightBaseProxy, null, initialPreflightTimeout)
|
||||
} catch (err) {
|
||||
if (err.statusCode === 412) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return makeReq(apiUrl, agent, timeout)
|
||||
}
|
||||
|
||||
const result = await postReqs()
|
||||
|
||||
@@ -2,7 +2,8 @@ import _ from 'lodash'
|
||||
import UrlParse from 'url-parse'
|
||||
|
||||
const app_config = require('../../config/app.json')
|
||||
const apiUrl = app_config[process.env.CYPRESS_CONFIG_ENV || process.env.CYPRESS_INTERNAL_ENV || 'development'].api_url
|
||||
|
||||
export const apiUrl = app_config[process.env.CYPRESS_CONFIG_ENV || process.env.CYPRESS_INTERNAL_ENV || 'development'].api_url
|
||||
|
||||
const CLOUD_ENDPOINTS = {
|
||||
api: '',
|
||||
@@ -40,7 +41,7 @@ const parseArgs = function (url, args: any[] = []) {
|
||||
return url
|
||||
}
|
||||
|
||||
const makeRoutes = (baseUrl: string, routes: typeof CLOUD_ENDPOINTS) => {
|
||||
const _makeRoutes = (baseUrl: string, routes: typeof CLOUD_ENDPOINTS) => {
|
||||
return _.reduce(routes, (memo, value, key) => {
|
||||
memo[key] = function (...args: any[]) {
|
||||
let url = new UrlParse(baseUrl, true)
|
||||
@@ -60,10 +61,6 @@ const makeRoutes = (baseUrl: string, routes: typeof CLOUD_ENDPOINTS) => {
|
||||
}, {} as Record<keyof typeof CLOUD_ENDPOINTS, (...args: any[]) => string>)
|
||||
}
|
||||
|
||||
const apiRoutes = makeRoutes(apiUrl, CLOUD_ENDPOINTS)
|
||||
export const apiRoutes = _makeRoutes(apiUrl, CLOUD_ENDPOINTS)
|
||||
|
||||
module.exports = {
|
||||
apiUrl,
|
||||
apiRoutes,
|
||||
makeRoutes: (baseUrl) => makeRoutes(baseUrl, CLOUD_ENDPOINTS),
|
||||
}
|
||||
export const makeRoutes = (baseUrl) => _makeRoutes(baseUrl, CLOUD_ENDPOINTS)
|
||||
|
||||
@@ -245,6 +245,8 @@ describe('lib/cloud/api', () => {
|
||||
require('../../../../lib/cloud/encryption')
|
||||
}, module)
|
||||
}
|
||||
|
||||
prodApi.resetPreflightResult()
|
||||
})
|
||||
|
||||
it('POST /preflight to proxy. returns encryption', () => {
|
||||
@@ -323,12 +325,75 @@ describe('lib/cloud/api', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sets timeout to 60 seconds', () => {
|
||||
it('sets timeout to 5 seconds when no CYPRESS_INITIAL_PREFLIGHT_TIMEOUT env is set', () => {
|
||||
sinon.stub(api.rp, 'post').resolves({})
|
||||
|
||||
return api.sendPreflight({})
|
||||
.then(() => {
|
||||
expect(api.rp.post).to.be.calledWithMatch({ timeout: 60000 })
|
||||
expect(api.rp.post).to.be.calledWithMatch({ timeout: 5000 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when CYPRESS_INITIAL_PREFLIGHT_TIMEOUT env is set to a negative number', () => {
|
||||
const configuredTimeout = -1
|
||||
let prevEnv
|
||||
|
||||
beforeEach(() => {
|
||||
prevEnv = process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT
|
||||
process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT = configuredTimeout
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT = prevEnv
|
||||
})
|
||||
|
||||
it('skips the no-agent preflight request', () => {
|
||||
preflightNock(API_PROD_PROXY_BASEURL)
|
||||
.replyWithError('should not be called')
|
||||
|
||||
preflightNock(API_PROD_BASEURL)
|
||||
.reply(200, decryptReqBodyAndRespond({
|
||||
reqBody: {
|
||||
envUrl: 'https://some.server.com',
|
||||
dependencies: {},
|
||||
errors: [],
|
||||
apiUrl: 'https://api.cypress.io/',
|
||||
projectId: 'abc123',
|
||||
},
|
||||
resBody: {
|
||||
encrypt: true,
|
||||
apiUrl: `${API_PROD_BASEURL}/`,
|
||||
},
|
||||
}))
|
||||
|
||||
return prodApi.sendPreflight({ projectId: 'abc123' })
|
||||
.then((ret) => {
|
||||
expect(ret).to.deep.eq({ encrypt: true, apiUrl: `${API_PROD_BASEURL}/` })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when CYPRESS_INITIAL_PREFLIGHT_TIMEOUT env is set to a positive number', () => {
|
||||
const configuredTimeout = 10000
|
||||
let prevEnv
|
||||
|
||||
beforeEach(() => {
|
||||
prevEnv = process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT
|
||||
process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT = configuredTimeout
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env.CYPRESS_INITIAL_PREFLIGHT_TIMEOUT = prevEnv
|
||||
api.rp.post.restore()
|
||||
})
|
||||
|
||||
it('makes the initial request with the number set in the env', () => {
|
||||
sinon.stub(api.rp, 'post').resolves({})
|
||||
|
||||
return api.sendPreflight({})
|
||||
.then(() => {
|
||||
expect(api.rp.post).to.be.calledWithMatch({ timeout: configuredTimeout })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user