← 목록
기타 2026-07-24 5KB 읽기 5분

크라우니캔버스 P1 수리 — connect() 무타임아웃 → 단일스레드 스톨블록 (2026-07-24)

개요

canvas.crowny.org(:9620) 감사 중 보고된 P1: 포트 9620 이중 LISTEN + 감사 중반부터 curl 전량 timeout(exit 28). 근본원인을 찾아 커넥터.한선의 원시소켓 아웃바운드 경로를 curl 경유로 교체하고 라이브 재현→수리→재검증까지 완료했다.

근본원인

커넥터.한선커넥터_HTTP요청()은 (space/nexus/cad/tele 4개 어댑터 헬스체크용) 소켓생성→ 소켓연결→소켓폴링(1500ms)→소켓받기 순서로 동작했다. 소켓폴링은 recv 단계만 타임아웃 게이트하지만, 소켓연결(SOCK_CONNECT, crownyc.c opcode 374)은 순수 POSIX connect()를 아무 타임아웃 없이 직접 호출한다 — SO_SNDTIMEO 미설정, 논블로킹+poll(POLLOUT) 경로 전무.

crowny-canvas는 완전 단일스레드 accept-loop(서버.한선 `동안(가동==1){TCP수락→TCP읽기→ 요청처리→TCP닫기})이고, /api/부팅·/api/커넥터상태` 핸들러가 이 4개 어댑터를 순차 호출한다. 어댑터들(space:9607, cad:9609 등) 자신도 전부 같은 단일스레드 crownyc 서버라 순간적으로 accept()가 지연될 수 있는데, 그 순간 canvas의 소켓연결이 걸리면 캔버스 프로세스 전체가 그 connect() 하나에 무기한 블록 — 다른 클라이언트의 완전히 무관한 요청(/health 포함)까지 전부 밀린다. 원 보고의 "curl 전량 timeout"·"ESTABLISHED 커넥션이 안 닫힘"· (옛 PID 기준) 이중 LISTEN 정황이 전부 이 메커니즘으로 설명된다.

라이브 재현 (수리 전, 2026-07-24)

8개 동시 curl → /api/부팅: 순차 1.1~1.2s 간격 누적 처리, 8s 타임아웃 2건(exit=28)
동시에 /health 단독 요청도 즉시 실패(연결거부) 관측

수리

/Users/ef/crowny-canvas/한선씨/커넥터.한선커넥터_HTTP요청(host,port,path)을 원시소켓 (소켓생성/소켓연결/소켓폴링/소켓받기)에서 체계()+curl --connect-timeout 1 -m 2 -w '%{http_code}' 로 교체. curl은 connect 단계까지 자체 타임아웃을 갖고 있어(원시 소켓연결의 구조적 결함을 VM 패치 없이 애플리케이션 레벨에서 우회) 이 문제를 근본적으로 없앤다.

  • 가드레일 처방("외부 서비스 연동은 원시소켓 금지 → 체계()+curl -m N")과 일치
  • 크라우니 표준라이브러리 libs/네트워크.한선헬스체크(주소,포트)도 동일 패턴(체계+curl
--max-time) — 새 위험 도입이 아니라 기존 승인 관례를 적용
  • 07-앱-계약.md §6 "셸 실행류 서버 0건" 조항과 대조: 이 체계() 호출은 host/port/path가
전부 커넥터_어댑터정보의 고정 상수 4쌍뿐, 사용자 요청 데이터가 절대 유입되지 않는다 (§6이 겨냥한 os-site deacon execSync류 RCE와 다른 계열). 오케스트레이터가 §6 문구에 "사용자 입력 미경유 고정 헬스체크 예외"를 명시적으로 추가하는 걸 권고.
  • 07-앱-계약.md의 "curl 아닌 한선씨 함수 사용" 원문은 소켓연결이 안전하다는 잘못된 전제
기반이었음 — 이번 실측으로 폐기, 문서 갱신 권고.

재검증 (수리 후)

STRICT 재컴파일: 커넥터.한선 단독(0 warning) + 서버.한선 전체(0 warning, 76426 큐브)
CONNECTOR_SELFTEST=1: 14/14 PASS (space/nexus/cad/tele 라이브 연결 포함)
launchctl kickstart -k gui/$(id -u)/org.crowny.canvas → 단일 PID 확인(lsof, dual-LISTEN 없음)
재현 명령 재실행: 5~8개 동시 curl → /api/부팅 전부 200(exit=28/000 재현 안 됨)
혼합 동시요청(부팅+health 인터리브) → 전부 bounded 시간 내 200, 무관 요청 즉시거부 재현 안 됨

잔여 이슈 (수정하지 않음 — 범위 밖)

  • ~/Library/LaunchAgents/org.crowny.canvas.plistEnvironmentVariablesRCVTIMEO_MS
선언돼 있으나, crownyc.c가 실제로 읽는 이름은 CROWNY_TCP_RCVTIMEO_MS다(이름 불일치 — memory feedback_단일스레드crownyc_스톨블록_loopback처방에 기록된 "처방" 자체가 이 오타를 갖고 있었음). 따라서 TCP수락(492)의 클라이언트 recv/send 타임아웃은 의도한 1500ms가 아니라 기본값 5000ms로 계속 동작 중이다. plist 수정은 가드레일 "공유 인프라 무접촉" 규칙 위반이라 이번 작업 범위에서 제외 — 오케스트레이터/사장님 확인 후 plist 키 이름 정정 권고 (RCVTIMEO_MSCROWNY_TCP_RCVTIMEO_MS, canvas 외 다른 loopback 서버 plist도 동일 점검 필요).
  • 단일스레드 순차처리 자체(연결 정상인 경우도 4어댑터 순회에 ~0.2~2s 소요, 동시요청이 늘수록
선형 누적)는 아키텍처 특성이며 이번 수리 범위가 아님 — 고부하 시나리오는 향후 멀티프로세스/ 비동기화 검토 과제로 남김.

관련 파일

  • /Users/ef/crowny-canvas/한선씨/커넥터.한선 (수리)
  • /Users/ef/crowny-canvas/한선씨/서버.한선 (재컴파일만, 무변경)
  • /Users/ef/crowny-canvas/서버.toau (재빌드 산출물)
  • /Users/ef/CrownyOS/crownyc/crownyc.c (원인 확인만, opcode 374/492 — 수정하지 않음, 다른 프로젝트 소유)
  • ~/Library/LaunchAgents/org.crowny.canvas.plist (잔여 이슈 확인만, 수정하지 않음)