Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] signal to catch exit via deno run --watch #14424

Closed
qgates opened this issue Apr 28, 2022 · 8 comments
Closed

[FR] signal to catch exit via deno run --watch #14424

qgates opened this issue Apr 28, 2022 · 8 comments
Labels

Comments

@qgates
Copy link

qgates commented Apr 28, 2022

During development in a docker container network, the deno server is started with deno run --watch. If project files are saved when deno is servicing a request, db transaction etc. there is no opportunity to clean up and if a source file is changed the process is killed instantly and restarted.

I have tried the following:

globalThis.addEventListener("unload", () => { console.log('restart event') })

and also

Deno.addSignalListener("SIGUSR1", () => {   // tried all allowable signals
  console.log("received end signal")
})

But I can never capture deno going down. This is quite problematic during development/testing.

Suggested solution

An early suggestions for watch implementation mentioned here suggested the possibility to intercept SIGUSR1; that would seem a good solution, with the signal configurable. During watch, allow a configurable grace period (default 2s) before deno restarts, along with a console message to indicate 'Waiting for clean shutdown...'

@qgates qgates changed the title [FR] signal to catch restart via deno run --watch [FR] signal to catch shutdown via deno run --watch Apr 28, 2022
@qgates qgates changed the title [FR] signal to catch shutdown via deno run --watch [FR] signal to catch exit via deno run --watch Apr 28, 2022
@stale
Copy link

stale bot commented Jun 27, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jun 27, 2022
@stale stale bot closed this as completed Jul 6, 2022
@ghost
Copy link

ghost commented Aug 6, 2022

I'm on Deno 1.24 (Linux) and listening for the unload event is working for me, e.g. addEventListener("unload", ...).

Here's my shutdown.ts module that I use for an Oak server application that creates some temp files and database. When it restarts during development, I want it to clean that stuff up! The signal import is from https://deno.land/[email protected]/signal/mod.ts and __DEV__ is just a boolean that I set whenever a particular env var is equal to "development".

This covers everything I need and shuts down gracefully if I hit Ctrl+C, or if SIGTERM is sent (from Task Manager, for instance) and finally now with the addition of addEventListener("unload", ...), it handles development mode gracefully.

import { __DEV__ } from "../config.ts";
import { signal } from "../deps.ts";

export type AppShutdownReason = "ERROR" | "NORMAL" | "SIGNAL";
export type AppShutdownHandler = (reason: AppShutdownReason) => Promise<void>;

const SIGNALS = {
  darwin: ["SIGINT", "SIGTERM"] as Deno.Signal[],
  linux: ["SIGINT", "SIGTERM"] as Deno.Signal[],
  /** Only SIGINT (ctrl+c) and SIGBREAK (ctrl+break) are supported. */
  windows: ["SIGINT"] as Deno.Signal[],
};

const [listenSignalFirst, ...listenSignalRest] = SIGNALS[Deno.build.os];
const listener = signal(listenSignalFirst, ...listenSignalRest);

let shutdownHandler: AppShutdownHandler | undefined;
let shuttingDown = false;

export async function listenForShutdown(handler?: AppShutdownHandler) {
  shutdownHandler = handler;
  if (__DEV__) {
    addEventListener("unload", function handleDevShutdown() {
      onShutdown("SIGNAL");
    });
  }
  // CONSIDER: Should we try/catch errors while listening for shutdown signals?
  for await (const _ of listener) {
    await shutdown("SIGNAL");
  }
}

function onShutdown(reason: AppShutdownReason) {
  const handler = shutdownHandler;
  if (handler) {
    return handler(reason);
  }
  return Promise.resolve();
}

export async function shutdown(
  reason: AppShutdownReason = "NORMAL",
  code?: number,
) {
  if (shuttingDown) {
    return;
  }
  shuttingDown = true;
  const exitCode = code ?? (reason === "ERROR" ? 1 : 0);
  try {
    console.log("");
    console.log(`Shutting down...`);
    await onShutdown(reason).catch((err) => {
      console.error("Shutdown error.", err);
    });
    listener.dispose();
    console.log("Goodbye.");
  } catch (ex) {
    console.error(ex);
  } finally {
    if (__DEV__ && exitCode !== 0) {
      // Don't exit the process in development mode. Let the runner restart it.
      console.error(`Exit code: ${exitCode}`);
    } else {
      Deno.exit(exitCode);
    }
  }
}

@ghost
Copy link

ghost commented Aug 6, 2022

Just wanted to leave one more useful comment for anyone dealing with temp files in development...

If you set the TMPDIR env var before making any calls to Deno.makeTempDir or makeTempFile functions, then you can globally set where all temp files and directories will be created. Here's what I have in my config.ts file:

if (__DEV__) {
  Deno.env.set("TMPDIR", `${Deno.cwd()}/tmp`);
}

and now all temp files are created in a tmp folder local to my project.

@ghost
Copy link

ghost commented Aug 10, 2022

One small problem I have with this shutdown code: If my SIGINT handler kicks off any async functions or sets any timers, then the Deno watcher exits before my process does.

@stale
Copy link

stale bot commented Oct 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Oct 9, 2022
@stale stale bot closed this as completed Oct 18, 2022
@MrFoxPro
Copy link

MrFoxPro commented Aug 4, 2023

addEventListener('unload') doesn't work for me
Having to listen unix socket - every time when app restarts, I need to recreate file, otherwise it throws an error that it's already in use.

@chromakode
Copy link
Contributor

chromakode commented Sep 30, 2023

I've been searching for a way to flush to async data stores before restarting, and haven't been able to find one. The restart happens before promises resolve. A signal or event would be very useful. Would you please consider reopening this?

@cowboyd
Copy link

cowboyd commented Oct 12, 2023

One option is to close stdin and then wait (for a timeout period) for receive EOF from child's stdout

https://matklad.github.io/2023/10/11/unix-structured-concurrency.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants