I'm setting up a new web project using Deno and oak.
I've passed an AbortSignal
into the listen
call and I'm listening for a SIGTERM
from the OS and calling abort, in case this is not built-in behaviour.
Similar to setups described here: Deno and Docker how to listen for the SIGTERM signal and close the server
Question: Upon abort, will the await listen(...)
call return immediately or after all remaining requests have completed?
If not then I guess I will need to accurately count concurrent requests using Atomics and wait until that counter drops to zero before ending the process.
Rather than rely on second hand information from someone else (which might not be correct), why not just do a test and find out for yourself (or review the source code)?
Here's a reproducible example which indicates that — when using Deno@1.28.2 with Oak@11.1.0 — the server gracefully shuts down: it still responds to a pending request even after the AbortSignal
is aborted:
so-74600368.ts
:
import {
Application,
type Context,
} from "https://deno.land/x/oak@v11.1.0/mod.ts";
import { delay } from "https://deno.land/std@0.166.0/async/delay.ts";
async function sendRequestAndLogResponseText(): Promise<void> {
try {
const response = await fetch("http://localhost:8000/");
if (!response.ok) {
throw new Error(`Response not OK (Status code: ${response.status})`);
}
const text = await response.text();
console.log(performance.now(), text);
} catch (ex) {
console.error(ex);
}
}
async function sendSquentialRequsets(numOfRequests: number): Promise<void> {
for (let i = 0; i < numOfRequests; i += 1) {
await sendRequestAndLogResponseText();
}
}
function printStartupMessage({ hostname, port, secure }: {
hostname: string;
port: number;
secure?: boolean;
}): void {
if (!hostname || hostname === "0.0.0.0") hostname = "localhost";
const address =
new URL(`http${secure ? "s" : ""}://${hostname}:${port}/`).href;
console.log(`Listening at ${address}`);
console.log("Use ctrl+c to stop");
}
async function main() {
const log = new Map<Context, boolean>();
const controller = new AbortController();
controller.signal.addEventListener("abort", () => {
console.log(performance.now(), "Abort method invoked");
});
const app = new Application();
app.use(async (ctx) => {
log.set(ctx, false);
if (log.size > 2) {
console.log(performance.now(), "Aborting");
controller.abort(new Error("Received third request. Aborting now."));
}
// A bit of artificial delay, to ensure that no unaccounted for latency
// might cause a non-deterministic/unexpected result:
await delay(300);
ctx.response.body = `Response OK: (#${log.size})`;
log.set(ctx, true);
});
app.addEventListener("listen", (ev) => {
console.log(performance.now(), "Server starting");
printStartupMessage(ev);
});
const listenerPromise = app.listen({
hostname: "localhost",
port: 8000,
signal: controller.signal,
})
.then(() => {
console.log(performance.now(), "Server stopped");
return { type: "server", ok: true };
})
.catch((reason) => ({ type: "server", ok: false, reason }));
const requestsPromise = sendSquentialRequsets(3)
.then(() => {
console.log(performance.now(), "All responses OK");
return { type: "requests", ok: true };
})
.catch((reason) => ({ type: "requests", ok: false, reason }));
const results = await Promise.allSettled([listenerPromise, requestsPromise]);
for (const result of results) console.log(result);
const allResponsesSent = [...log.values()].every(Boolean);
console.log({ allResponsesSent });
}
if (import.meta.main) main();
% deno --version
deno 1.28.2 (release, x86_64-apple-darwin)
v8 10.9.194.1
typescript 4.8.3
% deno run --allow-net=localhost so-74600368.ts
62 Server starting
Listening at http://127.0.0.1:8000/
Use ctrl+c to stop
378 Response OK: (#1)
682 Response OK: (#2)
682 Aborting
682 Abort method invoked
990 Server stopped
992 Response OK: (#3)
992 All responses OK
{ status: "fulfilled", value: { type: "server", ok: true } }
{ status: "fulfilled", value: { type: "requests", ok: true } }
{ allResponsesSent: true }