Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .bkit-memory.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
{
"activeFeature": null,
"phase": "archived",
"activeFeature": "asca-jest-teardown-leak",
"phase": "do",
"startedAt": "2026-04-22",
"parallelFeatures": [
{
"feature": "asca-jest-teardown-leak",
"phase": "do",
"startedAt": "2026-06-14",
"planPath": "docs/01-plan/features/asca-jest-teardown-leak.plan.md",
"scope": "jest 'worker failed to exit gracefully' open-handle ๋ˆ„์ˆ˜ ์ œ๊ฑฐ. ๊ทผ๋ณธ์›์ธ=StructuredLogger(lib/logging/structured-logger.ts:435 ๋ชจ๋“ˆ import์‹œ getInstanceโ†’constructor:172โ†’startProcessing:397 setInterval 100ms)๊ฐ€ ํ…Œ์ŠคํŠธ์—์„œ ์•ˆ ๊บผ์ง(--detectOpenHandles 6ํ•ธ๋“ค ์ „๋ถ€ ๋™์ผ). R-1=startProcessing interval .unref()(1์ค„, ํ”„๋กœ๋•์…˜ ๋ฌด๋ณ€๊ฒฝ, child logger๋„ ๋™์ผ ๊ฒฝ๋กœ๋ผ ์ผ๊ด„ ํ•ด๊ฒฐ).",
"doResult": "โœ… Do ์™„๋ฃŒ(2026-06-15). startProcessing() setInterval์— `if(typeof unref==='function') unref()` ์ถ”๊ฐ€(๋ธŒ๋ผ์šฐ์ € ์•ˆ์ „). ๊ฒ€์ฆ ์ „๋ถ€ ์ถฉ์กฑ: SC-1 detectOpenHandles์—์„œ 'Jest has detected open handles' ์‚ฌ๋ผ์ง(6โ†’0), SC-2 test:ci force-exit ๊ฒฝ๊ณ  0ยทexit 0, SC-3 389 passยทtsc 0ยทlint 0 errors(10 max-lines warning ๋ถˆ๋ณ€), SC-4 ํ”„๋กœ๋•์…˜ ๋ฌด๋ณ€๊ฒฝ(์ฝ”๋“œ diff=unref ๊ฐ€๋“œ 1๋ธ”๋ก). ๊ธฐ๋Šฅ ์ฝ”๋“œ 1ํŒŒ์ผ.",
"source": "/check 2026-06-13 test:ci ๊ฒฝ๊ณ  + jest --detectOpenHandles 2026-06-14",
"nextStep": "์ปค๋ฐ‹/PR โ†’ /pdca report"
},
{
"feature": "asca-csrf-origin-check",
"phase": "archived",
Expand Down
2 changes: 1 addition & 1 deletion .commit_message.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
๐Ÿ“ archive(pdca): warning-cleanup-cycle-2 stale (2026-04-22 ์‹œ์ž‘, 5์ฃผ+ ๋ฐฉ์น˜, analysis/report ๋ฏธ์ž‘์„ฑ) โ†’ docs/archive/2026-05/warning-cleanup-cycle-2-stale/
๐Ÿงช fix(logging): StructuredLogger processingInterval .unref() โ€” jest worker teardown ๋ˆ„์ˆ˜ ์ œ๊ฑฐ. lib/logging/structured-logger.ts:435 ๋ชจ๋“ˆ import์‹œ getInstanceโ†’startProcessing setInterval(100ms)์ด ํ…Œ์ŠคํŠธ์—์„œ ์•ˆ ๊บผ์ ธ "worker failed to exit gracefully" ๊ฒฝ๊ณ /์ž ์žฌ CI flaky ์œ ๋ฐœ(--detectOpenHandles 6ํ•ธ๋“ค). startProcessing interval์— unref(๋ธŒ๋ผ์šฐ์ € ์•ˆ์ „ ๊ฐ€๋“œ typeof) 1์ค„ ์ถ”๊ฐ€ โ†’ ํ”„๋กœ๋•์…˜ ๋™์ž‘ ๋ฌด๋ณ€๊ฒฝ(์•ฑ ์‚ด์•„์žˆ๋Š” ๋™์•ˆ ํ ์ฒ˜๋ฆฌ ๊ทธ๋Œ€๋กœ), child logger๋„ ๋™์ผ ๊ฒฝ๋กœ๋ผ ์ผ๊ด„ ํ•ด๊ฒฐ. ๊ฒ€์ฆ: detectOpenHandles 0ํ•ธ๋“คยทtest:ci exit 0/389 passยทforce-exit ๊ฒฝ๊ณ  ์†Œ๋ฉธยทtsc 0ยทlint 0 errors. asca-jest-teardown-leak do ์™„๋ฃŒ
124 changes: 124 additions & 0 deletions docs/01-plan/features/asca-jest-teardown-leak.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
template: plan (lean)
feature: asca-jest-teardown-leak
date: 2026-06-14
author: jaehong
project: ASCA (my-v0-project)
status: draft
---

# asca-jest-teardown-leak Plan

> **Summary**: jest๊ฐ€ ๋งค `test:ci` ์‹คํ–‰๋งˆ๋‹ค "A worker process has failed to exit
> gracefully ... improper teardown" ๊ฒฝ๊ณ ๋ฅผ ๋‚ด๋Š” **open-handle ๋ˆ„์ˆ˜**๋ฅผ
> ๊ฒฐ์ •์ ์œผ๋กœ ์ œ๊ฑฐํ•œ๋‹ค. ๊ทผ๋ณธ ์›์ธ์€ `StructuredLogger`์˜ `setInterval`์ด ๋ชจ๋“ˆ
> import ์‹œ ์ผœ์ง€๊ณ  ํ…Œ์ŠคํŠธ์—์„œ ์•ˆ ๊บผ์ง€๋Š” ๊ฒƒ. **CI flaky ์ž ์žฌ ์š”์ธ**(force-exit๊ฐ€
> ๊ฐ„ํ— ํƒ€์ด๋ฐ ์ด์Šˆ๋กœ ๋ฒˆ์งˆ ์ˆ˜ ์žˆ์Œ, smart-quote dashboard-test-flakiness์™€ ๋™์ผ
> ํด๋ž˜์Šค โ€” [[feedback_partial_hook_mock_teardown_flaky]]). **PDCA Phase**: Plan
> ยท **Source**: `/check`(2026-06-13) test:ci ๊ฒฝ๊ณ  +
> `jest --detectOpenHandles`(2026-06-14)

---

## 1. ๊ทผ๋ณธ ์›์ธ (๊ฒ€์ฆ๋จ โ€” `--detectOpenHandles` 6 handles ์ „๋ถ€ ๋™์ผ)

```
StructuredLogger.startProcessing lib/logging/structured-logger.ts:397
this.processingInterval = setInterval(() => this.processQueue(), 100)
```

์—ฐ์‡„:

- `lib/logging/structured-logger.ts:435`
`export const logger = StructuredLogger.getInstance()` โ†’ **๋ชจ๋“ˆ import
์‹œ์ **์— ์ธ์Šคํ„ด์Šคํ™”
- โ†’ `private constructor`(156)๊ฐ€ `this.startProcessing()`(172) ํ˜ธ์ถœ โ†’
`setInterval(โ€ฆ, 100ms)`(397)
- โ†’ `logger`๋ฅผ (์ง์ ‘/transitive) importํ•˜๋Š” ๋ชจ๋“  ํ…Œ์ŠคํŠธ์—์„œ ์ธํ„ฐ๋ฒŒ์ด ์‚ด์•„ ์žˆ์–ด
jest๊ฐ€ ์ข…๋ฃŒ ๋ชป ํ•จ โ†’ force-exit ๊ฒฝ๊ณ 
- child logger(`new StructuredLogger`, 291)๋„ ๋™์ผ ์ธํ„ฐ๋ฒŒ ์ƒ์„ฑ
- ์ด๋ฏธ `clearInterval` ์ •๋ฆฌ ์ฝ”๋“œ(321-323, `destroy`/flush ๋ฅ˜)๋Š” ์žˆ์œผ๋‚˜
**ํ…Œ์ŠคํŠธ์—์„œ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ**

๋ˆ„์ˆ˜ ํ™•์ธ ํ…Œ์ŠคํŠธ(์˜ˆ): `app/api/graphql/__tests__/route.test.ts:70`,
`lib/services/__tests__/member.service.test.ts:15`,
`lib/api/__tests__/response.test.ts:13` ๋“ฑ.

---

## 2. Scope

### In Scope

- [ ] `lib/logging/structured-logger.ts` `startProcessing()`์˜ interval์„
**`.unref()`** ์ฒ˜๋ฆฌ โ†’ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ด ํƒ€์ด๋จธ๋กœ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ถ™์žก์ง€ ์•Š๊ฒŒ ํ•จ
(ํ”„๋กœ๋•์…˜: ์•ฑ์ด ์‚ด์•„์žˆ๋Š” ๋™์•ˆ ์ธํ„ฐ๋ฒŒ์€ ๊ทธ๋Œ€๋กœ ๋™์ž‘, ์ข…๋ฃŒ๋งŒ ์•ˆ ๋ง‰์Œ)
- [ ] ๋ธŒ๋ผ์šฐ์ €-์•ˆ์ „ ๊ฐ€๋“œ (๋ธŒ๋ผ์šฐ์ € `setInterval` ๋ฐ˜ํ™˜๊ฐ’์—” `.unref` ์—†์Œ) โ€”
optional ํ˜ธ์ถœ
- [ ] ๊ฒ€์ฆ: `jest --detectOpenHandles`์—์„œ StructuredLogger ํ•ธ๋“ค 0, `test:ci`
๊ฒฝ๊ณ  ์†Œ๋ฉธ + exit 0, ์ „์ฒด 389 green

### Out of Scope (๋ณ„๋„/๋ถˆ์š”)

- child logger ๋ผ์ดํ”„์‚ฌ์ดํด ๋ฆฌํŒฉํ„ฐยทํ…Œ์ŠคํŠธ-side `afterAll(stop)` ํ›… (unref๋ฉด
๋ถˆ์š”)
- ํ”„๋กœ๋•์…˜ ๋กœ๊น… ๋™์ž‘ ๋ณ€๊ฒฝ (์ธํ„ฐ๋ฒŒ ์ฃผ๊ธฐยทํ ์ฒ˜๋ฆฌ ๋กœ์ง ๊ทธ๋Œ€๋กœ)
- max-lines component-split (๋ณ„ ์‚ฌ์ดํด)

---

## 3. ์ˆ˜์ • ๋ฐฉํ–ฅ (R-1)

```ts
// startProcessing() ๋‚ด๋ถ€
this.processingInterval = setInterval(() => {
this.processQueue()
}, 100)
// ์ถ”๊ฐ€: ํ…Œ์ŠคํŠธ/CLI๊ฐ€ ์ด ํƒ€์ด๋จธ ๋•Œ๋ฌธ์— ์ข…๋ฃŒ ๋ชป ํ•˜๋Š” ๊ฒƒ ๋ฐฉ์ง€ (ํ”„๋กœ๋•์…˜ ๋ฌด์˜ํ–ฅ)
this.processingInterval?.unref?.()
```

| ์˜ต์…˜ | ์ฑ„ํƒ | ์‚ฌ์œ  |
| ---------------------------------------- | :--: | ----------------------------------------------------------------------------------- |
| **(a) `.unref()`** | โœ… | 1์ค„ยทํ”„๋กœ๋•์…˜ ๋™์ž‘ ๋ฌด๋ณ€๊ฒฝยท๋ชจ๋“  ๋ˆ„์ˆ˜ ํ…Œ์ŠคํŠธ ์ผ๊ด„ ํ•ด๊ฒฐ. Node ํƒ€์ด๋จธ ํ‘œ์ค€ ํŒจํ„ด |
| (b) `NODE_ENV==='test'`๋ฉด interval skip | โœ— | ํ…Œ์ŠคํŠธ์„œ ํ ์ž๋™์ฒ˜๋ฆฌ ์•ˆ ๋จ โ†’ ๋กœ๊น… ์˜์กด ํ…Œ์ŠคํŠธ ์˜ํ–ฅ ๊ฐ€๋Šฅ + ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— test ๋ถ„๊ธฐ |
| (c) ํ…Œ์ŠคํŠธ๋งˆ๋‹ค `afterAll(logger.stop())` | โœ— | ๋‹ค์ˆ˜ ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์ˆ˜์ •ยท๋ˆ„๋ฝ ์œ„ํ—˜. unref๊ฐ€ ๊ทผ๋ณธ์  |

> `.unref()`๋Š” ํ”„๋กœ๋•์…˜์—์„œ "์•ฑ์ด ์ข…๋ฃŒ๋  ๋•Œ ์ด ์ธํ„ฐ๋ฒŒ์ด ์ข…๋ฃŒ๋ฅผ ์ง€์—ฐ์‹œํ‚ค์ง€
> ์•Š๋Š”๋‹ค"๋งŒ ์˜๋ฏธ โ€” ์ •์ƒ ์‹คํ–‰ ์ค‘์—” 100ms ํ ์ฒ˜๋ฆฌ ๊ทธ๋Œ€๋กœ ๋™์ž‘. child logger๋„ ๊ฐ™์€
> `startProcessing` ๊ฒฝ์œ ๋ผ ํ•œ ๋ฒˆ ์ˆ˜์ •์œผ๋กœ ์ปค๋ฒ„.

---

## 4. Success Criteria

| # | ๋ชฉํ‘œ | ์ธก์ • |
| ---- | -------------------- | ------------------------------------------------------------------------ |
| SC-1 | ํ•ธ๋“ค ๋ˆ„์ˆ˜ ์ œ๊ฑฐ | `jest --detectOpenHandles`์—์„œ StructuredLogger setInterval ํ•ธ๋“ค **0** |
| SC-2 | force-exit ๊ฒฝ๊ณ  ์†Œ๋ฉธ | `test:ci` ๋กœ๊ทธ์— "worker ... failed to exit gracefully" **์—†์Œ**, exit 0 |
| SC-3 | ํšŒ๊ท€ 0 | ์ „์ฒด test:ci 389 passยท์ปค๋ฒ„๋ฆฌ์ง€ ์ž„๊ณ„ ํ†ต๊ณผ, tsc 0ยทlint 0 errors |
| SC-4 | ํ”„๋กœ๋•์…˜ ๋ฌด๋ณ€๊ฒฝ | ๋กœ๊น… ํ/์ฃผ๊ธฐ ๋™์ž‘ ๋™์ผ (์ฝ”๋“œ diff = unref 1์ค„ + ๊ฐ€๋“œ) |

---

## 5. Risks

| ๋ฆฌ์Šคํฌ | ์™„ํ™” |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ `.unref` undefined | optional ํ˜ธ์ถœ `?.unref?.()` |
| unref๋กœ ํ”„๋กœ๋•์…˜ ๋กœ๊ทธ ์œ ์‹ค? | โŒ ์•„๋‹˜ โ€” unref๋Š” ์ข…๋ฃŒ ์ง€์—ฐ๋งŒ ํ•ด์ œ, ์‹คํ–‰ ์ค‘ ๋™์ž‘ ๋ฌด๋ณ€๊ฒฝ. (์•ฑ ์ข…๋ฃŒ ์‹œ ๋งˆ์ง€๋ง‰ ํ flush๋Š” ๊ธฐ์กด destroy/clearInterval(321) ๊ฒฝ๋กœ๊ฐ€ ๋‹ด๋‹น) |
| ๋‹ค๋ฅธ ๋ˆ„์ˆ˜์› ์ž”์กด | detectOpenHandles ์žฌ์‹คํ–‰์œผ๋กœ StructuredLogger ์™ธ ํ•ธ๋“ค 0 ํ™•์ธ |

---

## 6. Next

1. `/pdca do asca-jest-teardown-leak` โ€” `startProcessing()` unref 1์ค„ โ†’
`jest --detectOpenHandles` 0 ํ•ธ๋“ค + `test:ci` ๊ฒฝ๊ณ  ์†Œ๋ฉธ ํ™•์ธ โ†’ PR
2. Design ์ƒ๋žต ๊ถŒ๊ณ  (1์ค„ fix, ๊ทผ๋ณธ์›์ธ ๋ช…ํ™•)

## Version History

| Ver | Date | Changes | Author |
| --- | ---------- | -------------------------------------------------------------------------------------------- | ------- |
| 0.1 | 2026-06-14 | ์ตœ์ดˆ. detectOpenHandles๋กœ ๊ทผ๋ณธ์›์ธ(StructuredLogger setInterval ๋ฏธํ•ด์ œ) ํ™•์ •, R-1=`.unref()` | jaehong |
5 changes: 5 additions & 0 deletions lib/logging/structured-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,11 @@ export class StructuredLogger {
this.processingInterval = setInterval(() => {
this.processQueue()
}, 100)
// ์ด ํƒ€์ด๋จธ๊ฐ€ Node ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ(ํŠนํžˆ jest ํ…Œ์ŠคํŠธ teardown)๋ฅผ ๋ง‰์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.
// ํ”„๋กœ๋•์…˜ ๋™์ž‘์€ ๋™์ผ โ€” ์•ฑ์ด ์‚ด์•„์žˆ๋Š” ๋™์•ˆ ํ ์ฒ˜๋ฆฌ๋Š” ๊ทธ๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค.
if (typeof this.processingInterval.unref === 'function') {
this.processingInterval.unref()
}
}

private async processQueue(): Promise<void> {
Expand Down
Loading