"'진행 중인 녹음 없음' — 앱이 말했어, 녹음이 진행 중인 동안."
기능과 크래시
탭 문법은 힘 안 들게 하려던 거야: 홀드 로 눌러서-말하기; 빠른 탭 다음 두 번째 탭으로 핸즈프리 토글. 첫 구현은 뻔했고 틀렸어: 빠른 탭이 세션을 취소하고, 두 번째 탭이 새 핸즈프리를 시작. 가끔 됐어. 가끔 '진행 중인 녹음 없음' 으로 죽었어 — 찍히는 그 순간에, 사실상 거짓인 에러 메시지.
레이스
첫 탭이 async 취소를 쏴. 두 번째 탭이 새 세션을 시작. 취소가 느리면 — 가끔 느려, 오디오 무너뜨리기가 즉각적이지 않으니까 — 새 세션이 존재한 후 도착하고, 자기가 낡았는지 모르고, 엉뚱한 녹음을 무너뜨려. 이미 죽은 세션의 유령이 손을 뻗어 산 걸 죽여. 그다음 세션을 기대한 코드가 아무것도 못 찾고, 그 어이없음을 신고해.
탭 1 -> 세션 A 취소 (async, 느림...)
탭 2 -> 세션 B 시작 (빠름)
... A-취소가 드디어 착륙 ...
-> B 를 무너뜨림 # 유령이 엉뚱한 세션을 죽임
-> "진행 중인 녹음 없음"
해법 둘, 둘 다 필요
첫 해법이 레이스를 통째로 없애: 세션을 절대 재시작 안 함. 빠른 탭이 더는 취소 안 해 — 짧은 결정 창(약 0.45초) 동안 세션을 녹음 중으로 둬. 두 번째 탭이 오면, 같은 세션이 제자리에서 핸즈프리로 변환; 창이 두 번째 탭 없이 만료되면, 그때 취소. 유령이 착륙할 무너뜨리기-다음-시작 틈이 없어. 두 번째 해법은 심층 방어: 모든 오디오 세션에 세대 토큰 을 찍고, 어떤 취소든 작동 전에 자기 세대를 확인하게 해. 세대 1 의 취소가 세대 2 에 도착하면 그냥 no-op 이야. 해법 하나가 레이스를 없애고; 해법 둘이 레이스가 다시 나타나도 피해를 못 주게 보장해.