feat(meteor): add Meteor.shutdown() lifecycle hook#14434
feat(meteor): add Meteor.shutdown() lifecycle hook#14434dupontbertrand wants to merge 3 commits into
Conversation
Symmetric to Meteor.startup(), Meteor.shutdown(fn) registers a callback fired on SIGTERM / SIGINT before process exit. Useful for closing DB connections, flushing queues, releasing locks, etc. Hooks run in LIFO order sequentially with await. Per-hook errors are logged but do not abort subsequent hooks (best-effort cleanup). Total runtime is capped by METEOR_SHUTDOWN_TIMEOUT_MS (default 10000ms) to avoid stalling supervisor escalation (Galaxy, K8s, systemd) to SIGKILL. Exit codes follow POSIX: SIGINT -> 130, SIGTERM -> 143. Forum thread: https://forums.meteor.com/t/is-there-something-like-meteor-shutdown/64602
Adds the JSDoc @summary block consumed by the apibox tag plus an entry in docs/source/api/core.md, placed right after Meteor.startup since the two are conceptually paired. The prose covers: LIFO ordering with sequential await, best-effort error handling, hard timeout via METEOR_SHUTDOWN_TIMEOUT_MS, POSIX exit codes, server-only locus.
✅ Deploy Preview for v3-meteor-api-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for v3-migration-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughThis PR adds a graceful server shutdown API for Meteor. It implements a ChangesServer Shutdown
🎯 2 (Simple) | ⏱️ ~12 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tools/static-assets/server/boot.js`:
- Around line 473-477: The safety timeout created by setTimeout (the variable
timer) must not be unreferenced because timer.unref() can allow the event loop
to empty and prevent the timeout from firing; remove the call to timer.unref()
so the timeout reliably keeps the process alive until either clearTimeout(timer)
is called or it fires and calls process.exit(exitCode), leaving the existing
clearTimeout(timer) and the timeoutMs/exitCode behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a069aa43-2e0e-4e97-b82d-7ff730a7a183
📒 Files selected for processing (3)
docs/source/api/core.mdpackages/meteor/startup_server.jstools/static-assets/server/boot.js
An unref()-ed timer does not keep the event loop alive. If a shutdown hook hangs on a promise with no other pending I/O (e.g. after earlier hooks have closed the server's sockets and Mongo connection), the loop empties and the process exits 0 before the timeout fires, defeating the documented METEOR_SHUTDOWN_TIMEOUT_MS hard cap. clearTimeout() already prevents the timer from delaying the fast path, so unref() only weakened the guarantee.
Summary
This PR adds a server-side
Meteor.shutdown(fn)lifecycle hook, intended as the shutdown counterpart toMeteor.startup(fn).Registered hooks are called on
SIGTERMandSIGINTbefore the process exits, allowing applications to flush logs, drain queues, release locks, close sockets, or finish pending async cleanup before the supervisor escalates toSIGKILL.Opened as a draft because a few API and semantics details should be confirmed with the core team before landing:
Meteor.shutdown(fn)vsMeteor.onShutdown(fn)SIGHUP, Node'exit', or dev-mode wrapper shutdownForum context: https://forums.meteor.com/t/is-there-something-like-meteor-shutdown/64602
Why
Meteor exposes
Meteor.startup(fn)for the boot side of the lifecycle, but there's no symmetric API for shutdown. Today, applications that need to clean up on termination attach rawprocess.on('SIGTERM', …)handlers themselves, and the details that matter — per-hook timeout, error containment, ordering, single-point-of-exit — have to be reimplemented in each app.This PR codifies the missing half of the lifecycle so applications can rely on a consistent, framework-level shutdown hook.
Use cases
Patterns this enables:
pino,winston, Sentry, OpenTelemetry exporters all have async flush methods that need to complete before exit.What changed
Two commits, +89 lines total, no removed code, no existing content modified:
Commit 1 —
feat(meteor): add Meteor.shutdown() lifecycle hook(+56)tools/static-assets/server/boot.js(+39) —shutdownHooks: []bucket,callShutdownHooks(signal)runner,SIGTERM/SIGINTlistenerspackages/meteor/startup_server.js(+18) — publicMeteor.shutdown(fn)APICommit 2 —
docs(meteor): document Meteor.shutdown()(+33)packages/meteor/startup_server.js(+5) — JSDoc@summaryblockdocs/source/api/core.md(+28) — apibox entry + prose, placed right afterMeteor.startupUsage
Hooks receive the triggering signal name (
'SIGTERM'or'SIGINT') as their argument.Design choices
for…await)METEOR_SHUTDOWN_TIMEOUT_MS(default 10000ms)SIGKILL— exit well beforeMeteor.startup's "execute immediately when bootstrap is done" pathExit codes follow POSIX:
SIGINT→ 130,SIGTERM→ 143.LIFO in practice — if startup is
db.connect()→queue.start()→socket.listen()(each depending on the previous), shutdown should runsocket.close()→queue.drain()→db.close(). FIFO would close the DB before the queue had finished writing, producingMongoError: Topology closed.Known limitations / out of scope
This PR does not refactor existing shutdown-related listeners such as
packages/webapp/socket_file.js, and it does not integrate the dev-mode parent watchdog (startCheckForLiveParent,boot.js:180) with the new hook runner. In dev mode, hooks taking longer than ~3s can therefore be pre-empted by the watchdogprocess.exit(1); in production builds (noMETEOR_PARENT_PID) theMETEOR_SHUTDOWN_TIMEOUT_MScap is the sole guard. If the API direction is accepted, both can be handled in follow-up PRs.Validation
Manual validation against a fresh
meteor create --blazeapp. I kept this PR scope-tight without test changes, but I'm happy to add a Tinytest suite on this branch if reviewers prefer the tests to land with the API.awaitMETEOR_SHUTDOWN_TIMEOUT_MS=2000)process.exitSIGINT+SIGTERMidempotenceSIGINT→ 130,SIGTERM→ 143Open questions
Meteor.shutdown(fn)(symmetric withMeteor.startup) vsMeteor.onShutdown(fn)(avoids verb-form "shut down the server now" ambiguity).Meteor.startup).Meteor.shutdown(fn, { timeout: 5000 }))?Meteor.shutdownalso fire onSIGHUPand Node's'exit'event? Currently onlySIGINT/SIGTERM. Tied to thesocket_file.jsfollow-up.Summary by CodeRabbit
New Features
Documentation