feat(app): graceful shutdown on SIGTERM/SIGINT via App.stop#20
Merged
BlindMaster24 merged 4 commits intoApr 27, 2026
Merged
Conversation
2 tasks
98592de to
ece7ce0
Compare
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires the process lifecycle so a
SIGTERMorSIGINT(ctrl-c,docker stop, k8s eviction) drains running services and closes the DB before exit, instead of yanking the process mid-flight.Why: container orchestrators send
SIGTERMand thenSIGKILLafter a grace period. Until now we had only partial coverage (Telegram polling was stopping itself on signal, but the HTTP server kept holding connections, the parser kept polling the site, and the Sequelize pool was never closed). That caused half-written parser cache files on rolling restarts, Docker logs full ofreceived signal, exitingfollowed by hard kills, and dangling DB connections in logs.What's new:
AppServicegets an optionalstop?(): Promise<any> | any;.App.stop({ timeoutMs })iterates services in reverse registration order (so e.g.tg/vk/viber/google_calendarget stopped beforehttp/parser), calls each service'sstop()if defined, and applies a per-service timeout (default 15s) so one stuck service can't block shutdown. After all services are stopped it callssequelize.close().HttpService.stop()— captures thehttp.Serverreturned bylisten()andserver.close()s it on shutdown; existing in-flight requests are allowed to finish.ParserService.stop()— flips a_stoppingflag and resolves the currentdelayPromiseso the infinite parse loop breaks on the next tick instead of running another full parse.TgBot.stop()— exposes the grammYBot.stop()through the AppService interface; removed the inlineprocess.once('SIGINT'/'SIGTERM', ...)it used to register — that handler only stopped polling and left everything else running. Centralizing inApp.stop()fixes the ordering.src/index.ts— one shutdown handler for both signals. Logs, triggersApp.stop(), and a 30s hard-timeoutsetTimeout(..., 30_000).unref()guarantees the process exits even if something hangs past the per-service budget.Not touched (yet):
VkBot,ViberBot,AliceApp,VKApp,GoogleService,ImageService,Api,Timetable,BotService. VK long-polling stops cleanly when the event-loop drains; Viber and Alice run on the shared express server soHttpService.stop()handles them; the rest don't own background work. Follow-ups can land these later without changing the contract.Tests (
tests/app/shutdown.test.ts):Checks:
pnpm run test:all— 32 files / 210 tests passpnpm run ts-check/pnpm run lint/pnpm run format:check— cleanpnpm audit --audit-level=low— no known vulnerabilitiesRun the new tests with:
pnpm exec vitest run tests/app/shutdown.test.tsStacked on PR #19.
Review & Testing Checklist for Human
docker compose up -dthendocker compose stop bot— container should exit cleanly (exit code 0) within ~15s instead of being force-killed after grace period.docker compose logs botshould showОстановка...,Остановлено: http, etc., thenБД отключена.ctrl+cwhile a message is being handled — request should finish, then process exits.stop()—App.stop()should still close the DB and exit.stop()with() => new Promise(() => {})— process should still exit after 30s with code 1.Notes
src/index.tsis 30s. Tune these viaApp.stop({ timeoutMs })if you want tighter/looser SLAs.Link to Devin session: https://app.devin.ai/sessions/7732f5fd16e9448295cbabeb8b5f471a
Requested by: @BlindMaster24