From a6485292e409f738e74493af68fd473a3824ab6c Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 18 Jun 2026 22:57:48 +0900 Subject: [PATCH] =?UTF-8?q?docs(dm):=20MESSAGE=20race=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=EC=9D=84=20=EA=B8=B0=EB=A1=9D=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/20260610_DM_채팅화면/plan-task.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/20260610_DM_채팅화면/plan-task.md b/docs/20260610_DM_채팅화면/plan-task.md index d892064c..66bc5950 100644 --- a/docs/20260610_DM_채팅화면/plan-task.md +++ b/docs/20260610_DM_채팅화면/plan-task.md @@ -650,7 +650,7 @@ - 2026-06-18: `DmChatRoomViewModelTest`에 requestId 기반 독립 pending, `SEND_ACK` requestId 매칭, `ERROR` 실패, timeout 실패, retry 새 requestId, 중복 ACK 무시 테스트를 먼저 추가했다. 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` 실행 시 `DmChatMessageUiItem.requestId` 미정의 컴파일 오류로 RED를 확인했고, timeout 테스트는 구현 전 `DmChatRoomViewModelTest.kt:270` assertion failure로 RED를 확인했다. - 2026-06-18: `DmChatMessageUiItem`에 `requestId`를 추가하고, `DmChatRoomViewModel`의 단일 `isSending` 제한을 제거해 각 텍스트 전송이 새 `requestId`와 pending map으로 관리되도록 변경했다. `SEND_TEXT`는 WebSocket으로 전송하고, `SEND_ACK`/`ERROR`/send false/10초 timeout은 해당 requestId의 local item만 확정 또는 실패 처리한다. retry는 기존 `localId` item을 유지하면서 새 `requestId`를 발급하도록 변경했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` PASS를 확인했다. -- [ ] **Task 10.4: SEND_ACK보다 MESSAGE가 먼저 도착하는 race 처리** +- [x] **Task 10.4: SEND_ACK보다 MESSAGE가 먼저 도착하는 race 처리** - Files: - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt` - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt` @@ -662,8 +662,11 @@ - 검증: - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` - Expected: MESSAGE 선도착, ACK 후도착, timeout 후 ACK 복구, 중복 `messageId` 방지 테스트가 PASS. + - 검증 기록: + - 2026-06-18: `DmChatSocketParserTest`에 `MESSAGE` payload의 nullable `requestId` 보존 테스트를 추가하고, `DmChatRoomViewModelTest`에 `MESSAGE(requestId)` 선도착 시 pending local item 확정, `MESSAGE`에 `requestId`가 없는 경우 후도착 `SEND_ACK`와 `messageId` 중복 방지, timeout 실패 후 늦은 `SEND_ACK`의 같은 local item 복구 테스트를 추가했다. 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatSocketParserTest" --max-workers=1` 및 ViewModel 대상 테스트 실행에서 `DmChatSocketParserTest.kt:61`의 `DmChatSocketEvent.Message.requestId` 미정의 컴파일 오류로 RED를 확인했다. 이후 `DmChatSocketEvent.Message`와 parser가 nullable `requestId`를 보존하도록 변경하고, ViewModel이 `MESSAGE(requestId)`를 `SEND_ACK`와 동일한 pending 확정 경로로 처리하며 timeout 후 실패 requestId도 늦은 ACK로 복구할 수 있도록 변경했다. 재실행 결과 `DmChatSocketParserTest`와 `DmChatRoomViewModelTest`가 PASS했다. 병렬 Gradle 실행 중 1회 `kspDebugUnitTestKotlin`의 `StreamCorruptedException`이 발생했으나, 같은 ViewModel 테스트 단독 재실행에서 PASS를 확인했다. + - 2026-06-18: 리뷰에서 timeout된 `request-1`이 실패 기록에 남은 상태로 같은 local item을 `request-2`로 retry하고 `request-2` ACK 성공 후 늦은 `request-1` ACK가 도착하면 최신 성공 메시지를 오래된 ACK로 덮을 수 있다는 blocker를 확인했다. `DmChatRoomViewModelTest`에 `retry 성공 후 이전 timeout request ACK는 같은 local item을 덮어쓰지 않는다` 테스트를 추가했고, 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` 실행 결과 `DmChatRoomViewModelTest.kt:466` assertion failure로 RED를 확인했다. 이후 retry 시작 및 ACK 성공 확정 시 같은 `localId`에 묶인 stale failed request 기록을 제거하도록 변경했고, 같은 ViewModel 테스트 재실행 결과 PASS를 확인했다. -- [ ] **Task 10.5: Phase 9 코드 리뷰 결과 회귀 테스트 반영** +- [x] **Task 10.5: Phase 9 코드 리뷰 결과 회귀 테스트 반영** - Files: - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt` - Modify: `docs/20260610_DM_채팅화면/plan-task.md` @@ -675,6 +678,9 @@ - 검증: - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` - Expected: Phase 9 리뷰 3개 항목 중 ViewModel 세션/전송 항목은 Phase 10 테스트로 고정되고, leave/close 항목은 부정확한 테스트 제거 또는 Task 11.1 이관 기록으로 남는다. + - 검증 기록: + - 2026-06-18: Phase 9 리뷰 항목 중 `JOINED` 전 connected 미확정은 Task 10.1의 `roomId가 있으면 realtime 연결 후 connected callback에서 최신 메시지를 동기화한다` 테스트에서 `JOINED` 전 `isRealtimeConnected=false`, `JOINED` 후 `true`로 고정되어 있음을 확인했다. requestId 단위 독립 pending은 Task 10.3의 `서로 다른 텍스트는 각각 requestId로 독립 pending 전송한다` 테스트로 고정되어 있음을 확인했다. `LEAVE_ROOM + close` lifecycle 상세 검증은 Task 11.1 범위로 남기고, 실제 연결 없이 `closeCount == 0`만 확인하던 `realtime leave 중 중복 요청은 close를 반복할 수 있다` 테스트는 부정확한 회귀 테스트로 제거했다. Task 10.4까지 포함한 `DmChatRoomViewModelTest`는 이후 재실행해 PASS를 확인한다. + - 2026-06-18: Phase 10 전체 단위 코드 리뷰로 `DmChatRoomViewModel`, `DmChatSocketModels`, `DmChatRoomViewModelTest`, `DmChatSocketParserTest`의 diff를 재검토했다. `MESSAGE(requestId)` 선도착 시 pending local item 확정, requestId 없는 `MESSAGE`와 후도착 `SEND_ACK`의 `messageId` 중복 제거, timeout 후 늦은 `SEND_ACK` 복구, retry 성공 후 이전 timeout request ACK 무시, Phase 9 리뷰 회귀 테스트 반영 상태를 확인했고 blocking issue는 발견하지 못했다. fresh 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatSocketParserTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. 최초 샌드박스 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했다. Gradle deprecation warning은 출력됐지만 실패는 없었다. ### Phase 11: Lifecycle, reconnect, heartbeat, token 갱신 처리