Try to fix flakey socket hang up failures in stream cancel tests (#53318)

### What?

I think the flakiness from the new stream cancel tests is due to a uncaught error thrown from the priming. If it is during the priming, then we need to also explicitly check if we restart the test and reset the `streamable`.

### Why?

Flakey tests suck.

### How?

- Adds a `on('error', reject)` to catch the socket error and associate it with the test
- Explicitly checks for the `?write=` param to see if we're priming or getting results
This commit is contained in:
Justin Ridgewell 2023-07-28 18:59:24 -04:00 committed by GitHub
parent ce29de25e4
commit 3906374740
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 77 deletions

View file

@ -9,19 +9,19 @@ export async function GET(req: Request): Promise<Response> {
// This is so we don't confuse the request close with the connection close.
await req.text()
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
if (streamable) {
const old = streamable
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
if (write) {
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
}
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
const old = streamable!
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
}

View file

@ -11,19 +11,19 @@ export async function GET(req: Request): Promise<Response> {
// This is so we don't confuse the request close with the connection close.
await req.text()
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
if (streamable) {
const old = streamable
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
if (write) {
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
}
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
const old = streamable!
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
}

View file

@ -11,19 +11,20 @@ export default async function handler(req: Request): Promise<Response> {
// This is so we don't confuse the request close with the connection close.
await req.text()
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
if (streamable) {
const old = streamable
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
if (write) {
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
}
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
const old = streamable!
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
}

View file

@ -11,19 +11,19 @@ export default async function handler(req: Request): Promise<Response> {
// This is so we don't confuse the request close with the connection close.
await req.text()
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
if (streamable) {
const old = streamable
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
if (write) {
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
}
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
const s = (streamable = Streamable(+write!))
req.signal.onabort = () => {
s.abort()
}
return new Response(s.stream)
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
const old = streamable!
streamable = undefined
const i = await old.finished
return new Response(`${i}`)
}

View file

@ -15,25 +15,25 @@ export default function handler(
// Pages API requests have already consumed the body.
// This is so we don't confuse the request close with the connection close.
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
// The 2nd request should render the stats. We don't use a query param
// because edge rendering will create a different bundle for that.
if (readable) {
const old = readable
readable = undefined
return old.finished.then((i) => {
res.end(`${i}`)
if (write) {
const r = (readable = Readable(+write!))
res.on('close', () => {
r.abort()
})
return new Promise((resolve) => {
pipeline(r.stream, res, () => {
resolve()
res.end()
})
})
}
const write = new URL(req.url!, 'http://localhost/').searchParams.get('write')
const r = (readable = Readable(+write!))
res.on('close', () => {
r.abort()
})
return new Promise((resolve) => {
pipeline(r.stream, res, () => {
resolve()
res.end()
})
const old = readable!
readable = undefined
return old.finished.then((i) => {
res.end(`${i}`)
})
}

View file

@ -12,7 +12,7 @@ createNextDescribe(
jest.retryTimes(3)
function prime(url: string, noData?: boolean) {
return new Promise<void>((resolve) => {
return new Promise<void>((resolve, reject) => {
url = new URL(url, next.url).href
// There's a bug in node-fetch v2 where aborting the fetch will never abort
@ -29,6 +29,7 @@ createNextDescribe(
res.destroy()
resolve()
})
req.on('error', reject)
req.end()
if (noData) {
@ -58,9 +59,8 @@ createNextDescribe(
it('cancels stream making progress', async () => {
// If the stream is making regular progress, then we'll eventually hit
// the break because `res.destroyed` is true.
const url = path + '?write=25'
await prime(url)
const res = await next.fetch(url)
await prime(path + '?write=25')
const res = await next.fetch(path)
const i = +(await res.text())
expect(i).toBeWithin(1, 5)
}, 2500)
@ -68,9 +68,8 @@ createNextDescribe(
it('cancels stalled stream', async () => {
// If the stream is stalled, we'll never hit the `res.destroyed` break
// point, so this ensures we handle it with an out-of-band cancellation.
const url = path + '?write=1'
await prime(url)
const res = await next.fetch(url)
await prime(path + '?write=1')
const res = await next.fetch(path)
const i = +(await res.text())
expect(i).toBe(1)
}, 2500)
@ -78,9 +77,8 @@ createNextDescribe(
it('cancels stream that never sent data', async () => {
// If the client has never sent any data (including headers), then we
// haven't even established the response object yet.
const url = path + '?write=0'
await prime(url, true)
const res = await next.fetch(url)
await prime(path + '?write=0', true)
const res = await next.fetch(path)
const i = +(await res.text())
expect(i).toBe(0)
}, 2500)