diff --git a/cli/tests/unit/serve_test.ts b/cli/tests/unit/serve_test.ts index 0f97e17b8549dd..c26d3f7516edae 100644 --- a/cli/tests/unit/serve_test.ts +++ b/cli/tests/unit/serve_test.ts @@ -10,6 +10,7 @@ import { assertThrows, Deferred, deferred, + execCode, fail, } from "./test_util.ts"; @@ -56,6 +57,21 @@ Deno.test(async function httpServerShutsDownPortBeforeResolving() { listener!.close(); }); +Deno.test( + { permissions: { read: true, run: true } }, + async function httpServerUnref() { + const [statusCode, _output] = await execCode(` + async function main() { + const server = Deno.serve({ port: 4501, handler: () => null }); + server.unref(); + await server.finished; // This doesn't block the program from exiting + } + main(); + `); + assertEquals(statusCode, 0); + }, +); + Deno.test(async function httpServerCanResolveHostnames() { const ac = new AbortController(); const listeningPromise = deferred(); diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index c0c0d16ad8cd26..70d7ef7c4f21c3 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -1312,7 +1312,19 @@ declare namespace Deno { * the signal passed to {@linkcode ServeOptions.signal}. */ finished: Promise; + + /** + * Make the server block the event loop from finishing. + * + * Note: the server blocks the event loop from finishing by default. + * This method is only meaningful after `.unref()` is called. + */ + ref(): void; + + /** Make the server not block the event loop from finishing. */ + unref(): void; } + /** **UNSTABLE**: New API, yet to be vetted. * * Serves HTTP requests with the given handler. diff --git a/ext/http/00_serve.js b/ext/http/00_serve.js index 7186da1fedb06f..2fb36d044eaf67 100644 --- a/ext/http/00_serve.js +++ b/ext/http/00_serve.js @@ -43,6 +43,7 @@ const { SetPrototypeAdd, SetPrototypeDelete, Symbol, + SymbolFor, TypeError, Uint8Array, Uint8ArrayPrototype, @@ -660,13 +661,22 @@ function serve(arg1, arg2) { onListen({ port: listenOpts.port }); + let ref = true; + let currentPromise = null; + const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); + // Run the server const finished = (async () => { while (true) { const rid = context.serverRid; let req; try { - req = await op_http_wait(rid); + currentPromise = op_http_wait(rid); + if (!ref) { + core.unrefOp(currentPromise[promiseIdSymbol]); + } + req = await currentPromise; + currentPromise = null; } catch (error) { if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) { break; @@ -691,7 +701,21 @@ function serve(arg1, arg2) { } })(); - return { finished }; + return { + finished, + ref() { + ref = true; + if (currentPromise) { + core.refOp(currentPromise[promiseIdSymbol]); + } + }, + unref() { + ref = false; + if (currentPromise) { + core.unrefOp(currentPromise[promiseIdSymbol]); + } + }, + }; } internals.addTrailers = addTrailers;