Skip to content

fix: Fix connection and timeout handling issues in high concurrency scenarios for undici #4094

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

Open
wants to merge 1 commit into
base: release/v6.21.2
Choose a base branch
from
Open
Changes from all commits
Commits
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
48 changes: 47 additions & 1 deletion lib/web/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,10 @@ function isomorphicEncode (input) {
return input
}

const sleep = (ms) => {
return new Promise((resolve) => setTimeout(() => resolve({ value: false }), ms))
}

/**
* @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
* @see https://streams.spec.whatwg.org/#read-loop
Expand All @@ -1107,9 +1111,51 @@ function isomorphicEncode (input) {
async function readAllBytes (reader) {
const bytes = []
let byteLength = 0
let times = 0

const timesInfo = []
const timeout = 300000
const startTime = Date.now()

while (true) {
const { done, value: chunk } = await reader.read()
times++

if (Date.now() - startTime > timeout) {
throw new Error(`Timeout after ${timeout}ms while reading response body`)
}

const readPromise = reader.read()
const timeoutPromise = sleep(timeout)

const result = await Promise.race([readPromise, timeoutPromise])

timesInfo.push({
time: new Date().toISOString(),
times,
length: byteLength
})

if (result && result.value === false) {
try {
const waitingReqPromimse = reader.readRequests?.[0]?.promise
let waitingReqStatus = 'none'

if (waitingReqPromimse) {
waitingReqStatus = await Promise.race([
waitingReqPromimse.then(() => 'fulfilled').catch(() => 'rejected'),
new Promise(resolve => setTimeout(() => resolve('pending'), timeout))
])
}

if (waitingReqStatus === 'pending') {
throw new Error('Stream read operation is stuck in pending state')
}
} catch (e) {
throw new Error(`Error while checking read request status: ${e.message}`)
}
}

const { done, value: chunk } = result

if (done) {
// 1. Call successSteps with bytes.
Expand Down