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

HSMS 타임아웃, T3인지 T6인지 T7인지: 캡처로 가르는 법

호스트 로그에 timeout 한 줄만 있을 때 T3와 T6를 실제 캡처로 가른다. E37 기본값(T3 45초, T6 5초, T7 10초, T8 5초)까지.

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

호스트 로그에 timeout 한 줄만 찍혀 있으면 그다음 30분은 대체로 추측이다. HSMS 타이머는 다섯 개고 감시하는 구간이 전부 다르다. 어느 타이머가 터졌는지가 곧 어디를 봐야 하는지다. T6가 터졌으면 세션이 아직 안 열린 것이고, T3가 터졌으면 세션은 열렸는데 애플리케이션이 답을 안 준 것이다. 두 경우에 확인할 것이 하나도 겹치지 않는다.

호스트 로그의 timeout 한 줄을 무엇을 기다렸는지로 가르면 네 갈래가 된다 — 컨트롤 트랜잭션 응답은 T6, 데이터 메시지 응답은 T3, Select.req가 오지 않으면 T7, 절반만 도착한 프레임은 T8 timeout 무엇을 기다렸나 T6 T3 T7 T8 컨트롤 트랜잭션 응답 데이터 메시지 응답 Select.req가 오지 않음 절반만 도착한 프레임

다섯 개가 재는 구간은 서로 겹치지 않는다

SEMI E37(HSMS-SS)이 정의하는 타이머는 다섯 개다. 기본값과 범위는 E37의 파라미터 표에 실려 있는 값이다.

타이머E37 이름기본값 (범위)재는 구간터졌을 때
T3Reply Timeout45초 (1–120)W-bit가 선 데이터 메시지를 보낸 뒤 응답이 올 때까지세션은 살아 있고, 애플리케이션이 답을 못 만들었다
T5Connect Separation Timeout10초 (1–240)연결 시도와 다음 연결 시도 사이오류가 아니다. 재접속을 이 간격 안에 다시 시도하지 말라는 뜻이다
T6Control Transaction Timeout5초 (1–240)컨트롤 트랜잭션(Select, Deselect, Linktest)이 열려 있는 시간세션이 아직 안 열렸거나, 열린 줄 알았는데 아니다
T7NOT SELECTED Timeout10초 (1–240)TCP는 붙었는데 NOT SELECTED로 머무는 시간상대가 Select.req를 보내지 않는다
T8Network Intercharacter Timeout5초 (1–120)메시지 한 개를 이루는 바이트 사이의 간격프레임이 절반만 도착하고 멎었다

기본값은 출발점이지 합의된 값이 아니다. 실제 값은 호스트와 장비가 인터페이스 스펙에서 정하고, 45초짜리 T3를 그대로 쓰는 라인은 드물다. 응답 없는 S2F41 하나에 45초를 서 있어 보면 왜 다들 10초 근처로 당기는지 알게 된다.

누가 켜는 타이머인지가 절반이다. T3와 T6는 요청을 보낸 쪽이 켠다 — 데이터 메시지면 T3, 컨트롤 메시지면 T6. T5는 active(먼저 붙는 쪽) 전용이다. T7은 TCP가 붙은 뒤 SELECTED가 되기를 기다리는 쪽, 실무에서는 Select.req를 기다리는 passive 장비 쪽이다. T8은 메시지를 절반만 받아 든 쪽이면 어느 쪽이든 켠다.

만료 뒤 동작도 E37에서 서로 다르다. T6·T7·T8 만료는 연결 자체의 실패로 보고 TCP 연결을 끊는다. T3 만료는 그 트랜잭션 하나만 실패로 처리하고 연결은 그대로 둔다. 그리고 T3가 터졌을 때 상대에게 S9F9(Transaction Timer Timeout)를 보내는 규칙은 E37이 아니라 SECS-II(SEMI E5) 쪽 이야기다. HSMS 문서에서 S9F9를 찾다 시간 버리지 말 것.

혼동이 자주 나는 지점이 하나 더 있다. T1, T2, T4는 HSMS 타이머가 아니라 SECS-I(SEMI E4)의 시리얼 타이머다. E4는 T1을 문자 간, T2를 프로토콜, T4를 블록 간 타임아웃으로 쓴다. HSMS는 이 중 T3만 이름 그대로 물려받고 T5부터 T8까지를 새로 얹었다. 그러니 이더넷으로만 붙는 장비의 인터페이스 스펙에 T1이 적혀 있으면 그 문서는 시리얼 시절에서 복사된 것이다. 값을 맞추기 전에 그 항목이 왜 거기 있는지부터 물어보는 게 맞다.

캡처: 정상 왕복부터

아래는 2026-09-10에 시뮬레이터의 passive 리스너(127.0.0.1:5501)에 파이썬 소켓으로 붙어 직접 뜬 것이다. hex는 장비 쪽이 RX/TX로 기록한 실제 바이트고, 시간은 호스트 쪽에서 잰 값이다. 세션 ID는 00 0B(11). 원본 export는 content/demos/hsms-timeout-timers-t3-t5-t6-t7-t8.json에 그대로 넣어뒀다.

TX Select.req    00 00 00 0A 00 0B 00 00 00 01 00 00 03 01
RX Select.rsp    00 00 00 0A 00 0B 00 00 00 02 00 00 03 01      3.6 ms

앞 4바이트는 길이 프리픽스(0A = 10, 헤더만)이고 이어지는 10바이트가 헤더다. 바이트 0–1이 세션 ID, 컨트롤 메시지라 바이트 2–3은 0, 바이트 4가 PType(0 = SECS-II), 바이트 5가 SType(01 Select.req → 02 Select.rsp), 바이트 6–9가 SystemBytes 00 00 03 01. 요청 값을 그대로 돌려줬다.

TX S1F13 W=1     00 00 00 1A 00 0B 81 0D 00 00 00 00 03 02
                 01 02 41 06 48 4F 53 54 49 44 41 04 31 2E 30 30
RX S1F14         00 00 00 37 00 0B 01 0E 00 00 00 00 03 02
                 01 02 21 01 00 01 02 41 15 56 58 2D 39 30 30 30 20 50 6C 61 73 6D 61 20 45 74 63 68 65 72
                 41 0D 53 45 43 53 47 45 4D 2D 31 2E 34 2E 31      2.4 ms

데이터 메시지라 바이트 2가 81 — 최상위 비트가 W-bit, 나머지 7비트가 Stream 1이고, 바이트 3 0D가 Function 13이다. 응답의 바이트 2는 01이니 W-bit가 내려갔고, SystemBytes 00 00 03 02는 그대로다. 본문은 01 02(L[2]) 아래 21 01 00 = COMMACK 0, 그 아래 A[21] VX-9000 Plasma Etcher와 A[13] SECSGEM-1.4.1. 2.4 ms에 왔으니 T3가 셀 틈이 없었다.

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

Linktest는 SType 5 / 6이고 본문이 없다. 살아 있는 링크의 왕복이 1.2 ms인 회선에서 T6 기본값 5초가 터졌다면 그건 지연이 아니라 상대가 죽은 것이다.

T6는 이렇게 터진다

리스너에 ignoreLinktest 폴트를 걸었다. Linktest.req는 장비까지 도착하고(RX 로그에 남는다) 응답만 삼켜진다.

06:02:51.362 RX Select.req   00 00 00 0A 00 0B 00 00 00 01 00 00 04 01
06:02:51.362 TX Select.rsp   00 00 00 0A 00 0B 00 00 00 02 00 00 04 01   Select Status 0
06:02:51.364 RX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 04 02
06:02:51.364 FAULT           Linktest.req ignored (ignoreLinktest)
             (응답 없음)
06:02:56.37  호스트가 T6 = 5초에서 포기하고 연결을 끊음 — 실측 5.005초

포인트는 장비 로그에 RX가 남았다는 것이다. 패킷은 도착했고, 세션도 SELECTED다. 그래도 호스트에서는 "링크가 죽었다"로 보인다. Linktest는 컨트롤 트랜잭션이므로 여기서 터지는 건 T3가 아니라 T6다. 로그가 둘을 구분하지 않으면 이 둘을 같은 줄로 읽게 된다.

silentAfterSelect로 Select.req 자체를 삼키게 하면 같은 5초가 세션이 열리기도 전에 흐른다.

06:02:56.381 RX Select.req   00 00 00 0A 00 0B 00 00 00 01 00 00 05 01
06:02:56.381 FAULT           Select.req ignored (silentAfterSelect)
             (응답 없음)
06:03:01.39  호스트가 포기 — 실측 5.005초

TCP는 붙었는데 Select.rsp가 없는 상태, 즉 NOT SELECTED다. 여기서 T3를 찍는 호스트가 있다면 그건 타이머 문제가 아니라 상태 기계 버그다. 두 캡처 모두 뜨자마자 폴트를 {}로 되돌렸다.

T3 만료는 이 리스너로는 못 만든다. 데이터 메시지에는 항상 답을 하고, 답할 게 없으면 SxF0(Function 0, 트랜잭션 abort)라도 돌려준다. 그런데 이건 E5에서 엄연한 응답이라 받는 순간 T3는 멈춘다 — 실제 현장에서도 무응답과 SxF0는 전혀 다른 사건이다. 앞의 S1F13 왕복까지가 이 캡처가 T3에 대해 증명하는 전부다.

상대가 지켜준다고 믿으면 안 되는 두 개

T7과 T8은 직접 시험했고, 둘 다 결과가 같았다.

TCP만 붙이고 Select.req를 한 번도 보내지 않은 채 30초를 버텼다. FIN은 오지 않았고, 30초 뒤에 보낸 Select.req는 정상적으로 받아들여졌다.

06:03:01.391 TCP  연결됨 (127.0.0.1)
             ... 30.0초 동안 아무것도 보내지 않음, FIN도 오지 않음 ...
06:03:31.417 RX Select.req   00 00 00 0A 00 0B 00 00 00 01 00 00 06 01
06:03:31.417 TX Select.rsp   00 00 00 0A 00 0B 00 00 00 02 00 00 06 01

T7 기본값의 세 배를 놀고 있어도 연결이 살아 있다. 이 리스너는 T7을 강제하지 않는다.

두 번째는 프레임을 반으로 쪼갠 것이다. 14바이트 Select.req 중 앞 6바이트만 보내고 20초를 쉰 뒤 나머지 8바이트를 보냈다.

06:03:31.418 TCP  연결됨
TX (앞 6바이트)   00 00 00 0A 00 0B
             ... 20.0초 정지 ...
TX (나머지 8)     00 00 00 01 00 00 07 01
06:03:51.418 RX Select.req   00 00 00 0A 00 0B 00 00 00 01 00 00 07 01
06:03:51.418 TX Select.rsp   00 00 00 0A 00 0B 00 00 00 02 00 00 07 01

한 프레임의 첫 바이트와 마지막 바이트 사이가 20초, T8 기본값의 네 배다. 그래도 받아들이고 Select.rsp까지 돌려줬다. HSMS 길이 헤더와 TCP 프레이밍 글에서 이 시뮬레이터가 T8을 지키는지 확인하지 못했다고 적어뒀는데, 이번 캡처로 답이 나왔다. 지키지 않는다.

시뮬레이터 한 대의 성질이지 실제 장비가 이렇다는 뜻은 아니다. 그런데 호스트 입장에서 결론은 어느 쪽이든 같다. T7과 T8은 내가 켜는 것이지 상대가 대신 켜주는 것이 아니다. 상대가 T7을 안 지키면 죽은 소켓이 NOT SELECTED로 영원히 남고, T8을 안 지키면 절반짜리 메시지가 파서 버퍼에 영원히 남는다. 둘 다 재고할 필요 없이 그냥 호스트에 넣어야 하는 코드다. 파서 쪽 처리는 앞의 프레이밍 글에 적어뒀다.

로그 한 줄이 사건을 결정한다

timeout 일곱 글자만 찍는 로그는 위의 표를 무용지물로 만든다. 최소한 이 네 가지는 같은 줄에 있어야 한다.

  • 터진 타이머 이름 — T3인지 T6인지
  • 기다리던 SystemBytes — 캡처에서 요청을 찾는 유일한 열쇠다
  • SxFy 또는 SType — 데이터인지 컨트롤인지가 여기서 갈린다
  • 그때의 HSMS 상태 — NOT SELECTED에서 난 T3는 타이머 문제가 아니라 순서 문제다

SystemBytes를 안 찍으면 엉뚱한 SystemBytes로 돌아온 응답과 진짜 무응답을 로그만으로는 구분할 수 없다. 캡처를 다시 떠야 한다. 커밋 한 줄이면 될 것을 재현 한 번으로 갚는 셈이다.

그리고 T6가 아니라 T3가 터졌다면 이미 세션은 열려 있다는 뜻이다. 그때부터는 HSMS가 아니라 GEM 쪽 이야기다 — SELECTED인데 S1F13에 답이 없는 경우가 그 지점이다.

타이머 값을 조정하기 전에 어떤 타이머가 터졌는지부터 로그에서 읽을 수 있어야 한다. T3 값을 45초로 늘렸더니 증상이 사라졌다면 고친 게 아니라 미룬 것이다. 위 캡처는 SECS/GEM 시뮬레이터의 passive 리스너에 소켓 하나 열면 그대로 재현된다.