Repository: denoland/deno
Pull request: fix(cli/util): terminate watch file with sigint

Fix #30587 .

I've implemented 2 tests. The first one checks when a program is terminated while the watcher is waiting for file changes and the user interrupts the process. The second one checks when the code is running: the SIGINT should be intercepted and the process should quit.

Changed files:
filename: cli/util/file_watcher.rs
patch: @@ -15,6 +15,7 @@ use deno_core::futures::FutureExt;
 use deno_core::parking_lot::Mutex;
 use deno_lib::util::result::js_error_downcast_ref;
 use deno_runtime::fmt_errors::format_js_error;
+use deno_signals;
 use log::info;
 use notify::Error as NotifyError;
 use notify::RecommendedWatcher;
@@ -374,6 +375,9 @@ where
 
     select! {
       _ = receiver_future => {},
+      _ = deno_signals::ctrl_c() => {
+        return Ok(());
+      },
       _ = restart_rx.recv() => {
         print_after_restart();
         continue;
@@ -407,6 +411,9 @@ where
     // watched paths has changed.
     select! {
       _ = receiver_future => {},
+      _ = deno_signals::ctrl_c() => {
+        return Ok(());
+      },
       _ = restart_rx.recv() => {
         print_after_restart();
         continue;

filename: tests/unit/process_test.ts
patch: @@ -583,7 +583,7 @@ Deno.test(
     permissions: { run: true, read: true, write: true },
     ignore: Deno.build.os === "windows",
   },
-  async function non_existent_cwd(): Promise<void> {
+  async function nonExistentCwd(): Promise<void> {
     // @ts-ignore `Deno.run()` was soft-removed in Deno 2.
     const p = Deno.run({
       cmd: [
@@ -610,3 +610,70 @@ Deno.test(
     assertStringIncludes(stderr, "failed resolving cwd:");
   },
 );
+
+Deno.test(
+  {
+    permissions: { run: true, read: true, write: true },
+    ignore: Deno.build.os === "windows",
+  },
+  async function runWatchAndSigint(): Promise<void> {
+    const tempDir = await Deno.makeTempDir();
+    const tempFile = `${tempDir}/temp_watch_file.ts`;
+    await Deno.writeTextFile(tempFile, "console.log('watch test');");
+
+    // @ts-ignore `Deno.run()` was soft-removed in Deno 2.
+    const p = Deno.run({
+      cmd: ["deno", "run", "--watch", tempFile],
+      stdout: "piped",
+      stderr: "null",
+    });
+
+    Deno.kill(p.pid, "SIGINT");
+    const data = new Uint8Array(10);
+    const out = await p.stdout.read(data);
+    assertEquals(out, null);
+    p.stdout.close();
+    p.close();
+
+    await Deno.remove(tempFile);
+    await Deno.remove(tempDir);
+  },
+);
+
+Deno.test(
+  {
+    permissions: { run: true, read: true, write: true },
+    ignore: Deno.build.os === "windows",
+  },
+  async function runWatchWaitForSigint(): Promise<void> {
+    const tempDir = await Deno.makeTempDir();
+    const tempFile = `${tempDir}/temp_watch_file.ts`;
+    await Deno.writeTextFile(
+      tempFile,
+      `Deno.addSignalListener("SIGINT", () => {
+  console.log("SIGINT");
+  ac.abort();
+});
+
+Deno.serve({ signal: ac.signal }, () => new Response("Hello World"));
+`,
+    );
+
+    // @ts-ignore `Deno.run()` was soft-removed in Deno 2.
+    const p = Deno.run({
+      cmd: ["deno", "run", "--watch", tempFile],
+      stdout: "piped",
+      stderr: "null",
+    });
+
+    Deno.kill(p.pid, "SIGINT");
+    const data = new Uint8Array(10);
+    const out = await p.stdout.read(data);
+    assertEquals(out, null);
+    p.stdout.close();
+    p.close();
+
+    await Deno.remove(tempFile);
+    await Deno.remove(tempDir);
+  },
+);