`node ace serve` leaves an orphaned supervisor behind, which brings the server back on the next file change

Aperta
#5,165 0 commenti 1 reazione 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 5 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
node.js, typescript
Ambito
backend, devtools

Direzione di ricerca

Start by reading the serve command’s staysAlive declaration and the supervisor behavior in @adonisjs/assembler that keeps watching after its child exits. Reproduce the issue by starting node ace.js serve --hmr, ending its parent shell, then changing a watched file; the report names no source file or test. Done means the orphaned supervisor exits or can be opted out, without disrupting normal watch mode.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Package version

7.6.0

Describe the bug
Package version
@adonisjs/core 7.6.0
@adonisjs/application 9.1.0
@adonisjs/assembler 8.5.2
Node / OS 24.22.0 on macOS 26.6.2
Bug Description

Dev watch mode restarting a server that crashed is the right behaviour. What has no answer is the supervisor itself: the ace serve process never notices that the shell, terminal or IDE task that started it is gone. Kill that shell and the supervisor is reparented to pid 1 and keeps watching files. The next change to anything it watches starts the server again, so a process from a session that no longer exists comes back and takes the port that the next bun run dev needs, along with Vite's HMR port.

From the outside this shows up as "hot reload stopped working" or "port 3333 is already in use", and the process is easy to lose track of: the supervisor never listens on anything itself, only the child it starts does, so a port scan won't find an orphan whose server has died. The only way left to kill it by hand is pgrep "ace.js serve".

serve is declared staysAlive: true, and the supervisor's reaction to a dead child is deliberate: "Underlying HTTP server died. Still watching for changes" (@adonisjs/assembler 8.5.2). That's right while the shell is still there. What nothing checks is the other direction: whether anyone is still there to want that server.

What this costs in practice

One morning, a bun run dev on an unrelated project failed before it ever bound a port: the run died while setting up the HMR watcher, out of file descriptors. What the machine was carrying at that moment:

$ pgrep -fl "ace.js serve" | wc -l
29
$ pgrep -fl "ace.js serve"          # 26 x `ace.js serve --hmr` in one project, 3 x `ace.js serve` in another
 81234 node ace.js serve --hmr
 ...
$ lsof -ti :3334 :3335 :3336        # and not one of them listening on anything

Twenty-nine supervisors from two projects, none of them wanted any more, none of them listening, none of them findable by port, with 26 of them still holding a watcher over a project tree, ~58 MB of RSS each, about 1.7 GB in total. In a terminal they are invisible: the windows that started them were closed days ago, and a port scan (the usual way to find a leftover dev server) finds nothing at all, because the supervisor only ever holds a port while a child of its own is alive. Getting rid of them means pgrep -f "ace.js serve" | xargs kill, by name, by hand, on the chance that you notice them.

The new run was the one that had to open one watcher too many, and it was the one that failed. That project had nothing to do with any of the 29.

Reproduction
$ zsh -c 'node ace.js serve --hmr'      # any shell, terminal or IDE task
...
│    Server address: http://127.0.0.1:3333        │
│    Mode: hmr                                    │
[ info ] watching file system for changes...

$ ps -ax -o pid=,ppid=,etime=,command= | grep -E 'ace\.js serve|bin/server\.ts'
 2330 51308 00:12 zsh -c node ace.js serve --hmr
 2333  2330 00:11 node ace.js serve --hmr
 2375  2333 00:10 node ... bin/server.ts                    <- listening on 3333

$ kill -9 2330                                           # the shell goes away
$ ps -ax -o pid=,ppid=,etime=,command= | grep -E 'ace\.js serve|bin/server\.ts'
 2333    1 00:20 node ace.js serve --hmr                   <- reparented, still watching
 2375 2333 00:19 node ... bin/server.ts                    <- still listening on 3333

$ touch config/app.ts                                    # one file change
 2333    1 00:30 node ace.js serve --hmr
 2627 2333 00:06 node ... bin/server.ts                    <- restarted, 3333 taken again
What I would like

A dev supervisor belongs to the session that started it. If its parent is gone (ppid === 1, or the pid it was started with no longer exists), it should stop watching and exit, instead of waiting for a file change it can never be told about. If that's too aggressive for some setups, an opt-out flag (--keep-alive, or the reverse) would still be better than the current default. And when the child is dead, something other than SIGKILL from a ps sweep to leave the "still watching" state: it has no address and no port of its own to be found by.

Related
  • #5164: node ace test never exits while anything is still holding the event loop open (same family, different command)
Reproduction repo

No response

Lingua principale
TypeScript
Stelle
19.2k
Fork
670
Merge medio
7g 6h
PR unite (30g)
9

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di adonisjs/core

Tutte le issue di adonisjs/core

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.