예약 시각에 취소를 누르면 실행기와 취소 요청이 같은 기록을 동시에 만질 수 있다. 각각 예전 상태를 읽고 마지막으로 저장한 쪽이 이기게 두면 취소한 일을 실행하거나 성공한 일을 취소로 덮을 수 있다. 나는 같은 예약의 상태 전이를 순서대로 읽게 하고 어떤 상태에서 취소할 수 있는지 제한했다.
상태를 읽는 일과 바꾸는 일을 같은 순서에 둔다
ProjectJobs.change와 tick의 claim은 예약 ID별 locked를 사용한다. 단순히 저장 함수만 순서대로 부르는 것이 아니라 그 안에서 현재 상태를 다시 읽고 허용 여부를 판단한다. 요청이 처음 도착했을 때 보았던 queued를 끝까지 믿지 않는다.
같은 ID의 약속된 순서가 있어야 다른 요청이 만든 running이나 cancelled를 볼 수 있다. 잠금 없이 상태 문자열만 검사하면 두 요청이 모두 예전 값을 보고 통과할 수 있다. 나는 조건문과 저장 순서가 하나의 전이 경계를 이루는지 확인했다. 상태 이름을 많이 만든 것만으로 경합이 해결되지는 않는다.
취소가 먼저면 예정 시각이 지나도 전달하지 않는다
취소 경로는 queued와 paused에서만 cancelled를 저장한다. 취소 저장이 먼저 시작했다면 같은 항목의 tick은 그 전이를 기다린 뒤 현재 상태를 읽는다. 이미 cancelled면 dispatch하지 않는다. 취소를 화면에 표시한 뒤 늦은 running 저장이 그 결과를 뒤집지 않게 한다.
기존 시험은 cancelled 저장을 gate로 붙잡은 채 예약 tick을 시작한다. 해당 항목은 실행0회여야 하고 다른 프로젝트는 진행할 수 있어야 한다. gate를 푼 뒤 디스크도 cancelled인지 확인한다. 단순히 취소 함수가 반환한 값만 보는 것보다 실제 기록과 후속 실행까지 대조하는 시험이다.
실행이 먼저면 취소 상태를 덧씌우지 않는다
반대로 running 저장을 먼저 확보했으면 뒤의 cancel은 순서가 온 뒤 상태를 읽고 STATE_DENIED로 거절한다. 실행이 이미 진행한 사실을 cancelled로 바꾸지 않는다. 실제 실행은 결과를 기다려 성공과 검토 상태로 남는다.
사용자에게는 늦은 취소가 거절된 이유를 설명해야 한다. 이것이 실제 실행 중지 기능이 없다는 뜻은 아니다. 대기 예약 취소와 실행 중 프로그램 중지는 다른 수명이다. 중지를 보냈더라도 서비스에 이미 전달된 변경은 별도 확인이 필요하다. 나는 취소라는 한 단어로 그 세 결과를 묶지 않는다.
삭제와 재개도 같은 전이 경계를 따라야 한다
프로젝트의 예약 제거는 같은 ID 잠금에서 queued와 paused를 취소한다. 이미 실행된 항목을 삭제 처리로 덮지 않는다. 재개도 paused와 미래 시각 조건을 확인한다. 버튼 종류가 달라도 같은 기록을 바꾸는 경로라면 상태 순서가 맞아야 한다.
카페 전달 큐는 별도의 serial 메타데이터 경로를 쓰므로 모든 큐가 동일한 ID 잠금 구현이라고 설명하지 않는다. 공통 원칙은 현재 상태를 읽고 허용한 전이를 저장하는 것이다. 실제 긴 업무 실행과 메타데이터의 직렬화는 구분한다. 어느 층에서 무엇을 보호하는지 알아야 새로운 버튼을 붙일 때도 기존 계약을 지킬 수 있다.
실제 순서를 고정한 시험과 현장 결과를 구분한다
실행 우선 시험은 running 저장을 gate로 멈추고 cancel을 보낸 뒤 gate를 푼다. 취소는 거절되고 dispatch는1회, 최종 상태는 succeeded여야 한다. 삭제 우선 시험도 같은 방식으로 실행0회를 확인한다. 우연히 두 타이밍이 겹치기를 기다리는 대신 순서를 고정했다.
과거 Electron·MCP 오류 시험은 메인 저장 경계에 지연을 주어 이 경로를 확인한 기록이 있다. 이번 콘텐츠 작업은 원래 앱을 조작하지 않고 해당 코드와 기록을 읽었다. 모든 네트워크와 저장 실패 조합을 새로 검수했다고 말하지 않는다. 취소와 실행의 두 순서에서 확인한 결과를 정확히 남긴다.
마지막으로 누른 버튼보다 실제 전이가 중요하다
사용자는 취소 버튼을 늦게 눌렀어도 화면에서 취소됐다고 보길 기대할 수 있다. 하지만 실제 동작이 이미 시작됐다면 그 문구는 사실과 어긋난다. 나는 마지막 버튼 입력보다 어느 전이가 먼저 확정됐는지를 결과에 반영하는 것이 낫다고 본다.
직원 승인과 결재 취소도 같은 문제를 가진다. 현재 상태에서 허용하는 일과 이미 발생한 사실을 구분해야 한다. 직관적인 화면은 무조건 원하는 문구를 보여 주는 화면보다 왜 지금 이 선택이 가능한지 설명하는 화면이다. 예약 취소를 깊게 본 이유도 그 결과를 신뢰할 수 있게 만들기 위해서였다.