From d642e9a0c3ce63b8025bb9bbbd9c452d3c9a966f Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Wed, 12 Aug 2026 17:22:57 +0700 Subject: [PATCH 1/4] feat: add exp 429 retry --- packages/webdriver/src/request/index.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/webdriver/src/request/index.ts b/packages/webdriver/src/request/index.ts index dd5bb292e1a..fded6b7ee0a 100644 --- a/packages/webdriver/src/request/index.ts +++ b/packages/webdriver/src/request/index.ts @@ -34,6 +34,7 @@ export const COMMANDS_WITHOUT_RETRY = [ findCommandPathByName('performActions'), ] const MAX_RETRY_TIMEOUT = 100 // 100ms +const SESSION_429_RETRY_BASE_DELAY = 5000 // 5s const DEFAULT_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', 'Connection': 'keep-alive', @@ -207,6 +208,21 @@ export default abstract class WebDriverRequest extends EventEmitter { this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount }) log.warn(msg) log.info(`Retrying ${retryCount}/${totalRetryCount}`) + + const isSessionCreation = + fullRequestOptions.method === 'POST' && + (fullRequestOptions.url as URL)?.pathname?.endsWith('/session') + + if (isSessionCreation && !(response instanceof Error) && response.statusCode === 429) { + const delay = Math.round(SESSION_429_RETRY_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000) + + log.info(`Session creation rate-limited (429), retrying in ${delay}ms`) + + return new Promise(resolve => setTimeout(resolve, delay)).then( + () => this._request(fullRequestOptions, transformResponse, customWdRequestAgent, totalRetryCount, retryCount) + ) + } + return this._request(fullRequestOptions, transformResponse, customWdRequestAgent, totalRetryCount, retryCount) } From 6c50fca20bd4b34ef2f2ed18e022647782485035 Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Thu, 13 Aug 2026 18:39:32 +0700 Subject: [PATCH 2/4] fix: review --- packages/webdriver/src/request/index.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/webdriver/src/request/index.ts b/packages/webdriver/src/request/index.ts index fded6b7ee0a..493bf66bb04 100644 --- a/packages/webdriver/src/request/index.ts +++ b/packages/webdriver/src/request/index.ts @@ -34,7 +34,7 @@ export const COMMANDS_WITHOUT_RETRY = [ findCommandPathByName('performActions'), ] const MAX_RETRY_TIMEOUT = 100 // 100ms -const SESSION_429_RETRY_BASE_DELAY = 5000 // 5s +const RETRY_429_BASE_DELAY = 5000 // 5s const DEFAULT_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', 'Connection': 'keep-alive', @@ -209,14 +209,10 @@ export default abstract class WebDriverRequest extends EventEmitter { log.warn(msg) log.info(`Retrying ${retryCount}/${totalRetryCount}`) - const isSessionCreation = - fullRequestOptions.method === 'POST' && - (fullRequestOptions.url as URL)?.pathname?.endsWith('/session') + if (!(response instanceof Error) && response.statusCode === 429) { + const delay = Math.round(RETRY_429_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000) - if (isSessionCreation && !(response instanceof Error) && response.statusCode === 429) { - const delay = Math.round(SESSION_429_RETRY_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000) - - log.info(`Session creation rate-limited (429), retrying in ${delay}ms`) + log.debug(`Request rate-limited (429), retrying in ${delay}ms`) return new Promise(resolve => setTimeout(resolve, delay)).then( () => this._request(fullRequestOptions, transformResponse, customWdRequestAgent, totalRetryCount, retryCount) From fef8500dde2becf22dceb6c6201344caf644411e Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Fri, 14 Aug 2026 00:59:13 +0700 Subject: [PATCH 3/4] fix: review 2 --- packages/webdriver/src/request/index.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/webdriver/src/request/index.ts b/packages/webdriver/src/request/index.ts index 493bf66bb04..0de570dc5e5 100644 --- a/packages/webdriver/src/request/index.ts +++ b/packages/webdriver/src/request/index.ts @@ -35,6 +35,7 @@ export const COMMANDS_WITHOUT_RETRY = [ ] const MAX_RETRY_TIMEOUT = 100 // 100ms const RETRY_429_BASE_DELAY = 5000 // 5s +const RETRY_429_MAX_COUNT = 5 const DEFAULT_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', 'Connection': 'keep-alive', @@ -191,12 +192,14 @@ export default abstract class WebDriverRequest extends EventEmitter { * @param {Error} error error object that causes the retry * @param {string} msg message that is being shown as warning to user */ - const retry = (error: Error, msg: string) => { + const retry = async (error: Error, msg: string) => { /** * stop retrying if totalRetryCount was exceeded or there is no reason to * retry, e.g. if sessionId is invalid */ - if (retryCount >= totalRetryCount || error.message.includes('invalid session id')) { + const is429 = !(response instanceof Error) && response.statusCode === 429 + const effectiveRetryCount = is429 ? Math.min(totalRetryCount, RETRY_429_MAX_COUNT) : totalRetryCount + if (retryCount >= effectiveRetryCount || error.message.includes('invalid session id')) { log.error(`Request failed with status ${response.statusCode} due to ${error}`) this.emit('response', { error }) this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount }) @@ -209,14 +212,10 @@ export default abstract class WebDriverRequest extends EventEmitter { log.warn(msg) log.info(`Retrying ${retryCount}/${totalRetryCount}`) - if (!(response instanceof Error) && response.statusCode === 429) { + if (is429) { const delay = Math.round(RETRY_429_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000) - log.debug(`Request rate-limited (429), retrying in ${delay}ms`) - - return new Promise(resolve => setTimeout(resolve, delay)).then( - () => this._request(fullRequestOptions, transformResponse, customWdRequestAgent, totalRetryCount, retryCount) - ) + await new Promise(resolve => setTimeout(resolve, delay)) } return this._request(fullRequestOptions, transformResponse, customWdRequestAgent, totalRetryCount, retryCount) From a16b25fcd5f40f105d532a2bba54c95404fee3c5 Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Fri, 14 Aug 2026 17:45:33 +0700 Subject: [PATCH 4/4] fix: review 3 --- packages/webdriver/src/request/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/webdriver/src/request/index.ts b/packages/webdriver/src/request/index.ts index 0de570dc5e5..9dd4c56be62 100644 --- a/packages/webdriver/src/request/index.ts +++ b/packages/webdriver/src/request/index.ts @@ -35,7 +35,7 @@ export const COMMANDS_WITHOUT_RETRY = [ ] const MAX_RETRY_TIMEOUT = 100 // 100ms const RETRY_429_BASE_DELAY = 5000 // 5s -const RETRY_429_MAX_COUNT = 5 +const RETRY_429_MAX_DELAY = 30000 // 30s const DEFAULT_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', 'Connection': 'keep-alive', @@ -198,8 +198,7 @@ export default abstract class WebDriverRequest extends EventEmitter { * retry, e.g. if sessionId is invalid */ const is429 = !(response instanceof Error) && response.statusCode === 429 - const effectiveRetryCount = is429 ? Math.min(totalRetryCount, RETRY_429_MAX_COUNT) : totalRetryCount - if (retryCount >= effectiveRetryCount || error.message.includes('invalid session id')) { + if (retryCount >= totalRetryCount || error.message.includes('invalid session id')) { log.error(`Request failed with status ${response.statusCode} due to ${error}`) this.emit('response', { error }) this.emit('performance', { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount }) @@ -213,7 +212,7 @@ export default abstract class WebDriverRequest extends EventEmitter { log.info(`Retrying ${retryCount}/${totalRetryCount}`) if (is429) { - const delay = Math.round(RETRY_429_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000) + const delay = Math.min(Math.round(RETRY_429_BASE_DELAY * 2 ** (retryCount - 1) + Math.random() * 1000), RETRY_429_MAX_DELAY) log.debug(`Request rate-limited (429), retrying in ${delay}ms`) await new Promise(resolve => setTimeout(resolve, delay)) }