# 20260815 — KẾ HOẠCH THỰC HIỆN G2 (PERSISTENCE RESTART) Nguồn: `big_tasks.md` — header **G2 — Persistence restart (2-3 tuần)**. Trạng thái roadmap: G0 ✅, G1 ✅ (G1.1–G1.5), **G2 ✅ (G2.0–G2.6)**, G3 ✅ (G3.1–G3.3), G4 ✅ (G4.1–G4.3 sandbox PoC). Branch: `standalone-shm-bridge`. File này là plan G2; G3/G4 thực hiện tiếp nối (nhật ký ở mục 5). --- ## 1. PHÂN TÍCH G2 ### 1.1 Mục tiêu Restart không mất instrument — sau khi bridge respawn (stall 3s / crash / restart sample-rate / DAW mở lại), người dùng bấm PLAY nghe lại ngay, preset + vị trí transport được khôi phục tự động. ### 1.2 Hiện trạng (đã verify trong code) | Thành phần | File | Hiện trạng liên quan G2 | |---|---|---| | Bridge C++ (daw_vst_bridge) | `native_bridge/src/main.cpp` (1250 dòng) | Control loop: LOAD type=2, TRANSPORT type=3 (0=STOP, 1=PLAY, 2=SET_POSITION), OPEN_GUI type=4. `playheadSamples` lưu trong audio loop. Watchdog thread (parent chết → TerminateProcess). | | Quản lý instrument | `native_bridge/src/NativeInstrumentEngine.cpp/.h` | `InstrumentEngineManager`: map channel→instance + `paths_` (path/channel). `FluidSynthInstrument::bank_[16]` giữ bank mỗi channel. | | VST3 preset | `native_bridge/src/Vst3Instrument.cpp` (G0.4) | `captureState()`/`restoreState()` CHỈ trong RAM (`savedComponentState_`/`savedControllerState_` buffers) — mất khi process chết. | | Rust shell | `src-tauri/src/lib.rs` | `spawn_bridge`, pump thread (stall 3s → kill + restart 1 lần → `bridge-down`), `kill_bridge`, `restart_bridge_with_sample_rate`, `transport_control`, `load_native_instrument`, `bridge_status`. `SHUTTING_DOWN` khi window đóng → kill bridge KHÔNG dump. | | JS client | `app/static/js/services/nativeBridgeService.js` | `loadInstrument`, `transport(kind, playhead)`, `onAudio`, `onStatusChange`, `queryStatus`. | | JS app | `app/static/js/app.precompiled.js` | `loadVstToBridge`/`loadToBridge` (LOAD), `__bridgeLoadedChannels` (reset `{}` khi restart → mất), `updatePlayhead` gửi `set_position` ~1/s (JS là nguồn playhead duy nhất), bootstrap gọi `restart_bridge_with_sample_rate` ở startup. | ### 1.3 Khoảng trống (gap) 1. **Không có gì ghi ra đĩa** — G0.4 preset chỉ sống trong RAM của bridge; respawn = mất hết channel/preset/transport. 2. **Kill không có cơ hội dump** — pump thấy stall → `kill_bridge` ngay; window đóng → `kill_process_tree`; cả hai đều không báo bridge ghi state trước. 3. **Respawn không tự load** — bridge mới start với SHM trống; JS `__bridgeLoadedChannels={}` và chỉ load lại khi track phát/đổi (không tự động, không khôi phục preset). 4. **Guard chặn restore khi PLAY** — G2.4: luồng restore phải chạy được khi `isPlayingRef.current === true` (hiện guard `if(window.__bridgePlaying) return` trong `openVstGuiRetry`, và `!isPlayingRef.current` quanh `openNativeGUI` dòng ~817 — áp dụng cho restore cũng phải gỡ). ### 1.4 Nguyên tắc thiết kế (đề xuất) - **Write-on-change là chính**: bridge tự ghi state ra đĩa NGAY khi state đổi (LOAD/TRANSPORT/params) — an toàn cả khi crash đột ngột, không phụ thuộc cơ hội dump trước kill. Dump-before-kill chỉ là lớp phụ (khi có thời gian: restart sample-rate, window close). - **Bridge tự restore khi start**: bridge đọc file state khi khởi động → tự push LOAD các channel + restore preset + set transport — Rust không cần biết preset blob (giữ IPC đơn giản). - **File state dùng chung**: `%APPDATA%/SonicForgeDAW/state/bridge_state.json` (cùng cây logs/ipc — đã có sẵn layout). - **Không đụng audio loop**: mọi IO đĩa chạy trên UiThread (worker job queue) hoặc thread riêng; tuyệt đối không trong vòng render. --- ## 2. PHÂN RÃ TASKS + ESTIMATE | ID | Task | File đích | Nội dung | Estimate | |---|---|---|---|---| | G2.0 | Format + module state file | `native_bridge/src/StateStore.h/.cpp` (mới) + `CMakeLists.txt` | JSON (path/type/channel/preset-base64/transport) + atomic write (write tmp → rename). API: `load()`, `save(StateSnapshot)`, `clear()`. | 1 ngày | | G2.1 | Save đầy đủ state ra đĩa | `main.cpp`, `Vst3Instrument.cpp`, `NativeInstrumentEngine.cpp/.h` | Snapshot toàn bộ channel: path, type, VST3 component+controller state (từ `captureState` → base64), SF2 bank/program, transport `playheadSamples`. Ghi khi: LOAD xong, TRANSPORT đổi, params đổi (định kỳ 1-2s nếu dirty). | 2 ngày | | G2.2 | Dump state trước kill | `src-tauri/src/lib.rs`, `main.cpp` | (a) Write-on-change phủ hầu hết. (b) Trước `kill_bridge`/restart: Rust push control type=5 (DUMP) + chờ ≤300ms → kill. (c) Window close: gửi DUMP trước `kill_process_tree`. | 1.5 ngày | | G2.3 | Auto-reload sau respawn | `main.cpp` (startup), `lib.rs` (env `SF_STATE_FILE`) | Bridge start: đọc state file (nếu có) → `uiWorker.post` LOAD từng channel theo thứ tự → restore preset → nếu từng PLAY thì set transport + PLAY. Guard: chỉ restore khi channel trống; log vào bridge.log. | 2 ngày | | G2.4 | Gỡ guard chặn restore khi PLAY | `app/static/js/app.precompiled.js` | Trong luồng restore, bỏ điều kiện dừng vì `isPlayingRef.current`/`__bridgePlaying`: LOAD instrument + restore preset khi đang PLAY vẫn phải chạy (khác với open GUI — G0.2 vẫn giữ defer cho OPEN_GUI). | 0.5 ngày | | G2.5 | Regression driver stress | `native_bridge/debug/test_g2_stress.py` (mới, theo mẫu `test_g15_stress.py`) | N vòng: LOAD 2 inst (Nexus+EZkeys) → PLAY → kill bridge → respawn → verify writeIndex advancing + preset giữ + 0 crash. | 1 ngày | | G2.6 | Verify + bắt bugs + fix | — | Chạy selfcheck/stress/probe trên Windows; thu logs; sửa bug tìm được. | 2-4 ngày | | | **Tổng** | | | **10-12 ngày (~2-3 tuần — khớp roadmap)** | ### Phụ thuộc `G2.1 → G2.0` · `G2.2 → G2.1` (dùng StateStore) · `G2.3 → G2.1 + G2.2` · `G2.5 → G2.3 + G2.4` · `G2.6 → tất cả`. ### Thứ tự triển khai khuyến nghị G2.0 → G2.1 → G2.3 (lõi: save + tự restore) → G2.4 (guard) → G2.2 (dump trước kill — lớp phụ) → G2.5 → G2.6. Lý do: G2.2 phụ thuộc G2.1 nhưng giá trị thấp hơn G2.3; làm G2.3 sớm để stress sớm. --- ## 3. KẾ HOẠCH THỰC HIỆN (CÀI ĐẶT → KIỂM TRA → BẮT BUGS → FIX → REPORT) > **PHÂN CÔNG MÁY (bắt buộc):** > - **Máy Linux (máy này):** CHỈ lập kế hoạch, review code, ghi chú, theo dõi tiến độ, cập nhật file báo cáo. > KHÔNG sửa code C++/Rust/JS, không build bridge (thiếu vcpkg/fluidsynth/sfizz/VST3 SDK/rustc-MSVC). > Verify tối đa tại đây: `node --check` (JS), `ast.parse` (py), review, `shm_selfcheck` (đã pass Linux trước đây). > - **Máy Windows (máy chạy app thật):** SỬA CODE, build (`build_bridge.ps1`), deploy (`deploy_g12.ps1`), > chạy probe/stress, bắt bugs, fix, commit + push. Mọi thay đổi code G2 đều phát sinh ở đây. > - Luồng: **Windows sửa code + commit + push** → **Linux pull, review, cập nhật ghi chú/report + push** → > Windows pull lại, verify tiếp. > > Sau MỖI task: mục "📌 Lưu ý & quy trình" ghi rõ việc nào làm ở máy nào. ### G2.0 — StateStore - **Cài đặt**: thêm `StateStore.h/.cpp`; JSON đơn giản (viết tay serializer — tránh thêm dep nlohmann vào bridge, dự án không có sẵn). Atomic write: `bridge_state.json.tmp` → `rename`. - **Kiểm tra**: unit selfcheck — compile `StateStore` riêng (Linux: `g++ -fsyntax-only` nếu muốn xem syntax; Windows: compile thật trong target + test nhỏ ghi/đọc JSON round-trip). - **Bắt bugs**: escape chuỗi path (dấu `"` `\` trong path Windows), rename fail khi file khoá, UTF-8. - **Fix + report**: ghi kết quả vào mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): tạo `native_bridge/src/StateStore.h/.cpp`; thêm 2 file vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`; nhớ CRLF (ghi file qua Python `io.open(newline='')` hoặc editor giữ CRLF — cả repo native_bridge là CRLF). Build: `build_bridge.ps1`. Test: chạy exe/test nhỏ in JSON ra, verify round-trip. Commit: `G2.0: StateStore (JSON state file + atomic write)`. - **Linux** (ghi chú): sau khi Windows push, pull + review file mới + cập nhật bảng mục 5 + dòng nhật ký. Không sửa code. ### G2.1 — Save đầy đủ - **Cài đặt**: thêm `InstrumentEngineManager::snapshot()` (dưới `mu_`) trả về danh sách channel → path/type/state-blob; `Vst3Instrument::serializeState()` tái dùng `captureState()` buffers → base64; `FluidSynthInstrument` expose bank/program; hook ghi vào các điểm LOAD xong / TRANSPORT / timer dirty. - **Kiểm tra**: `shm_selfcheck` không đổi (layout SHM giữ nguyên — không thêm field vào SHM); review không đụng audio loop; trên Windows: probe LOAD → đọc `bridge_state.json` thấy đủ path/preset. - **Bắt bugs**: base64 không khớp byte (size/precision), ghi giữa lúc instance đang reload (phải chờ `reloadingCh_` off), path empty. - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): sửa `NativeInstrumentEngine.cpp/.h` (thêm `snapshot()`), `Vst3Instrument.cpp` (serializeState), `main.cpp` (hook ghi sau LOAD/TRANSPORT + timer dirty). Build + deploy + probe LOAD → mở `%APPDATA%\SonicForgeDAW\state\bridge_state.json` kiểm tra path/preset. Commit: `G2.1: save full instrument state to disk (path/type/preset/transport)`. - **Linux** (ghi chú): pull + review: snapshot phải nằm dưới `mu_`, không gọi IO trong audio loop; `shm_selfcheck` vẫn pass. Cập nhật mục 5 + nhật ký. ### G2.2 — Dump trước kill - **Cài đặt**: thêm control type=5 (DUMP_STATE) trong enum + handler main.cpp (push snapshot qua StateStore); Rust `kill_bridge`/window-close gửi control 5 + sleep 300ms trước kill; `restart_bridge_with_sample_rate` làm tương tự. - **Kiểm tra**: review Rust+C++ control type khớp; Linux `node --check` không liên quan; Windows: kill bridge giữa PLAY → file state phải đủ (write-on-change đã đảm bảo, DUMP là phụ). - **Bắt bugs**: race Rust gửi DUMP sau khi bridge chết (guard shm unavailable), 300ms không đủ khi VST đang createView (attach_in_flight → chờ theo flag SHM offset 11160). - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): sửa `native_bridge/src/main.cpp` (case 5), `src-tauri/src/lib.rs` (`kill_bridge`, `restart_bridge_with_sample_rate`, window-close handler). Build bridge + `cargo check` (src-tauri). Deploy → kill bridge giữa PLAY → kiểm tra state file đủ. Commit: `G2.2: dump state before kill (control type=5 + Rust DUMP before kill_bridge)`. - **Linux** (ghi chú): pull + review: type 5 không đụng enum cũ (2/3/4 giữ nguyên), Rust guard khi `ShmState` None; `cargo check` bắt buộc chạy trên Windows (Linux thiếu toolchain). Cập nhật mục 5 + nhật ký. ### G2.3 — Auto-reload sau respawn - **Cài đặt**: bridge startup (sau khi map SHM, trước audio loop): đọc `SF_STATE_FILE` (Rust set env khi spawn) → `uiWorker.post` từng channel: LOAD (type 2) → `restoreState()` → cuối cùng nếu state có `playing=true` → TRANSPORT play + set_position. Log từng bước vào bridge.log. - **Kiểm tra**: Linux review + `shm_selfcheck`; Windows: kill bridge khi đang PLAY → chờ respawn → verify writeIndex advancing + preset nghe lại đúng. - **Bắt bugs**: restore chạy trước khi Rust pump gắn `bridge-audio` (JS `_reconnectIfNeeded` vẫn cần); load nhiều channel cùng path (g_loadMutex + reloadingCh phải serialize); double-load khi JS cũng tự load (JS phải bỏ `__bridgeLoadedChannels` reset → giữ state thật). - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): sửa `main.cpp` (startup restore block), `lib.rs` (set env `SF_STATE_FILE` khi spawn). Build + deploy + kill bridge giữa PLAY → chờ respawn (spawn.log) → verify writeIndex + preset. Nếu cần phía JS: sửa `app.precompiled.js` + `node --check`. Commit: `G2.3: auto-reload instruments from state file after bridge respawn`. - **Linux** (ghi chú): pull + review: restore phải post qua `uiWorker` (không chạy trong audio loop), serialize bằng `g_loadMutex` sẵn có; xác nhận `__bridgeLoadedChannels` flow trên JS. Cập nhật mục 5 + nhật ký. ### G2.4 — Gỡ guard chặn restore khi PLAY - **Cài đặt**: trong `app.precompiled.js` luồng auto-restore (điểm G2.3 phía JS): cho phép `loadVstToBridge`/restore chạy khi PLAY; GIỮ NGUYÊN guard `__bridgePlaying` cho riêng OPEN_GUI (G0.2 — defer GUI vẫn đúng). - **Kiểm tra**: `node --check app.precompiled.js`; Windows: PLAY đang chạy → kill/respawn → verify restore xong trong khi PLAY, không mất tiếng. - **Bắt bugs**: restore gọi `openVstGuiRetry` nhầm khi PLAY (guard cũ trả `true` sớm → GUI không mở nhưng LOAD cũng bị bỏ) — tách 2 guard riêng. - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (sửa code): sửa `app/static/js/app.precompiled.js` (tách guard restore vs guard OPEN_GUI); chạy `node --check app/static/js/app.precompiled.js`. Deploy app (build JS bundle → copy vào install) → PLAY → kill/respawn → verify. Commit: `G2.4: allow instrument restore while PLAY (keep OPEN_GUI defer)`. - **Linux** (ghi chú): pull + review: xác nhận G0.2 guard `__bridgePlaying` vẫn còn cho OPEN_GUI; `node --check` cũng chạy được tại đây nếu có node. Cập nhật mục 5 + nhật ký. ### G2.5 — Stress driver - **Cài đặt**: `test_g2_stress.py` theo mẫu `test_g15_stress.py`: 5 + 10 iterations LOAD/OPEN_GUI/PLAY/STOP + kill bridge giữa chừng → verify respawn + writeIndex + preset. - **Kiểm tra**: chạy trên Windows; ghi PASS/FAIL theo iteration. - **Bắt bugs**: crash/restart khi kill giữa attach, preset sai sau lần restore thứ N. - **Fix + report**: mục 5. - **📌 Lưu ý & quy trình**: - **Windows** (tạo script + chạy): tạo `native_bridge/debug/test_g2_stress.py` (chép khung `test_g15_stress.py`: ctypes SHM, `write_control`, `wait_log`, `bridge_pid`, `bad_since`); thêm bước kill `daw_vst_bridge` giữa vòng và chờ respawn. Chạy: `python native_bridge\debug\test_g2_stress.py 5` rồi `10`. Thu kết quả PASS/FAIL + logs. Commit: `G2.5: stress driver for persistence restart (N loop + kill/respawn)`. - **Linux** (ghi chú): pull + review script (không chạy được — cần SHM Windows); cập nhật mục 5 với kết quả PASS/FAIL do Windows báo. ### G2.6 — Verify tổng + bắt bugs + fix - Chạy lại toàn bộ regression G1.5 (không hỏng G1) + G2.5 + probe G0.4. - Thu `bridge.log`/`spawn.log` thực (không tin probe PASS đơn lẻ — learning cũ). - Sửa mọi bug tìm được, commit theo từng task (như G1.2–G1.5). - **📌 Lưu ý & quy trình**: - **Windows** (chạy + fix): chạy `test_g15_stress.py` (regression G1) + `test_g2_stress.py` (G2) + probe G0.4; fix bug tìm được, commit từng cái. - **Linux** (báo cáo tổng): sau khi Windows báo kết quả cuối, tổng hợp toàn bộ vào mục 5 + nhật ký, đánh dấu hoàn thành, commit báo cáo cuối: `G2: report — persistence restart done (verdict từ Windows)`. Push. --- ## 4. RỦI RO | Rủi ro | Ảnh hưởng | Giảm thiểu | |---|---|---| | VST3 state blob không restore được (Nexus setState fail) | Mất preset dù load lại được path | Fallback: giữ path + chỉ restore blob khi OK; log `setState failed`; không chặn load. | | Ghi file mỗi LOAD → IO chậm | Stall audio? | Ghi trên UiThread job (không trong audio loop); atomic rename. | | JS `__bridgeLoadedChannels` stale | Double-load / bỏ load | Reset map theo sự kiện bridge respawn thật (bridge-audio trở lại) thay vì chỉ restart-bootstrap. | | Restore khi đang PLAY (G2.4) gây click/glitch | Chất lượng âm | LOAD đã có reloadingCh_ mute cơ chế sẵn (G1.4) — tái dùng; restore nhanh, không chờ. | | Không build được trên Linux | Không verify cục bộ | Verify Linux: syntax/selfcheck/review; chạy thật trên Windows theo quy trình G1. | --- ## 5. BÁO CÁO KẾT QUẢ (điền sau khi thực hiện) > Cột Cài đặt/Kiểm tra/Bugs/Fix: **Windows** điền (người sửa code). Cột Trạng thái + Nhật ký: **Linux** cập nhật sau khi review. | ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái | |---|---|---|---|---|---| | G2.0 | StateStore.h/.cpp (JSON thủ công, atomic tmp+rename, UTF-8, API save/load/clear, defaultPath=%APPDATA%/SonicForgeDAW/state/bridge_state.json); them vao CMakeLists target daw_vst_bridge; test_state_store.cpp (round-trip self-check) | Build PASS (daw_vst_bridge.exe), round-trip PASS (save/load 2 inst: path Windows co space+backslash, presetBase64 co newline/quote/backslash; khong con .tmp; clear xoa file) | — (module moi, chua hook vao bridge) | — | ✅ (Win xong + Linux review OK) | | G2.1 | main.cpp: globals g_stateDirty/g_transportPlaying/g_lastSnapMutex/g_lastSnap, snapshotsEqual (bo playhead); stateFile = SF_STATE_FILE fallback defaultPath; playheadSamples -> std::atomic; saveStateJob (UiThread, skip khi isReloading, chi save khi snapshot doi); TRANSPORT set g_transportPlaying + dirty; cuoi LOAD dirty=true; periodic 2s dirty + debounce 300ms. Vst3Instrument.h: khai bao bankOf/programOf. StateStore.cpp: ensureParentDir() (CreateDirectoryA chain / mkdir) goi dau save(). | Build PASS (daw_vst_bridge.exe). Deploy PASS (PID moi: sonicforge-daw 9876, daw_engine 24632, daw_vst_bridge 7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> bridge_state.json co 7 instrument (ch0-6 frontend da load), playing=true, presetBase64 non-empty (Nexus 3468/3488, EZkeys 24764); STOP -> playing=false. State round-trip test_state_store.cpp PASS. | test script ban dau ky vong dung 1 instrument (frontend da load 7) -> FAIL gia, chuc nang PASS | assert dung so instrument | ✅ (Win xong + Linux review OK) | | G2.2 | main.cpp: control type=5 DUMP_STATE (post saveStateJob len uiWorker ngay lap tuc). lib.rs: flush_bridge_state() — push_control(5,0,0,0) + sleep 300ms, goi dau kill_bridge() (cover restart_bridge_with_sample_rate + stall-restart) va truoc khi giet bridge trong window-close Destroyed | Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri) + deploy + verify live PASS: DUMP qua SHM -> log "DUMP_STATE - flushing state now", state file intact (7 inst), writeIndex advancing | Runtime Rust flush chua test duoc khi khong build app Tauri (cargo check xac nhan compile) | | ✅ (Linux review OK) | | G2.3 | main.cpp: startup restore block (doc SF_STATE_FILE, guard channel trong, post LOAD job tung channel qua uiWorker, assign + loadSerializedState preset / selectProgram bank-program, g_loadMutex serialize, restore transport PLAY + playhead neu state playing). Vst3Instrument: base64Decode + loadSerializedState (parse [4B BE compLen][comp][4B BE ctrlLen][ctrl], goi restoreState). INativeInstrument.h: virtual loadSerializedState no-op. lib.rs: spawn_bridge set env SF_STATE_FILE = %APPDATA%/SonicForgeDAW/state/bridge_state.json. | Build PASS (bridge + cargo check). Deploy PASS. Verify live PASS: LOAD ch5 + PLAY (playhead 5000) -> kill bridge -> respawn -> log "restoring 7 instruments" + "restored ch=0..6" (Nexus + EZkeys) + "restoring transport PLAY playhead=5000" + ch5 preset 3488B giu nguyen; state file playing=true playhead=5000 inst=7; writeIndex advancing (14859->15234). | - | - | ✅ (Win xong + Linux review OK) | | G2.4 | main.cpp: LOAD case double-load guard — skip khi channel da co cung path (lower_plugin_path(ch) == lower_path_str(path)), giu instance + preset da restore khi JS reload sau respawn/bootstrap | Verify live PASS: LOAD lai Nexus ch5 sau respawn -> log "LOAD skip ch=5 (already loaded)", preset ch5 3488B giu nguyen, writeIndex advancing; 15 LOAD skip trong log (JS reload 7ch x2 + test) | Khong can sua JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying (da chay khi PLAY); chi openVstGuiRetry giu guard OPEN_GUI (G0.2, dung) | | ✅ (Linux review OK) | | G2.5 | native_bridge/debug/test_g2_stress.py (mới, khung test_g15_stress.py: ctypes SHM, write_control, wait_log, bridge_pid, bad_since) + bước kill daw_vst_bridge giữa vòng + chờ watchdog respawn; verify restore + preset giữ + LOAD-skip guard (G2.4) + writeIndex advancing; test_g15_stress.py: LOAD ch5 chấp nhận "LOAD skip" (guard G2.4 đổi log) | Chạy 5 + 10 iterations PASS (tổng 20 lần kill/respawn): LOAD skip pre-kill/post-respawn OK, restore ch5/ch6 OK, watchdog "[tauri] bridge restarted" OK, bridge alive; preset Nexus giữ nguyên (exact), EZkeys sim>=0.87; writeIndex advancing (delta 283-376) | Assert preset ch6 byte-exact FAIL giả: EZkeys nhúng PlayHeadPos vào state riêng (đổi cả giữa 2 save thường, ratio 0.872) | ch5 so exact, ch6 so ratio>=0.7 + |len chênh|<=64 | ✅ (Win xong + Linux review OK) | | G2.6 | Chạy lại regression G1.5 (test_g15_stress.py) + G2.5 (5+10 iter) + probe G0.4 (bản cập nhật PID + guard-aware, OPEN_GUI full path) | G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive); G2.5 PASS 5+5+10 (20 lần kill/respawn, preset giữ, không crash); G0.4 probe PASS: LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> capture 2628B -> reload -> restore 2628B -> OPEN_GUI lại attach OK, không restart/EXCEPTION/FAILED | Không có bug sản phẩm mới; 2 bug test-script: (1) probe G0.4 so sánh PID int vs list -> báo restart giả, (2) preset EZkeys byte-diff do PlayHeadPos runtime (đã phân tích ở G2.5) | Sửa test scripts (guard-aware wait, pid compare); không sửa code sản phẩm | ✅ (Win xong + Linux review OK) | ### 5.1 Review + verify phía Linux (2026-08-16, Linux — chỉ review/ghi chú, KHÔNG sửa code) Kết quả review toàn bộ diff G2 (`git diff 1fe2d26~1..b59e416`, 15 files; không có file JS trong diff). **Code review — nhất trí với báo cáo Windows, không tìm thấy bug sản phẩm:** - `Vst3Instrument.cpp`: `base64Encode`/`base64Decode` đúng RFC 4648 (padding `=`, decode bỏ qua whitespace); `loadSerializedState` có bounds-check cả componentLen lẫn controllerLen trước khi copy — blob hỏng không gây OOB; gán buffer xong mới gọi `restoreState()` (đúng thứ tự). - `NativeInstrumentEngine.cpp`: `snapshot()` giữ `mu_` suốt `captureState()` — audio loop cũng serialize bằng `mu_` per block nên không race capture với process; skip channel đang reload (`reloadingCh_`, instance cũ sắp chết); `bank_/program_` cập nhật đúng (reset khi `loadSoundFontFile`, set khi program_select/programChange). - `main.cpp`: `saveStateJob` defer khi có channel reloading (đặt lại dirty → retry sau debounce) — không ghi state lúc instance nửa chết; `snapshotsEqual` loại playhead (không ghi file mỗi ~2s khi PLAY); periodic mark 2s + debounce 300ms; startup restore chỉ nhận channel trống (`instruments.get(ch)==nullptr`) và serialize bằng `g_loadMutex`; LOAD guard so sánh lower-path — chỉ skip khi CÙNG path, path khác vẫn replace bình thường; case 5 DUMP_STATE post `saveStateJob` trực tiếp (không chờ debounce). - `lib.rs`: `flush_bridge_state` an toàn khi bridge treo (SHM do Rust sở hữu, control 5 không được consume — vô hại). - `test_g2_stress.py` + `test_state_store.cpp` + sửa `test_g15_stress.py`: logic vững (wait_load chấp nhận real hoặc skip; SequenceMatcher ≥0.7 cho EZkeys vì PlayHeadPos runtime; bad_since watch EXCEPTION/FAILED; round-trip cover escape `\n \t \" \\`, atomic .tmp, missing file, clear). **Findings (minor, không chặn; Linux không sửa code — chờ Windows quyết định):** 1. **F1 (minor)**: window-close gọi `flush_bridge_state()` ở `run()` VÀ `kill_bridge()` giờ cũng tự gọi → DUMP_STATE 2 lần khi đóng app (+~300ms shutdown). Harmless; có thể bỏ lời gọi thừa trong `run()`. 2. **F2 (portability note)**: `lib.rs` luôn set `SF_STATE_FILE` sang đường dẫn Windows; nếu chạy bridge non-Windows, env thành `./SonicForgeDAW/state/bridge_state.json` (theo CWD) thay vì Linux fallback `$HOME/.sonicforge/state/` của `StateStore::defaultPath`. Không ảnh hưởng target Windows (cùng path APPDATA); ghi nhận để sửa khi cần bridge chạy Linux. 3. **F3 (cosmetic)**: `loadSerializedState` in cùng message "bad ctrlLen" cho 2 check khác nhau (thiếu header / độ dài vượt) — chỉ log, không ảnh hưởng hành vi. **Verify trên Linux (cú pháp/logic — máy Linux không build Windows):** - `python3 -m py_compile native_bridge/debug/test_g2_stress.py`, `test_g15_stress.py`: PASS. - `g++ -std=c++17 -Wall -Wextra debug/test_state_store.cpp + src/StateStore.cpp` (Linux): compile sạch (1 warning benign multi-line comment) + run **"state_store round-trip PASS"** → StateStore portable, atomic write + escape verified trên Linux. - `node --check app/static/js/app.precompiled.js`: PASS (G2 không sửa JS — diff stat không có file JS). - Rust: `rustc`/`cargo` chỉ là rustup proxy, chưa cài default toolchain → không chạy được `cargo check` trên Linux (khớp ghi chú big_tasks.md "máy Linux không có rustc toolchain"); Windows đã `cargo check` PASS ở G2.2/G2.3. **Kết luận Linux**: code G2 nhất quán với báo cáo Windows; không có bug sản phẩm; 2 findings minor (F1, F2) + 1 cosmetic (F3) không chặn merge. Trạng thái G2.0–G2.6: ✅ (Win xong + Linux review xác nhận). ### 5.2 Audit bảo mật / bộ nhớ / conflict child process (2026-08-16, Linux — chỉ review/ghi chú, KHÔNG sửa code) Đọc toàn bộ: `native_bridge/src/main.cpp` (1410), `NativeInstrumentEngine.cpp` (439), `Vst3Instrument.cpp` (873), `StateStore.cpp` (288), `SandboxVst3Host.cpp` (192), `plugin_host_main.cpp` (163), `SharedMemoryIPC.cpp` (125), `src-tauri/src/shm.rs` (201), `src-tauri/src/lib.rs` (791), `app/static/js/services/nativeBridgeService.js` (180), `app/static/js/app.precompiled.js` (luồng bridge). Mục tiêu: race UAF / double-buffer / conflict 2 bridge cùng SHM / orphan / command injection / luồng có đúng thiết kế. **KẾT LUẬN: không có bug chặn release; 2 MEDIUM (F-MEM-5 UAF, F-PROC-1 injection), 1 MEDIUM (F-PROC-2 race restart×watchdog), 1 MEDIUM audio-correctness (F-MEM-8), 2 LOW (F-PROC-3, F5). Tất cả nằm ở đường hiếm (deschedule >10ms, path thù địch, restart trùng stall) — đề xuất Windows xử lý theo ưu tiên F-PROC-1 > F-MEM-5 > F-PROC-2.** **F-MEM-5 (MEDIUM — UAF trên audio thread):** `InstrumentEngineManager::get()` (NativeInstrumentEngine.cpp:300) lock `mu_` rồi trả raw `unique_ptr::get()` sau unlock. `assign()`/`unload()` (dòng 182/240) swap map DƯỚI `mu_` nhưng destroy `oldInst` NGOÀI lock, sau `sleep 10ms`. Drain loop control queue chạy trên **audio thread** (main.cpp:1115) gọi `instruments.get(ch)` rồi deref sau unlock: handleOpenGui dedupe `i0->hasAttachedView()` (main.cpp:794), pending-GUI sweep (884/909/927/953), STOP transport `inst->controlChange(ch,64,0)` (main.cpp:1290). Nếu UiThread assign xong + ngủ 10ms + reset oldInst trong lúc audio thread bị deschedule giữa get() và deref → deref instance đã free → UAF (cửa sổ ~10ms+). Các site trong attach job (823) an toàn vì cùng UiThread với assign. Hướng fix: giữ `mu_` quanh get+use như MIDI dispatch (đã làm đúng ở dòng 338), hoặc get() trả shared_ptr. **F-MEM-8 (MEDIUM — audio correctness):** double-buffer G4.3 không handshake (SharedMemoryIPC.h): bridge đọc `writeSlot` + `audioLeft/Right` trong lúc child flip slot → block rách/glitch (không memory corruption — mảng cố định 256). `processAudioBlock` đọc con trỏ `ipc_` non-atomic trong lúc watchdog respawn child (UB lý thuyết, x86 OK). Ghi nhận theo dõi. **F-PROC-1 (MEDIUM — command injection):** `SandboxVst3Host::spawnChild()` (SandboxVst3Host.cpp:76-78) nội suy `--path "..."` + `--sr` vào cmdline, gọi `CreateProcessA(nullptr, buf)` — path lấy từ LOAD control / state file. Path chứa `"` + metachar (VD `" && calc`) → thực thi lệnh tuỳ ý (cùng user). Fix: `lpApplicationName` + lpCommandLine arg đã quote đúng, hoặc truyền path qua env/SHM. Severity giữ MEDIUM vì attacker cần kiểm soát path (file .vst3 thù địch / state file sửa tay). **F-PROC-2 (MEDIUM — race restart × watchdog → 2 bridge cùng SHM):** `restart_bridge_with_sample_rate` (invoke thread) và pump watchdog (thread riêng) đều chạy `kill_bridge()` → `spawn_bridge()`. `kill_bridge` serialize bằng `BridgeProcess` mutex nhưng **kill→spawn không nguyên tử**: 2 luồng cùng đến `spawn_bridge` → 2 `daw_vst_bridge.exe` cùng map SHM (tên `SonicForge_DAW_IPC_` — CÙNG tên) → double LOAD / OPEN_GUI trùng / control queue rối. `set_bridge_child` overwrite child cũ → rò rỉ bridge đầu (không kill, log redirect task sống theo pipe). Khả năng overlap cao hơn vì cả 2 luồng đều ngủ 300ms trong `flush_bridge_state`. Kích hoạt thực tế: bootstrap JS gọi restart (mỗi lần webview load) trùng lúc bridge mới không kịp heartbeat → watchdog stall 3s. Fix: 1 mutex phủ cả kill+spawn, hoặc spawn_bridge kiểm tra "không còn child tracked" trước khi spawn. **F-PROC-3 (LOW — orphan race khi đóng app):** pump thread check `SHUTTING_DOWN` ở đầu vòng, nhưng giữa `kill_bridge` và `spawn_bridge` trong cùng 1 vòng không check lại → Destroyed lấy `None` (pump vừa kill), pump spawn bridge sau khi app đang tắt → orphan tạm thời. Child có `--parent` monitor nên tự thoát khi Tauri exit → transient, không nghiêm trọng. Fix: check SHUTTING_DOWN lần nữa trước spawn_bridge. **F5 (LOW — observability):** `open_vst_gui` command (lib.rs:626) luôn trả `Ok(())` kể cả khi SHM unavailable / push fail — lỗi chỉ `eprintln` vào stderr Tauri (không vào bridge.log) → JS không biết thất bại, retry vô ích. Nên trả Err để JS dừng sớm. **F6 (LOW — comment lệch code):** `openVstGuiRetry` cap thực tế `n=min(attempts,2)` (2 lần) trong khi comment ghi "Cap 5" (bản cũ ghi 20). Không lỗi hành vi, chỉ lệch comment. **Luồng xác nhận ĐÚNG thiết kế:** - Control queue drain TRƯỚC audio render trong cùng loop (main.cpp:1115-1315) — control không bị đói. - LOAD job post lên UiThread, serialize `g_loadMutex`; audio thread không stall (comment G3.1) — writeIndex stall 3s chỉ khi heartbeat thread kẹt thật. - OPEN_GUI: defer khi PLAY (G0.1) + pending sweep khi `transportStopped && instruments.get(ch)`; dedupe `hasAttachedView` chống spam retry; attach job trên UiThread (COM STA) + g_loadMutex + AttachInFlightGuard + same-path silence + destroy child window trên owner thread (0xc000041d fix). - STOP transport: CC64=0 release sustain TRƯỚC allNotesOff (V8 bug 3) — đúng. - DUMP_STATE (type=5) post saveStateJob trực tiếp, không chờ debounce; kill_bridge flush 300ms; Rust pump drop guard trước kill (chống deadlock G3.1). - G2.4 LOAD-skip guard (lower-path compare) — idempotent sau respawn, preset giữ. - Rust pump: liveness = heartbeat chuyên dụng (không phải writeIndex), phân loại HANG/CRASH, max 1 restart/incident; `restart_attempts` reset khi hb advance. - Destroyed: SHUTTING_DOWN trước kill → pump dừng (trừ race F-PROC-3). - JS: `__bridgePlaying` guard openVstGuiRetry; load instrument im lặng khi PLAY (không tự mở GUI); `__bridgeLoadedChannels` reset cả connected lẫn !connected (V8 bug 4) → reconnect reload đúng, không mở GUI; C++ LOAD-skip làm reload idempotent. - shm.rs layout khớp C++ (selfcheck 12/1040/11168/15268); `push_control` zero-fill arg2 → `std::string(c.arg2)` an toàn với mọi writer hiện tại; MIDI count non-atomic (race benign, volatile). **Verify Linux:** - `g++ -std=c++17 -Wall -Wextra -fanalyzer` StateStore.cpp: compile sạch (0 warning) + `test_state_store.cpp` run **"state_store round-trip PASS"**. - `SharedMemoryIPC.cpp` / `SandboxVst3Host.cpp` / `plugin_host_main.cpp`: Windows-only (xung đột POSIX `shm_open`, thiếu HANDLE/CreateProcessA) — không build được trên Linux, đúng kỳ vọng. - `node --check` app.precompiled.js + nativeBridgeService.js: PASS. - Rust: không có toolchain (như G2.6). ### Nhật ký thực hiện - **G2.0** (2026-08-16, Windows): tao `native_bridge/include/StateStore.h` + `native_bridge/src/StateStore.cpp` (CRLF, JSON viet tay, atomic write .tmp→rename, escape \ " , load parser an toan). Them StateStore.cpp vao CMakeLists target daw_vst_bridge. Build bridge PASS. Test round-trip `native_bridge/debug/test_state_store.cpp` PASS (compile cl vcvars64 + chay). Commit `G2.0: StateStore (JSON state file + atomic write)`. - **G2.1** (2026-08-16, Windows): save day du state ra disk. main.cpp: globals + snapshotsEqual + stateFile env fallback + playheadSamples atomic + saveStateJob (UiThread, skip isReloading, chi ghi khi snapshot doi) + hook TRANSPORT/LOAD + periodic 2s dirty debounce 300ms. Them khai bao bankOf/programOf vao Vst3Instrument.h (thieu declaration -> build fail). StateStore.cpp: ensureParentDir() (CreateDirectoryA chain, Linux mkdir) goi dau save() (fix C2001 do heredoc backslash mangle qua script fix_bs.py). Build PASS. Deploy PASS (PID 9876/24632/7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> state file 7 inst + playing=true + presetBase64 day du; STOP -> playing=false. StateStore round-trip test PASS. Commit `G2.1: save full instrument state to disk (path/type/preset/transport)`. - **G2.3** (2026-08-16, Windows): auto-reload sau respawn. - **G2.2** (2026-08-16, Windows): dump state truoc kill. main.cpp: them control type=5 DUMP_STATE sau case 4 — post saveStateJob len uiWorker ngay (khong cho debounce 300ms/2s), log "DUMP_STATE - flushing state now". lib.rs: them flush_bridge_state(app) — shm.push_control(5,0,0,0,"") roi sleep 300ms (du thoi gian UiThread ghi xong file); goi o dau kill_bridge() (cover ca restart_bridge_with_sample_rate va stall-restart) va trong on_window_event Destroyed truoc khi kill_process_tree(bridge). Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri). Deploy + verify live PASS: push DUMP qua SHM driver -> log "DUMP_STATE - flushing state now" xuat hien, state file intact (7 inst, playing=false), writeIndex advancing. Ghi chu: nhanh Rust flush chi cargo check (khong build app Tauri) — logic don gian, compile OK. Commit `G2.2: flush bridge state to disk before kill (control type=5 DUMP + Rust pre-kill)`. - **G2.4** (2026-08-16, Windows): go double-load khi JS reload sau respawn. main.cpp: them guard dau LOAD case (truoc setReloading) — neu channel da co cung path (so sanh lower_plugin_path(ch) vs lower_path_str(path)) thi log "LOAD skip ch=N (already loaded)" va return, khong assign lai (thay instance se mat preset da restore tu state file). Ly do: JS reconnect/bootstrap reload gui LOAD cung path sau respawn -> truoc day assign() thay instance -> mat preset. Guard PLAY cua G2.4 (cho restore chay khi PLAY) da co san o JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying, chi openVstGuiRetry giu guard OPEN_GUI (G0.2 — defer GUI khi PLAY van dung). Build PASS (daw_vst_bridge.exe) + deploy + verify live PASS: sau respawn, LOAD lai Nexus ch5 -> "LOAD skip ch=5 (already loaded: ...Nexus.vst3)", preset 3488B giu nguyen, writeIndex advancing; tong 15 LOAD skip (JS reload 7ch x2 + test ch5). Commit `G2.4: skip redundant LOAD of already-loaded instrument (keep restored preset)`. main.cpp: block restore truoc audio loop - doc state file, chi restore channel trong (guard instruments.get(ch)==nullptr), post LOAD job (assign + loadSerializedState / selectProgram) serialize bang g_loadMutex, neu state playing thi set playhead + g_transportPlaying=true + g_stateDirty. Vst3Instrument.cpp: base64Decode (b64val lookup) + loadSerializedState (parse blob, goi restoreState); Vst3Instrument.h + INativeInstrument.h: khai bao. lib.rs: spawn_bridge them env SF_STATE_FILE (fallback StateStore::defaultPath khi khong set). Build PASS (daw_vst_bridge.exe + cargo check). Deploy + verify live PASS: LOAD ch5 + PLAY playhead 5000 -> taskkill bridge -> respawn tu dong -> log restore 7 inst (Nexus ch0-5, EZkeys ch6) + restore transport PLAY playhead=5000 + ch5 preset 3488B giu nguyen; state file playing=true inst=7; writeIndex advancing. Commit `G2.3: auto-reload instruments from state file after bridge respawn`. - **G2.5** (2026-08-16, Windows): tao native_bridge/debug/test_g2_stress.py (khung test_g15_stress.py). N vong: LOAD Nexus ch5 + EZkeys ch6 (real hoac skip deu OK) -> PLAY -> taskkill daw_vst_bridge -> cho watchdog "[tauri] bridge restarted" + "restored ch=5/6" -> LOAD lai -> verify "LOAD skip" (guard G2.4) + preset giu (ch5 exact, ch6 sim>=0.7 — EZkeys nhung PlayHeadPos vao state, ratio 0.872 giua save thong thuong) + writeIndex advancing. Chay 5 + 10 iterations: PASS (20 lan kill/respawn, khong crash, bridge alive). Kem sua test_g15_stress.py: LOAD ch5 chap nhan "LOAD skip" (guard G2.4 doi log). Commit `G2.5: stress driver for persistence restart (N loop + kill/respawn)`. - **G2.6** (2026-08-16, Windows): verify tong. - **G2.0–G2.6 review + verify** (2026-08-16, Linux — máy Linux chỉ review/ghi chú, KHÔNG sửa code): đọc toàn bộ diff G2 (`1fe2d26~1..b59e416`, 15 files) — Vst3Instrument base64 + loadSerializedState (bounds-check đủ), NativeInstrumentEngine snapshot (mu_ chặn race với audio loop, skip reloading), main.cpp saveStateJob/restore/LOAD-guard/DUMP/debounce, lib.rs flush_bridge_state + env SF_STATE_FILE, test_g2_stress.py + test_state_store.cpp + test_g15_stress.py. Verify Linux: py_compile 2 stress scripts PASS; g++ compile + run test_state_store PASS (StateStore portable); node --check app.precompiled.js PASS; rustc/cargo không có default toolchain → không cargo check (Windows đã check PASS). Findings: F1 double flush_bridge_state khi window-close (minor), F2 SF_STATE_FILE thiếu Linux fallback (portability, không ảnh hưởng target Windows), F3 log "bad ctrlLen" trùng cho 2 check (cosmetic). Không sửa code (đúng phân máy). Chi tiết ở mục 5.1. G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive). G2.5 PASS 5+5+10 (20 lan kill/respawn, preset giu, khong EXCEPTION/FAILED). Probe G0.4 (ban cap nhat: PID dong, LOAD guard-aware, OPEN_GUI full path): PASS — LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> "state captured ch=5 component=2628B" -> reload -> "state restored ch=5 component=2628B" -> OPEN_GUI lai attach OK, khong restart. Bug tim duoc: khong co bug san pham; 2 bug test-script (probe PID int vs list -> bao restart gia; preset EZkeys byte-diff do PlayHeadPos runtime). Commit `G2.6: verify — regression G1.5 + stress G2.5 + probe G0.4 all PASS`. - **Audit bảo mật/bộ nhớ/conflict child process** (2026-08-16, Linux — chỉ review/ghi chú, KHÔNG sửa code): đọc toàn bộ main.cpp/NativeInstrumentEngine/Vst3Instrument/StateStore/SandboxVst3Host/plugin_host_main/SharedMemoryIPC/shm.rs/lib.rs/nativeBridgeService.js/app.precompiled.js. Findings: F-MEM-5 UAF audio-thread get()+deref vs assign() destroy oldInst ngoài lock sau 10ms (MEDIUM, sites 794/884/909/927/953/1290); F-MEM-8 double-buffer không handshake (MEDIUM audio); F-PROC-1 command injection `--path "..."` CreateProcessA (MEDIUM); F-PROC-2 race restart×watchdog → 2 bridge cùng SHM (MEDIUM); F-PROC-3 orphan race Destroyed (LOW); F5 open_vst_gui luôn Ok (LOW); F6 comment cap 2 vs 5 (LOW). Luồng chính đúng thiết kế: drain control trước render, LOAD UiThread+g_loadMutex, OPEN_GUI defer PLAY + pending sweep, STOP flush CC64=0, DUMP_STATE trực tiếp, pump drop-guard trước kill, max 1 restart/incident, JS __bridgePlaying + __bridgeLoadedChannels reset. Verify Linux: StateStore -fanalyzer sạch + round-trip PASS; 3 file Windows-only không build được (đúng); node --check 2 JS PASS. Không sửa code (đúng phân máy). Chi tiết mục 5.2. - **Fix audit 5.2** (2026-08-16, Windows): xử lý toàn bộ findings 5.2. F-PROC-1: SandboxVst3Host.cpp thêm quote_arg (escape \ và " theo Windows CRT) + CreateProcessA(hostExe.c_str(), ...) + bỏ token exe khỏi cmdline -> path thù địch chứa " giữ 1 argv (probe F-PROC-1 PASS: spawn ch=7 cmdline giữ trọn path trong --path, child không map HACKED_SHM, không nhận --sr 1000, load fail như mong đợi). F-MEM-5: NativeInstrumentEngine.h + .cpp thêm has() + template withInstrument() (giữ mu_ quanh call); main.cpp đổi 6 site audio-thread: 784/794 hasAttachedView dedupe, 1073 restore guard, 1283 CC64=0, 1318 pending-GUI, 1337 state-dirty -> has()/withInstrument(); giữ nguyên 8 site UiThread (an toàn cùng thread assign). F-PROC-2: lib.rs thêm static BRIDGE_RESTART_LOCK serialize kill+spawn giữa restart_bridge_with_sample_rate và pump watchdog. F-PROC-3: pump re-check SHUTTING_DOWN sau lock + sau kill trước spawn. F5: open_vst_gui trả Err (lock poisoned / SHM unavailable / queue full), frontend try/catch sẵn. F6: comment Cap 5 -> Cap 2 (hard min(attempts,2)). F-MEM-8: ghi nhận theo dõi, không fix (đúng audit). Build: daw_vst_bridge + plugin_host PASS; cargo build --release PASS (2m53s). Deploy install/ (kill trước, copy2). Regression: G1.5 PASS 3/3 (writeIndex delta 287), G2.5 PASS 2/2 (watchdog restart + preset giữ exact), probe F-PROC-1 PASS (không injection). Commit: fix 5.2 audit findings on Windows. --- ## 6. PHỤ LỤC A — QUY TRÌNH THỰC HIỆN TRÊN MÁY WINDOWS > Quy trình 2 máy: **Windows sửa code + build + test + fix + commit + push** → > **Linux pull, review, ghi chú, cập nhật báo cáo + push** → Windows pull lại, verify tiếp. > Các lệnh dưới chạy trên máy Windows (PowerShell), đường dẫn theo máy dev `C:\Users\locpham\SonicForgeStudio`. ### 6.1 Cấu trúc + đường dẫn cố định | Thứ | Vị trí | |---|---| | Repo (Windows) | `C:\Users\locpham\SonicForgeStudio` | | Build bridge | `native_bridge\build\` (CMake) — exe: `native_bridge\build\Release\daw_vst_bridge.exe` | | Install (bundle chạy thật) | `C:\Users\locpham\SonicForgeStudio\install\` — app: `sonicforge-daw.exe` | | Logs | `%APPDATA%\SonicForgeDAW\logs\` — `bridge.log` (stdout bridge), `spawn.log` (watchdog/restart), `engine.log` | | State file G2 (MỚI) | `%APPDATA%\SonicForgeDAW\state\bridge_state.json` (+ `.tmp` khi atomic write) | | VST test | `C:\Program Files\Common Files\VST3\Nexus.vst3`, `...\Toontrack\EZkeys 2.vst3` | | SHM | `SonicForge_DAW_IPC_`; offsets: controlQueue @2836 (8×1040), count @11156, attachInFlight @11160, writeIndex @4, size 11164. Types: 2=LOAD, 3=TRANSPORT, 4=OPEN_GUI, **5=DUMP_STATE (G2.2 thêm)** | | Probe | `native_bridge\build\Release\gui_probe.exe` (variants: main_own/worker_own_nopump/…/ui_thread/shared_worker) | ### 6.2 Build ```powershell # trong repo Windows cd native_bridge\build cmake -DCMAKE_CXX_FLAGS_RELEASE='/O2 /Ob2 /DNDEBUG /Zi' -DCMAKE_EXE_LINKER_FLAGS_RELEASE='/DEBUG' . if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } cmake --build . --config Release --target daw_vst_bridge ``` hoặc chạy script có sẵn: `native_bridge\debug\build_bridge.ps1` (dùng build dir cũ). - Thêm file mới (G2.0 `StateStore.cpp`) → nhớ thêm vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`, rồi re-run cmake. - **Lưu ý CRLF**: toàn bộ `native_bridge/` + `CMakeLists.txt` là CRLF — patch đa dòng phải qua script Python `io.open(newline='')`, không heredoc `<<'EOF'` (khớp sai). ### 6.3 Deploy (mẫu `deploy_g12.ps1`) ```powershell $install = 'C:\Users\locpham\SonicForgeStudio\install' $new = 'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 2 Copy-Item -Path $new -Destination (Join-Path $install 'daw_vst_bridge.exe') -Force Start-Process -FilePath (Join-Path $install 'sonicforge-daw.exe') Start-Sleep -Seconds 8 Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path | Format-Table -AutoSize ``` - 3 process phải kill hết: `sonicforge-daw`, `daw_engine`, `daw_vst_bridge` (bridge cũ không chết → 2 bridge map chung SHM → race). - Sau start: lấy **APP_PID mới** (tasklist/Get-Process) → truyền vào probe (G2.5) — PID đổi mỗi lần deploy. ### 6.4 Verify không cần engine (Linux + Windows đều chạy được) - `shm_selfcheck.cpp` (đã build từ trước; layout 11164 → sau G2 **không đổi** vì state ghi ra đĩa, không thêm field SHM): chạy pass = control queue/attach flag/writeIndex đúng. - JS: `node --check app/static/js/app.precompiled.js` (sau mỗi lần sửa JS). - Rust (G2.2): `cargo check` trong `src-tauri/` — máy Linux hiện không có toolchain → bắt buộc trên Windows. ### 6.5 Stress (G2.5, theo mẫu `test_g15_stress.py`) ```powershell python native_bridge\debug\test_g2_stress.py ``` - Đọc SHM trực tiếp (ctypes), đẩy control LOAD/OPEN_GUI/PLAY/STOP, theo dõi `bridge.log`. - Verify: bridge alive, writeIndex advancing mỗi iter, không EXCEPTION/FAILED/crash. - **G2 thêm bước kill giữa chừng**: `Stop-Process daw_vst_bridge` → chờ respawn (spawn.log) → verify writeIndex + preset restored. - Cảnh báo learning cũ: **probe PASS có thể dương tính giả** → luôn kiểm tra `bridge.log`/`spawn.log` thực; dùng chung `since` window (log cùng job xuất hiện ~đồng thời), không reset. ### 6.6 Vòng lặp bắt bugs → fix → report 1. **Windows** chạy verify → lỗi → thu log (`bridge.log`, `spawn.log`, `engine.log`) + (nếu crash) minidump/cdb. 2. **Windows** xác định lỗi thuộc task nào → sửa code → build → deploy → verify lại → commit + push. 3. **Linux** pull → review + ghi chú, cập nhật bảng mục 5 + nhật ký → push. 4. **Windows** pull lại, tiếp tục task sau. 5. Kết thúc G2.6: chạy lại regression G1.5 (`test_g15_stress.py`) — G2 không được làm hỏng G1. ### 6.7 Tiện ích / lưu ý khác - `tasklist` CSV: dùng `subprocess.run(['tasklist'])` full output + `line.split()[1]`. - `sleep` không tồn tại trong sh (máy Windows) → `python -c "import time; time.sleep(N)"`. - `timeout` Windows khác GNU → dùng wrapper python subprocess. - Dấu hiệu lỗi quen thuộc: `0xC0000409` ucrtbase = fail-fast/terminate (không bắt được bằng catch(...) thường); `0xC00000FD` = stack overflow (IME recursion); `0xc000041d` = cross-thread destroy.