diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 8b526c001004c5..9f21daeff16bfd 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -2680,6 +2680,8 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1, try { headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse); } catch (err) { + if (self.ownsFd) + tryClose(fd); self.destroy(err); return; } @@ -2693,6 +2695,8 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1, const ret = self[kHandle].respond(headersList, streamOptions); if (ret < 0) { + if (self.ownsFd) + tryClose(fd); self.destroy(new NghttpError(ret)); return; } diff --git a/test/parallel/test-http2-respond-file-fd-leak.js b/test/parallel/test-http2-respond-file-fd-leak.js new file mode 100644 index 00000000000000..45e12185008a45 --- /dev/null +++ b/test/parallel/test-http2-respond-file-fd-leak.js @@ -0,0 +1,51 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const http2 = require('http2'); +const fs = require('fs'); + +const fname = fixtures.path('elipses.txt'); + +const server = http2.createServer(); + +server.on('stream', common.mustCall((stream) => { + const originalClose = fs.close; + let fdClosed = false; + + fs.close = common.mustCall(function(fd, cb) { + fdClosed = true; + return originalClose.apply(this, arguments); + }); + + const headers = { + ':method': 'GET', + 'content-type': 'text/plain' + }; + + stream.respondWithFile(fname, headers); + + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_HTTP2_INVALID_PSEUDOHEADER'); + })); + + stream.on('close', common.mustCall(() => { + fs.close = originalClose; + assert.strictEqual(fdClosed, true); + })); +})); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}`); + const req = client.request(); + + req.on('close', common.mustCall(() => { + client.close(); + server.close(); + })); + + req.on('error', common.mustCall()); + req.end(); +}));