# 20260815 — KẾ HOẠCH THỰC HIỆN G3 (HEALTH / WATCHDOG THÔNG MINH) Nguồn: `big_tasks.md` — header **G3 — Health / Watchdog thông minh (1-2 tuần)**. Trạng thái roadmap: G0 ✅, G1 ✅, G2 ✅ (G2.0–G2.6). Branch: `standalone-shm-bridge`. --- ## 1. PHÂN TÍCH G3 ### 1.1 Mục tiêu Watchdog thông minh, cô lập lỗi: - G3.1 heartbeat riêng: heartbeat bridge tách khỏi logic audio. - G3.2 phân loại stall: phân biệt hang/crash/stall để xử lý đúng mức. - G3.3 SEH quanh process từng channel: 1 plugin crash → chỉ mute channel đó, không sập cả bridge. ### 1.2 Hiện trạng (đã verify trong code) | Thành phần | File | Hiện trạng liên quan G3 | |---|---|---| | SHM layout | `native_bridge/include/SharedMemoryIPC.h` + `src-tauri/src/shm.rs` (phải khớp, assert size) | `bridgeWriteIndex` @4 (mỗi block render), `attachInFlight` @11160 (G0.3). Tổng 11164B. Không có heartbeat riêng. | | Watchdog Rust | `src-tauri/src/lib.rs` pump thread | Liveness = `write_index()` đổi trong 3s; nếu `attach_in_flight` → deadline 15s; stall → kill + spawn lại 1 lần rồi `bridge-down`. | | Bridge C++ | `native_bridge/src/main.cpp` | Main loop = audio loop (render + drain control); mọi attach/load chạy trên UiThread (`uiWorker.post`); heartbeat không tồn tại. | ### 1.3 Khoảng trống (gap) 1. **Liveness gắn với audio loop** — writeIndex chỉ tăng khi audio loop còn render; attach job dài (Nexus >3s, G0.3) làm audio loop nghẽn → watchdog tưởng bridge chết → phải kéo deadline lên 15s (đánh đổi: bridge thật chết cũng chờ 15s). 2. **Không phân loại được stall** — hang (audio loop kẹt, process còn sống) vs crash (process chết) xử lý giống nhau. 3. **1 plugin chết kéo cả bridge** — chưa có SEH/process con từng channel (G3.3). ### 1.4 Nguyên tắc thiết kế (đề xuất) - **Heartbeat là nguồn liveness duy nhất** (G3.1): bridge chạy thread riêng tăng `heartbeat` ~10Hz trong SHM, độc lập audio loop → attach dài không còn ảnh hưởng deadline; bỏ ngoại lệ `attach_in_flight` trong watchdog. - **Audio stall ≠ bridge chết** (G3.1 lót đường G3.2): heartbeat còn mà writeIndex đứng → log "audio stall (heartbeat OK)" — không restart. - **Field append-only** — giữ offset cũ (như G0.3): thêm `heartbeat` sau `attachInFlight` @11164. - **Không đụng audio loop** — heartbeat thread riêng, Sleep(100ms), không lock. --- ## 2. PHÂN RÃ TASKS + ESTIMATE | ID | Task | File đích | Nội dung | Estimate | |---|---|---|---|---| | G3.1 | Heartbeat riêng | `SharedMemoryIPC.h`, `shm.rs`, `main.cpp`, `lib.rs`, `shm_selfcheck.cpp` | SHM thêm `heartbeat:u32` (append @11164, size 11168); bridge thread riêng tăng 100ms; Rust pump dùng heartbeat cho liveness (deadline 3s, bỏ ngoại lệ attach); writeIndex stall khi heartbeat còn → log warning, không restart. | 1 ngày | | G3.2 | Phân loại stall | `lib.rs`, `main.cpp` | hang vs crash vs stall; xử lý đúng mức (restart / mute / chỉ log). | 2 ngày | | G3.3 | SEH từng channel | `main.cpp` | 1 plugin crash → chỉ mute channel, không sập bridge. | 3 ngày | | | **Tổng** | | | **6 ngày (~1.5 tuần — khớp roadmap)** | ### Phụ thuộc `G3.2 → G3.1` (cần heartbeat) · `G3.3 → G3.2` (cần phân loại). ### Thứ tự triển khai khuyến nghị G3.1 → G3.2 → G3.3. --- ## 3. KẾ HOẠCH THỰC HIỆN (CÀI ĐẶT → KIỂM TRA → BẮT BUGS → FIX → REPORT) ### G3.1 — Heartbeat riêng - **Cài đặt**: - `SharedMemoryIPC.h`: thêm `volatile uint32_t heartbeat;` sau `attachInFlight` (append-only, offset 11164). - `shm.rs`: thêm field `heartbeat: u32` + accessor `heartbeat()` + sửa assert size 11168. - `main.cpp`: trước main loop, spawn thread `[shmIPC]{ for(;;){ Sleep(100); shmIPC->heartbeat++; } }` detach. - `lib.rs` pump: liveness = heartbeat (đổi trong 3s → restart 1 lần → `bridge-down`); writeIndex chỉ dùng emit audio; heartbeat còn + writeIndex đứng ≥3s → log `[tauri] audio stall (heartbeat OK)`, không restart. Bỏ `attach_in_flight` khỏi deadline. - `shm_selfcheck.cpp`: assert size 11168. - **Kiểm tra**: build bridge (build_bridge.ps1) + `cargo build --release` (src-tauri) → copy exe vào install/ → deploy → probe SHM: heartbeat tăng ~10Hz; kill bridge → heartbeat đứng → watchdog restart (log) → heartbeat tăng lại; regression G1.5 subset + G2.5 stress vẫn PASS. - **Bắt bugs**: attach dài không còn risk restart (bỏ ngoại lệ 15s); heartbeat sai offset (assert size bắt). - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): patch bytes cho file CRLF (SharedMemoryIPC.h, main.cpp), text thường cho lib.rs (LF). Build bridge + cargo release. Deploy: kill 3 proc → copy `target/release/sonicforge-daw.exe` + `native_bridge/build/Release/daw_vst_bridge.exe` vào `install/` → start → PIDs mới. Chạy probe heartbeat + stress. Commit: `G3.1: dedicated bridge heartbeat — watchdog liveness independent of audio loop`. - **Linux** (ghi chú): pull + review; không chạy được (cần SHM Windows); cập nhật mục 5. --- ## 4. RỦI RO | Rủi ro | Ảnh hưởng | Giảm thiểu | |---|---|---| | Heartbeat thread bị kill cùng process | Vẫn restart như cũ (không tệ hơn) | OK — heartbeat chết = process chết = restart đúng. | | Heartbeat sai offset → đè field khác | Hỏng SHM | Assert size 11168 ở cả 2 phía (shm.rs test + shm_selfcheck.cpp). | | Bỏ ngoại lệ attach 15s → attach dài bị restart | Mất GUI giữa attach | Heartbeat thread độc lập → attach không làm heartbeat đứng; chỉ restart khi process thật chết. | | Audio loop kẹt vĩnh viễn (plugin hang) nhưng heartbeat còn | Không restart → câm vĩnh viễn | G3.2 phân loại + xử lý (log cảnh báo ở G3.1, có dấu hiệu trong log). | --- ## 5. BÁO CÁO KẾT QUẢ (điền sau khi thực hiện) | ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái | |---|---|---|---|---|---| | G3.1 | Heartbeat @11164 (size 11168), thread riêng Sleep(100) tăng `heartbeat`; pump liveness = heartbeat (deadline 3s, restart 1 lần); bỏ ngoại lệ attach 15s; writeIndex chỉ emit audio + stall log. | Probe G3.1 PASS (hb 10Hz; kill bridge → restart 3.8s, log stalled); G2.5 stress PASS; G1.5 stress PASS (fix test script đọc log 1 lần/poll); G0.4 probe PASS. | G1.5 fail trước đó là test-env: test script gọi log_lines() mỗi index (140k dòng) + window VST cũ dính → OPEN_GUI dedupe silent; flood 896 dòng allNotesOff pre-existing. | Không cần fix thêm. | ✅ | | G3.2 | lib.rs: khi heartbeat stalled 3s + restart_attempts==0, lấy pid từ BridgeProcess state → process_alive(pid) (OpenProcess + GetExitCodeProcess, windows-sys) → log `HUNG (pid N alive, heartbeat stalled)` / `CRASHED (pid N gone)` / `UNKNOWN (no tracked pid)`; Cargo.toml thêm feature Win32_System_Threading. | Probe CRASH PASS (kill bridge → log `CRASHED (pid 4432 gone)` sau ~10s → restart); Probe HUNG PASS (NtSuspendProcess → log `HUNG (pid 6232 alive, heartbeat stalled)` → TerminateProcess → restart). Regression G1.5 + G3.1 PASS. | Không có. | Không cần. | ✅ | | G3.3 | NativeInstrumentEngine.h/cpp: `crashedCh_[16]` + markCrashed/isCrashed; `channelQuiet` trả true khi crashed (mute dispatch+render); SEH helper `SafeProcessChannel` (`__try/__except EXCEPTION_EXECUTE_HANDLER`, tách khỏi hàm có C++ unwinding để tránh C2712); renderAll nếu false → `crashedCh_[ch]=true` + log muted until reload + continue (không cộng output); assign() reset flag; /EHa đã bật sẵn. CMakeLists thêm target `seh_channel_test`. | Bridge build PASS; Cargo build PASS; `seh_channel_test.exe` PASS (stub ném access violation → SafeProcessChannel false, block zero, manager isCrashed set, renderAll no-op, process sống); regression G1.5 + G3.1 PASS. | Không có. | Không cần. | ✅ | ### Nhật ký thực hiện ### G3.1 — Hoàn tất (2026-08-15) - SHM: `heartbeat` append @11164, tổng 11168B (SharedMemoryIPC.h + shm.rs field/accessor/assert + shm_selfcheck.cpp assert). - main.cpp: thread riêng `Sleep(100)` → `shmIPC->heartbeat++` (độc lập audio loop). - lib.rs pump: liveness = heartbeat, deadline 3s → kill+spawn 1 lần/bridge-down; bỏ attach_deadline 15s; writeIndex chỉ emit audio; heartbeat còn + writeIndex đứng → log `[tauri] audio stall (heartbeat OK)`, không restart. - Verify: probe heartbeat PASS (tăng ~10Hz, kill → watchdog restart 3.8s, spawn.log `bridge heartbeat stalled 3s — restart attempt #1`); G2.5 stress PASS; G1.5 stress PASS; G0.4 probe PASS. - G1.5 fail trước đó = test-env, không phải regression G3.1: (1) test script `wait_log` đọc cả file log mỗi index truy cập (140k+ dòng) → poll chậm >10s → STOP/OPEN_GUI timeout giả; fix: đọc 1 lần/poll. (2) window VST cũ (0x870D76, 0x2F0754) giữ view giữa các lần chạy → OPEN_GUI dedupe silent (handleOpenGui `hasAttachedView()` return không log). (3) flood 896 dòng `[midi] OFF` = allNotesOff() log từng pitch (7 ch × 128) — pre-existing, STOP log vẫn 0.15s. ### G3.2 + G3.3 — Hoàn tất (2026-08-15) - G3.2: pump watchdog phân loại stall — khi heartbeat stalled 3s + chưa restart lần nào, lấy pid từ BridgeProcess state, kiểm tra process còn sống (OpenProcess/GetExitCodeProcess) → log HUNG (pid alive) / CRASHED (pid gone) / UNKNOWN (no tracked pid). Verify: CRASH test (kill bridge → `CRASHED (pid 4432 gone)` → restart bridge mới); HUNG test (NtSuspendProcess giữ process sống nhưng heartbeat đứng → `HUNG (pid 6232 alive, heartbeat stalled)` → TerminateProcess → restart). Cả 2 PASS. - G3.3: SEH per-channel — `SafeProcessChannel` bọc processAudioBlock trong `__try/__except(EXCEPTION_EXECUTE_HANDLER)`; plugin fault → mute channel (crashedCh_), log `[NativeBridge] G3.3: channel N plugin crashed in processAudioBlock (SEH) — muted until reload`, các channel khác tiếp tục render, bridge không sập; assign() (reload instance) reset mute. Helper tách riêng vì SEH không hợp lệ trong hàm có C++ unwinding (C2712). Verify: `seh_channel_test.exe` (stub processAudioBlock gây access violation → SafeProcessChannel trả false, crash bị nuốt, crashed flag set, renderAll no-op) PASS; bridge + cargo build PASS. - Regression: G1.5 stress 1 iter PASS (đã fix test script đọc log 1 lần/poll + đóng GUI VST dính giữa các lần chạy); G3.1 heartbeat probe PASS (hb ~10Hz, kill → restart 3.7s). G2.5 không chạy lại (không đụng audio loop). - Commit `0b8e80f` (6 files: lib.rs, Cargo.toml, NativeInstrumentEngine.h/.cpp, CMakeLists.txt, tests/seh_channel_test.cpp). `install/*.exe` không commit; `native_bridge/debug/test_g15_stress.py` (fix helper) commit riêng `85273a6`. - G4 (sandbox plugin): tuỳ chọn, 2-3 tháng sau.