gRPC Deadline 설정
gRPC는 기본적으로 Deadline을 설정하지 않으므로 호출마다 timeout을 지정하고, 서버는 남은 시간을 후속 호출로 전파한다.
기본 동작
deadline은 호출이 완료되어야 하는 시각이다. gRPC는 기본적으로 deadline을 설정하지 않으므로, 서버가 응답하지 않으면 클라이언트는 무기한 대기한다. 대기 중인 호출은 양측의 자원을 계속 점유하므로 모든 호출에 deadline을 지정한다.
timeout 인자
Python grpcio에서는 stub(생성된 클라이언트 객체)의 메서드를 호출할 때 timeout에 초 단위 값을 지정한다. 지정하지 않으면 deadline이 없다.
import grpc
import status_pb2
import status_pb2_grpc
with grpc.insecure_channel("localhost:50051") as channel:
stub = status_pb2_grpc.StatusServiceStub(channel)
request = status_pb2.StatusRequest(device_id="d-01")
try:
# 2초 안에 응답이 없으면 호출을 끝낸다
reply = stub.GetStatus(request, timeout=2.0)
except grpc.RpcError as e:
if e.code() != grpc.StatusCode.DEADLINE_EXCEEDED:
raise
print("기한 초과")
서버 측 처리
deadline은 grpc-timeout 헤더에 담겨 서버로 전달된다. deadline이 지나면 클라이언트의 호출은 DEADLINE_EXCEEDED로 종료되고, 서버 측 호출도 자동으로 취소된다. 다만 실행 중이던 핸들러(호출을 처리하는 서버 메서드)까지 중단되지는 않는다. 장시간 실행되는 작업은 주기적으로 context.is_active()를 확인하여 스스로 종료한다.
Deadline 전파
서버가 요청을 처리하는 중에 다른 서버를 호출한다면, 수신한 deadline을 그 호출에도 전파해야 전체 처리가 하나의 deadline 내에 완료된다. 자동 전파 여부는 언어마다 다르다. Java와 Go는 기본적으로 전파하고 C++는 별도로 활성화한다. Python에서는 남은 시간을 조회하여 직접 전달하는 방법이 확실하다.
def GetStatus(self, request, context):
# 남은 시간(초)을 후속 호출의 기한으로 넘긴다. 기한이 없으면 None이다
remaining = context.time_remaining()
battery = self.battery_stub.GetBattery(request, timeout=remaining)
return status_pb2.StatusReply(battery=battery.percent)