← 목록
기타 2026-06-10 10KB 읽기 10분

한선씨 게이트웨이 안정화 분석 — 크래시 규명 + 수정/재설계 판단

개요

라이브 :8080/:8443 한선씨 게이트웨이(crownyc run /tmp/게이트웨이통합.toau)가 수분마다 silent 크래시(로그 무에러, ~30초 자가복구)하는 문제를 코드 감사 + 그림자 포트(:8099) 실측으로 규명했다. 결론부터: crownyc VM은 정상(mind/auth/bank 7일+ 생존), 게이트웨이 고유의 두 버그가 원인이며, 재설계가 아니라 간단수정 3~4건으로 충분히 완성도 높게 안정화 가능하다.

1. 크래시 트리거 — 두 개의 독립 근본원인 (둘 다 실측 확인)

원인 A — SIGPIPE (즉발형, exit 141)

  • 현상: 클라이언트가 응답 수신 중 RST(connection abort)를 보내면, 끊긴 소켓에 TCP쓰기(opcode 494 = raw write())/소켓보내기(opcode 375 = send(fd,...,0))가 쓰면서 커널이 SIGPIPE → 기본동작=프로세스 종료.
  • 소스 근거:
  • crownyc.c opcode 494 write((int)va, str_ptr, str_len)MSG_NOSIGNAL 없음.
  • opcode 375 send(fd, s, slen, 0) — flag 0, MSG_NOSIGNAL 없음.
  • 전역 SIGPIPE 핸들러 부재: signal() 호출은 오직 opcode 766(시그널)을 한선씨가 명시 호출할 때만(crownyc.c:12094~12099). 게이트웨이/프록시/통합 어디에도 시그널(13,1) 호출 없음.
  • 재현: /tmp/sigpipe2.toau(대용량 응답 + SO_LINGER=0 RST 클라 30개) → exit 141. 실제 게이트웨이통합.toau(:8099)에 RST 클라 1개만으로도 exit 141, 3회 반복 100% 일관.
  • 종료코드 141 = 128+13 = SIGPIPE(signal 13). stderr 로그 없음 → silent.
  • 원인 B — 배열 힙 단조 누수 (지속형, exit 1)

  • 현상: 요청마다 줄분리/배열 리터럴이 1024큐브 블록을 영구 할당하는데 이 배열 힙은 GC가 전혀 회수하지 않음.
  • 소스 근거:
  • crownyc.c:5259 case 405(ARRAY): int base = mem_count; mem_count += 1024; — 해제 경로 없음.
  • str_gc(crownyc.c:1675)는 문자열 풀/힙 핸들만 compact. 배열 mem_count 블록은 GC 대상 아님.
  • mem_count 리셋은 프로그램 로드 시 단 1회. 요청 루프(게이트웨이통합.한선:460 동안(참))엔 리셋 없음.
  • MEM_MAX 12,000,000 / 시작 mem_count 10000.
  • 요청당 누수: _호스트추출·_경로추출·_요청재작성·_컨텐츠길이 각 줄분리 1 + _upstream분해 리터럴 1 + 도메인헤더목록 리터럴 1 ≈ 요청당 5~7 블록. (12,000,000-10,000)/~5120 ≈ 2,300~2,400 요청 → OOM. 초당 수건이면 수분 = 관측된 크래시 주기와 일치.
  • 크래시 경로: OOM 시 ARRAY가 base=0/edge 반환 → 오염 배열 꺼내(INDEX)가 보호영역 침범 → OOB 누적. crownyc.c:5289 OOB 누적 100회 → exit(1) "VM 즉시 중단"(stderr → nohup 로그 미포착, silent).
  • 재현: /tmp/memtest.한선(요청당 배열 3개) → 반복 3500 직후 "메모리 범위 벗어남" 플러드. /tmp/memtest4.한선(OOB 반복) → "배열 범위 초과 100회 누적 — VM 즉시 중단" exit(1).
  • 왜 mind/auth/bank는 7일+ 생존 (모든 모순 해소)

    단순 요청-응답형: (1) 응답 쓰기 도중 클라 RST를 거의 안 받음 → SIGPIPE 안 맞음. (2) 요청당 줄분리/배열 리터럴 미사용 → mem_count 평탄. 게이트웨이만 스트리밍 프록시/정적 대용량 전송 중 브라우저 abort(reload/이탈/모바일 단절/stunnel 전달 reset)에 상시 노출 + 요청당 5~7 배열 누수. "단순 GET 폭격(:8099) 안 죽음"·"mind 7일 생존"이 정확히 설명됨 — 차이는 graceful FIN(생존) vs mid-write RST(SIGPIPE) + 배열 누적.

    2. 레거시 JS 게이트웨이 강건성 대조 — 한선씨가 빠뜨린 것

    #레거시 패턴 (lib/)한선씨 현황크래시 직결
    aproxy.js:157/206/379 .on('error') + Node SIGPIPE 자동무시 + gateway.js uncaughtExceptionwrite 반환값 미확인, SIGPIPE 가드 없음★★★ (원인 A)
    bproxyRes.pipe(res) 백프레셔 자동청크 직통 OK, 백프레셔 재시도 없음, 16KB concat 캡부차
    -(배열 힙 누수는 레거시엔 없는 VM 메모리 모델 문제)str_gc가 배열 미회수★★★ (원인 B)
    ccluster.fork × min(cpus,27) + 워커 재생성단일 프로세스 → 1크래시=전체다운(워치독 의존)영향확대
    dupstream http.Agent keepAliveConnection:close 강제, 매 요청 신규 소켓부차
    e'upgrade'→proxyWebSocket 양방향 pipe+timeout교대폴링(블로킹)부차
    fContent-Length>maxBody 사전 413본문 크기 상한 없음(회수 가드만)부차
    gRETRYABLE×GET/HEAD×3회재시도 없음, 즉시 502/503품질
    hpath traversal baseDir 검증\x00BAD 마커로 일부 처리보안
    itrident 3진 헬스 게이트헬스 상태머신 없음품질
    부차 위험은 전부 가드(소켓폴링 5000/3000ms, WS 루프 100000 상한, 버퍼/소켓 opcode bounds-check, STR_CAT -1 반환)가 있어 단독 크래시 원인은 아님. 치명적인 건 a(SIGPIPE)와 배열 힙 누수 둘뿐.

    3. 핵심 판단 — 간단수정으로 충분한가?

    결론: 재설계 불필요. 간단수정으로 충분히 완성도 높은 안정화 가능.

    근거:

    1. crownyc VM은 장기구동 안정이 실증됨(mind 7일+, auth, bank). 게이트웨이가 죽는 건 VM 한계가 아니라 게이트웨이가 트리거하는 두 개의 특정 버그다.
    2. 두 버그 모두 국소적·근본적으로 막을 수 있다 — SIGPIPE는 1줄, 배열 누수는 요청 핸들러 전후 mem_count 스냅샷/복원 또는 줄분리 호출 최소화로 차단.
    3. 레거시의 cluster/H2/재시도/trident는 가용성·성능 향상이지 크래시 제거에 필수가 아니다. 안정화 후 점진 이식하면 된다.
    따라서 권장 경로: 1단계 간단수정으로 크래시 박멸(즉시) → 2단계 레거시 패턴 선택 이식으로 강건성 격상(후속 트랙).

    두 프로그램 장단

    • 레거시 JS: 강건(uncaughtException·error 이벤트·cluster·재시도·백프레셔로 구조적 면역). 단 nginx 대체 목표와 "한선씨=주언어" 헌법에 역행. 참조·이식 원천으로 가치.
    • 한선씨 게이트웨이: 헌법 정합·자립. 단 VM 메모리 모델(배열 GC 부재)과 SIGPIPE 미가드라는 두 함정에 노출. 두 곳만 막으면 레거시 수준 안정성 도달 가능.

    4. 우선순위 수정 계획

    즉시 적용 가능한 간단수정 (크래시 박멸 — 이것만으로 충분)

    S1 (★최우선, 근본·즉효) — SIGPIPE 면역

    • 옵션 1(VM, 1줄, 전 서비스 면역): crownyc.c main 진입부(mem_init 직후, 13772 부근)에 signal(SIGPIPE, SIG_IGN);. crownyc 재빌드만으로 게이트웨이 무수정 즉시 해결.
    • 옵션 2(VM, 더 정확): opcode 494/375/499의 write/sendMSG_NOSIGNAL + 반환 -1·errno==EPIPE를 연결종료로 정상처리.
    • 옵션 3(한선씨 측, VM 미수정 시): 게이트웨이통합.한선 엔트리 최상단에 시그널(13, 1)(SIGPIPE,SIG_IGN) 호출 추가. → 재컴파일만으로 가능, 라이브 crownyc 무변경.
    S2 (★최우선, 근본) — 배열 힙 누수 차단
    • 옵션 1(한선씨 측, 즉시): 게이트웨이통합.한선 _요청처리에서 요청 문자열을 한 번만 줄분리줄들 배열을 _호스트추출/_경로추출/_컨텐츠길이에 인자로 전달(현재 함수마다 재분리). 요청당 누수 5~7 → 2~3 블록으로 절반↓ (속도 지연, 근본 아님).
    • 옵션 2(VM, 근본): crownyc가 핸들러 콜프레임 종료 시 배열 힙을 자동 회수, 또는 mem_count 마커 저장/복원 빌트인 opcode 노출. 게이트웨이 통합서버 루프가 _요청처리 전후로 mem_count 스냅샷·복원 → OOM 영구 차단. 권장(최소수정·근본).
    S3 (안전망) — write 반환값 가드
    • 한선씨 _프록시스트리밍/응답 송신부에서 TCP쓰기/소켓보내기 반환 -1 시 연결중단 분기(루프 탈출). SIGPIPE를 SIG_IGN으로 막은 뒤 EPIPE를 깨끗이 처리.
    S1(옵션3) + S2(옵션1)은 라이브 crownyc 무변경, 게이트웨이통합.한선 재컴파일만으로 즉시 적용 가능. S1(옵션1)+S2(옵션2)는 VM 재빌드를 수반하므로 라이브 무중단 절차로 별도 적용 시 모든 한선씨 소켓 서비스에 영구 면역.

    후속 강건성 격상 (선택 이식, 크래시 무관·가용성 향상)

    • P1: 본문 크기 상한(레거시 f) — _요청누적읽기에 Content-Length>maxBody 413.
    • P2: GET/HEAD 재시도 3회(레거시 g).
    • P3: trident 3진 헬스 게이트(레거시 i) — Ta 서비스 503 차단·복구탐색.
    • P4: 멀티워커(레거시 c) — 단일프로세스 → SO_REUSEPORT 다중 accept(VM 멀티프로세스 모델 검토 후).

    5. 라이브 생존 재확인 (2026-06-10)

    lsof 확인: :8080(crownyc 55453) :8443(stunnel 55452) :9100(crownyc 55455) 모두 LISTEN. 백엔드 mind(7750, crownyc 22396, "2 6월26" 다일 uptime) :9400(bank) :9401(auth) ALIVE. 재현은 전부 그림자 :8099 / /tmp/*.한선에서만 수행, 잔류 프로세스 0. 라이브 무접촉 유지.

    관련 파일

    • 한선씨: /Users/ef/crowny-gateway/한선게이트웨이/게이트웨이통합.한선, 프록시.한선
    • 컴파일본: /tmp/게이트웨이통합.toau
    • VM 소스(수정 대상): /Users/ef/CrownyOS/crownyc/crownyc.c — 494/375/499(write/send), 5259(ARRAY), 5289(OOB exit), 1675(str_gc), 12094(시그널 opcode), main 13772
    • 레거시 참조: /Users/ef/crowny-gateway/lib/{gateway.js,proxy.js,trident.js,middleware.js,shield.js}

    잔여 이슈

    • crownyc.c SIGPIPE SIG_IGN + 배열 힙 GC/콜프레임 회수 패치는 VM 재빌드 수반 → VM 담당 트랙에서 라이브 무중단 절차로 적용 권장(이 분석은 비파괴 전용).
    • S1(옵션3)+S2(옵션1) 한선씨측 즉시수정은 게이트웨이 세션이 바로 적용 가능.