test(int): убрать избыточный forceExit — bounded teardown уже расхангивает test:int (#382) #383
Open
agent_coder
wants to merge 1 commits from
fix/382-int-open-handles into develop
pull from: fix/382-int-open-handles
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:feat/381-resumable-sse-pr2
vvzvlad:feat/381-resumable-sse-pr1
vvzvlad:test/351-nested-generator
vvzvlad:docs/agents-workspace-build-order
vvzvlad:feat/git-sync-2
vvzvlad:feature/offline-sync
vvzvlad:perf/342-code-splitting
vvzvlad:perf/344-background-rerenders
vvzvlad:perf/346-compression-cache
vvzvlad:perf/348-backend-lowhanging
vvzvlad:feat/196-multi-cursor
vvzvlad:feat/370-page-versioning
vvzvlad:perf/343-typing-latency
vvzvlad:develop
vvzvlad:docs/how-to-test
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:refactor/345-server-converter
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:fix/362-metrics-route-cardinality
vvzvlad:fix/e2e-callout-and-gate-build
vvzvlad:fix/docker-re2-toolchain
vvzvlad:feat/git-sync
vvzvlad:fix/media-roundtrip-stability
vvzvlad:fix/340-comment-panel-perf
vvzvlad:fix/332-deferred-tools
vvzvlad:fix/329-ephemeral-suggestions
vvzvlad:fix/330-search-in-page
vvzvlad:fix/328-resolved-anchor-spam
vvzvlad:fix/331-intraline-diff
vvzvlad:fix/324-coverage-gate
vvzvlad:fix/325-mobile-390
vvzvlad:feat/293-A-git-sync-package
vvzvlad:feat/300-avatar-oklch
vvzvlad:fix/321-banner-mobile
vvzvlad:feat/300-avatar-colors
vvzvlad:feat/315-comment-suggestions
vvzvlad:feat/scroll-restore-stable-wait
vvzvlad:feat/300-agent-avatar-stack
vvzvlad:feat/300-avatar-polish
vvzvlad:refactor/294-tool-spec-registry
vvzvlad:feat/scroll-restore-ux
vvzvlad:fix/responsive-tablet-sidebar
vvzvlad:feature/ai-chat-page-change-observability
vvzvlad:image-inline-center
vvzvlad:fix/283-short-remap-title
vvzvlad:fix/283-slash-layout
vvzvlad:image-inline-row
vvzvlad:feat/276-ai-chat-dock
vvzvlad:fix/269-table-menu-refocus
vvzvlad:docs/dev-stand-guide
vvzvlad:feat/266-scroll-position
vvzvlad:fix/260-collab-docname-slugid
vvzvlad:test/244-phase2-tail
vvzvlad:fix/262-reindex-progress-realtime
vvzvlad:fix/258-changelog-compare-links
vvzvlad:fix/244-dataloss-bugs
vvzvlad:feat/246-spoiler
vvzvlad:feat/221-image-captions
vvzvlad:test/244-part-b
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
No Reviewers
Labels
Clear labels
epic
needs-human
review/approved
review/changes-requested
review/needs
Large multi-phase effort spanning many changes
эскалация: нужно решение человека
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
No Label
review/approved
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#383
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/382-int-open-handles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Убирает
"forceExit": trueизapps/server/test/jest-integration.json. closes #382Пластырь в develop добавил ДВЕ вещи:
forceExitИ boundeddestroyTestDb(sql.end({ timeout: 5 })). Настоящий фикс — bounded teardown;forceExitбыл избыточным и вдобавок МАСКИРОВАЛ, выходит ли процесс сам. Убираю его; boundeddestroyTestDbоставлен как защита (как и просили в DoD).Почему зависало (root cause)
Изначальный
destroyTestDbзвалKysely.destroy()→ postgres.jssql.end()БЕЗ таймаута..end()без таймаута ждёт незавершённые/залипшие соединения пула бесконечно. В CI одно соединение оставалось занятым на моментafterAll→.end()блокировался → hook-таймаут 60с, а открытые сокеты пула держали процесс → «Jest did not exit» → ~14 мин до отсечки джобы. Bounded{ timeout: 5 }даёт grace-период на слив активных запросов, затем force-закрывает сокеты — teardown всегда завершается.Аудит открытых handles (почему forceExit можно убрать безопасно)
Прошёл по ВСЕМ спекам, которые создают внешние handles:
ai-chat-stream.int-spec.ts— реальныйhttp.createServer+ сокет-клиент; закрывается вfinallyчерезserver.close()(makeRealResponse().cleanup). Heartbeat — это счётчик, НЕ таймер (в сервисе нет ни одногоsetInterval/setTimeout), так что «залипшего» интервала нет.public-share-workspace-limiter.int-spec.ts—new Redis(..., { lazyConnect: false }); закрываетсяawait redis.quit()вafterAll.getTestDb) — закрываются boundeddestroyTestDbвafterAllкаждой спеки.Спеки НЕ поднимают полноценный Nest-app (нет
NestFactory/createTestingModule/*.module-импортов) → нет BullMQ-воркеров и лишних Redis-подключений.How verified (локальный стенд: pg18 + redis, реальный конвертер собран)
pnpm --filter server test:int(реальная CI-команда, без forceExit): 16 suites / 66 passed, процесс выходит САМ за 21–48с, без «Jest did not exit» и без «Force exiting» — 2 прогона.--detectOpenHandles --runInBand: чисто, ни одного открытого handle (3 прогона; в т.ч. при ВРЕМЕННОМ откате teardown к unbounded.end()— суита всё равно выходила сама, т.е. детерминированной течи в спеках на стенде нет).ai-chat-stream— без 60-сек залипания вafterAll.Честно: исходное CI-зависание на локальном стенде воспроизвести НЕ удалось даже с unbounded teardown (суита выходит сама, detectOpenHandles чист) — это указывает, что детерминированной течи в спеках нет, а блокировка была на уровне
.end()в специфичных для CI условиях (залипшее соединение). Bounded teardown ровно это и снимает. Финальная проверка — CI этого PR (джобаtest / test, шагRun server integration tests): она гоняетtest:intуже без forceExit и упадёт по таймауту, если где-то остался handle.Checklist
forceExitубран из jest-integration.jsondestroyTestDbоставлен (защита от залипшего соединения)--detectOpenHandlesчист, суита выходит сама ~20–48с🤖 Авто-ревью · #383 (test(int): убрать избыточный forceExit — bounded teardown уже расхангивает test:int, #382), голова
84334a1fВердикт: PASS ✅ — удаление
forceExitбезопасно и полезно; настоящий фикс (boundeddestroyTestDb) на месте и работает. Воспроизвёл объективку сам — сьют выходит чисто без forceExit. Ставлю review/approved.Дифф — ровно одна строка:
- "forceExit": trueизapps/server/test/jest-integration.json. BoundeddestroyTestDb(sql.end({ timeout: 5 })) оставлен как реальное исправление зависания.Проверено объективно (CI-fidelity, реальный прогон)
Поднял
pgvector/pgvector:pg17+redis:7в Docker (совпадает с дефолтамиdb.ts:docmost:docmost_dev_pw@localhost:5432,redis://127.0.0.1:6379), прогнал ровно как CI —jest --config test/jest-integration.json, без forceExit:Процесс вышел сам, без предупреждения «Jest did not exit» — то есть forceExit ничего живого не маскировал. (Строки
disk on fire/duplicate key/Corrupt tool_allowlistв логе — это симулируемые ошибки самих error-path тестов, не падения; все 66 зелёные.)Аудит открытых хэндлов (почему forceExit избыточен)
Прошёлся по всем 16 int-спекам — каждый долгоживущий хэндл явно закрывается:
destroyTestDbвafterAll(db.ts:88):sql.end({ timeout: 5 })закрывает пул НАПРЯМУЮ (неKysely.destroy(), который звал бы.end()без таймаута) — с жёстким грейсом, зависнуть не может. Это и был root-cause ханга (#382).global-teardown.tsтоже зовёт его.public-share-workspace-limiter.int-spec.ts, реальный ioredis, db:15) →afterAll→redis.quit().ai-chat-stream.int-spec.ts:58) → тест-скоупный,server.close()внутри самого теста.Убрав forceExit, будущая утечка хэндла теперь всплывёт как видимый ханг, а не замаскируется — плюс к наблюдаемости. Совпадает с DoD #382 (bounded teardown — защита, forceExit — лишний).
Аспекты (судил напрямую — 1-строчный тест-конфиг)
Единственный существенный вопрос PR — «выходит ли процесс сам без forceExit» — проверил реальным прогоном сьюта (Docker pgvector+redis, 66/66, exit 0, 48s, ноль «did not exit»). Ветка чистая, можно мёржить.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.