1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix(ext/node): server.close() does graceful shutdown (#24184)

This commit is contained in:
Divy Srivastava 2024-06-14 06:38:50 +05:30 committed by GitHub
parent 368eb9073b
commit e19ee6eecc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 128 additions and 14 deletions

View file

@ -753,26 +753,52 @@ function serveHttpOn(context, addr, callback) {
PromisePrototypeCatch(callback(req), promiseErrorHandler); PromisePrototypeCatch(callback(req), promiseErrorHandler);
} }
try {
if (!context.closing && !context.closed) { if (!context.closing && !context.closed) {
context.closing = op_http_close(rid, false); context.closing = await op_http_close(rid, false);
context.close(); context.close();
} }
await context.closing; await context.closing;
} catch (error) {
if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)) {
return;
}
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
return;
}
throw error;
} finally {
context.close(); context.close();
context.closed = true; context.closed = true;
}
})(); })();
return { return {
addr, addr,
finished, finished,
async shutdown() { async shutdown() {
try {
if (!context.closing && !context.closed) { if (!context.closing && !context.closed) {
// Shut this HTTP server down gracefully // Shut this HTTP server down gracefully
context.closing = op_http_close(context.serverRid, true); context.closing = op_http_close(context.serverRid, true);
} }
await context.closing; await context.closing;
} catch (error) {
// The server was interrupted
if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)) {
return;
}
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
return;
}
throw error;
} finally {
context.closed = true; context.closed = true;
}
}, },
ref() { ref() {
ref = true; ref = true;

View file

@ -1775,8 +1775,12 @@ export class ServerImpl extends EventEmitter {
} }
if (listening && this.#ac) { if (listening && this.#ac) {
if (this.#server) {
this.#server.shutdown();
} else if (this.#ac) {
this.#ac.abort(); this.#ac.abort();
this.#ac = undefined; this.#ac = undefined;
}
} else { } else {
this.#serveDeferred!.resolve(); this.#serveDeferred!.resolve();
} }
@ -1785,6 +1789,26 @@ export class ServerImpl extends EventEmitter {
return this; return this;
} }
closeAllConnections() {
if (this.#hasClosed) {
return;
}
if (this.#ac) {
this.#ac.abort();
this.#ac = undefined;
}
}
closeIdleConnections() {
if (this.#hasClosed) {
return;
}
if (this.#server) {
this.#server.shutdown();
}
}
address() { address() {
return { return {
port: this.#addr.port, port: this.#addr.port,

View file

@ -2,6 +2,7 @@
import EventEmitter from "node:events"; import EventEmitter from "node:events";
import http, { type RequestOptions } from "node:http"; import http, { type RequestOptions } from "node:http";
import url from "node:url";
import https from "node:https"; import https from "node:https";
import net from "node:net"; import net from "node:net";
import { assert, assertEquals, fail } from "@std/assert/mod.ts"; import { assert, assertEquals, fail } from "@std/assert/mod.ts";
@ -1040,3 +1041,66 @@ Deno.test("[node/http] ServerResponse default status code 200", () => {
Deno.test("[node/http] maxHeaderSize is defined", () => { Deno.test("[node/http] maxHeaderSize is defined", () => {
assertEquals(http.maxHeaderSize, 16_384); assertEquals(http.maxHeaderSize, 16_384);
}); });
Deno.test("[node/http] server graceful close", async () => {
const server = http.createServer(function (_, response) {
response.writeHead(200, {});
response.end("ok");
server.close();
});
const { promise, resolve } = Promise.withResolvers<void>();
server.listen(0, function () {
// deno-lint-ignore no-explicit-any
const port = (server.address() as any).port;
const testURL = url.parse(
`http://localhost:${port}`,
);
http.request(testURL, function (response) {
assertEquals(response.statusCode, 200);
response.on("data", function () {});
response.on("end", function () {
resolve();
});
}).end();
});
await promise;
});
Deno.test("[node/http] server closeAllConnections shutdown", async () => {
const server = http.createServer((_req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
data: "Hello World!",
}));
});
server.listen(0);
const { promise, resolve } = Promise.withResolvers<void>();
setTimeout(() => {
server.close(() => resolve());
server.closeAllConnections();
}, 2000);
await promise;
});
Deno.test("[node/http] server closeIdleConnections shutdown", async () => {
const server = http.createServer({ keepAliveTimeout: 60000 }, (_req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
data: "Hello World!",
}));
});
server.listen(0);
const { promise, resolve } = Promise.withResolvers<void>();
setTimeout(() => {
server.close(() => resolve());
server.closeIdleConnections();
}, 2000);
await promise;
});