[Regression on closed #549/#328] main.ts still never calls app.enableShutdownHooks(), so SIGTERM skips all lifecycle cleanup
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 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分
- マージ済み PR(30日)
- 20
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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 日以内に返信
MergeFi/backend の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
zapcooking/frontend#946 ·
メンテナーはふだん 1 日以内に返信
-
enhancement good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 77/100
liberation-data/drivine#733 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
makecindy/cindy#10062 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信