|
| 1 | +'use strict' |
| 2 | + |
| 3 | +const { test } = require('node:test') |
| 4 | +const assert = require('node:assert') |
| 5 | +const { once } = require('node:events') |
| 6 | +const { createServer } = require('node:http2') |
| 7 | + |
| 8 | +const { Client } = require('..') |
| 9 | +const { kQueue } = require('../lib/core/symbols') |
| 10 | + |
| 11 | +// Regression test for https://github.com/nodejs/undici/issues/5404. |
| 12 | +// HTTP/2 streams can complete out of order. Completing the second stream first |
| 13 | +// must not clear the first request's queue slot; otherwise destroying the |
| 14 | +// client can lose or mis-error the still-running request. |
| 15 | +test('h2: out-of-order completion preserves running requests during destroy', async () => { |
| 16 | + const server = createServer() |
| 17 | + let firstStream |
| 18 | + |
| 19 | + server.on('sessionError', () => {}) |
| 20 | + server.on('stream', (stream, headers) => { |
| 21 | + switch (headers[':path']) { |
| 22 | + case '/first': |
| 23 | + firstStream = stream |
| 24 | + break |
| 25 | + case '/second': |
| 26 | + stream.respond({ ':status': 200 }) |
| 27 | + stream.end('second') |
| 28 | + break |
| 29 | + case '/third': |
| 30 | + stream.respond({ ':status': 200 }) |
| 31 | + stream.end('third') |
| 32 | + break |
| 33 | + default: |
| 34 | + stream.respond({ ':status': 404 }) |
| 35 | + stream.end() |
| 36 | + } |
| 37 | + }) |
| 38 | + |
| 39 | + await once(server.listen(0), 'listening') |
| 40 | + |
| 41 | + const client = new Client(`http://localhost:${server.address().port}`, { |
| 42 | + allowH2: true, |
| 43 | + useH2c: true, |
| 44 | + maxConcurrentStreams: 2 |
| 45 | + }) |
| 46 | + |
| 47 | + try { |
| 48 | + const first = client.request({ path: '/first', method: 'GET' }) |
| 49 | + const firstError = first.then( |
| 50 | + () => null, |
| 51 | + err => err |
| 52 | + ) |
| 53 | + |
| 54 | + const second = await client.request({ path: '/second', method: 'GET' }) |
| 55 | + assert.strictEqual(await second.body.text(), 'second') |
| 56 | + |
| 57 | + const third = await client.request({ path: '/third', method: 'GET' }) |
| 58 | + assert.strictEqual(await third.body.text(), 'third') |
| 59 | + |
| 60 | + assert.strictEqual(firstStream.destroyed, false) |
| 61 | + assert.deepStrictEqual(client[kQueue].map(request => request?.path), ['/first']) |
| 62 | + |
| 63 | + await client.destroy(new Error('boom')) |
| 64 | + assert.strictEqual((await firstError).message, 'boom') |
| 65 | + } finally { |
| 66 | + await client.destroy().catch(() => {}) |
| 67 | + server.close() |
| 68 | + } |
| 69 | +}) |
0 commit comments