크라우니캔버스 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
- 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.plist의EnvironmentVariables에RCVTIMEO_MS가
CROWNY_TCP_RCVTIMEO_MS다(이름 불일치 —
memory feedback_단일스레드crownyc_스톨블록_loopback처방에 기록된 "처방" 자체가 이 오타를
갖고 있었음). 따라서 TCP수락(492)의 클라이언트 recv/send 타임아웃은 의도한 1500ms가 아니라
기본값 5000ms로 계속 동작 중이다. plist 수정은 가드레일 "공유 인프라 무접촉" 규칙 위반이라
이번 작업 범위에서 제외 — 오케스트레이터/사장님 확인 후 plist 키 이름 정정 권고
(RCVTIMEO_MS → CROWNY_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(잔여 이슈 확인만, 수정하지 않음)