From f3a129f2e986a97ebd65f797815c0bc575a7da09 Mon Sep 17 00:00:00 2001 From: yao wei Date: Mon, 2 Mar 2026 23:57:24 -0800 Subject: [PATCH 1/2] fix(funasr): fix race condition in suppress_stdout causing startup crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When three model-loading threads run in parallel, each calls suppress_stdout() which saves and restores sys.stdout. Due to interleaving, one thread could capture another thread's devnull as its old_stdout, and later restore to a closed file — causing `ValueError: I/O operation on closed file` when the server tried to print the init result. Fix by restoring to sys.__stdout__ (Python's original stdout) instead of the locally saved reference, which is always stable regardless of thread order. Co-Authored-By: Claude Sonnet 4.6 --- funasr_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/funasr_server.py b/funasr_server.py index 78b1863..11640b1 100644 --- a/funasr_server.py +++ b/funasr_server.py @@ -55,13 +55,13 @@ def get_log_path(): @contextlib.contextmanager def suppress_stdout(): """上下文管理器:临时重定向stdout到devnull,避免FunASR库的非JSON输出干扰IPC通信""" - old_stdout = sys.stdout devnull = open(os.devnull, "w") try: sys.stdout = devnull yield finally: - sys.stdout = old_stdout + # 使用 sys.__stdout__ 恢复原始stdout,避免多线程race condition + sys.stdout = sys.__stdout__ devnull.close() From 986fc99227240d47650a4f5ae2d02559c7a1aab1 Mon Sep 17 00:00:00 2001 From: yao wei Date: Tue, 3 Mar 2026 11:23:25 -0800 Subject: [PATCH 2/2] fix: prevent false 'model error' popup during startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the app starts, initializeAtStartup() runs async Python checks before calling preInitializeModels() (which sets initializationPromise). During this brief window, checkStatus() returns initializing: false even though FunASR is just starting up, causing useModelStatus to set stage:'error' → popup fires on mic click even with AI optimization disabled. Two fixes: 1. funasrManager.checkStatus(): also return initializing:true when !this.isInitialized (startup not yet complete), not just when initializationPromise is set. 2. useModelStatus.js: when models are downloaded but server not yet ready, treat as 'loading' instead of 'error'. Only show error when models are genuinely missing or FunASR isn't installed. Co-Authored-By: Claude Sonnet 4.6 --- src/helpers/funasrManager.js | 2 +- src/hooks/useModelStatus.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helpers/funasrManager.js b/src/helpers/funasrManager.js index e964085..d21eb96 100644 --- a/src/helpers/funasrManager.js +++ b/src/helpers/funasrManager.js @@ -1231,7 +1231,7 @@ class FunASRManager { installed: installStatus.installed, models_downloaded: modelStatus.models_downloaded, missing_models: modelStatus.missing_models || [], - initializing: this.initializationPromise !== null + initializing: this.initializationPromise !== null || !this.isInitialized }; } } catch (error) { diff --git a/src/hooks/useModelStatus.js b/src/hooks/useModelStatus.js index 728a8fc..b7afbd3 100644 --- a/src/hooks/useModelStatus.js +++ b/src/hooks/useModelStatus.js @@ -105,8 +105,8 @@ export const useModelStatus = () => { progress: 100, stage: 'ready' })); - } else if (serverStatus.initializing) { - // 模型已下载,正在加载 + } else if (serverStatus.initializing || serverStatus.models_downloaded) { + // 模型已下载,正在加载(包括启动竞争窗口期 initializing 尚未设置的情况) setModelStatus(prev => ({ ...prev, isLoading: true, @@ -118,12 +118,12 @@ export const useModelStatus = () => { stage: 'loading' })); } else { - // 模型已下载但服务器未就绪 + // 模型未下载或服务器真实失败 setModelStatus(prev => ({ ...prev, isLoading: false, isReady: false, - modelsDownloaded: true, + modelsDownloaded: false, missingModels: [], error: serverStatus.error || '服务器未就绪', progress: 0,