From 32eabb3a88dee8dfd951683f9c2dc024175fc576 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 10:46:13 +0900 Subject: [PATCH 01/11] =?UTF-8?q?ci:=20macOS=20PNG=20=EB=A0=8C=EB=8D=94=20?= =?UTF-8?q?hang=20=EC=A7=84=EB=8B=A8=20step=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test-other-os (macos-latest / py3.12) 잡이 단일 테스트 test_returns_png_magic 에서 33분 hang 하는 반면 로컬 ARM64 macOS 에선 동일 호출이 53.5 ms 로 끝남. 한글 폰트 fallback chain 부재가 원인이라는 가설을 검증하려고 brew 로 NanumGothic 설치 + pytest-timeout/faulthandler 로 thread dump 를 받도록 macOS 한정 step 분기. 검증 후 close 예정인 임시 브랜치. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d7aff9..e8e5849 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,7 +171,25 @@ jobs: python-version: "3.12" - run: uv sync --no-install-project --group all - run: uv run maturin develop --release - - run: uv run pytest tests/ -m "not slow" -v + # * macOS PNG 렌더 hang 진단 — Korean font 부재 가설 검증 + thread dump + - name: Install Korean font (macOS diag) + if: runner.os == 'macOS' + run: brew install --cask font-nanum-gothic + - name: Install pytest-timeout (macOS diag) + if: runner.os == 'macOS' + run: uv pip install pytest-timeout + - name: Run pytest (non-macOS) + if: runner.os != 'macOS' + run: uv run pytest tests/ -m "not slow" -v + - name: Run pytest (macOS, faulthandler + 5min timeout) + if: runner.os == 'macOS' + env: + PYTHONFAULTHANDLER: '1' + RUST_BACKTRACE: full + run: | + uv run pytest tests/ -m "not slow" -v -s \ + --timeout=300 --timeout-method=thread \ + -o faulthandler_timeout=240 # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 test-slow: From bb8a7e9f9070769a584d9184facc7b2973dda1d5 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 11:01:08 +0900 Subject: [PATCH 02/11] =?UTF-8?q?ci:=20macOS=20hang=20=EC=A7=84=EB=8B=A8?= =?UTF-8?q?=20=EA=B0=95=ED=99=94=20=E2=80=94=20native=20stack=20sampling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1차 진단에서 NanumGothic 설치에도 동일 hang → 폰트 부재 가설 기각. Python faulthandler 는 _inner.render_png (PyO3 진입) 직전에서 멈춰 Rust 측 hang 위치를 못 보여줌. macOS sample 명령으로 90 초 wait 후 hung process 의 native frame (Rust + Skia + system call 까지) 을 20 초 sampling. 폰트 인식 확인 (fc-list / system_profiler) 도 함께. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8e5849..2d5d4c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,25 +171,49 @@ jobs: python-version: "3.12" - run: uv sync --no-install-project --group all - run: uv run maturin develop --release - # * macOS PNG 렌더 hang 진단 — Korean font 부재 가설 검증 + thread dump - - name: Install Korean font (macOS diag) + # * macOS PNG 렌더 hang 진단 — 첫 round 결과로 폰트 부재 가설 기각 후, native stack sampling 으로 hang 위치 식별 + - name: Korean font inventory (macOS diag) if: runner.os == 'macOS' - run: brew install --cask font-nanum-gothic - - name: Install pytest-timeout (macOS diag) - if: runner.os == 'macOS' - run: uv pip install pytest-timeout + run: | + brew install --cask font-nanum-gothic + echo "=== fc-list 한글 폰트 ===" + (fc-list :lang=ko 2>&1 | head -10) || echo "(fc-list 미설치 또는 미인식)" + echo "=== system_profiler 나눔 ===" + system_profiler SPFontsDataType 2>&1 | grep -iE "nanum|gothic" | head -10 || echo "(미인식)" - name: Run pytest (non-macOS) if: runner.os != 'macOS' run: uv run pytest tests/ -m "not slow" -v - - name: Run pytest (macOS, faulthandler + 5min timeout) + - name: Run pytest (macOS) — sample native stack on hang if: runner.os == 'macOS' env: - PYTHONFAULTHANDLER: '1' RUST_BACKTRACE: full + RUST_LOG: debug run: | - uv run pytest tests/ -m "not slow" -v -s \ - --timeout=300 --timeout-method=thread \ - -o faulthandler_timeout=240 + set +e + uv run pytest tests/test_render_png.py::TestRenderPng::test_returns_png_magic -v -s & + PYTEST_PID=$! + # ^ 정상이면 분 단위 안에 끝남. 90초 wait 후 살아있으면 hang 으로 판정. + for i in $(seq 1 9); do + sleep 10 + if ! kill -0 "$PYTEST_PID" 2>/dev/null; then + wait "$PYTEST_PID"; EXIT=$? + echo "pytest 종료 in ~$((i*10))s exit=$EXIT" + # 정상 종료라면 나머지 테스트도 돌려서 회귀 가드 + if [ "$EXIT" = "0" ]; then + exec uv run pytest tests/ -m "not slow" -v + fi + exit $EXIT + fi + done + echo "=== HANG DETECTED — sampling native stacks ===" + PIDS="$PYTEST_PID $(pgrep -P "$PYTEST_PID" || true)" + for PID in $PIDS; do + kill -0 "$PID" 2>/dev/null || continue + echo "----- sample PID=$PID (20s) -----" + sample "$PID" 20 2>&1 | tail -400 || true + done + kill -9 "$PYTEST_PID" 2>/dev/null + exit 1 # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 test-slow: From ee172c49cf741264634eb50be1b03c2688b82d34 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 11:15:54 +0900 Subject: [PATCH 03/11] =?UTF-8?q?ci:=20sample=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B0=9B=EC=95=84=20?= =?UTF-8?q?artifact=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 진단에서 sample 출력에 tail -400 을 걸어 자식 PID 의 Call graph 가 잘려 Binary Images 만 남았음. sample 의 -file 옵션으로 파일에 저장 → Call graph 부분만 awk 로 추출해 stdout 에 노출 + 전체 sample 파일은 actions/upload-artifact 로 업로드해 다운로드 후 분석 가능하게 함. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d5d4c6..b9a630e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,9 +187,9 @@ jobs: if: runner.os == 'macOS' env: RUST_BACKTRACE: full - RUST_LOG: debug run: | set +e + mkdir -p /tmp/diag uv run pytest tests/test_render_png.py::TestRenderPng::test_returns_png_magic -v -s & PYTEST_PID=$! # ^ 정상이면 분 단위 안에 끝남. 90초 wait 후 살아있으면 hang 으로 판정. @@ -198,7 +198,6 @@ jobs: if ! kill -0 "$PYTEST_PID" 2>/dev/null; then wait "$PYTEST_PID"; EXIT=$? echo "pytest 종료 in ~$((i*10))s exit=$EXIT" - # 정상 종료라면 나머지 테스트도 돌려서 회귀 가드 if [ "$EXIT" = "0" ]; then exec uv run pytest tests/ -m "not slow" -v fi @@ -206,14 +205,27 @@ jobs: fi done echo "=== HANG DETECTED — sampling native stacks ===" + # ^ 자식까지 포함 (uv → python pytest). sample 출력을 파일로 받아 artifact 업로드. PIDS="$PYTEST_PID $(pgrep -P "$PYTEST_PID" || true)" for PID in $PIDS; do kill -0 "$PID" 2>/dev/null || continue - echo "----- sample PID=$PID (20s) -----" - sample "$PID" 20 2>&1 | tail -400 || true + OUT="/tmp/diag/sample_${PID}.txt" + echo "----- sampling PID=$PID → $OUT -----" + sample "$PID" 15 -file "$OUT" 2>&1 | head -3 + echo ">>> Call graph head (PID=$PID) <<<" + awk '/^Call graph:/,/^Binary Images:/' "$OUT" 2>/dev/null | head -120 || echo "(파일 미생성)" + echo done + ls -la /tmp/diag/ kill -9 "$PYTEST_PID" 2>/dev/null exit 1 + - name: Upload sample diagnostic artifacts + if: always() && runner.os == 'macOS' + uses: actions/upload-artifact@v4 + with: + name: macos-png-hang-samples + path: /tmp/diag/ + if-no-files-found: warn # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 test-slow: From 39f8ecd34bd69be56b42f5bb6e1711307d32b0e3 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 11:29:30 +0900 Subject: [PATCH 04/11] =?UTF-8?q?ci:=20mitigation=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=E2=80=94=20chain=20=EC=A0=84=EC=B2=B4=20=ED=95=9C=EA=B8=80=20?= =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3차 진단에서 PID 8552 (Python) call graph 가 CoreText TDownloadableFontManager::Download → libFontRegistryUI → CFMessagePortSendRequest → mach_msg2_trap 으로 IPC 무한 대기 임을 확인. 원인은 macOS CoreText 의 downloadable-font auto-activation: 시스템에 없는 폰트가 요청되면 FontRegistryUI daemon 에 IPC 로 다운로드 가능성을 물어보고 응답 대기 — Actions runner 환경에서 daemon 이 응답 못 함. NanumGothic 만으로 효과 없었던 이유는 상류 fallback chain 의 앞쪽 폰트 (Noto Sans KR 등) 가 없어서 거기서부터 다운로드 lookup 발동. chain 전체 (Noto Sans KR / Noto Serif KR / Noto CJK / Nanum Gothic) 를 설치해 첫 매치에서 lookup 이 끝나도록 함. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9a630e..26d21c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,15 +171,18 @@ jobs: python-version: "3.12" - run: uv sync --no-install-project --group all - run: uv run maturin develop --release - # * macOS PNG 렌더 hang 진단 — 첫 round 결과로 폰트 부재 가설 기각 후, native stack sampling 으로 hang 위치 식별 - - name: Korean font inventory (macOS diag) + # * macOS PNG 렌더 hang — 3차 진단으로 CoreText downloadable-font IPC hang 확인 (TDownloadableFontManager::Download → FontRegistryUI). chain 첫 폰트 부재 시 macOS 가 다운로드 시도 → daemon 응답 없음 → 무한 IPC wait. + # mitigation: 상류 fallback chain 의 모든 한글 폰트 사전 설치로 다운로드 lookup 자체를 막음. + - name: Install Korean font chain (macOS mitigation) if: runner.os == 'macOS' run: | - brew install --cask font-nanum-gothic + brew install --cask \ + font-noto-sans-kr \ + font-noto-serif-kr \ + font-noto-sans-cjk \ + font-nanum-gothic echo "=== fc-list 한글 폰트 ===" - (fc-list :lang=ko 2>&1 | head -10) || echo "(fc-list 미설치 또는 미인식)" - echo "=== system_profiler 나눔 ===" - system_profiler SPFontsDataType 2>&1 | grep -iE "nanum|gothic" | head -10 || echo "(미인식)" + fc-list :lang=ko 2>&1 | head -20 || echo "(fc-list 미인식)" - name: Run pytest (non-macOS) if: runner.os != 'macOS' run: uv run pytest tests/ -m "not slow" -v From f9afc8038f9c2388ee35a82156fdf1c78ab0e8f7 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 11:53:12 +0900 Subject: [PATCH 05/11] =?UTF-8?q?ci:=20=EC=83=81=EB=A5=98=20rhwp=20CLI=20?= =?UTF-8?q?=EB=8F=84=20macOS=20=EC=97=90=EC=84=9C=20hang=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 진단 PR 의 macOS 잡 끝에 상류 rhwp 자체 CLI (export-svg) 실행 step 추가. 상류 CLI 도 SkiaRenderer::new 의 FontMgr::default() 진입점을 공유하므로 같은 hang 이 재현되는지 확인. timeout 90s, exit 124 면 hang 으로 판정. PNG path 특이성 vs 상류 전반 영향 구분. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26d21c8..5f6a2c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,6 +229,24 @@ jobs: name: macos-png-hang-samples path: /tmp/diag/ if-no-files-found: warn + # * 상류 CLI 자체도 hang 하는지 확인 — rhwp export-svg 가 같은 FontMgr 진입점을 거침 + - name: Upstream rhwp CLI 재현 (macOS, export-svg) + if: always() && runner.os == 'macOS' + working-directory: external/rhwp + run: | + set +e + cargo build --release --bin rhwp 2>&1 | tail -5 + BIN=./target/release/rhwp + [ -x "$BIN" ] || { echo "(rhwp 바이너리 빌드 실패)"; exit 0; } + mkdir -p /tmp/svg_diag + echo "=== export-svg page 0, 90s timeout ===" + time timeout 90 "$BIN" export-svg samples/aift.hwp -o /tmp/svg_diag -p 0 + RC=$? + case "$RC" in + 124) echo "⚠️ TIMEOUT — 상류 CLI 도 동일하게 hang" ;; + 0) echo "✓ 상류 CLI export-svg 정상 종료 — PNG path 특이성" ;; + *) echo "exit=$RC (정상도 timeout 도 아님)" ;; + esac # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 test-slow: From 1426d18b89a39353ac831048be8de997d939f879 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 12:06:31 +0900 Subject: [PATCH 06/11] =?UTF-8?q?ci:=20=EC=83=81=EB=A5=98=20CLI=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20step=20=EC=9D=98=20binary=20path=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 시도는 working-directory: external/rhwp 에서 target/release/rhwp 를 찾았으나 rhwp-python 의 Cargo.toml 이 workspace 를 정의하고 external/rhwp 를 path dependency 로 포함하므로 cargo build 산출물이 repo root 의 target/ 에 떨어짐. working-directory 제거 + sample 경로를 external/rhwp/samples/aift.hwp 로 보정. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f6a2c9..5d55efc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,18 +229,17 @@ jobs: name: macos-png-hang-samples path: /tmp/diag/ if-no-files-found: warn - # * 상류 CLI 자체도 hang 하는지 확인 — rhwp export-svg 가 같은 FontMgr 진입점을 거침 + # * 상류 CLI 자체도 hang 하는지 확인 — rhwp export-svg 가 같은 FontMgr 진입점을 거침. workspace target 이 root 에 있음 (Cargo.toml workspace) 이라 working-directory 안 잡고 root 에서 빌드. - name: Upstream rhwp CLI 재현 (macOS, export-svg) if: always() && runner.os == 'macOS' - working-directory: external/rhwp run: | set +e - cargo build --release --bin rhwp 2>&1 | tail -5 - BIN=./target/release/rhwp - [ -x "$BIN" ] || { echo "(rhwp 바이너리 빌드 실패)"; exit 0; } + cargo build --release --bin rhwp 2>&1 | tail -3 + BIN=target/release/rhwp + [ -x "$BIN" ] || { echo "(빌드 산출물 없음)"; find . -name "rhwp" -type f -maxdepth 5 2>/dev/null | head -5; exit 0; } mkdir -p /tmp/svg_diag echo "=== export-svg page 0, 90s timeout ===" - time timeout 90 "$BIN" export-svg samples/aift.hwp -o /tmp/svg_diag -p 0 + time timeout 90 "$BIN" export-svg external/rhwp/samples/aift.hwp -o /tmp/svg_diag -p 0 RC=$? case "$RC" in 124) echo "⚠️ TIMEOUT — 상류 CLI 도 동일하게 hang" ;; From dc595ebe050f87d35667a62c200ffb41dd2fcf82 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 12:19:36 +0900 Subject: [PATCH 07/11] =?UTF-8?q?ci:=20=EC=83=81=EB=A5=98=20CLI=20step=20w?= =?UTF-8?q?orking-directory=20+=20=EB=91=90=20target=20path=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit workspace root 에서 --bin rhwp 빌드 시도가 default-run packages 에서 타깃을 못 찾아 실패함. working-directory: external/rhwp 로 돌려서 cargo 가 그 디렉토리의 [[bin]] 정의를 인식하게 함. 빌드 산출물은 workspace 멤버 발견 결과에 따라 ../../target/release/rhwp 또는 ./target/release/rhwp 둘 중 하나로 떨어지므로 양쪽 다 시도. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d55efc..f62a483 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,17 +229,19 @@ jobs: name: macos-png-hang-samples path: /tmp/diag/ if-no-files-found: warn - # * 상류 CLI 자체도 hang 하는지 확인 — rhwp export-svg 가 같은 FontMgr 진입점을 거침. workspace target 이 root 에 있음 (Cargo.toml workspace) 이라 working-directory 안 잡고 root 에서 빌드. + # * 상류 CLI 자체도 hang 하는지 확인 — rhwp export-svg 가 같은 FontMgr 진입점을 거침. external/rhwp 디렉토리에서 빌드하되 산출물은 workspace target 으로 떨어질 수 있어 두 path 다 시도. - name: Upstream rhwp CLI 재현 (macOS, export-svg) if: always() && runner.os == 'macOS' + working-directory: external/rhwp run: | set +e cargo build --release --bin rhwp 2>&1 | tail -3 - BIN=target/release/rhwp - [ -x "$BIN" ] || { echo "(빌드 산출물 없음)"; find . -name "rhwp" -type f -maxdepth 5 2>/dev/null | head -5; exit 0; } + BIN=../../target/release/rhwp + [ -x "$BIN" ] || BIN=./target/release/rhwp + [ -x "$BIN" ] || { echo "(빌드 산출물 없음)"; find ../.. -name "rhwp" -type f -maxdepth 5 2>/dev/null | head -5; exit 0; } mkdir -p /tmp/svg_diag - echo "=== export-svg page 0, 90s timeout ===" - time timeout 90 "$BIN" export-svg external/rhwp/samples/aift.hwp -o /tmp/svg_diag -p 0 + echo "=== export-svg page 0, 90s timeout · using $BIN ===" + time timeout 90 "$BIN" export-svg samples/aift.hwp -o /tmp/svg_diag -p 0 RC=$? case "$RC" in 124) echo "⚠️ TIMEOUT — 상류 CLI 도 동일하게 hang" ;; From 170ef4154d4bd8de7e16a205619a5d3009271e34 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 12:32:59 +0900 Subject: [PATCH 08/11] =?UTF-8?q?ci:=20macOS=20=EC=97=90=20timeout=20?= =?UTF-8?q?=EB=AA=85=EB=A0=B9=20=EC=97=86=EC=9D=8C=20=E2=80=94=20sleep=20l?= =?UTF-8?q?oop=20=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 시도가 timeout 명령으로 exit 127 (command not found) 종료됨. GNU coreutils 미포함이라 macOS runner 에 timeout 도 gtimeout 도 없음. background 실행 + sleep 5s 폴링 + 경과 90s 시 kill -9 패턴으로 직접 구현. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f62a483..8122ddd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -240,14 +240,28 @@ jobs: [ -x "$BIN" ] || BIN=./target/release/rhwp [ -x "$BIN" ] || { echo "(빌드 산출물 없음)"; find ../.. -name "rhwp" -type f -maxdepth 5 2>/dev/null | head -5; exit 0; } mkdir -p /tmp/svg_diag - echo "=== export-svg page 0, 90s timeout · using $BIN ===" - time timeout 90 "$BIN" export-svg samples/aift.hwp -o /tmp/svg_diag -p 0 - RC=$? - case "$RC" in - 124) echo "⚠️ TIMEOUT — 상류 CLI 도 동일하게 hang" ;; - 0) echo "✓ 상류 CLI export-svg 정상 종료 — PNG path 특이성" ;; - *) echo "exit=$RC (정상도 timeout 도 아님)" ;; - esac + echo "=== export-svg page 0, 90s soft timeout · using $BIN ===" + # macOS 는 GNU `timeout` 미포함 — background + sleep loop 로 구현 + "$BIN" export-svg samples/aift.hwp -o /tmp/svg_diag -p 0 & + CLI_PID=$! + START=$(date +%s) + while kill -0 "$CLI_PID" 2>/dev/null; do + sleep 5 + ELAPSED=$(( $(date +%s) - START )) + if [ "$ELAPSED" -ge 90 ]; then + echo "⚠️ TIMEOUT ${ELAPSED}s — 상류 CLI 도 동일하게 hang" + kill -9 "$CLI_PID" 2>/dev/null + wait "$CLI_PID" 2>/dev/null + exit 0 + fi + done + wait "$CLI_PID"; RC=$? + ELAPSED=$(( $(date +%s) - START )) + if [ "$RC" = "0" ]; then + echo "✓ 상류 CLI export-svg ${ELAPSED}s 에 정상 종료 — PNG path 특이성" + else + echo "exit=$RC after ${ELAPSED}s (timeout 도 정상도 아님)" + fi # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 test-slow: From 24b4b19953bd1821147917dde730b2bef06f568e Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 13:01:09 +0900 Subject: [PATCH 09/11] =?UTF-8?q?ci:=20=ED=9B=84=EB=B3=B4=20B=20fix=20?= =?UTF-8?q?=ED=9A=A8=EA=B3=BC=20=EA=B2=80=EC=A6=9D=20=E2=80=94=20CoreText?= =?UTF-8?q?=20auto-activation=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20pat?= =?UTF-8?q?ch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CTFontManagerSetAutoActivationSetting(null, 1) 호출을 SkiaLayerRenderer::new() 진입 직후에 삽입하는 patch 를 ci/patches/ 에 추가. macOS 잡에서 maturin develop --release 직전에 git apply 로 working tree 에만 반영 (external/rhwp 자체 commit 없음). PNG hang 이 사라지면 후보 B 확정, 그대로면 후보 A 또는 다른 방향 탐색 필요. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 8 ++++++++ ci/patches/macos-coretext-fix.patch | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 ci/patches/macos-coretext-fix.patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8122ddd..e469eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,6 +170,14 @@ jobs: with: python-version: "3.12" - run: uv sync --no-install-project --group all + # * 실험 patch: macOS 만 SkiaLayerRenderer::new() 진입에 CoreText auto-activation 비활성화. external/rhwp 자체에는 commit 하지 않고 runtime 에만 apply. + - name: Apply experimental CoreText fix (macOS only) + if: runner.os == 'macOS' + run: | + cd external/rhwp + git apply ../../ci/patches/macos-coretext-fix.patch + echo "=== patch applied ===" + git diff --stat - run: uv run maturin develop --release # * macOS PNG 렌더 hang — 3차 진단으로 CoreText downloadable-font IPC hang 확인 (TDownloadableFontManager::Download → FontRegistryUI). chain 첫 폰트 부재 시 macOS 가 다운로드 시도 → daemon 응답 없음 → 무한 IPC wait. # mitigation: 상류 fallback chain 의 모든 한글 폰트 사전 설치로 다운로드 lookup 자체를 막음. diff --git a/ci/patches/macos-coretext-fix.patch b/ci/patches/macos-coretext-fix.patch new file mode 100644 index 0000000..00f86bc --- /dev/null +++ b/ci/patches/macos-coretext-fix.patch @@ -0,0 +1,24 @@ +diff --git a/src/renderer/skia/renderer.rs b/src/renderer/skia/renderer.rs +index eea1f807..33407294 100644 +--- a/src/renderer/skia/renderer.rs ++++ b/src/renderer/skia/renderer.rs +@@ -26,6 +26,19 @@ pub struct SkiaLayerRenderer { + + impl SkiaLayerRenderer { + pub fn new() -> Self { ++ // [diagnostic patch from rhwp-python ci/patches/macos-coretext-fix.patch] ++ // macOS 의 CoreText downloadable-font lookup IPC (fontd mach port) 무한 대기 회피 시도. ++ #[cfg(target_os = "macos")] ++ unsafe { ++ extern "C" { ++ fn CTFontManagerSetAutoActivationSetting( ++ bundle_id: *const core::ffi::c_void, ++ setting: u32, ++ ); ++ } ++ // kCTFontManagerAutoActivationDisabled = 1 ++ CTFontManagerSetAutoActivationSetting(core::ptr::null(), 1); ++ } + Self { + font_mgr: FontMgr::default(), + custom_typefaces: HashMap::new(), From 53056dc797ea8cc14f72d94e8969e9c6592dc1fc Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 13:16:25 +0900 Subject: [PATCH 10/11] =?UTF-8?q?ci:=20=ED=9B=84=EB=B3=B4=20A=20patch=20?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20=E2=80=94=20match=5Ffamily=5Fs?= =?UTF-8?q?tyle=20=EC=82=AC=EC=A0=84=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 후보 B (CTFontManagerSetAutoActivationSetting) 가 효과 없음을 확인. 검증 에이전트의 회의 — 해당 API 가 타사 font manager activation 알림 용일 뿐 libFontRegistryUI downloadable lookup 은 안 끔 — 이 직접 검증됨. 후보 A 로 교체. font_mgr.family_names() 로 시스템 폰트 family set 을 SkiaLayerRenderer 생성 시 캐시하고, match_family_style 호출 전 에 멤버십 체크해 missing family 는 skip. CoreText 의 downloadable lookup path 자체에 진입하지 않게 함. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- ci/patches/macos-coretext-fix.patch | 55 ++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e469eb0..2a76126 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,7 +170,7 @@ jobs: with: python-version: "3.12" - run: uv sync --no-install-project --group all - # * 실험 patch: macOS 만 SkiaLayerRenderer::new() 진입에 CoreText auto-activation 비활성화. external/rhwp 자체에는 commit 하지 않고 runtime 에만 apply. + # * 실험 patch: macOS family lookup 에 시스템 폰트 사전 필터링 적용 — match_family_style 호출 전에 멤버십 체크해 missing family 를 CoreText 에 던지지 않음. external/rhwp 자체에는 commit 하지 않고 runtime 에만 apply. - name: Apply experimental CoreText fix (macOS only) if: runner.os == 'macOS' run: | diff --git a/ci/patches/macos-coretext-fix.patch b/ci/patches/macos-coretext-fix.patch index 00f86bc..e38220a 100644 --- a/ci/patches/macos-coretext-fix.patch +++ b/ci/patches/macos-coretext-fix.patch @@ -1,24 +1,47 @@ diff --git a/src/renderer/skia/renderer.rs b/src/renderer/skia/renderer.rs -index eea1f807..33407294 100644 +index eea1f807..5193fedf 100644 --- a/src/renderer/skia/renderer.rs +++ b/src/renderer/skia/renderer.rs -@@ -26,6 +26,19 @@ pub struct SkiaLayerRenderer { +@@ -2,7 +2,7 @@ use skia_safe::{ + font, paint, surfaces, Canvas, Color, EncodedImageFormat, Font, FontMgr, FontStyle, Paint, + PathBuilder, PathEffect, Rect, Typeface, + }; +-use std::collections::HashMap; ++use std::collections::{HashMap, HashSet}; + + use crate::error::HwpError; + use crate::model::image::ImageEffect; +@@ -22,13 +22,20 @@ pub struct SkiaLayerRenderer { + /// key = primary face name (Typeface::family_name), value = Typeface. + /// SVG 의 `--font-path` 와 같은 패턴으로 ttfs 디렉토리의 한컴 전용 폰트 (HY견명조 등) 도 사용 가능. + custom_typefaces: HashMap, ++ /// [diagnostic patch] 시스템 폰트 family list 사전 캐시 — macOS 의 ++ /// CoreText downloadable-font lookup IPC hang 회피용. match_family_style ++ /// 호출 전에 멤버십 체크해 missing family 를 CoreText 에 던지지 않음. ++ system_families: HashSet, + } impl SkiaLayerRenderer { pub fn new() -> Self { -+ // [diagnostic patch from rhwp-python ci/patches/macos-coretext-fix.patch] -+ // macOS 의 CoreText downloadable-font lookup IPC (fontd mach port) 무한 대기 회피 시도. -+ #[cfg(target_os = "macos")] -+ unsafe { -+ extern "C" { -+ fn CTFontManagerSetAutoActivationSetting( -+ bundle_id: *const core::ffi::c_void, -+ setting: u32, -+ ); -+ } -+ // kCTFontManagerAutoActivationDisabled = 1 -+ CTFontManagerSetAutoActivationSetting(core::ptr::null(), 1); -+ } ++ let font_mgr = FontMgr::default(); ++ let system_families: HashSet = font_mgr.family_names().collect(); Self { - font_mgr: FontMgr::default(), +- font_mgr: FontMgr::default(), ++ font_mgr, custom_typefaces: HashMap::new(), ++ system_families, + } + } + +@@ -326,6 +333,11 @@ impl SkiaLayerRenderer { + } + } + for family in &families { ++ // [diagnostic patch] macOS 의 CoreText downloadable lookup IPC ++ // hang 회피 — 시스템에 없는 family 는 match_family_style 에 던지지 않음. ++ if !self.system_families.contains(family.as_str()) { ++ continue; ++ } + if let Some(tf) = self.font_mgr.match_family_style(family, font_style) { + push(&mut chain, &mut seen, tf); + } From be473ffad2517d71e2b7d56a1cebb00f4c7ce647 Mon Sep 17 00:00:00 2001 From: DanMeon Date: Mon, 11 May 2026 13:31:42 +0900 Subject: [PATCH 11/11] =?UTF-8?q?ci:=20patch=20v3=20=E2=80=94=20family.as?= =?UTF-8?q?=5Fstr()=20unstable=20feature=20=ED=9A=8C=ED=94=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 후보 A patch v2 의 family.as_str() 가 macOS runner 의 Rust 1.95.0 에서 unstable str_as_str feature 로 에러. AsRef::::as_ref(family) 로 변경해 stable 한 표현으로 교정. 로컬 cargo check release OK 확인. Co-Authored-By: Claude Opus 4.7 (1M context) --- ci/patches/macos-coretext-fix.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/patches/macos-coretext-fix.patch b/ci/patches/macos-coretext-fix.patch index e38220a..0dc5674 100644 --- a/ci/patches/macos-coretext-fix.patch +++ b/ci/patches/macos-coretext-fix.patch @@ -39,7 +39,7 @@ index eea1f807..5193fedf 100644 for family in &families { + // [diagnostic patch] macOS 의 CoreText downloadable lookup IPC + // hang 회피 — 시스템에 없는 family 는 match_family_style 에 던지지 않음. -+ if !self.system_families.contains(family.as_str()) { ++ if !self.system_families.contains(AsRef::::as_ref(family)) { + continue; + } if let Some(tf) = self.font_mgr.match_family_style(family, font_style) {