123 lines
7.5 KiB
Markdown
123 lines
7.5 KiB
Markdown
# BÁO CÁO BUG: Mở VST GUI khi đang PLAY → dự án câm, GUI không load
|
||
|
||
Ngày: 2026-08-15 · Repo: `C:/Users/locpham/SonicForgeStudio` · git status sạch
|
||
|
||
## 1. Tóm tắt
|
||
|
||
Nguyên nhân gốc: **nhiều thread cùng vào 1 plugin DLL trong lúc `view->attached()`**.
|
||
Khi đang PLAY, audio loop `process()` chạy trên các instance Nexus (ch0-4) cùng lúc
|
||
worker thread của ch5 chạy `attachView()`. Nexus throw exception trong
|
||
`createView/attached` → attach FAILED → frontend retry ~30 lần (0.3s/lần) →
|
||
`bridgeWriteIndex` stall 3s → Tauri watchdog kill+respawn bridge → **mất hết
|
||
instrument → câm toàn bộ**. GUI không bao giờ load được.
|
||
|
||
Không phải "instrument track khác bị reset" (giả thuyết 1 sai ở bước này — reload
|
||
reset state chỉ xảy ra khi GUI ĐÃ mở thành công rồi đóng rồi mở lại, không phải lần
|
||
attach đầu).
|
||
|
||
## 2. Chuỗi bug đầy đủ (xác nhận live, bridge.log)
|
||
|
||
```
|
||
... 6x Nexus loaded ch0-5 + EZkeys ch6 ...
|
||
[NativeBridge] transport PLAY playhead=0
|
||
[NativeBridge] GUI attach EXCEPTION ch=5 — plugin threw (createView/attached)
|
||
[NativeBridge] GUI attach FAILED hwnd=0000000002250F36 plugin=Nexus
|
||
[tauri] bridge restarted (attempt 1) ← watchdog: writeIndex stall 3s
|
||
[NativeBridge] Starting DAW Host Bridge Engine... ← restart, mất hết instrument
|
||
```
|
||
|
||
1. `handleOpenGui` (main.cpp ~943): OPEN_GUI ch5 → silence mọi channel cùng
|
||
plugin path → `g_loadMutex` → `try { reloadForGUI(); attachView(hwnd); } catch(...)`.
|
||
2. `attachView` (Vst3Instrument.cpp:550): `controller->createView(kEditor)` →
|
||
`view->attached(hwnd, kPlatformTypeHWND)` → **Nexus throw** khi đang PLAY
|
||
(audio loop process() cùng DLL đang chạy trên instance khác).
|
||
3. catch → `ok=false` → log EXCEPTION + FAILED → PostMessage WM_CLOSE (GUI không load).
|
||
4. Frontend `openVstGuiRetry` spam lại ~30 lần; mỗi lần chạy lại job (silence +
|
||
mutex + reload/attach) → main loop bị nghẽn → `bridgeWriteIndex` stall 3s.
|
||
5. `lib.rs`: stall >3s → `kill_bridge` + `spawn_bridge` (restart_attempts==0) →
|
||
mất toàn bộ instrument state → **CÂM**.
|
||
|
||
## 3. Bằng chứng thực nghiệm
|
||
|
||
### 3.1 Probe kiến trúc chính xác (bridge_two: 2 worker STA riêng + main pump)
|
||
|
||
`gui_probe.exe <plugin> bridge_two <secs>` — kết quả 76 VST3 hệ thống (sweep):
|
||
`native_bridge/debug/sweep_bridge_two_8s.json`, `probe_attach_detail.py` (bổ sung).
|
||
|
||
⚠️ **Hiệu chỉnh sweep cũ**: exit code 0xC0000409 của TẤT CẢ 76 plugin là
|
||
**artifact của probe** — `ProbeWorker::th` không join → `~std::thread` trên
|
||
joinable thread → `std::terminate` → fail-fast. RESULT line in ra nhưng không flush
|
||
kịp. Không phải 76 plugin cùng crash teardown.
|
||
|
||
Phân loại đúng dựa trên output per-attach:
|
||
|
||
| Nhóm | Số plugin | Bằng chứng | Ý nghĩa |
|
||
|---|---|---|---|
|
||
| **HANG attach inst2** | 30 | `workerA openGUI inst1=1` rồi `workerB openGUI inst2` in ra, **không có kết quả** — Nexus 25s không về | 2 instance cùng DLL, 2 apartment → attach instance 2 HANG trong `view->attached()` |
|
||
| **BOTH-OK** | 20 | `workerB openGUI inst2=1` — Kontakt, BlueARP, Ozone 11 (18) | Attach 2 instance OK; chỉ crash teardown artifact |
|
||
| **HANG/crash attach inst1** | 18 | `workerA hwnd=` in ra, không in `openGUI inst1` — RX 11 (18) + ACTP | Chết ngay instance 1 |
|
||
| **CRASH EARLY** | 8 | exit 1.2-4.6s, 0xC0000005 (AV): Melodyne, Orb Arpeggios/Melody, smartEQ3/live, smartreverb, DUNE3, ACDD | Crash thật trong attach |
|
||
|
||
Không plugin nào HANG >18s (không WATCHDOG-KILL) — main pump cứu được các trường
|
||
hợp cần pump; vấn đề là **conflict thread/apartment khi 2 instance cùng DLL**.
|
||
|
||
### 3.2 Nexus: hang khi 2 instance — OK khi 1 thread
|
||
|
||
- `bridge_two` (2 worker STA, 2 instance Nexus): inst1 attach OK, **inst2 HANG 25s+** (xác nhận bằng run 25s).
|
||
- `same_thread` (load + attach cùng 1 thread): **OK** (returned=1).
|
||
- `main_own` (load+attach main thread): **OK** (RESULT done=1 attached_ok=1).
|
||
- `two_workers_close` (đóng editor inst1 trước khi attach inst2, có closeGUI + attach qua temp thread): Nexus **HANG ngay attach inst1**; Kontakt chạy hết, OK cả 2.
|
||
|
||
→ 2 thread/apartment cùng 1 DLL = conflict thật (không riêng Nexus: 30 plugin hang
|
||
inst2, 8 crash AV). Kontakt/Ozone/BlueARP chịu được.
|
||
|
||
### 3.3 Giả thuyết (a) reload mất state — XÁC NHẬN từ code
|
||
|
||
`Vst3Instrument::reload()` (Vst3Instrument.cpp:615): `closeGUI()` + terminate +
|
||
`delete state_` + `loadPlugin(path_)` **MỚI** → preset/state mất khi mở lại GUI
|
||
(reopen). Kích hoạt bởi `reloadForGUI()` khi `hasAttachedOnce_ && !guiAttached_`
|
||
(đã từng mở GUI rồi đóng). Không phải nguyên nhân của bug câm lần này (attach đầu
|
||
chưa thành công nên chưa tới reload), nhưng là bug phụ: **mở GUI → đóng → mở lại =
|
||
mất preset/state của track**.
|
||
|
||
## 4. Kết luận nguyên nhân
|
||
|
||
1. **Root cause**: attach GUI chạy worker thread trong khi audio loop `process()`
|
||
đang chạy instance cùng DLL (2+ thread 1 DLL). Nexus throw → attach fail.
|
||
Probe không audio: biểu hiện hang (inst2); app thật có audio: biểu hiện
|
||
exception (ch5). Cùng gốc.
|
||
2. **Khuếch đại**: frontend retry spam 30× → stall 3s → watchdog tưởng bridge chết
|
||
→ restart → mất hết instrument → câm. Đây là lý do dự án chết hoàn toàn, không
|
||
chỉ mất GUI.
|
||
3. `g_loadMutex` + silence hiện có chưa đủ: không đợi renderAll thoát hẳn instance
|
||
cùng DLL trước khi `createView`, và Nexus vẫn throw ngay cả khi đã silence
|
||
(silence chỉ ngăn process gọi tiếp, không chặn instance đang nằm trong process()).
|
||
|
||
## 5. Đề xuất fix (xếp theo ưu tiên)
|
||
|
||
1. **Chặn/defer OPEN_GUI khi transport PLAY** — đơn giản nhất, an toàn nhất:
|
||
nếu đang PLAY, đợi transport STOP (hoặc tự tạm dừng) trước khi attach, resume
|
||
sau. Loại bỏ hoàn toàn conflict audio-loop × attachView. (`ponytail:` người dùng
|
||
muốn mở GUI khi đang chơi thì nâng cấp thành pause-resume tự động, hoặc đợi
|
||
`setProcessing(false)` + đợi renderAll thoát.)
|
||
2. **Giới hạn retry**: bỏ `openVstGuiRetry` spam; thử tối đa 2-3 lần có backoff.
|
||
3. **Watchdog đừng giết khi đang attach GUI**: phân biệt stall do GUI job dài (có
|
||
attach-in-flight flag) với stall do bridge chết thật; nâng ngưỡng stall khi có
|
||
attach đang chạy.
|
||
4. **Fix state reset**: `reload()` nên save/restore state (`getState`/`setState`)
|
||
thay vì load instance mới mất preset. (Bug phụ, độc lập.)
|
||
5. (Dài hạn) 1 apartment chung cho mọi instance cùng DLL (`shared_worker` pattern
|
||
đã có trong probe) — loại conflict nhiều-thread-1-DLL triệt để.
|
||
|
||
## 6. Files liên quan
|
||
|
||
- `native_bridge/src/main.cpp` — handleOpenGui (943), silence/close-other (1015-1090), attach job + catch (1132-1160)
|
||
- `native_bridge/src/Vst3Instrument.cpp` — reload() (615), reloadForGUI() (528), attachView() (550), closeGUI() (640)
|
||
- `native_bridge/src/gui_probe.cpp` — probe bridge_two (243), two_workers_close (318)
|
||
- `native_bridge/debug/` — sweep_all_gui.py, sweep_bridge_two_8s.json, probe_attach_detail.py, probe_app4_live.py, probe_app4.py
|
||
- Bridge log: `%AppData%/SonicForgeDAW/logs/bridge.log`
|
||
|
||
Trạng thái live hiện tại: app câm (bridge 21932 sống, không instrument, transport
|
||
STOP). Test thêm bằng `probe_app4_live.py` sẽ lại làm hư session — cần xác nhận
|
||
trước khi chạy tiếp.
|