1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

fix(ext/http): resource leak on HttpConn.close() (#11805)

This commit adds tracking of resources that are related
to "HttpConn" so they can be closed automatically
when closing the connection.
This commit is contained in:
Bartek Iwańczuk 2021-08-23 16:15:59 +02:00 committed by GitHub
parent 2c17045aa8
commit 2187c11e5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 2 deletions

View file

@ -815,3 +815,27 @@ unitTest(
}
},
);
// https://github.com/denoland/deno/issues/11743
unitTest(
{ perms: { net: true } },
async function httpServerDoesntLeakResources() {
const listener = Deno.listen({ port: 4505 });
const [conn, clientConn] = await Promise.all([
listener.accept(),
Deno.connect({ port: 4505 }),
]);
const httpConn = Deno.serveHttp(conn);
await Promise.all([
httpConn.nextRequest(),
clientConn.write(new TextEncoder().encode(
`GET / HTTP/1.1\r\nHost: 127.0.0.1:4505\r\n\r\n`,
)),
]);
httpConn.close();
listener.close();
clientConn.close();
},
);

View file

@ -24,6 +24,10 @@
ArrayPrototypePush,
ArrayPrototypeSome,
Promise,
Set,
SetPrototypeAdd,
SetPrototypeDelete,
SetPrototypeValues,
StringPrototypeIncludes,
StringPrototypeToLowerCase,
StringPrototypeSplit,
@ -38,6 +42,11 @@
class HttpConn {
#rid = 0;
// This set holds resource ids of resources
// that were created during lifecycle of this request.
// When the connection is closed these resources should be closed
// as well.
managedResources = new Set();
constructor(rid) {
this.#rid = rid;
@ -85,7 +94,8 @@
/** @type {ReadableStream<Uint8Array> | undefined} */
let body = null;
if (typeof requestRid === "number") {
body = createRequestBodyStream(requestRid);
SetPrototypeAdd(this.managedResources, requestRid);
body = createRequestBodyStream(this, requestRid);
}
const innerRequest = newInnerRequest(
@ -97,6 +107,7 @@
const signal = abortSignal.newSignal();
const request = fromInnerRequest(innerRequest, signal, "immutable");
SetPrototypeAdd(this.managedResources, responseSenderRid);
const respondWith = createRespondWith(
this,
responseSenderRid,
@ -108,6 +119,13 @@
/** @returns {void} */
close() {
for (const rid of SetPrototypeValues(this.managedResources)) {
try {
core.close(rid);
} catch (_e) {
// pass, might have already been closed
}
}
core.close(this.#rid);
}
@ -178,6 +196,7 @@
respBody = new Uint8Array(0);
}
SetPrototypeDelete(httpConn.managedResources, responseSenderRid);
let responseBodyRid;
try {
responseBodyRid = await core.opAsync("op_http_response", [
@ -200,6 +219,7 @@
// If `respond` returns a responseBodyRid, we should stream the body
// to that resource.
if (responseBodyRid !== null) {
SetPrototypeAdd(httpConn.managedResources, responseBodyRid);
try {
if (respBody === null || !(respBody instanceof ReadableStream)) {
throw new TypeError("Unreachable");
@ -231,6 +251,7 @@
} finally {
// Once all chunks are sent, and the request body is closed, we can
// close the response body.
SetPrototypeDelete(httpConn.managedResources, responseBodyRid);
try {
await core.opAsync("op_http_response_close", responseBodyRid);
} catch { /* pass */ }
@ -280,7 +301,7 @@
};
}
function createRequestBodyStream(requestRid) {
function createRequestBodyStream(httpConn, requestRid) {
return new ReadableStream({
type: "bytes",
async pull(controller) {
@ -298,6 +319,7 @@
} else {
// We have reached the end of the body, so we close the stream.
controller.close();
SetPrototypeDelete(httpConn.managedResources, requestRid);
core.close(requestRid);
}
} catch (err) {
@ -305,10 +327,12 @@
// error.
controller.error(err);
controller.close();
SetPrototypeDelete(httpConn.managedResources, requestRid);
core.close(requestRid);
}
},
cancel() {
SetPrototypeDelete(httpConn.managedResources, requestRid);
core.close(requestRid);
},
});