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