[bug][ci] e2e-server в Develop виснет до лимита 6ч — jest не завершается (открытые хендлы AppModule) #252

Open
opened 2026-06-29 00:05:48 +03:00 by Ghost · 0 comments

Симптом

Job e2e-server в workflow Develop (.github/workflows/develop.yml) не завершается: вместо нескольких минут он крутится до максимума GitHub Actions в 6 часов, после чего отменяется. Из-за этого:

  • общий run Develop никогда не приходит к честному статусу failure — он либо часами in_progress, либо cancelled (в т.ч. из-за concurrency: cancel-in-progress);
  • GitHub не шлёт письмо о провале (письма уходят только для failure, не для cancelled), поэтому падения тестов остаются незамеченными по почте.

Пример: run #28331123496 — job test упал за 3 мин, e2e-mcp прошёл за 5 мин, а e2e-server висел >2.5 ч (в истории были прогоны ровно по 6h0m).

Причина

pnpm --filter ./apps/server test:e2ejest --config test/jest-e2e.json. Единственный спек apps/server/test/app.e2e-spec.ts поднимает полный AppModule (NestFastifyApplication).

Граф AppModule держит долгоживущие ресурсы, которые после теста остаются открытыми:

  • BullMQ-очереди/воркеры: notification.processor, attachment.processor, collaboration/processors/history.processor;
  • планировщики @nestjs/schedule: temporary-note-cleanup.service, trash-cleanup.service;
  • Redis-соединения (ioredis);
  • collaboration websocket / Hocuspocus-сервер.

app.close() в afterEach не гасит все эти хендлы, поэтому после прохождения единственного теста jest не может завершить процесс по событийному циклу и зависает. Так как у job не было timeout-minutes, он докручивался до дефолтного лимита 6ч.

Косвенное подтверждение: дефолтный per-test timeout jest (5с) сработал бы, если бы виснул сам app.init() в beforeEach — значит зависание происходит после завершения теста, на этапе выхода процесса (классический «open handles»).

Уже сделано (митигация)

Добавлены timeout-minutes в CI-джобы (.github/workflows/develop.yml, .github/workflows/test.yml): e2e-server: 15, e2e-mcp: 20, build: 30, test: 20. Теперь зависание обрывается за минуты и даёт failure (→ письмо), а не 6ч. Это не чинит причину — только ограничивает ущерб.

Что надо сделать (правильный фикс)

  1. Диагностика: прогнать jest --config test/jest-e2e.json --detectOpenHandles --runInBand против реального Postgres (pgvector) + Redis (например, через services: прямо в CI) и зафиксировать конкретные открытые хендлы.
  2. Корректный teardown: закрывать в afterAll/onModuleDestroy BullMQ-о��ереди и воркеры, ioredis-клиентов, таймеры планировщика, collab-сервер. Либо прагматичный стопгап — --forceExit (+ --runInBand) для test:e2e.
  3. Рассмотреть, нужен ли e2e полный AppModule для smoke-теста GET /, или достаточно урезанного тест-модуля.

Ссылки

  • .github/workflows/develop.yml (job e2e-server)
  • apps/server/test/app.e2e-spec.ts
  • apps/server/test/jest-e2e.json
  • apps/server/package.json (test:e2e)
## Симптом Job `e2e-server` в workflow **Develop** (`.github/workflows/develop.yml`) не завершается: вместо нескольких минут он крутится до максимума GitHub Actions в 6 часов, после чего отменяется. Из-за этого: - общий run **Develop никогда не приходит к честному статусу `failure`** — он либо часами `in_progress`, либо `cancelled` (в т.ч. из-за `concurrency: cancel-in-progress`); - **GitHub не шлёт письмо о провале** (письма уходят только для `failure`, не для `cancelled`), поэтому падения тестов остаются незамеченными по почте. Пример: run #28331123496 — job `test` упал за 3 мин, `e2e-mcp` прошёл за 5 мин, а `e2e-server` висел >2.5 ч (в истории были прогоны ровно по `6h0m`). ## Причина `pnpm --filter ./apps/server test:e2e` → `jest --config test/jest-e2e.json`. Единственный спек `apps/server/test/app.e2e-spec.ts` поднимает **полный `AppModule`** (`NestFastifyApplication`). Граф `AppModule` держит долгоживущие ресурсы, которые после теста остаются открытыми: - BullMQ-очереди/воркеры: `notification.processor`, `attachment.processor`, `collaboration/processors/history.processor`; - планировщики `@nestjs/schedule`: `temporary-note-cleanup.service`, `trash-cleanup.service`; - Redis-соединения (ioredis); - collaboration websocket / Hocuspocus-сервер. `app.close()` в `afterEach` не гасит все эти хендлы, поэтому после прохождения единственного теста **jest не может завершить процесс по событийному циклу и зависает**. Так как у job не было `timeout-minutes`, он докручивался до дефолтного лимита 6ч. Косвенное подтверждение: дефолтный per-test timeout jest (5с) сработал бы, если бы виснул сам `app.init()` в `beforeEach` — значит зависание происходит **после** завершения теста, на этапе выхода процесса (классический «open handles»). ## Уже сделано (митигация) Добавлены `timeout-minutes` в CI-джобы (`.github/workflows/develop.yml`, `.github/workflows/test.yml`): `e2e-server: 15`, `e2e-mcp: 20`, `build: 30`, `test: 20`. Теперь зависание обрывается за минуты и даёт `failure` (→ письмо), а не 6ч. **Это не чинит причину — только ограничивает ущерб.** ## Что надо сделать (правильный фикс) 1. **Диагностика:** прогнать `jest --config test/jest-e2e.json --detectOpenHandles --runInBand` против реального Postgres (pgvector) + Redis (например, через `services:` прямо в CI) и зафиксировать конкретные открытые хендлы. 2. **Корректный teardown:** закрывать в `afterAll`/`onModuleDestroy` BullMQ-о��ереди и воркеры, ioredis-клиентов, таймеры планировщика, collab-сервер. Либо прагматичный стопгап — `--forceExit` (+ `--runInBand`) для `test:e2e`. 3. Рассмотреть, нужен ли e2e полный `AppModule` для smoke-теста `GET /`, или достаточно урезанного тест-модуля. ## Ссылки - `.github/workflows/develop.yml` (job `e2e-server`) - `apps/server/test/app.e2e-spec.ts` - `apps/server/test/jest-e2e.json` - `apps/server/package.json` (`test:e2e`)
Ghost added the bugtest labels 2026-06-29 00:05:48 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#252