Devin.KR

시간 초과와 재시도 한도

95분 안팎

학습 목표

20ms 시간 초과 뒤 최대 3회 시도하고 실패하면 오류 상태로 전이합니다.

개념

요청 하나의 제한과 전체 예산

무응답 센서를 기다리는 동안 버튼 정지까지 멈추면 통신 오류가 사용자 제어 오류로 커집니다. 요청 하나를 끝낼 시간과 다시 시도할 횟수를 나누어 정합니다. 이 실습은 실제 대기 대신 now와 started를 받아 판단하는 C 함수를 만듭니다. 판단 함수 안에서 sleep이나 반복 재시도를 하지 않기 때문에 메인 루프가 다른 이벤트를 처리할 수 있습니다. 기다리는 상태 자체를 반환값으로 드러냅니다.

20ms는 SENSOR-48 요청의 포함 경계입니다. 응답 예정 시간이 20이고 경과 시간도 20이면 READY입니다. 응답이 없거나 예정 시간이 21 이상이면 경과 20에서 요청을 종료합니다. 앞 모듈의 HAL은 delay_ms가 20 이하일 때 성공하고 21 이상일 때 실패했습니다. 이번 계층은 무응답을 언제 끝낼지 추가하며 그 성공 경계를 바꾸지 않습니다. 경계 시각에서는 허용된 응답 확인을 시간 초과보다 먼저 수행합니다.

처음 요청을 포함해 총 세 번 시도합니다. attempts는 실패 수가 아니라 이미 시작한 요청 번호이며 가능한 값은 1·2·3입니다. 첫 실패와 두 번째 실패는 RETRY, 세 번째 실패는 EXHAUSTED입니다. 최대 세 번 재시도라고 쓰면 첫 시도에 더해 네 번으로 읽힐 수 있어 총 세 번이라고 문서화합니다. 0이나 4 이상의 번호는 계약 위반으로 EXHAUSTED를 반환하고 더 진행하지 않습니다.

함수 결과는 WAITING·READY·RETRY·EXHAUSTED 네 가지입니다. WAITING은 아직 결론을 낼 시간이 아니고 READY는 응답 조건을 만족한다는 뜻입니다. RETRY는 이번 요청을 끝냈지만 상위 계층에 다음 시도 여지가 있음을 알립니다. EXHAUSTED는 예산 소진입니다. 결과를 참·거짓 하나로 줄이면 대기와 실패를 구별하기 어렵습니다. 헤더의 enum과 호출자의 분기를 함께 읽습니다.

경과 시간은 uint32_t elapsed = now - started로 계산합니다. 순환하는 unsigned 시계가 앞으로 진행한다는 전제에서 짧은 구간을 비교합니다. started가 UINT32_MAX이고 now가 19면 경과는 20입니다. now가 started보다 작다고 음수 오류로 처리하면 정상적인 시계 순환을 놓칩니다. 반대로 임의로 거꾸로 입력하는 시간을 허용하는 것은 아닙니다. 입력 관찰 간격은 2의 31승 ms 미만이라고 계약에 적습니다.

판단 순서는 시도 번호 확인, 허용 시간 안의 응답 준비 확인, 경과 20 미만인지 확인, 남은 예산 확인입니다. response_ms가 0이면 시작 tick에서도 READY입니다. response_ms가 20인데 메인 루프가 30ms에 다시 호출되어도 READY로 봅니다. 가상 fixture는 응답이 제한 안에 도착해 보존되었다는 모델입니다. 실물 드라이버가 응답을 보존하는지와 호출 지연을 어떻게 기록하는지는 별도 계약으로 확인합니다.

WAITING일 때 시작 시각을 갱신하지 않습니다. 매 호출마다 started를 now로 바꾸면 elapsed가 계속 0이어서 제한이 도달하지 않습니다. attempts도 호출 횟수마다 증가시키지 않습니다. 아직 같은 요청을 기다리는 중이며 새로운 요청을 시작할 때만 다음 번호를 부여합니다. 단위 테스트는 같은 started에 now 119와 120을 넣어 대기와 종료를 비교합니다. 변수의 의미를 주석과 테스트에 맞춥니다.

retry_decide는 장치에 요청하지 않고 결정만 반환하는 순수 함수입니다. 이 함수를 호출했다고 새로운 요청이 자동으로 실행되지 않습니다. 메인 상태 머신이 RETRY를 받아 RECOVER에서 휴지 시간을 기다린 후 MEASURE로 이동합니다. 마지막 미션의 휴지는 실패 시각부터 100ms입니다. 이 숫자는 자체 복구 정책이며 장치 보편 표준은 아닙니다. 요청 제한 20ms와 휴지 100ms를 한 상수로 묶지 않습니다.

지속 무응답 예를 시작 100ms로 잡습니다. 첫 요청은 120에서 종료하고 220에 두 번째를 시작합니다. 두 번째는 240에서 종료하고 340에 세 번째를 시작하며 360에서 ERROR에 머뭅니다. 따라서 예산 전체가 60ms라고 말하면 휴지와 호출 간격이 빠집니다. 각 시도의 처리 제한 합과 전체 벽시계 경과는 다른 수치입니다. 호출이 늦어지면 전이는 다음 관찰 tick에서 일어납니다.

재시도는 일시적 실패에만 사용합니다. NACK나 시간 초과는 fixture를 바꾸어 다음 시도에서 정상으로 돌아올 수 있습니다. ADC 범위 오류나 UART 공간 부족은 같은 인자로 다시 불러도 고쳐지지 않습니다. 이 미션은 그런 오류를 ERROR로 처리합니다. 모든 반환 코드에 동일한 재시도를 붙이지 않습니다. 원인별 조치가 달라져야 잘못된 설정을 센서 장애로 오해하지 않습니다.

starter는 제한과 READY를 구현해 두었지만 종료 뒤 항상 RETRY를 반환합니다. 19ms 대기와 20ms 응답 테스트는 통과하고 세 번째 시간 초과는 실패합니다. retry.c의 TODO에서 attempts와 상한을 비교해 결과를 나눕니다. 전체 파일을 다시 작성할 필요는 없습니다. 테스트를 삭제하거나 세 번째 기대값을 RETRY로 바꾸면 예산 요구를 검증하지 못합니다. 제출 코드는 기존 테스트를 유지합니다.

FAIL line 뒤의 조건식은 어떤 입력과 결과가 어긋났는지 알려 줍니다. retry_decide(120,100,21,3)==EXHAUSTED가 실패하면 응답 예정 21ms, 실제 경과 20ms, 시도 번호 3을 각각 읽습니다. response_ms와 now를 같은 시각으로 해석하면 비교를 잘못 고칠 수 있습니다. make의 Error 줄은 검사 프로그램의 비정상 종료를 전하는 요약이며 최초 FAIL 줄을 먼저 확인합니다.

컴파일 진단과 동작 실패를 분리합니다. undeclared identifier는 이름 또는 헤더 문제이고 undefined symbols 같은 링크 진단은 retry.c가 빌드 대상에서 빠졌는지 확인합니다. -Wall -Wextra -Werror가 경고를 오류로 취급하므로 사용하지 않는 변수도 빌드를 막을 수 있습니다. 경고 옵션을 지워 통과시키기보다 변수 역할을 정리합니다. 정상 빌드 뒤 실행된 테스트가 실패해야 기능 미완성 근거가 됩니다.

테스트는 19/20 경과, 20/21 응답 예정, 시도 1/2/3, 0/4 계약 위반, 시계 순환을 나눕니다. 하나의 큰 시나리오만 만들면 실패 위치가 모호합니다. response_ms 0과 늦게 관찰한 정상 응답도 포함합니다. 각 입력을 표로 적고 반환을 손으로 예측한 다음 실행합니다. 이 검사는 실제 센서의 최대 응답 시간을 측정하지 않으며 정해진 정책과 코드가 일치하는지 확인합니다.

시도 예산이 소진되면 다음 정상 fixture가 와도 자동으로 첫 요청을 시작하지 않습니다. ERROR에서 별도의 복구 입력을 기다려 새 작업 묶음을 시작합니다. 실패를 성공으로 바꾸는 fixture와 정책상 복구를 허용하는 이벤트는 구별합니다. 현장에서 센서 전원을 다시 켰다는 이유만으로 이전 작업의 성공을 확정하지 않습니다. 계수 초기화 시점은 새 작업 시작이나 명시 복구에 두고 관찰 tick에 두지 않습니다.

완료 기록에는 실제 테스트 요약과 요청 번호 의미를 씁니다. 세 번째 실패가 종료되는 이유, 정확히 20ms의 응답이 허용되는 이유, 상위 루프에 기다림을 반환하는 이유를 설명합니다. 서재 오류 처리 장에서는 반환 규약과 정리 흐름을 더 읽습니다. 이번 실습의 함수는 동적 메모리나 파일을 열지 않으므로 해당 예제를 옮기지 않습니다. 다음 통합에서 SPI 선택 해제와 결과 공개를 같은 실패 계약으로 연결합니다.

따라하기

응답과 제한 시각

20ms 응답은 성공을 먼저 판정합니다. 무응답 예정 21ms는 경과 20에 종료합니다.

for elapsed, response in ((19, 20), (20, 20), (19, 21), (20, 21)):
    result = "READY" if response <= 20 and elapsed >= response else "WAITING" if elapsed < 20 else "TIMEOUT"
    print(elapsed, response, result)

실행 결과

19 20 WAITING
20 20 READY
19 21 WAITING
20 21 TIMEOUT

상한 TODO와 실패 식

starter에서 실행하고 test_retry.c의 세 번째 실패 조건을 읽습니다. retry.c의 TODO를 수정합니다. 환경 오류와 FAIL을 구분합니다.

make -s test

완성 예산 확인

수정한 폴더에서 실행하여 solution의 실제 요약과 비교합니다. 시계 순환과 0/4 계약 위반도 같은 검사에 포함됩니다.

make -s test

실행 결과

retry-budget: 11 cases, 0 failures

확인 문제

실습

retry.c의 시도 한도 TODO를 구현합니다. 총 3회이며 20ms 응답은 성공 우선입니다. test_retry.c를 보존하고 make test를 실행합니다. 19/20·20/21·2/3 경계 및 시계 순환 판단을 설명합니다.

시작 코드·테스트 내려받기

실행 명령

make test

기대 결과

retry-budget: 11 cases, 0 failures; 종료 코드 0

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 센서가 응답하지 않을 때 점검할 순서를 설명해 주시면 됩니다.
  • 센서 기록기의 상태 머신을 설명해 주시면 됩니다.