0

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.

Luke Puplett
  • 42,091
  • 47
  • 181
  • 266

1 Answers1

1

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 }

jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • 1
    I was going to, I'm learning TS/JS and Deno, so I can't turn this around as fast as you (and thanks for your precious time) and I half expected someone would just know. Most of my reputation on SO comes from answering my own questions :) – Luke Puplett Nov 28 '22 at 15:58