← 전체 글
SECS/GEM/약 9분 읽기/— 조회

같은 SystemBytes로 두 건을 띄우면 응답을 구분할 수 없다

장비는 SystemBytes를 검사하지 않고 그대로 돌려준다. 같은 값으로 요청 두 개를 동시에 띄운 실제 캡처와, 호스트가 지켜야 할 SystemBytes 할당 규칙.

SECS/GEMMESSCADA문제 해결체크리스트

호스트 로그에 타임아웃이 찍혔는데 캡처에는 응답이 제시간에 들어와 있다. 여기까지는 흔한 그림이다. 그런데 이번엔 SystemBytes까지 정확히 일치한다. 어긋난 게 없다.

그러면 남은 질문은 하나다. 그 응답이 어느 요청의 응답인가. 호스트가 같은 SystemBytes로 두 건을 동시에 띄웠다면, 먼저 들어온 응답이 나중 요청을 닫아버리고 두 번째 응답은 갈 곳이 없어진다. 남은 한 건은 타이머가 끝날 때까지 기다리다 죽는다. 아래는 실제 소켓으로 그 상황을 만들어 본 캡처다.

같은 SystemBytes 00 00 07 02로 Linktest.req 두 건을 띄우면 바이트가 완전히 같은 Linktest.rsp 두 건이 돌아온다 호스트장비 Linktest.req 00 00 07 02 Linktest.req 00 00 07 02 Linktest.rsp 00 00 07 02 Linktest.rsp 00 00 07 02 같은 SystemBytes

장비는 SystemBytes를 검사하지 않는다

시뮬레이터의 passive 리스너(127.0.0.1:5501)에 소켓을 직접 열어 보냈다. 먼저 세션을 세운다.

TX Select.req     00 00 00 0A 00 0B 00 00 00 01 00 00 07 01
RX Select.rsp     00 00 00 0A 00 0B 00 00 00 02 00 00 07 01

헤더 마지막 4바이트가 SystemBytes다. 00 00 07 01을 보냈고 같은 값이 돌아왔다. SEMI E37은 응답이 요청의 SystemBytes를 바꾸지 않고 그대로 실어 보내도록 규정한다. 정상이다.

그럼 값을 이상하게 넣으면 어떻게 되나. 0과 0xFFFFFFFF를 넣어봤다.

TX Linktest.req   00 00 00 0A 00 0B 00 00 00 05 00 00 00 00
RX Linktest.rsp   00 00 00 0A 00 0B 00 00 00 06 00 00 00 00

TX Linktest.req   00 00 00 0A 00 0B 00 00 00 05 FF FF FF FF
RX Linktest.rsp   00 00 00 0A 00 0B 00 00 00 06 FF FF FF FF

둘 다 그대로 메아리쳤다. 거부도 경고도 없다. 이 리스너에게 SystemBytes는 검사 대상이 아니라 복사 대상이다.

E37이 특정 값을 예약해 두었는지는 확인하지 못했다. 문서 조항을 직접 대조하지 않았다. 다른 장비가 어떻게 나오는지도 이 캡처로는 알 수 없다. 확실한 건 하나다. 호스트를 짜면서 "이상한 값이면 장비가 걸러주겠지"에 기댈 근거가 없다는 것.

같은 값을 두 개 띄우면

같은 소켓에서 Linktest.req 두 개를 SystemBytes 00 00 07 02로 똑같이 맞춰 연달아 보냈다. 첫 응답을 기다리지 않고 보냈으니 두 건이 동시에 열려 있다.

TX Linktest.req A  00 00 00 0A 00 0B 00 00 00 05 00 00 07 02
TX Linktest.req B  00 00 00 0A 00 0B 00 00 00 05 00 00 07 02

RX Linktest.rsp    00 00 00 0A 00 0B 00 00 00 06 00 00 07 02
RX Linktest.rsp    00 00 00 0A 00 0B 00 00 00 06 00 00 07 02

응답 두 개가 바이트 단위로 완전히 같다. 길이, SessionID 00 0B, PType, SType 06, SystemBytes까지 전부 동일하다. 장비 쪽 로그에도 RX 두 건과 TX 두 건이 그대로 남았다. 장비는 시킨 대로 했다.

문제는 호스트 안에 있다. 대기 중인 트랜잭션을 SystemBytes로 키를 잡아 맵에 넣는 게 흔한 구조인데, B를 넣는 순간 A의 자리를 덮어쓴다. 첫 응답이 도착하면 B가 닫히고, 두 번째 응답은 매칭될 항목이 없어 조용히 버려진다. A는 끝까지 응답을 못 받고 타이머로 죽는다.

로그에 남는 문장은 "응답 하나 안 옴"이다. 원인은 장비도 네트워크도 아니다. 호스트가 열쇠를 두 개 똑같이 깎았을 뿐이다.

할당 규칙

  • 커넥션 하나에 카운터 하나. Stream별로도, 메시지 종류별로도 나누지 마라. Select.req, Linktest.req, 데이터 메시지가 전부 같은 SystemBytes 공간을 쓴다. 위 캡처에서도 Select와 Linktest에 같은 카운터를 돌렸다.
  • 유일해야 하는 범위는 "열려 있는 트랜잭션들 사이"다. 응답을 받아 닫힌 값은 재사용해도 된다. 커넥션 수명 전체에서 유일할 필요는 없다.
  • 32비트라 언젠가 되돌아온다. 0xFFFFFFFF 다음은 0이다. 되돌아오는 것 자체를 막을 필요는 없고, 그 값을 쥔 열린 트랜잭션이 없는지만 보면 된다. 위 캡처가 보여주듯 0도 0xFFFFFFFF도 장비 입장에서는 평범한 값이다.
  • 올리는 건 원자적으로. 스레드 두 개가 같은 카운터를 읽고 쓰면 위 캡처와 정확히 같은 상황이 만들어진다. 이게 실무에서 제일 흔한 경로다.
  • 재접속 때 카운터를 이어 쓸지 초기화할지 정해두라. 소켓이 닫혔으니 이전 세션의 늦은 응답이 새 소켓으로 들어올 일은 없다. 어느 쪽이든 되지만, 정해두지 않으면 두 사람이 다르게 짠다.

확인하지 못한 것

E37 조항 번호까지 대조하지는 않았다. SystemBytes가 요청과 응답을 묶는 식별자이고 응답이 값을 그대로 돌려준다는 규칙은 E37에 있다. "열린 트랜잭션 사이에서 유일해야 한다"는 그 규칙이 성립하기 위한 조건이지, 내가 문서에서 인용한 문장이 아니다.

타이머도 짚어두자. 위 시나리오는 Linktest, 즉 제어 트랜잭션이라 T6가 지킨다. 같은 실수를 데이터 메시지로 옮기면 T3다. 어느 쪽이 울렸는지 구분하는 법은 T3, T6, T7, T8 구분하기에 정리해 뒀다.

위 캡처는 전부 SECS/GEM 시뮬레이터의 passive 리스너에 소켓을 열어 받은 것이다. fault는 하나도 걸지 않았다. 정상 장비가 정상적으로 응답한 결과가 저것이다. 같은 값으로 두 건을 띄워보면 여러분 호스트 스택이 두 번째 응답을 어떻게 처리하는지 5분이면 나온다. 로그에 아무것도 안 남는 쪽이면 그게 제일 나쁘다.

응답은 왔는데 SystemBytes가 아예 다른 경우는 다른 이야기다. 그건 장비는 응답했는데 호스트는 no reply 쪽이다.