<!-- task=01_kotlin_subprocess_cleanup plan=0 tag=CROSSTEST_PROCESS -->

# Plan - CROSSTEST_PROCESS

## 이 파일을 읽는 구현 에이전트에게

**구현 마지막 단계에서 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 이 파일이 비어 있으면 작업은 완료가 아니다.**
구현 체크리스트를 기준으로 작업하고, 계획과 리뷰 stub의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 실제 출력을 리뷰 파일에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게`에 있는 archive 지시와 `코드리뷰 전용 체크리스트`는 리뷰 에이전트 전용이므로 실행하거나 수정하지 않는다.

## 배경

Kotlin 서버 crosstest 중 TypeScript client runner는 timeout 시 하위 프로세스 정리와 stream join timeout을 갖고 있다. Dart/Go/Python client runner는 같은 Kotlin process orchestration 구조를 쓰지만 `destroyForcibly()`와 무제한 `join()`만 사용한다. timeout 또는 pipe 종료 실패 시 Kotlin orchestration이 멈출 수 있으므로 같은 정리 패턴으로 맞춘다.

## 분석 결과

### 읽은 파일

- `kotlin/crosstest/kotlin_dart.kt`
- `kotlin/crosstest/kotlin_go.kt`
- `kotlin/crosstest/kotlin_python.kt`
- `kotlin/crosstest/kotlin_typescript.kt`

### 테스트 커버리지 공백

- timeout 경로의 process tree/stream reader 정리: 기존 정상 crosstest는 성공 경로만 확인한다. 강제로 child process를 멈추게 하는 회귀 테스트는 없다.
- 정상 경로의 PASS line parsing: 기존 `./gradlew run -PmainClass=...` crosstest로 확인 가능하다.

### 심볼 참조

- renamed/removed symbol: none.
- 참고 심볼: `destroyProcessTree`는 `kotlin/crosstest/kotlin_typescript.kt:294`에만 있고, `STREAM_JOIN_TIMEOUT_MS`는 `kotlin/crosstest/kotlin_typescript.kt:42`에만 있다.

### 분할 판단

split decision policy를 먼저 평가했다. 이 task는 Kotlin 도메인의 세 runner만 다루며, 동일한 process-control 패턴을 같은 파일 구조에 적용하는 하나의 기계적 변경이다. Dart/Go/Python orchestrator 변경은 언어별 process API와 검증 방식이 달라 별도 sibling task로 분리했다.

Sibling task:

- `01_kotlin_subprocess_cleanup`: Kotlin server가 Dart/Go/Python client를 실행하는 runner 정리.
- `02_dart_typescript_process_cleanup`: Dart server가 TypeScript client를 실행하는 runner 정리.
- `03_go_typescript_process_cleanup`: Go server가 TypeScript client를 실행하는 runner 정리.
- `04_python_typescript_process_cleanup`: Python server가 TypeScript client를 실행하는 runner 정리.

### 범위 결정 근거

`kotlin/crosstest/kotlin_typescript.kt`는 이미 기준 패턴을 갖고 있으므로 검증 참조로만 사용하고 수정하지 않는다. Kotlin core implementation, protobuf schema, client helper behavior는 변경하지 않는다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. process control, stdout/stderr parsing, timeout exit-status contract를 다루는 terminal-agent 성격의 crosstest runner 작업이다.

## 구현 체크리스트

- [ ] [CROSSTEST_PROCESS-1] Kotlin Dart/Go/Python client runner의 timeout process cleanup을 Kotlin TypeScript runner 수준으로 맞춘다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [CROSSTEST_PROCESS-1] Kotlin subprocess cleanup 통일

#### 문제

`kotlin/crosstest/kotlin_dart.kt:322`, `kotlin/crosstest/kotlin_go.kt:322`, `kotlin/crosstest/kotlin_python.kt:321`은 child timeout 후 직접 process만 kill한다. 이어서 `stdoutThread.join()`/`stderrThread.join()`을 무제한 호출한다(`kotlin/crosstest/kotlin_dart.kt:326`, `kotlin/crosstest/kotlin_go.kt:326`, `kotlin/crosstest/kotlin_python.kt:325`). TypeScript runner의 보강된 기준은 `destroyProcessTree`와 bounded join이다(`kotlin/crosstest/kotlin_typescript.kt:294`, `kotlin/crosstest/kotlin_typescript.kt:344`).

#### 해결 방법

세 파일에 `STREAM_JOIN_TIMEOUT_MS`와 `destroyProcessTree`를 추가하고 timeout 경로 및 join을 기준 패턴과 맞춘다.

Before (`kotlin/crosstest/kotlin_go.kt:322`):

```kotlin
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
    process.destroyForcibly()
}
stdoutThread.join()
stderrThread.join()
```

After:

```kotlin
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
    destroyProcessTree(process)
}
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
    "$label stream readers did not finish"
}
```

구현 시 `label`이 없는 파일은 `validateResultLines`에 넘기는 문자열과 같은 값을 지역 변수로 빼서 사용한다.

#### 수정 파일 및 체크리스트

- [ ] `kotlin/crosstest/kotlin_dart.kt`: `STREAM_JOIN_TIMEOUT_MS`, `destroyProcessTree`, bounded join 적용.
- [ ] `kotlin/crosstest/kotlin_go.kt`: 같은 변경 적용.
- [ ] `kotlin/crosstest/kotlin_python.kt`: 같은 변경 적용.

#### 테스트 작성

별도 테스트 파일은 작성하지 않는다. 변경 대상 자체가 executable crosstest runner이고, timeout 강제 회귀 테스트는 child process fixture를 새로 만들어야 하므로 이번 범위를 넘는다. 정상 경로는 각 runner의 전체 crosstest로 검증한다.

#### 중간 검증

```bash
cd kotlin
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt
```

예상 결과: 세 명령 모두 `PASS all kotlin-server/...-client crosstests passed`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `kotlin/crosstest/kotlin_dart.kt` | CROSSTEST_PROCESS-1 |
| `kotlin/crosstest/kotlin_go.kt` | CROSSTEST_PROCESS-1 |
| `kotlin/crosstest/kotlin_python.kt` | CROSSTEST_PROCESS-1 |

## 최종 검증

```bash
cd kotlin
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinDartKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinGoKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinPythonKt
./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
```

예상 결과: 네 명령 모두 PASS. Gradle cache 출력은 허용한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
