← 목록
기타 2026-07-06 12KB 읽기 12분

[STR] 진단 라인 VM 이슈 조사 (원인 특정)

개요

#31 세션 관찰: crownyc run <toau> 실행 시 [STR] count=... 진단 라인이 반복 출력되며 기대 출력이 안 나오는 현상. learn/lookup 경로와 무관한 별도 VM 이슈로 추정 — 조사 결과 추정이 맞았다: 이 진단은 문자열 풀(heap) 자원 게이트 로직이며, 학습DB/lookup 파이프라인과 무관.

공유 crownyc/crownyc.c는 이번 작업에서 수정하지 않았음(조사·재현·원인특정만, 결재 옴).

[STR] 출력 지점 3곳 (grep 확정, crownyc.c)

라인스트림조건반복성
2328stderr문자열 풀 최초 malloc 실패 (기동 즉시 fatal)1회, exit(1)
2469stderrstr_new(): GC + 2배 성장 시도 후에도 여전히 str_cap/str_pool_size 부족 (= 절대 상한(STR_HARD_COUNT=64M슬롯, STR_HARD_POOL=1GB)에 도달)반복 — 실패할 때마다 매 호출 출력, streak>=50에서 자가종료(exit 42). CROWNY_NO_STREXIT=1이면 무한 반복
9750stdoutopcode 365 (문자열통계/STR_STATS) — 사용자가 명시 호출한 진단 빌트인호출한 만큼
[VM] 문자열 핸들 상한(480000) 초과... (line 2491, exit 43)은 접두어가 다르지만 같은 문자열-자원 계열 게이트라 함께 조사.

재현 (실측, 공유 VM 미수정 — 격리 빌드로 검증)

  1. 동적 성장 정상 동작 확인: -DSTR_INIT_COUNT=64 -DSTR_INIT_POOL=4096(테스트 파일
tests/STR풀성장스트레스.한선의 문서화된 소캡 빌드)으로 별도 바이너리를 /tmp에 빌드해 실행 → [STR] 메시지 전혀 없이 300개 문자열 생성/검증 전부 통과(OK STR풀성장). 즉 정상 워크로드에서는 캡이 작아도 GC+동적성장이 투명하게 처리한다.
  1. 절대상한 도달 재현: `-DSTR_INIT_COUNT=8 -DSTR_INIT_POOL=64 -DSTR_HARD_COUNT=32
-DSTR_HARD_POOL=512`(절대 상한 자체를 작게 낮춤)로 빌드해 같은 테스트 실행 →
   [STR] 풀한계(성장 상한 도달)! count=9/16 pool=504/512 len=8 gc_runs=19 gc_freed=36 streak=1
   ... (매 실패 호출마다 1줄, streak 1→50 증가) ...
   [STR] 풀고갈 지속(50회) — wedge 방지 자가재기동 종료(코드 42)
   
exit code = 42. stdout은 완전히 비어있음 — 프로그램이 의도한 출력값("생성 = 300") 등 실제 출력이 단 한 줄도 안 나감(지연이 아니라 소실 — VM이 루프 도중 exit(42)로 죽어버림).

정확한 임계치 (실측)

  • [STR] 풀한계 메시지는 str_cap과 str_pool_size 양쪽 모두 GC+2배성장을 거쳐도 절대상한
(STR_HARD_COUNT=64M 슬롯 / STR_HARD_POOL=1GB)에 도달해야만 발화한다. 이 절대상한은 #ifndef 가드가 있어 -D로 낮출 수 있지만(테스트 전용), 프로덕션 빌드 기본값은 초기캡의 32배(64M/2M) 여유가 있어 통상 워크로드로는 사실상 도달 불가.
  • 같은 코드 경로에 있는 STR_ID_MAX=480000(crownyc.c:2307, #define 하드코딩, -D 재정의
불가)은 별개의 훨씬 낮은 게이트다. str_cap(초기 2,097,152) < STR_ID_MAX보다 커서, 살아있는 문자열이 실제로 480,000개에 근접하는 워크로드에서는 이 게이트가 먼저 걸린다 — 이때 메시지는 [STR]이 아니라 `[VM] 문자열 핸들 상한(480000) 초과 — GC 후에도 N 생존, 풀 누수 의심`이며 1회성 exit(43)(반복 안 됨, 즉시 프로세스 종료). 코드 주석에 명시된 실제 발병 사례: "2026-07-05 게이트웨이 2분주기 사망 원인 — GC 0회로 exit43".

영향 범위 (오늘 스택)

  • str_new()(2469, [STR]) 경로: 64M슬롯/1GB급 살아있는 문자열이 필요 — 브레인번역
235KB 적재·컴포저 조합 등 오늘 언급된 워크로드 규모로는 도달 불가능(추정 아님, 산수: 1GB / 평균 수백 바이트 문자열이라도 수백만 개 필요, 235KB 입력으로는 나올 수 없는 규모).
  • STR_ID_MAX(480000, [VM]) 경로: 장시간 상주 프로세스가 요청마다 문자열을 대량 생성하고
일부를 계속 살려두는 패턴(로그 누적, 세션/히스토리 캐시, 맵에 담긴 대용량 파싱 트리 등)에서 현실적으로 도달 가능 — 이미 게이트웨이에서 실제로 걸렸던 전례(2026-07-05, 위 주석 참조). 브레인번역/컴포저가 상주 서버가 아니라 1회성 VM 실행분이라면 이 경로도 도달 가능성 낮음. 다만 "상주 아님"이라는 전제가 깨지는 배치·루프 실행 형태로 쓰이면 재검토 필요.
  • opcode 365(문자열통계, stdout) 경로: CrownyOS/crownyc, crowny-butler, CrownyTVM,
crowny-hanseono, crowny-engine, CrownyDoc 전 범위 재귀검색 결과, 현재도 과거 백업본에도 이 빌트인을 호출하는 .한선/.rpn 소스가 전혀 없음. 즉 지금 이 순간 이 경로가 자동으로 발화할 소스는 확인되지 않음 — 발화하려면 (a) 세션이 임시/휘발성 디버그 코드에서 직접 호출했거나 (b) 소스에서는 이미 제거됐지만 재컴파일 안 된 구 .toau 바이너리가 여전히 그 호출을 담고 있어야 함(소스-바이너리 불일치 함정, TOAU 오염 함정과 유사 계열이나 다른 메커니즘).

원인 특정: 무해한 로깅인가 vs 실제 고갈 전조인가

둘 다 아니고, 셋으로 나뉜다 (실측 근거):

  1. str_new()[STR] 풀한계 메시지 — 무해한 로깅이 아니다. 발화 자체가 곧 실질적 자원
고갈(절대상한 도달)이며, 발화 후 모든 신규 문자열 생성은 handle=-1로 실패해 실제 데이터 손상/출력 소실로 직결된다(재현 실측: stdout 완전 공백). stderr 전용이라 stdout/.toau 파이프 자체를 오염시키지는 않음(2>&1 안 쓰면).
  1. STR_ID_MAX 초과의 [VM] 메시지 — 마찬가지로 실해악(exit 43로 프로세스 즉시 죽음).
stderr 전용, 1회성.
  1. opcode 365 문자열통계(stdout) — 설계상 진단용으로 무해(GC/카운트 스냅샷 출력일 뿐,
실패나 종료를 유발하지 않음)이나, stdout에 직접 printf 하도록 구현돼 있어 이게 만약 루프 안에서 반복 호출되면 실제 프로그램 출력과 같은 스트림에 섞여 "출력이 안 나오는 것처럼 보이는" 현상(사실은 진단 줄에 파묻힘)을 만들 수 있다. 이 지점만 유일하게 "stdout 오염" 관점에서 TOAU 파이프 오염 함정과 같은 부류(같은 스트림에 진단이 섞임)이고, 나머지 둘은 stderr라 다른 부류다.

부수 발견 — 흩어진 crownyc 바이너리 (버전 혼선 리스크)

파일시스템에 crownyc 바이너리가 5개 존재:

경로빌드일풀한계 문자열핸들 상한(STR_ID_MAX) 문자열
/Users/ef/CrownyOS/crownyc/crownyc (정본)7/5있음있음
/Users/ef/crowny-engine/dist-mac/crownyc6/27있음없음 — 2026-07-03~05 수정 이전
/Users/ef/CrownyOS/crownyc/pkg/crownyc4/18있음없음 — 마찬가지로 구버전
/Users/ef/crowny-hanseon-dist/linux/bin/crownyc6/21(미검사, Linux ELF)(미검사)
/Users/ef/Downloads/CrownyTVM/crownyc3/28없음(구형 최소 VM)없음
PATH에 crownyc alias/심볼릭은 없음(which -a crownyc → not found) — 즉 항상 explicit path로 실행돼야 하나, 여러 프로젝트 디렉토리에 구버전 사본이 방치돼 있어 잘못된 디렉토리에서 ./crownyc run을 실행하면 2026-07-03(STR_HANDLE_BASE 충돌 수정)·2026-07-05(STR_ID_MAX GC 보강) 이전 로직으로 실행될 위험이 있다. 이번 세션 관찰이 정본이 아닌 이 구버전 중 하나를 탔을 가능성은 세션 #31 실행 경로를 직접 확인 못 해 추정으로 남긴다.

회피 / 수리 제안

회피(셸/래퍼 수준, 즉시 적용 가능, 공유 VM 미수정):

  • [STR]/[VM] 계열은 전부 stderr이므로, 파이프라인에서 실제 산출물(stdout)만 소비한다면
2>/dev/null이나 2> log파일로 분리하면 산출물 자체는 오염되지 않는다. 단, 발화했다는 것 자체가 이미 데이터 손상/프로세스 조기종료를 의미하므로 필터링은 증상 은폐일 뿐 근본대응 아님 — exit code(42/43) 체크를 반드시 병행해야 한다.
  • 정본 실행 시 항상 /Users/ef/CrownyOS/crownyc/crownyc절대경로를 쓰거나, 실행 스크립트
최상단에 cd /Users/ef/CrownyOS/crownyc를 강제해 구버전 사본(dist-mac, pkg, CrownyTVM)이 실수로 잡히지 않게 한다.
  • opcode 365(문자열통계)를 디버그용으로 쓸 일이 있으면 stdout이 곧 프로그램 출력과 같은
스트림임을 인지하고, 실제 산출물 파이프라인에는 절대 넣지 말 것(디버그 전용 실행에서만).

수리(공유 VM 변경 필요 — 안건 티켓, 수정 금지·문안만):

티켓: opcode 365(STR_STATS/문자열통계) stdout→stderr 전환
  • 위치: crownyc.c:9750 printf("[STR] count=...")
  • 문제: 같은 파일의 다른 두 STR 진단(2328, 2469)은 전부 fprintf(stderr,...)인데 이 지점만
printf(stdout)로 구현돼 있어, 향후 누군가 디버깅 목적으로 이 빌트인을 루프에 넣으면 실제 프로그램 출력과 뒤섞인다. 단순 일관성 수정: printf(...)fprintf(stderr, ...).
  • 리스크: 낮음(순수 진단 출력 스트림 변경, 로직/오퍼란드 불변). 회귀 대상: STR_STATS를 stdout
에서 파싱하는 기존 스크립트가 있는지 사전 확인 필요(현재 조사 범위 내에서는 없음).
>
티켓(참고, 선택): STR_ID_MAX 근접 시 사전 경고 레벨 추가
  • 위치: crownyc.c:2485 부근
  • 제안: str_countSTR_ID_MAX의 예: 90%(432,000)를 넘으면 exit 전에 한 번 저강도 경고를
stderr로 남겨, 운영 중 "곧 죽는다"를 조기 관측 가능하게(현재는 exit(43) 순간에만 알 수 있음). 2026-07-05 게이트웨이 사망 재발 방지 관측성 개선 성격, 급하지 않음.

결론 요약

  • 재현조건(실측): [STR] 풀한계는 str_cap/pool이 절대 상한(64M/1GB)까지 GC+성장해도 부족할
때만 발화 — 정상 워크로드에서 도달 불가능한 임계. 도달하면 실제로 stdout 출력이 소실되고 exit(42)로 자가종료(실해악 확인).
  • 더 낮고 현실적인 게이트는 STR_ID_MAX=480000(exit 43, [VM] 접두어) — 이미 게이트웨이에서
실제로 걸렸던 이력이 코드 주석에 남아있음.
  • 세션이 실제 본 문자열이 [STR] count=...라면, opcode 365(문자열통계, stdout)일 가능성이
코드상 가장 부합하지만 현재 소스 전 범위에 호출부가 없어 재컴파일 안 된 구 .toau 바이너리 잔존 또는 구버전 crownyc 바이너리(pkg/dist-mac, 2026-07-03/05 수정 이전) 오사용이 유력한 간접 원인으로 추정(직접 확인은 세션 #31 실행 커맨드 접근 필요, 미확보).
  • 공유 VM(crownyc/crownyc.c) 수정은 하지 않았음 — 위 두 티켓만 안건으로 남김.

관련 파일

  • /Users/ef/CrownyOS/crownyc/crownyc.c (2328, 2459-2503, 9745-9764 — STR 진단 3지점 + 게이트 로직)
  • /Users/ef/CrownyOS/crownyc/hanseonc_high.c:1859 (문자열통계 키워드→opcode 365 매핑)
  • /Users/ef/CrownyOS/crownyc/tests/STR풀성장스트레스.한선 (소캡 빌드로 동적성장 검증하는 기존 스트레스 테스트, 재현에 그대로 활용)
  • 흩어진 구버전 바이너리: /Users/ef/crowny-engine/dist-mac/crownyc, /Users/ef/CrownyOS/crownyc/pkg/crownyc, /Users/ef/Downloads/CrownyTVM/crownyc, /Users/ef/crowny-hanseon-dist/linux/bin/crownyc

잔여 이슈

  1. 세션 #31이 실제로 어느 crownyc 바이너리/어느 .toau를 실행했는지 원본 커맨드를 확보 못함 —
확보되면 위 3개 가설(절대상한/ID_MAX/opcode365) 중 하나로 확정 가능.
  1. opcode 365 stdout→stderr 티켓은 결재 대기(수정 금지 지시로 미적용).
  2. /Users/ef/crowny-hanseon-dist/linux/bin/crownyc(Linux ELF)는 이번 조사에서 문자열 grep을
생략함(미검사) — 배포판에도 같은 구버전 위험이 있는지 후속 확인 필요.