모든 이야기

응답을 기다리다 포기한 쓰기가 나중에 실행되면 생기는 일

응답이 너무 늦어지는 버스를 만나면 bridge에 timeout을 넣고 오류를 반환하고 싶어진다. 시스템이 영원히 기다리는 것을 막는다는 목적은 분명하다. 그런데 upstream에 실패를 알린 뒤 downstream이 원래 쓰기를 수행하면 어떻게 될까? bridge timeout은 대기 시간을 줄이는 장치인 동시에, 이미 수락한 거래의 책임을 정하는 장치여야 한다.

시간 제한을 다룰 때 첫 구분은 요청이 downstream에서 수락되었는지다. 아직 handshake가 없었다면 대상이 수행할 거래가 생기지 않은 상태와 연결할 수 있다. 이미 수락되었다면 대상이 작업 중일 수 있다. 또한 AXI에서 valid로 제시한 정보를 아직 수락하지 않았다는 이유만으로 임의 철회할 수는 없다. handshake까지 유지해야 하는 약속과, 경로를 격리하거나 리셋하는 복구 정책을 구분해야 한다. upstream에서 오류를 보고했다고 그 대상의 내부 실행이 되돌아가지는 않는다. AXI에는 이미 수락한 일반 거래를 임의 시점에 취소하는 공통 취소 채널이 없다. Arm IHI0022H, A3.2·A5.1의 일반 거래 계약

가상 bridge가 요청 수락 뒤 응답 대기를 최대 16사이클로 정했다고 하자. downstream 장치는 쓰기 한 번에 내부 카운터를 1 증가시키며, 응답만 늦게 돌아오는 상황이다. 카운터의 초기값은 0이고 다른 접근은 없다.

경계 0: 원래 쓰기가 downstream에 수락됨
경계 3: 카운터가 1로 증가
경계 16: bridge가 upstream에 timeout 오류를 반환
경계 18: host가 같은 쓰기를 재시도하여 수락됨
경계 20: 원래 쓰기의 늦은 응답 도착
경계 21: 재시도로 카운터가 2로 증가

host가 의도한 동작이 카운터 한 번 증가였다면 결과 2는 맞지 않는다. timeout은 “정해진 시간 안에 응답을 확인하지 못했다”는 사실을 말한다. “요청이 실행되지 않았다”는 사실까지 보장한 것은 아니다. 같은 주소에 같은 데이터로 다시 쓰는 접근이더라도 대상 레지스터가 명령을 실행하는 의미라면 부작용은 두 번 생길 수 있다.

어떤 bridge에서는 이런 경계 정책이 프로토콜을 연결하기 위해 필요하다. TileLink 1.7.1의 legacy bus 연결 설명은 진행을 보장하지 않는 버스에 대한 제한과 오류 반환을 다룬다. 미검증 legacy 쪽 응답이 제한을 넘으면 TileLink 오류를 만들고, 원래 응답이 나중에 오면 버려야 한다. 반대로 TileLink 네트워크 내부에서 기다리는 메시지를 다른 메시지로 바꾸는 timeout은 금지하며 watchdog reset과도 구분한다. 연결 경계와 네트워크 내부의 규칙이 다르다. TileLink 1.7.1, 4.4 Interfacing with Legacy Buses

여기서 문서가 bridge의 요청을 취소한다고 표현하는 것을, AXI 대상이 이미 수행한 효과까지 없앤다는 뜻으로 읽어서는 안 된다. bridge가 upstream 거래를 오류로 끝내고 늦은 응답을 정리하는 것과, 대상이 수행한 쓰기를 rollback하는 것은 다르다. 후자를 원한다면 대상과 복구 방식에 그 의미를 추가로 약속해야 한다.

늦은 응답은 새로운 요청과 섞이지 않도록 추적해야 한다. 가상 구현이 downstream ID를 하나만 쓰는데 timeout 시점에 그 ID의 상태를 즉시 비우고 재시도에 다시 사용했다고 하자. 경계 20의 응답을 현재 pending 요청의 성공으로 처리하면 새 요청을 너무 일찍 완료시킨다. 늦은 응답에 “과거 거래”라는 추가 비트가 달려 있지 않으면 단순 비교로 구별할 수 없다.

일반 AXI4에서 같은 ID의 응답 순서를 이용해 해결할 수도 있지만, 과거 거래에 대한 추적이 남아 있어야 한다. 예를 들어 timeout된 첫 항목을 버릴 대상으로 표시한 채 보관하고, 그 항목의 늦은 응답을 먼저 소모한 뒤 새 항목의 응답을 기다린다. 읽기 burst라면 늦은 한 beat만 지우는 것이 아니라 원래 거래의 남은 응답까지 처리해야 한다. 다른 ID와 복수 거래를 지원하면 이에 맞는 구분 상태가 필요하다.

문제는 응답이 영원히 오지 않을 수 있다는 점이다. 버릴 항목을 끝없이 보관하면서 계속 새 거래를 허용하면 추적 공간이 무한히 필요해질 수 있다. timeout 뒤 대상 경로를 격리하고 복구 절차를 거치게 하거나, 미완료 거래 수를 제한하는 정책이 필요할 수 있다. TileLink의 같은 절도 이런 저장 한계를 고려해 막힌 legacy 버스를 비활성화하는 선택을 허용한다. 실제 선택은 시스템이 약속한 복구와 진행 요구에 맞춰야 한다.

시간 숫자도 명확하게 정의해야 한다. 요청을 처음 제시한 순간부터인지, downstream 수락 뒤부터인지, 응답 valid를 기다리는 시간인지, 응답 수락까지의 시간인지에 따라 같은 파형에서 결과가 달라진다. upstream이 응답 ready를 내려 정상 응답을 받지 않는 시간까지 대상의 실패로 세면 정상 거래를 timeout으로 오해할 수 있다. 위 예제는 downstream 수락부터 응답의 도착을 기다리는 제한으로 한정했다.

bridge timeout을 설계할 때는 제한 값을 고르기 전에 시간 초과 뒤에도 남는 일을 적어 보자. 이미 발생한 부작용, 늦은 응답, ID의 재사용, retry가 각각 어떤 상태를 요구하는지 보인다. 기다림을 오류로 끝내는 기능이 정확한 거래 의미를 제공하려면, 대상이 정말 실행하지 않았는지 또는 실행 여부가 불확실한지까지 복구 계약에서 구별해야 한다.