Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions lib/dispatcher/client-h2.js
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,10 @@ function onHttp2SessionGoAway (errorCode, lastStreamID) {
client[kHTTP2Session] = null
}

if (this[kOpenStreams] === 0) {
unrefH2Session(this)
}

clearHttp2IdleTimeout(this)
clearNoStreamsTimeout(this)

Expand Down
43 changes: 42 additions & 1 deletion test/http2-max-concurrent-streams-zero-unref.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const { test } = require('node:test')
const { fork } = require('node:child_process')
const { once } = require('node:events')
const { createSecureServer } = require('node:http2')
const { createSecureServer, constants } = require('node:http2')
const { join } = require('node:path')
const { key, cert } = require('@metcoder95/https-pem')

Expand Down Expand Up @@ -86,3 +86,44 @@ test('a queued request keeps the process alive when the last stream closes', { t
t.assert.strictEqual(code, 0)
t.assert.deepStrictEqual(messages, ['warmed', 'queued', 'UND_ERR_HEADERS_TIMEOUT'])
})

// When the peer advertises SETTINGS_MAX_CONCURRENT_STREAMS = 0, requests are queued without a stream.
// When the peer sends GOAWAY, the detached session must be unref'd for the child to exit.
test('a session detached by GOAWAY while requests are queued does not keep the process alive', { timeout: 10000 }, async (t) => {
const server = createSecureServer({ key, cert })
t.after(() => server.close())

server.on('stream', (stream) => {
stream.on('error', () => {})
stream.respond({ ':status': 200 })
stream.end('ok')
})

let session
server.on('session', (s) => { session = s })

server.listen(0)
await once(server, 'listening')

const child = fork(join(__dirname, 'fixtures/h2-no-streams-client.js'), [String(server.address().port)])
t.after(() => child.kill())

const messages = []
child.on('message', (message) => {
messages.push(message)

if (message === 'warmed') {
try {
session.settings({ maxConcurrentStreams: 0 }, () => child.send('drained', () => {}))
} catch {}
} else if (message === 'queued') {
session.goaway(constants.NGHTTP2_NO_ERROR, 1)
}
})

const [code, signal] = await once(child, 'close')

t.assert.strictEqual(signal, null)
t.assert.strictEqual(code, 0)
t.assert.deepStrictEqual(messages, ['warmed', 'queued', 'settled'])
})
Loading