Skip to content

feat(backend): implement graceful shutdown with drain, flush, and connection teardown (#349) - #461

Open
prissca wants to merge 1 commit into
Epta-Node:mainfrom
prissca:backend/349-graceful-shutdown-drain-flush
Open

feat(backend): implement graceful shutdown with drain, flush, and connection teardown (#349)#461
prissca wants to merge 1 commit into
Epta-Node:mainfrom
prissca:backend/349-graceful-shutdown-drain-flush

Conversation

@prissca

@prissca prissca commented Aug 30, 2026

Copy link
Copy Markdown

Summary

setupGracefulShutdown() already existed and was unit-tested, but src/index.ts's actual production entry point (main()) never called it — it had its own, much simpler inline SIGTERM/SIGINT handler that only closed the HTTP server, with no draining, no DB closes, no task/agent state cleanup. This PR wires the real function in and fixes the gaps that made it unsafe to rely on:

  • api/app.ts's close() now awaits jobWorker.stop(jobWorkerStopTimeoutMs) (new AppOptions field, default 10s) before closing the HTTP/WS server — previously it called jobWorker.stop() without awaiting it, so "drain" did nothing: the server closed immediately regardless of in-flight work.
  • Removed the Phase 3 taskDb.failRunningTasks() call from setupGracefulShutdown, which force-marked every running task's DAG nodes as failed on shutdown. That directly worked against the job queue's own resumption path: JobWorker.start() already calls recoverIncompleteJobs(), which resets any job still "active" from a previous run back to "pending" for retry — a job stuck mid-drain is meant to resume, not be declared dead. (failRunningTasks() itself is left in db/tasks.ts in case it's useful elsewhere; it's just no longer called from the graceful-shutdown path.)
  • Added Phase 4: eventBus.store.close() — the event store was never closed on shutdown even though every other DB was.
  • src/index.ts's main() now calls setupGracefulShutdown(httpServer, close, config, { cleanupService, reconciliationService, globalAgentRegistry }) instead of its own inline handler; setupGracefulShutdown's signature gained an optional 4th extras argument (backward compatible — the existing 3-argument test call still passes) to stop those three services, which main() previously stopped inline but setupGracefulShutdown had no way to reach before.
  • New test in src/queue/worker.test.ts: a job still "active" when a JobWorker.stop() call times out is picked up and completed by a fresh JobWorker instance over the same store — the restart-mid-stream scenario the acceptance criteria asks for, exercised at the layer that actually owns resumption (recoverIncompleteJobs()).
  • tests/shutdown.test.ts updated: asserts failRunningTasks/createTaskDb are no longer called, asserts the three extra services are stopped, asserts the event store and job DB are closed, and adds a backward-compatibility test for the 3-argument call.

Necessary prerequisite: several backend files were corrupted by a bad merge

package.json, jest.config.js, tsconfig.json, config/index.ts, api/app.ts, api/routes/stream.ts, api/routes/agents.ts, api/routes/stats.ts, and api/routes/health.ts each contained two full, conflicting versions of their own content concatenated together, which blocked npm install/npm test outright. Same root cause and fix already documented in Epta-Node/ai-net#443 (a different fork, issue #359) and this repo's #460 (issue #353) — kept the newer half matching actual codebase usage in each file, discarded the stale duplicate. This PR's health.ts fix is the minimal corruption fix only (no /live or /ready extensions — those are #353's PR, #460); api/app.ts here additionally includes the close()/job-worker-drain change described above, which #460's app.ts deliberately does not.

Acceptance Criteria

  • In-flight tasks complete or resume on restart
  • E2E test validates restart mid-stream

Test plan

  • npx jest tests/shutdown.test.ts — 5/5 passing (full phase sequence, extras stopped, no more failRunningTasks, event store + job DB closed, 3-arg backward compatibility, forced-exit-on-timeout).
  • npx jest src/queue/worker.test.ts — 10/10 passing, including the new restart-mid-stream test.

Closes #349

…nection teardown (Epta-Node#349)

setupGracefulShutdown() already existed and was unit-tested, but src/index.ts's
actual production entry point (main()) never called it — it had its own,
much simpler inline SIGTERM/SIGINT handler that only closed the HTTP
server, with no draining, no DB closes, no task/agent state cleanup. This
PR wires the real function in and fixes the gaps that made it unsafe to
rely on:

- api/app.ts's close() now awaits jobWorker.stop(jobWorkerStopTimeoutMs)
  (new AppOptions field, default 10s) before closing the HTTP/WS server —
  previously it called jobWorker.stop() without awaiting it, so "drain"
  did nothing: the server closed immediately regardless of in-flight work.
- Removed the Phase 3 taskDb.failRunningTasks() call from
  setupGracefulShutdown, which force-marked every running task's DAG
  nodes as failed on shutdown. That directly worked against the job
  queue's own resumption path: JobWorker.start() already calls
  recoverIncompleteJobs(), which resets any job still "active" from a
  previous run back to "pending" for retry — a job stuck mid-drain is
  meant to resume, not be declared dead. (failRunningTasks() itself is
  left in db/tasks.ts in case it's useful elsewhere; it's just no longer
  called from the graceful-shutdown path.)
- Added Phase 4: eventBus.store.close() — the event store was never
  closed on shutdown even though every other DB was.
- src/index.ts's main() now calls setupGracefulShutdown(httpServer, close,
  config, { cleanupService, reconciliationService, globalAgentRegistry })
  instead of its own inline handler; setupGracefulShutdown's signature
  gained an optional 4th `extras` argument (backward compatible — the
  existing 3-argument test call still passes) to stop those three
  services, which main() previously stopped inline but
  setupGracefulShutdown had no way to reach before.
- New test in src/queue/worker.test.ts: a job still "active" when a
  JobWorker.stop() call times out is picked up and completed by a
  *fresh* JobWorker instance over the same store — the restart-mid-stream
  scenario the acceptance criteria asks for, exercised at the layer that
  actually owns resumption (recoverIncompleteJobs()).
- tests/shutdown.test.ts updated: asserts failRunningTasks/createTaskDb
  are no longer called, asserts the three extra services are stopped,
  asserts the event store and job DB are closed, and adds a
  backward-compatibility test for the 3-argument call.

## Necessary prerequisite: several backend files were corrupted by a bad merge

package.json, jest.config.js, tsconfig.json, config/index.ts, api/app.ts,
api/routes/stream.ts, api/routes/agents.ts, api/routes/stats.ts, and
api/routes/health.ts each contained two full, conflicting versions of
their own content concatenated together, which blocked `npm install`/
`npm test` outright. Same root cause and fix already documented in
Epta-Node#443 (a different fork, issue Epta-Node#359) and this repo's
Epta-Node#460 (issue Epta-Node#353) — kept the newer half matching actual codebase usage
in each file, discarded the stale duplicate. This PR's health.ts fix is
the minimal corruption fix only (no /live or /ready extensions — those
are Epta-Node#353's PR, Epta-Node#460); api/app.ts here additionally includes the
close()/job-worker-drain change described above, which Epta-Node#460's app.ts
deliberately does not.

## Acceptance Criteria

- [x] In-flight tasks complete or resume on restart
- [x] E2E test validates restart mid-stream

## Test plan

- npx jest tests/shutdown.test.ts — 5/5 passing (full phase sequence,
  extras stopped, no more failRunningTasks, event store + job DB closed,
  3-arg backward compatibility, forced-exit-on-timeout).
- npx jest src/queue/worker.test.ts — 10/10 passing, including the new
  restart-mid-stream test.

Closes Epta-Node#349
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@prissca Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Obiajulu-gif is attempting to deploy a commit to the Jaja's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement graceful shutdown with drain, flush, and connection teardown

2 participants