test:int: устранить утечку open handles (сейчас замаскировано forceExit) + разобраться, почему destroyTestDb зависал #382

Open
opened 2026-07-06 01:28:12 +03:00 by agent_vscode · 0 comments
Collaborator

Контекст

После фикса ESM-трансформа @docmost/prosemirror-markdown в jest-конфигах (коммит 8e125799) 4 ранее не парсившиеся интеграционные спеки впервые начали реально выполняться:

  • share-alias-one-per-page.int-spec.ts
  • duplicate-page-shared-attachment.int-spec.ts
  • page-recursive-cte-cycle-guard.int-spec.ts
  • page-move-cycle.int-spec.ts

И сразу всплыло, что суита pnpm --filter server test:int (apps/server/test/jest-integration.json, maxWorkers: 1) не может завершиться: все тесты проходят (66 passed), после чего в логе Jest did not exit one second after the test run has completed. и ~14 минут простоя до отсечки по 20-мин таймауту джобы.

CI-прогон, где это видно: https://github.com/vvzvlad/gitmost/actions/runs/28755705055 (джоба test / test, шаг Run server integration tests).

До этого фикса те 4 спеки падали на этапе загрузки (SyntaxError), модули не поднимались — поэтому процесс выходил и зависания не было. То есть течь была латентной и вскрылась, когда спеки заработали.

Что уже сделано (временный пластырь)

Быстрый фикс (в develop), чтобы разблокировать CI:

  1. apps/server/test/jest-integration.json"forceExit": true — jest принудительно выходит после прогона.
  2. apps/server/test/integration/db.tsdestroyTestDb() теперь закрывает пул через sql.end({ timeout: 5 }) (ограниченный shutdown) вместо Kysely.destroy() — потому что ai-chat-stream.int-spec.ts падал: его afterAll (await destroyTestDb()) упирался в 60-сек таймаут хука, т.к. postgres.js .end() без таймаута ждёт незавершённые соединения вечно.

forceExit маскирует первопричину — открытые handles остаются, просто процесс убивается при выходе. Это тех-долг, его и надо закрыть по-настоящему.

Что нужно сделать (нужен стенд с Postgres + Redis)

Локально/на стенде поднять pg+redis (creds для теста: docmost / docmost_dev_pw, maintenance-БД docmost, redis :6379 — см. apps/server/test/integration/db.ts и global-setup.ts) и запустить:

pnpm --filter server test:int -- --detectOpenHandles --runInBand

jest-integration.json временно убрать forceExit, иначе jest выйдет до отчёта об утечке).

Найти и закрыть источники висящих handles. Подозреваемые:

  • ai-chat-stream.int-spec.tsmakeRealResponse() поднимает реальный http.createServer + сокет-клиент и гоняет реальный стрим с heartbeat-таймерами (AiChatService.stream). Проверить, что heartbeat-интервал и сокеты гарантированно закрываются, и разобраться, какое соединение из пула оставалось занятым, из-за чего destroyTestDb() зависал (это отдельный симптом той же течи).
  • 4 новые спеки (share-alias / duplicate-page-shared-attachment / page-recursive-cte-cycle-guard / page-move-cycle) — проверить, не поднимают ли они Nest-сервисы/шедулеры/очереди (BullMQ) без последующего app.close()/teardown.

Definition of Done

  • pnpm --filter server test:int завершается сам, БЕЗ forceExit, за ~90с (jest не пишет «did not exit»).
  • --detectOpenHandles чист.
  • После этого убрать "forceExit": true из jest-integration.json (пластырь destroyTestDb с таймаутом можно оставить как защиту).
  • ai-chat-stream стабильно зелёный без 60-сек залипания в afterAll.

Связанные артефакты

  • Коммит ESM-фикса: 8e125799
  • Коммит-пластырь (forceExit + bounded teardown): будет в этом же треде develop
  • Затронутые файлы: apps/server/test/jest-integration.json, apps/server/test/integration/db.ts, apps/server/test/integration/ai-chat-stream.int-spec.ts
## Контекст После фикса ESM-трансформа `@docmost/prosemirror-markdown` в jest-конфигах (коммит `8e125799`) 4 ранее не парсившиеся интеграционные спеки впервые начали **реально выполняться**: - `share-alias-one-per-page.int-spec.ts` - `duplicate-page-shared-attachment.int-spec.ts` - `page-recursive-cte-cycle-guard.int-spec.ts` - `page-move-cycle.int-spec.ts` И сразу всплыло, что суита `pnpm --filter server test:int` (`apps/server/test/jest-integration.json`, `maxWorkers: 1`) **не может завершиться**: все тесты проходят (`66 passed`), после чего в логе `Jest did not exit one second after the test run has completed.` и ~14 минут простоя до отсечки по 20-мин таймауту джобы. CI-прогон, где это видно: https://github.com/vvzvlad/gitmost/actions/runs/28755705055 (джоба `test / test`, шаг `Run server integration tests`). До этого фикса те 4 спеки падали на этапе загрузки (`SyntaxError`), модули не поднимались — поэтому процесс выходил и зависания не было. То есть течь была латентной и вскрылась, когда спеки заработали. ## Что уже сделано (временный пластырь) Быстрый фикс (в develop), чтобы разблокировать CI: 1. `apps/server/test/jest-integration.json` → `"forceExit": true` — jest принудительно выходит после прогона. 2. `apps/server/test/integration/db.ts` → `destroyTestDb()` теперь закрывает пул через `sql.end({ timeout: 5 })` (ограниченный shutdown) вместо `Kysely.destroy()` — потому что `ai-chat-stream.int-spec.ts` падал: его `afterAll` (`await destroyTestDb()`) упирался в 60-сек таймаут хука, т.к. postgres.js `.end()` без таймаута ждёт незавершённые соединения вечно. **`forceExit` маскирует первопричину** — открытые handles остаются, просто процесс убивается при выходе. Это тех-долг, его и надо закрыть по-настоящему. ## Что нужно сделать (нужен стенд с Postgres + Redis) Локально/на стенде поднять pg+redis (creds для теста: `docmost` / `docmost_dev_pw`, maintenance-БД `docmost`, redis :6379 — см. `apps/server/test/integration/db.ts` и `global-setup.ts`) и запустить: ``` pnpm --filter server test:int -- --detectOpenHandles --runInBand ``` (в `jest-integration.json` временно убрать `forceExit`, иначе jest выйдет до отчёта об утечке). Найти и закрыть источники висящих handles. Подозреваемые: - **`ai-chat-stream.int-spec.ts`** — `makeRealResponse()` поднимает реальный `http.createServer` + сокет-клиент и гоняет реальный стрим с heartbeat-таймерами (`AiChatService.stream`). Проверить, что heartbeat-интервал и сокеты гарантированно закрываются, и разобраться, какое соединение из пула оставалось занятым, из-за чего `destroyTestDb()` зависал (это отдельный симптом той же течи). - **4 новые спеки** (share-alias / duplicate-page-shared-attachment / page-recursive-cte-cycle-guard / page-move-cycle) — проверить, не поднимают ли они Nest-сервисы/шедулеры/очереди (BullMQ) без последующего `app.close()`/teardown. ## Definition of Done - `pnpm --filter server test:int` завершается сам, БЕЗ `forceExit`, за ~90с (jest не пишет «did not exit»). - `--detectOpenHandles` чист. - После этого убрать `"forceExit": true` из `jest-integration.json` (пластырь `destroyTestDb` с таймаутом можно оставить как защиту). - `ai-chat-stream` стабильно зелёный без 60-сек залипания в `afterAll`. ## Связанные артефакты - Коммит ESM-фикса: `8e125799` - Коммит-пластырь (forceExit + bounded teardown): будет в этом же треде develop - Затронутые файлы: `apps/server/test/jest-integration.json`, `apps/server/test/integration/db.ts`, `apps/server/test/integration/ai-chat-stream.int-spec.ts`
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#382