M24: fix lag am khi switch GUI VST luc play — blocking FX-GUI ops (capture poll/wait_chain_ready) ra khoi event loop qua asyncio.to_thread + lock per-session, pump audio khong stall (pump_gap 60-200ms -> 0)

This commit is contained in:
2026-09-07 12:13:20 +07:00
parent 821019324f
commit a88fc5b3f1
+91 -54
View File
@@ -693,6 +693,72 @@ class PreviewRequest(BaseModel):
preset_data: Optional[str] = None # base64 bytes .vstpreset (project nhúng)
# M24: thao tac GUI native (hide/open/close editor in-process fxrt) co work
# blocking (capture_editor_preset poll file time.sleep 0.05 x3s,
# wait_chain_ready sleep-poll, terminate proc.wait(5)). Truoc M24 cac sleep nay
# chay DONG BO trong async endpoint -> block event loop uvicorn -> pump audio
# `_pump_output` (cung loop) stall 60-200ms -> worklet queue can -> underrun ->
# am lag roi moi play lai moi lan switch GUI luc dang play (log fxrt_pump.log:
# pump_gap ngay sau GUI op). Fix: chay blocking work trong thread
# (asyncio.to_thread) duoi lock per-session de GIU thu tu hide->open nhu cu.
_FXGUI_OP_LOCKS = {} # session name -> asyncio.Lock
_FXGUI_OP_LOCKS_GUARD = threading.Lock()
def _fxgui_op_lock(session_name):
with _FXGUI_OP_LOCKS_GUARD:
lock = _FXGUI_OP_LOCKS.get(session_name)
if lock is None:
lock = asyncio.Lock()
_FXGUI_OP_LOCKS[session_name] = lock
return lock
def _fxgui_open_native_blocking(sess, plugin_path):
"""M24 worker thread (asyncio.to_thread) cho nhanh mo GUI native: cho chain
ready tren bridge, terminate fallback GUI cu, gui ed.open + retry 700ms.
Tra dict ket qua native; None neu plugin khong phai slot chain (roi xuong
fallback); raise HTTPException 409 khi session chet giua chung."""
_slot = sess.chain_slot_for_path(plugin_path)
if _slot is None:
return None
_ready = sess.wait_chain_ready(timeout=15)
logger.info("[fxgui] open native path=%s shm=%s slot=%s ready=%s",
plugin_path, sess.name, _slot, _ready)
if not _ready:
# M13: chua READY (proc respawn / chain reload cham) — cho them
# 5s; van chet thi ed.open chi vao ctrl ring (C++ KHONG reset
# ring khi respawn → proc moi doc duoc) nen van gui duoc.
sess.wait_chain_ready(timeout=5)
if not sess.alive():
logger.warning("[fxgui] open native bo — session chet path=%s", plugin_path)
raise HTTPException(status_code=409,
detail="Session FX realtime đã đóng — bật lại FX PWR rồi thử mở GUI.")
_killed = _terminate_fx_gui_for_path(plugin_path)
_already = fx_realtime.editor_find(plugin_path)
_same = _already is not None and _already[0] == sess.name
_sent = sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] open sent=%s killed_fallback=%s already=%s same=%s proc_pid=%s state=%s",
_sent, _killed, bool(_already), _same,
sess.proc.pid if getattr(sess, "proc", None) else None,
getattr(getattr(sess, "h", None), "state", "?"))
fx_realtime.editor_register(plugin_path, sess.name, _slot)
# M13 workaround click-1: gui doi khi khong hien window ngay (owner
# gan sau show → Win32 an window). Gửi lai ed.open sau 700ms = ban
# sao click-2 — C++ log "ALREADY open — re-show" thi re-show chinh
# la dieu can. ponytail: bo block nay khi log A (prefix p/t) xac
# nhan root cause that (show truoc owner).
def _retry_open(delay):
_time.sleep(delay)
try:
if sess.alive():
sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] retry ed.open path=%s slot=%s", plugin_path, _slot)
except Exception:
pass
threading.Thread(target=_retry_open, args=(0.7,), daemon=True).start()
return {"success": True, "started": not _same,
"already_running": bool(_same), "native": True,
"embed_url": None, "session_id": sess.name, "slot": _slot}
@router.post("/fx-gui")
async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_current_user)):
"""Mở native GUI của VST FX (mastering chain / FX rack) qua bridge.
@@ -726,62 +792,25 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
# JS set_chain async / watchdog respawn) -> chờ tối đa 15s cho slot xuất hiện;
# session chết giữa chừng -> bỏ (fallback embed bị chặn bên dưới cho master).
if req.master and _sess is not None and _sess.alive() and _sess.chain_slot_for_path(plugin_path) is None:
# M24: poll async (khong time.sleep dong bo — block event loop lam pump
# audio stall). Y nghia giu nguyen: cho toi da 15s slot xuat hien.
for _ in range(150):
if not _sess.alive():
break
if _sess.chain_slot_for_path(plugin_path) is not None:
break
_time.sleep(0.1)
await asyncio.sleep(0.1)
if _sess is not None and _sess.alive():
_slot = _sess.chain_slot_for_path(plugin_path)
if _slot is not None:
# Bug 2-GUI: fallback bridge fx-gui (embed/float) còn sống cho cùng
# plugin — instance VST THỨ 2 + window riêng; mở native phải loại nó
# trước, nếu không 1 plugin hiện 2 GUI (1 floating native + 1 embed)
# và floating không tắt được qua UI. Terminate proc cũ → frontend
# embed panel của nó onDead → tự đóng.
# M8: set_chain (juce bridge) reload async (watchdog respawn) —
# cho chain moi ap tren bridge truoc khi ed.open, khong thi C++
# bao "slot ngoai chain (M) — bo qua". Khong co reload dang chay
# -> tra ngay (khong them latency mo GUI).
_ready = _sess.wait_chain_ready(timeout=15)
logger.info("[fxgui] open native path=%s shm=%s slot=%s ready=%s",
plugin_path, _sess.name, _slot, _ready)
if not _ready:
# M13: chua READY (proc respawn / chain reload cham) — cho them
# 5s; van chet thi ed.open chi vao ctrl ring (C++ KHONG reset
# ring khi respawn → proc moi doc duoc) nen van gui duoc.
_sess.wait_chain_ready(timeout=5)
if not _sess.alive():
logger.warning("[fxgui] open native bo — session chet path=%s", plugin_path)
raise HTTPException(status_code=409,
detail="Session FX realtime đã đóng — bật lại FX PWR rồi thử mở GUI.")
_killed = _terminate_fx_gui_for_path(plugin_path)
_already = fx_realtime.editor_find(plugin_path)
_same = _already is not None and _already[0] == _sess.name
_sent = _sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] open sent=%s killed_fallback=%s already=%s same=%s proc_pid=%s state=%s",
_sent, _killed, bool(_already), _same,
_sess.proc.pid if getattr(_sess, "proc", None) else None,
getattr(getattr(_sess, "h", None), "state", "?"))
fx_realtime.editor_register(plugin_path, _sess.name, _slot)
# M13 workaround click-1: gui doi khi khong hien window ngay (owner
# gan sau show → Win32 an window). Gửi lai ed.open sau 700ms = ban
# sao click-2 — C++ log "ALREADY open — re-show" thi re-show chinh
# la dieu can. ponytail: bo block nay khi log A (prefix p/t) xac
# nhan root cause that (show truoc owner).
def _retry_open(delay):
_time.sleep(delay)
try:
if _sess.alive():
_sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] retry ed.open path=%s slot=%s", plugin_path, _slot)
except Exception:
pass
threading.Thread(target=_retry_open, args=(0.7,), daemon=True).start()
return {"success": True, "started": not _same,
"already_running": bool(_same), "native": True,
"embed_url": None, "session_id": _sess.name, "slot": _slot}
# M24: toan bo nhanh native (wait_chain_ready sleep-poll, terminate
# fallback proc.wait, ed.open) chay trong worker thread duoi lock per
# session — event loop khong block -> pump audio khong stall khi switch
# GUI luc dang play. None khi plugin chua phai slot -> roi xuong
# fallback ben duoi nhu cu.
async with _fxgui_op_lock(_sess.name):
_native = await asyncio.to_thread(
_fxgui_open_native_blocking, _sess, plugin_path)
if _native is not None:
return _native
# Tránh mở 2 GUI cùng plugin: editor NATIVE (instance DSP trong session
# fxrt) của plugin này đang mở ở session SỐNG → KHÔNG spawn instance 2 /
# bridge fx-gui nữa (nguồn CPU 100% + 2 GUI); trả native để frontend hiện
@@ -904,8 +933,12 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_
# M5: capture state editor TRƯỚC khi đóng (preset user chỉnh trong
# GUI → lưu về project như WM_FXGUI_CAPTURE cũ; preset_b64 dùng để
# mở lại GUI / nạp chain với đúng cài đặt). Không lỗi nếu rỗng.
_preset = _sess.capture_editor_preset(_slot)
_sess.send_editor_cmd("close", slot=_slot)
# M24: capture (poll file sleep toi 3s) trong thread duoi lock —
# khong block event loop/pump audio.
async with _fxgui_op_lock(_sess.name):
_preset = await asyncio.to_thread(
_sess.capture_editor_preset, _slot)
_sess.send_editor_cmd("close", slot=_slot)
return {"success": True, "closed": True, "native": True,
"preset_b64": _preset}
# session đã chết — rơi xuống spawn fallback (đóng bridge fx-gui cũ nếu có)
@@ -958,8 +991,12 @@ async def hide_fx_gui(req: FxGuiNativeRequest,
# M13: capture preset TRUOC khi an — dong mastering panel / save project
# giua luc chinh GUI native khong mat preset moi (truoc day chi capture o
# /fx-gui/close; hide chi an -> chain luu preset cu/rong sau khi reload).
_preset = sess.capture_editor_preset(slot)
sess.send_editor_cmd("hide", slot=slot)
# M24: capture (poll file sleep toi 3s) trong thread duoi lock — day la
# blocker chinh moi lan switch GUI (JS fire-and-forget hide truoc open);
# truoc day sleep dong bo trong async endpoint -> pump audio stall -> lag.
async with _fxgui_op_lock(sess.name):
_preset = await asyncio.to_thread(sess.capture_editor_preset, slot)
sess.send_editor_cmd("hide", slot=slot)
return {"success": True, "applied": True, "native": True,
"preset_b64": _preset}