[Regression on closed #549/#328] main.ts still never calls app.enableShutdownHooks(), so SIGTERM skips all lifecycle cleanup
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 58/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- node.js, postgresql, typescript
调研方向
Start with src/main.ts and src/main.spec.ts, which the issue identifies as the bootstrap and existing test entry point. Read how the app mock is set up, then verify shutdown hooks are enabled before listen() and covered by the test. The issue also proposes deployment documentation about the termination grace period; done means hooks run on process signals and the test protects against their removal.
由索引模型根据 Issue 内容生成。
描述
Location
src/main.ts:12-74 — bootstrap(). grep -rn "enableShutdownHooks" src/ returns nothing.
Problem
Nest does not listen for process termination signals unless the application explicitly opts in with app.enableShutdownHooks(). Without that call, SIGTERM/SIGINT terminate the process directly: onModuleDestroy, beforeApplicationShutdown and onApplicationShutdown never run on any provider, and app.close() is never invoked.
This is closed issue #328, re-filed and closed again as #549. The call is still absent.
Why it matters
This application has three kinds of state that need an orderly stop, and none of them get one:
1. In-flight money movement. SorobanClientService.pollTransaction polls a submitted transaction for up to roughly 60 seconds (see the discussion in src/common/idempotency/idempotency.interceptor.ts:37-52). A pod killed mid-poll loses the polling loop while the transaction is already on the network. The Escrow row is left in whatever interim state it held, and the IdempotencyKey row stays PROCESSING until STALE_PROCESSING_MS (180s) elapses and a retry reclaims it — at which point the retry may re-submit a mutation whose first attempt actually landed on-chain. A graceful shutdown that drains in-flight requests before closing is precisely what keeps that window shut.
2. Scheduled jobs. ScheduleModule.forRoot() is registered (app.module.ts:39) and @Cron jobs exist for bounty expiry and idempotency cleanup. ScheduleModule cancels its timers in onApplicationShutdown. Without the hook, a job that fires during the termination grace period runs against a connection pool that is about to vanish.
3. The database connection pool. TypeOrmModule closes its pool in onApplicationShutdown. Skipping it leaves Postgres to reap the abandoned backends on its own timeout. Across a rolling deploy or a crash-loop this accumulates idle connections and can exhaust max_connections — which then fails the new pods' startup, turning a routine restart into an outage.
Containers and orchestrators all stop processes with SIGTERM (docker stop, a Kubernetes pod deletion, a systemd restart), so this affects every single deploy and scale-down, not an edge case.
Reproduction / failure path
- Start the app against a real Postgres:
npm run start:prod. - In a second shell, watch the server's connections:
SELECT count(*) FROM pg_stat_activity WHERE application_name LIKE '%node%' OR datname = 'mergefi';
- Send
SIGTERMto the Node process (kill <pid>, notkill -9). - Observed: the process exits immediately with no shutdown logging, and the Postgres backends remain in
idlestate until the server's owntcp_keepalives/idle_session_timeoutreaps them. Nest'sonApplicationShutdownhooks never fire — add a provider with anonApplicationShutdown()that logs, and confirm the line is never printed. - Expected: the hooks fire, the pool is closed, the scheduler's timers are cancelled, and the process exits after cleanup.
Suggested fix
Call it before listen() so the signal handlers are installed before the process can start accepting traffic:
const app = await NestFactory.create(AppModule, { rawBody: true });
app.enableShutdownHooks();
Pair it with a request-draining grace period so in-flight escrow mutations are allowed to complete rather than cut off — Nest's app.close() stops accepting new connections and waits for the HTTP server to finish current requests, which is what the hook enables.
Because this has now been removed twice, pin it with a test. src/main.spec.ts already mocks NestFactory.create; assert on the returned app mock:
expect(appMock.enableShutdownHooks).toHaveBeenCalled();
Also add a HEALTHCHECK-adjacent note to the deployment docs: with the hooks enabled, the orchestrator's terminationGracePeriodSeconds must exceed the Soroban poll ceiling (~60s) for the drain to be useful, otherwise the pod is still force-killed mid-poll.
- 主要语言
- TypeScript
- 星标
- 2
- 派生
- 90
- 平均合并
- 31 分钟
- 30 天内合并 PR
- 20
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
MergeFi/backend 的其他 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
bug security
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
bug testing
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
bug security
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
相似的 Issue
-
Codex使用可 コード
难度 2/5 1-3 小时 新手友好度 72/100
ryuki-moro/Nicomaru#218 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
sveltejs/svelte-eslint-parser#1008 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
silverbucket/inbox-rs#351 ·
维护者通常 1 天内回复
-
[Bug]: The H2 series' and the H3C's fan 3 is labelled "Chamber Fan", Bambu calls it the exhaust fan未关闭area:ui
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
bug front P2
难度 2/5 1-3 小时 新手友好度 86/100
invoicerr-app/invoicerr#964 ·
维护者通常 1 天内回复