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:
parent
2c17045aa8
commit
2187c11e5d
2 changed files with 50 additions and 2 deletions
|
@ -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();
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue