V45.39: fix SERVER OFFLINE lau + UI load cham - engine ghi port file (engine_port.txt), UI bootstrap doc port engine cua chinh no (get_engine_port) + timeout/retry 60s, health poll self-heal reload, khong taskkill /IM daw_engine (giet engine instance khac), /api/v1/plugins/available chay scan trong threadpool (khong chan event loop -> /health het timeout)
This commit is contained in:
@@ -194,6 +194,14 @@ def get_scanner():
|
||||
|
||||
@router.get("/available")
|
||||
async def list_plugins(current_user: dict = Depends(get_current_user)):
|
||||
# Scan VST/SF chạy subprocess đồng bộ (fx_vst_bridge --scan, timeout 300s/
|
||||
# dir; Ozone treo tới 30s/module) — chạy trực tiếp trên event loop sẽ chặn
|
||||
# /health và mọi request khác → UI báo SERVER: OFFLINE suốt thời gian scan.
|
||||
# Chạy trong threadpool để event loop luôn phản hồi.
|
||||
return await asyncio.to_thread(_list_plugins_impl)
|
||||
|
||||
|
||||
def _list_plugins_impl():
|
||||
d = _effective_dirs()
|
||||
pm = PluginManager(vst_dir=d["vst_dir"], sf_dir=d["soundfont_dir"], upload_sf_dir=UPLOAD_SF_DIR, extra_vst_dirs=d["plugin_dirs"], user_dirs_override=d.get("plugin_dirs_user_set", False))
|
||||
avail = pm.list_available()
|
||||
|
||||
+16
-3
@@ -23741,15 +23741,28 @@ const App = () => {
|
||||
return () => observer.disconnect();
|
||||
}, [timelineWrapperNode]);
|
||||
useEffect(() => {
|
||||
let cancelled = false, busy = false, iv = null;
|
||||
let cancelled = false, busy = false, iv = null, offlineCount = 0;
|
||||
const check = () => {
|
||||
if (busy || cancelled) return;
|
||||
busy = true;
|
||||
const ctl = new AbortController();
|
||||
const t = setTimeout(() => ctl.abort(), 3000);
|
||||
fetch(API_BASE_URL, { signal: ctl.signal })
|
||||
.then(r => { if (!cancelled) setServerStatus(r.ok ? 'connected' : 'error'); })
|
||||
.catch(() => { if (!cancelled) setServerStatus('offline'); })
|
||||
.then(r => { if (!cancelled) { setServerStatus(r.ok ? 'connected' : 'error'); offlineCount = 0; } })
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setServerStatus('offline');
|
||||
// Self-heal: engine chết/mất kết nối liên tục (~9s) → reload trang
|
||||
// để bootstrap chạy lại: ưu tiên port engine của chính app
|
||||
// (engine_port.txt) + fallback quét 8000-8010. Trước đây kẹt
|
||||
// OFFLINE vĩnh viễn khi engine bị instance khác giết port.
|
||||
offlineCount++;
|
||||
if (offlineCount >= 3) {
|
||||
offlineCount = 0;
|
||||
try { window.location.reload(); } catch (e) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => { busy = false; clearTimeout(t); });
|
||||
};
|
||||
check();
|
||||
|
||||
+2755
-2745
File diff suppressed because one or more lines are too long
@@ -51,7 +51,7 @@
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202608290945"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202608290945"></script>
|
||||
<script src="/static/js/services/chordTheory.js?v=202608290945"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608311858" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202609011700" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202608290945">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -116,6 +116,17 @@ def main():
|
||||
port = _pick_port()
|
||||
os.environ["SF_PORT"] = str(port)
|
||||
|
||||
# Announce port to Tauri shell: UI bootstrap đọc %APPDATA%/SonicForgeDAW/
|
||||
# engine_port.txt (qua lệnh get_engine_port) để kết nối ĐÚNG engine của
|
||||
# chính nó thay vì quét port — quét 8000-8010 có thể dính engine của
|
||||
# instance khác (2 app chạy cùng lúc) → UI mất server khi instance đó tắt.
|
||||
try:
|
||||
port_file = os.path.join(os.path.dirname(log_dir), "engine_port.txt")
|
||||
with open(port_file, "w", encoding="utf-8") as f:
|
||||
f.write(str(port))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Log ra file — khi dong goi console=False, stdout khong nhin thay duoc.
|
||||
try:
|
||||
logging.basicConfig(
|
||||
|
||||
+24
-2
@@ -294,7 +294,8 @@ pub fn run() {
|
||||
transport_control,
|
||||
set_fx_chain,
|
||||
restart_bridge_with_sample_rate,
|
||||
dbg_log
|
||||
dbg_log,
|
||||
get_engine_port
|
||||
])
|
||||
.setup(|app| {
|
||||
// V10 (FIX_DRAG_DROP_WINDOWS): disable Tauri's built-in drag-drop
|
||||
@@ -741,7 +742,12 @@ pub fn run() {
|
||||
// taskkill chi nhan MOT /IM moi lan goi — goi rieng cho tung
|
||||
// process, khong gop /IM ... /IM (truoc day chi cai dau chay,
|
||||
// fx_vst_bridge/plugin_host song sot thanh orphan).
|
||||
for im in ["daw_engine.exe", "daw_vst_bridge.exe",
|
||||
// KHONG taskkill /IM daw_engine.exe: image name dung chung moi
|
||||
// instance — se giet ca engine cua app KHAC dang chay (2 app
|
||||
// cung luc: dong 1 cai → app kia mat server → SERVER OFFLINE).
|
||||
// Engine cua chinh minh da duoc kill_process_tree o tren; con
|
||||
// sot (crash/khong parent) tu thoat nho watchdog SF_PARENT_PID.
|
||||
for im in ["daw_vst_bridge.exe",
|
||||
"fx_vst_bridge.exe", "plugin_host.exe"] {
|
||||
let _ = std::process::Command::new("taskkill")
|
||||
.args(["/IM", im, "/T", "/F"])
|
||||
@@ -904,6 +910,22 @@ fn bridge_status(app: AppHandle) -> Result<BridgeStatus, String> {
|
||||
/// V28: JS timing diagnostics — append `{unix_ms} {tag} {extra}` to
|
||||
/// %APPDATA%/SonicForgeDAW/logs/dbg.log (create dirs as needed). Fire-and-forget
|
||||
/// từ app.jsx (_dbgLog) để định vị delay Play/Stop không cần devtools.
|
||||
/// Port engine đã chọn (desktop_engine.py ghi %APPDATA%/SonicForgeDAW/
|
||||
/// engine_port.txt ngay sau _pick_port). UI bootstrap ưu tiên dùng port này
|
||||
/// để kết nối ĐÚNG engine của chính mình; -1 khi chưa có (engine đang khởi
|
||||
/// động) → bootstrap retry/fallback quét 8000-8010.
|
||||
#[tauri::command]
|
||||
fn get_engine_port() -> i32 {
|
||||
let appdata = std::env::var("APPDATA").unwrap_or_else(|_| ".".into());
|
||||
let path = std::path::Path::new(&appdata)
|
||||
.join("SonicForgeDAW")
|
||||
.join("engine_port.txt");
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<i32>().ok())
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn dbg_log(tag: String, extra: String) -> Result<(), String> {
|
||||
let Some(appdata) = std::env::var("APPDATA").ok() else { return Ok(()) };
|
||||
|
||||
+34
-5
@@ -20,16 +20,45 @@
|
||||
// (origin tauri:// giữ nguyên -> __TAURI__ sống), base href + API_BASE_URL
|
||||
// trỏ engine HTTP (CORS * đã bật).
|
||||
(async function () {
|
||||
// Đợi engine của CHÍNH app này (port do desktop_engine.py ghi vào
|
||||
// engine_port.txt, đọc qua lệnh Tauri get_engine_port) tối đa 60s.
|
||||
// Fallback: quét 8000-8010 (engine cũ/cài chồng) nhưng CÓ timeout mỗi
|
||||
// port — trước đây fetch không timeout + không retry: port giữ kết nối
|
||||
// không phản hồi → UI treo "Đang khởi động" vĩnh viễn; engine khởi
|
||||
// động chậm (cold start) → "Không tìm thấy" dù sau đó engine sống.
|
||||
var base = null;
|
||||
for (var p = 8000; p <= 8010 && !base; p++) {
|
||||
var deadline = Date.now() + 60000;
|
||||
function withTimeout(ms) {
|
||||
var ctl = new AbortController();
|
||||
var t = setTimeout(function () { ctl.abort(); }, ms);
|
||||
return { ctl: ctl, t: t };
|
||||
}
|
||||
while (!base && Date.now() < deadline) {
|
||||
try {
|
||||
var r = await fetch('http://127.0.0.1:' + p + '/health', { cache: 'no-store' });
|
||||
if (r.ok) base = 'http://127.0.0.1:' + p + '/';
|
||||
} catch (e) { /* port chưa mở */ }
|
||||
if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) {
|
||||
var port = await window.__TAURI__.core.invoke('get_engine_port');
|
||||
if (port && port > 0) {
|
||||
var h = withTimeout(2000);
|
||||
try {
|
||||
var r = await fetch('http://127.0.0.1:' + port + '/health', { signal: h.ctl.signal, cache: 'no-store' });
|
||||
if (r.ok) base = 'http://127.0.0.1:' + port + '/';
|
||||
} finally { clearTimeout(h.t); }
|
||||
}
|
||||
}
|
||||
} catch (e) { /* engine chưa ghi port — retry */ }
|
||||
if (base) break;
|
||||
for (var p = 8000; p <= 8010 && !base; p++) {
|
||||
var h2 = withTimeout(1000);
|
||||
try {
|
||||
var r2 = await fetch('http://127.0.0.1:' + p + '/health', { signal: h2.ctl.signal, cache: 'no-store' });
|
||||
if (r2.ok) base = 'http://127.0.0.1:' + p + '/';
|
||||
} catch (e) { /* port chưa mở */ } finally { clearTimeout(h2.t); }
|
||||
}
|
||||
if (!base) await new Promise(function (res) { setTimeout(res, 1500); });
|
||||
}
|
||||
if (!base) {
|
||||
document.getElementById('msg').textContent =
|
||||
'Không tìm thấy SonicForge Engine (port 8000–8010). Hãy đóng và chạy lại ứng dụng. ' +
|
||||
'Không tìm thấy SonicForge Engine (port 8000–8010) sau 60s. Hãy đóng và chạy lại ứng dụng. ' +
|
||||
'Nếu lặp lại, xem log: %APPDATA%\\SonicForgeDAW\\logs\\engine.log';
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user