refactor(backend): phase 5 - optimize concurrency with RwLock and async IO
Replace Mutex with RwLock for AppState.config to enable concurrent reads, improving performance for tray menu building and query operations that previously blocked each other unnecessarily. Key changes: - Migrate AppState.config from Mutex<MultiAppConfig> to RwLock<MultiAppConfig> - Distinguish read-only operations (read()) from mutations (write()) across all command handlers and service layers - Offload blocking file I/O in import/export commands to spawn_blocking threads, minimizing lock hold time and preventing main thread blocking - Extract load_config_for_import() to separate I/O logic from state updates - Update all integration tests to use RwLock semantics Performance impact: - Concurrent reads: Multiple threads can now query config simultaneously (tray menu, provider list, MCP config) - Reduced contention: Write locks only acquired during actual mutations - Non-blocking I/O: Config import/export no longer freezes UI thread All existing tests pass with new locking semantics.
This commit is contained in:
@@ -89,6 +89,11 @@
|
||||
- **阶段 4:服务层抽象 🚧**
|
||||
- 新增 `services/provider.rs` 并实现 `ProviderService::switch`,负责供应商切换时的业务流程(live 回填、持久化、MCP 同步),命令层通过薄封装调用并负责状态持久化。
|
||||
- 扩展 `ProviderService` 提供 `delete` 能力,统一 Codex/Claude 清理逻辑;`tests/provider_service.rs` 校验切换与删除在成功/失败场景(包括缺失供应商、缺少 auth、删除当前供应商)下的行为,确保命令/托盘复用时拥有回归护栏。
|
||||
- **阶段 5:锁与阻塞优化 🚧**
|
||||
- `AppState` 由 `Mutex<MultiAppConfig>` 切换为 `RwLock<MultiAppConfig>`,命令层根据读/写语义分别使用 `read()` 与 `write()`,避免查询场景被多余互斥阻塞。
|
||||
- 配套更新托盘初始化、服务层、MCP/Provider/Import Export 命令及所有集成测试,确保新锁语义下的并发安全;`cargo test` 全量通过(含命令、服务层集成用例)。
|
||||
- 针对可能耗时的配置导入/导出命令,抽取 `load_config_for_import` 负责文件 IO 与备份逻辑,并在命令层通过 `tauri::async_runtime::spawn_blocking` 下沉至阻塞线程执行,主线程仅负责状态写入与响应组装。
|
||||
- 其余命令(如设置查询、单文件读写)评估后维持同步执行,以免引入不必要的线程切换;后续若新增批量 IO 场景,再按同一模式挂载到阻塞线程。
|
||||
|
||||
## 渐进式重构路线
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ pub async fn get_mcp_config(
|
||||
.to_string();
|
||||
let mut cfg = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let app_ty = AppType::from(app.as_deref().unwrap_or("claude"));
|
||||
let (servers, normalized) = mcp::get_servers_snapshot_for(&mut cfg, &app_ty);
|
||||
@@ -84,7 +84,7 @@ pub async fn upsert_mcp_server_in_config(
|
||||
) -> Result<bool, String> {
|
||||
let mut cfg = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let app_ty = AppType::from(app.as_deref().unwrap_or("claude"));
|
||||
let mut sync_targets: Vec<AppType> = Vec::new();
|
||||
@@ -115,7 +115,7 @@ pub async fn upsert_mcp_server_in_config(
|
||||
|
||||
let cfg2 = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
for app_ty_to_sync in sync_targets {
|
||||
match app_ty_to_sync {
|
||||
@@ -135,7 +135,7 @@ pub async fn delete_mcp_server_in_config(
|
||||
) -> Result<bool, String> {
|
||||
let mut cfg = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let app_ty = AppType::from(app.as_deref().unwrap_or("claude"));
|
||||
let existed = mcp::delete_in_config_for(&mut cfg, &app_ty, &id)?;
|
||||
@@ -143,7 +143,7 @@ pub async fn delete_mcp_server_in_config(
|
||||
state.save()?;
|
||||
let cfg2 = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
match app_ty {
|
||||
AppType::Claude => mcp::sync_enabled_to_claude(&cfg2)?,
|
||||
@@ -169,7 +169,7 @@ pub async fn set_mcp_enabled(
|
||||
pub async fn sync_enabled_mcp_to_claude(state: State<'_, AppState>) -> Result<bool, String> {
|
||||
let mut cfg = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let normalized = mcp::normalize_servers_for(&mut cfg, &AppType::Claude);
|
||||
mcp::sync_enabled_to_claude(&cfg)?;
|
||||
@@ -186,7 +186,7 @@ pub async fn sync_enabled_mcp_to_claude(state: State<'_, AppState>) -> Result<bo
|
||||
pub async fn sync_enabled_mcp_to_codex(state: State<'_, AppState>) -> Result<bool, String> {
|
||||
let mut cfg = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let normalized = mcp::normalize_servers_for(&mut cfg, &AppType::Codex);
|
||||
mcp::sync_enabled_to_codex(&cfg)?;
|
||||
@@ -216,7 +216,7 @@ fn set_mcp_enabled_internal(
|
||||
id: &str,
|
||||
enabled: bool,
|
||||
) -> Result<bool, AppError> {
|
||||
let mut cfg = state.config.lock()?;
|
||||
let mut cfg = state.config.write()?;
|
||||
let changed = mcp::set_enabled_and_sync_for(&mut cfg, &app_ty, id, enabled)?;
|
||||
drop(cfg);
|
||||
state.save()?;
|
||||
@@ -234,7 +234,7 @@ pub fn set_mcp_enabled_test_hook(
|
||||
}
|
||||
|
||||
fn import_mcp_from_claude_internal(state: &AppState) -> Result<usize, AppError> {
|
||||
let mut cfg = state.config.lock()?;
|
||||
let mut cfg = state.config.write()?;
|
||||
let changed = mcp::import_from_claude(&mut cfg)?;
|
||||
drop(cfg);
|
||||
if changed > 0 {
|
||||
@@ -249,7 +249,7 @@ pub fn import_mcp_from_claude_test_hook(state: &AppState) -> Result<usize, AppEr
|
||||
}
|
||||
|
||||
fn import_mcp_from_codex_internal(state: &AppState) -> Result<usize, AppError> {
|
||||
let mut cfg = state.config.lock()?;
|
||||
let mut cfg = state.config.write()?;
|
||||
let changed = mcp::import_from_codex(&mut cfg)?;
|
||||
drop(cfg);
|
||||
if changed > 0 {
|
||||
|
||||
@@ -60,7 +60,7 @@ pub async fn get_providers(
|
||||
|
||||
let config = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
|
||||
let manager = config
|
||||
@@ -85,7 +85,7 @@ pub async fn get_current_provider(
|
||||
|
||||
let config = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
|
||||
let manager = config
|
||||
@@ -114,7 +114,7 @@ pub async fn add_provider(
|
||||
let is_current = {
|
||||
let config = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = config
|
||||
.get_manager(&app_type)
|
||||
@@ -145,7 +145,7 @@ pub async fn add_provider(
|
||||
{
|
||||
let mut config = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = config
|
||||
.get_manager_mut(&app_type)
|
||||
@@ -178,7 +178,7 @@ pub async fn update_provider(
|
||||
let (exists, is_current) = {
|
||||
let config = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = config
|
||||
.get_manager(&app_type)
|
||||
@@ -215,7 +215,7 @@ pub async fn update_provider(
|
||||
{
|
||||
let mut config = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = config
|
||||
.get_manager_mut(&app_type)
|
||||
@@ -274,7 +274,7 @@ pub async fn delete_provider(
|
||||
{
|
||||
let mut config = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
ProviderService::delete(&mut config, app_type, &id).map_err(|e| e.to_string())?;
|
||||
}
|
||||
@@ -286,7 +286,7 @@ pub async fn delete_provider(
|
||||
|
||||
/// 切换供应商
|
||||
fn switch_provider_internal(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> {
|
||||
let mut config = state.config.lock().map_err(AppError::from)?;
|
||||
let mut config = state.config.write().map_err(AppError::from)?;
|
||||
|
||||
ProviderService::switch(&mut config, app_type, id)?;
|
||||
|
||||
@@ -323,7 +323,7 @@ pub async fn switch_provider(
|
||||
|
||||
fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result<(), AppError> {
|
||||
{
|
||||
let config = state.config.lock()?;
|
||||
let config = state.config.read()?;
|
||||
if let Some(manager) = config.get_manager(&app_type) {
|
||||
if !manager.get_all_providers().is_empty() {
|
||||
// 已存在供应商则视为已导入,保持与原逻辑一致
|
||||
@@ -358,7 +358,7 @@ fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result
|
||||
None,
|
||||
);
|
||||
|
||||
let mut config = state.config.lock()?;
|
||||
let mut config = state.config.write()?;
|
||||
let manager = config
|
||||
.get_manager_mut(&app_type)
|
||||
.ok_or_else(|| AppError::Message(format!("应用类型不存在: {:?}", app_type)))?;
|
||||
@@ -420,7 +420,7 @@ pub async fn query_provider_usage(
|
||||
let (api_key, base_url, usage_script_code, timeout) = {
|
||||
let config = state
|
||||
.config
|
||||
.lock()
|
||||
.read()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
|
||||
let manager = config.get_manager(&app_type).ok_or("应用类型不存在")?;
|
||||
@@ -601,7 +601,7 @@ pub async fn get_custom_endpoints(
|
||||
.ok_or_else(|| "缺少 providerId".to_string())?;
|
||||
let mut cfg_guard = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
|
||||
let manager = cfg_guard
|
||||
@@ -647,7 +647,7 @@ pub async fn add_custom_endpoint(
|
||||
|
||||
let mut cfg_guard = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = cfg_guard
|
||||
.get_manager_mut(&app_type)
|
||||
@@ -696,7 +696,7 @@ pub async fn remove_custom_endpoint(
|
||||
|
||||
let mut cfg_guard = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = cfg_guard
|
||||
.get_manager_mut(&app_type)
|
||||
@@ -734,7 +734,7 @@ pub async fn update_endpoint_last_used(
|
||||
|
||||
let mut cfg_guard = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
let manager = cfg_guard
|
||||
.get_manager_mut(&app_type)
|
||||
@@ -779,7 +779,7 @@ pub async fn update_providers_sort_order(
|
||||
|
||||
let mut config = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| format!("获取锁失败: {}", e))?;
|
||||
|
||||
let manager = config
|
||||
|
||||
@@ -193,19 +193,23 @@ fn sync_claude_live(
|
||||
/// 导出配置文件
|
||||
#[tauri::command]
|
||||
pub async fn export_config_to_file(file_path: String) -> Result<Value, String> {
|
||||
// 读取当前配置文件
|
||||
let config_path = crate::config::get_app_config_path();
|
||||
let config_content =
|
||||
fs::read_to_string(&config_path).map_err(|e| AppError::io(&config_path, e).to_string())?;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let config_path = crate::config::get_app_config_path();
|
||||
let config_content =
|
||||
fs::read_to_string(&config_path).map_err(|e| AppError::io(&config_path, e))?;
|
||||
|
||||
// 写入到指定文件
|
||||
fs::write(&file_path, &config_content).map_err(|e| AppError::io(&file_path, e).to_string())?;
|
||||
let target_path = PathBuf::from(&file_path);
|
||||
fs::write(&target_path, &config_content).map_err(|e| AppError::io(&target_path, e))?;
|
||||
|
||||
Ok(json!({
|
||||
"success": true,
|
||||
"message": "Configuration exported successfully",
|
||||
"filePath": file_path
|
||||
}))
|
||||
Ok::<_, AppError>(json!({
|
||||
"success": true,
|
||||
"message": "Configuration exported successfully",
|
||||
"filePath": file_path
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("导出配置失败: {}", e))?
|
||||
.map_err(|e: AppError| e.to_string())
|
||||
}
|
||||
|
||||
/// 从文件导入配置
|
||||
@@ -214,15 +218,26 @@ pub async fn import_config_from_file(
|
||||
file_path: String,
|
||||
state: tauri::State<'_, crate::store::AppState>,
|
||||
) -> Result<Value, String> {
|
||||
import_config_from_path(Path::new(&file_path), &state)
|
||||
.map_err(|e| e.to_string())
|
||||
.map(|backup_id| {
|
||||
json!({
|
||||
"success": true,
|
||||
"message": "Configuration imported successfully",
|
||||
"backupId": backup_id
|
||||
})
|
||||
})
|
||||
let path_buf = PathBuf::from(&file_path);
|
||||
let (new_config, backup_id) =
|
||||
tauri::async_runtime::spawn_blocking(move || load_config_for_import(&path_buf))
|
||||
.await
|
||||
.map_err(|e| format!("导入配置失败: {}", e))?
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
{
|
||||
let mut guard = state
|
||||
.config
|
||||
.write()
|
||||
.map_err(|e| AppError::from(e).to_string())?;
|
||||
*guard = new_config;
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"success": true,
|
||||
"message": "Configuration imported successfully",
|
||||
"backupId": backup_id
|
||||
}))
|
||||
}
|
||||
|
||||
/// 从文件导入配置的核心逻辑,供命令及测试复用。
|
||||
@@ -230,6 +245,17 @@ pub fn import_config_from_path(
|
||||
file_path: &Path,
|
||||
state: &crate::store::AppState,
|
||||
) -> Result<String, AppError> {
|
||||
let (new_config, backup_id) = load_config_for_import(file_path)?;
|
||||
|
||||
{
|
||||
let mut guard = state.config.write().map_err(AppError::from)?;
|
||||
*guard = new_config;
|
||||
}
|
||||
|
||||
Ok(backup_id)
|
||||
}
|
||||
|
||||
fn load_config_for_import(file_path: &Path) -> Result<(MultiAppConfig, String), AppError> {
|
||||
let import_content = fs::read_to_string(file_path).map_err(|e| AppError::io(file_path, e))?;
|
||||
|
||||
let new_config: crate::app_config::MultiAppConfig =
|
||||
@@ -240,12 +266,7 @@ pub fn import_config_from_path(
|
||||
|
||||
fs::write(&config_path, &import_content).map_err(|e| AppError::io(&config_path, e))?;
|
||||
|
||||
{
|
||||
let mut guard = state.config.lock().map_err(AppError::from)?;
|
||||
*guard = new_config;
|
||||
}
|
||||
|
||||
Ok(backup_id)
|
||||
Ok((new_config, backup_id))
|
||||
}
|
||||
|
||||
/// 同步当前供应商配置到对应的 live 文件
|
||||
@@ -256,7 +277,7 @@ pub async fn sync_current_providers_live(
|
||||
{
|
||||
let mut config_state = state
|
||||
.config
|
||||
.lock()
|
||||
.write()
|
||||
.map_err(|e| AppError::from(e).to_string())?;
|
||||
sync_current_providers_to_live(&mut config_state).map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ fn create_tray_menu(
|
||||
app: &tauri::AppHandle,
|
||||
app_state: &AppState,
|
||||
) -> Result<Menu<tauri::Wry>, AppError> {
|
||||
let config = app_state.config.lock().map_err(AppError::from)?;
|
||||
let config = app_state.config.read().map_err(AppError::from)?;
|
||||
|
||||
let mut menu_builder = MenuBuilder::new(app);
|
||||
|
||||
@@ -433,7 +433,7 @@ pub fn run() {
|
||||
|
||||
// 首次启动迁移:扫描副本文件,合并到 config.json,并归档副本;旧 config.json 先归档
|
||||
{
|
||||
let mut config_guard = app_state.config.lock().unwrap();
|
||||
let mut config_guard = app_state.config.write().unwrap();
|
||||
let migrated = migration::migrate_copies_into_config(&mut config_guard)?;
|
||||
if migrated {
|
||||
log::info!("已将副本文件导入到 config.json,并完成归档");
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::app_config::MultiAppConfig;
|
||||
use crate::error::AppError;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// 全局应用状态
|
||||
pub struct AppState {
|
||||
pub config: Mutex<MultiAppConfig>,
|
||||
pub config: RwLock<MultiAppConfig>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -16,13 +16,13 @@ impl AppState {
|
||||
});
|
||||
|
||||
Self {
|
||||
config: Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存配置到文件
|
||||
pub fn save(&self) -> Result<(), AppError> {
|
||||
let config = self.config.lock().map_err(AppError::from)?;
|
||||
let config = self.config.read().map_err(AppError::from)?;
|
||||
|
||||
config.save()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde_json::json;
|
||||
use std::{fs, path::Path, sync::Mutex};
|
||||
use std::{fs, path::Path, sync::RwLock};
|
||||
use tauri::async_runtime;
|
||||
|
||||
use cc_switch_lib::{
|
||||
@@ -728,7 +728,7 @@ fn import_config_from_path_overwrites_state_and_creates_backup() {
|
||||
.expect("write import file");
|
||||
|
||||
let app_state = AppState {
|
||||
config: Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let backup_id =
|
||||
@@ -757,7 +757,7 @@ fn import_config_from_path_overwrites_state_and_creates_backup() {
|
||||
"saved config should record new current provider"
|
||||
);
|
||||
|
||||
let guard = app_state.config.lock().expect("lock state after import");
|
||||
let guard = app_state.config.read().expect("lock state after import");
|
||||
let claude_manager = guard
|
||||
.get_manager(&AppType::Claude)
|
||||
.expect("claude manager in state");
|
||||
@@ -784,7 +784,7 @@ fn import_config_from_path_invalid_json_returns_error() {
|
||||
fs::write(&invalid_path, "{ not-json ").expect("write invalid json");
|
||||
|
||||
let app_state = AppState {
|
||||
config: Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let err = import_config_from_path(&invalid_path, &app_state).expect_err("import should fail");
|
||||
@@ -802,7 +802,7 @@ fn import_config_from_path_missing_file_produces_io_error() {
|
||||
|
||||
let missing_path = Path::new("/nonexistent/import.json");
|
||||
let app_state = AppState {
|
||||
config: Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let err = import_config_from_path(missing_path, &app_state)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fs;
|
||||
use std::{fs, sync::RwLock};
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
@@ -37,14 +37,14 @@ fn import_default_config_claude_persists_provider() {
|
||||
let mut config = MultiAppConfig::default();
|
||||
config.ensure_app(&AppType::Claude);
|
||||
let state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
import_default_config_test_hook(&state, AppType::Claude)
|
||||
.expect("import default config succeeds");
|
||||
|
||||
// 验证内存状态
|
||||
let guard = state.config.lock().expect("lock config");
|
||||
let guard = state.config.read().expect("lock config");
|
||||
let manager = guard
|
||||
.get_manager(&AppType::Claude)
|
||||
.expect("claude manager present");
|
||||
@@ -71,7 +71,7 @@ fn import_default_config_without_live_file_returns_error() {
|
||||
let home = ensure_test_home();
|
||||
|
||||
let state = AppState {
|
||||
config: std::sync::Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let err = import_default_config_test_hook(&state, AppType::Claude)
|
||||
@@ -113,7 +113,7 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
||||
.expect("seed ~/.claude.json");
|
||||
|
||||
let state = AppState {
|
||||
config: std::sync::Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let changed =
|
||||
@@ -123,7 +123,7 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
||||
"import should report inserted or normalized entries"
|
||||
);
|
||||
|
||||
let guard = state.config.lock().expect("lock config");
|
||||
let guard = state.config.read().expect("lock config");
|
||||
let claude_servers = &guard.mcp.claude.servers;
|
||||
let entry = claude_servers
|
||||
.get("echo")
|
||||
@@ -155,7 +155,7 @@ fn import_mcp_from_claude_invalid_json_preserves_state() {
|
||||
.expect("seed invalid ~/.claude.json");
|
||||
|
||||
let state = AppState {
|
||||
config: std::sync::Mutex::new(MultiAppConfig::default()),
|
||||
config: RwLock::new(MultiAppConfig::default()),
|
||||
};
|
||||
|
||||
let err =
|
||||
@@ -197,13 +197,13 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
|
||||
);
|
||||
|
||||
let state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
set_mcp_enabled_test_hook(&state, AppType::Codex, "codex-server", true)
|
||||
.expect("set enabled should succeed");
|
||||
|
||||
let guard = state.config.lock().expect("lock config");
|
||||
let guard = state.config.read().expect("lock config");
|
||||
let entry = guard
|
||||
.mcp
|
||||
.codex
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde_json::json;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use cc_switch_lib::{
|
||||
get_codex_auth_path, get_codex_config_path, read_json_file, switch_provider_test_hook,
|
||||
@@ -71,7 +72,7 @@ command = "say"
|
||||
);
|
||||
|
||||
let app_state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
switch_provider_test_hook(&app_state, AppType::Codex, "new-provider")
|
||||
@@ -94,7 +95,7 @@ command = "say"
|
||||
"config.toml should contain synced MCP servers"
|
||||
);
|
||||
|
||||
let locked = app_state.config.lock().expect("lock config after switch");
|
||||
let locked = app_state.config.read().expect("lock config after switch");
|
||||
let manager = locked
|
||||
.get_manager(&AppType::Codex)
|
||||
.expect("codex manager after switch");
|
||||
@@ -142,7 +143,7 @@ fn switch_provider_missing_provider_returns_error() {
|
||||
.current = "does-not-exist".to_string();
|
||||
|
||||
let app_state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
let err = switch_provider_test_hook(&app_state, AppType::Claude, "missing-provider")
|
||||
@@ -210,7 +211,7 @@ fn switch_provider_updates_claude_live_and_state() {
|
||||
}
|
||||
|
||||
let app_state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
switch_provider_test_hook(&app_state, AppType::Claude, "new-provider")
|
||||
@@ -227,7 +228,7 @@ fn switch_provider_updates_claude_live_and_state() {
|
||||
"live settings.json should reflect new provider auth"
|
||||
);
|
||||
|
||||
let locked = app_state.config.lock().expect("lock config after switch");
|
||||
let locked = app_state.config.read().expect("lock config after switch");
|
||||
let manager = locked
|
||||
.get_manager(&AppType::Claude)
|
||||
.expect("claude manager after switch");
|
||||
@@ -304,7 +305,7 @@ fn switch_provider_codex_missing_auth_returns_error_and_keeps_state() {
|
||||
}
|
||||
|
||||
let app_state = AppState {
|
||||
config: std::sync::Mutex::new(config),
|
||||
config: RwLock::new(config),
|
||||
};
|
||||
|
||||
let err = switch_provider_test_hook(&app_state, AppType::Codex, "invalid")
|
||||
@@ -317,7 +318,7 @@ fn switch_provider_codex_missing_auth_returns_error_and_keeps_state() {
|
||||
other => panic!("expected config error, got {other:?}"),
|
||||
}
|
||||
|
||||
let locked = app_state.config.lock().expect("lock config after failure");
|
||||
let locked = app_state.config.read().expect("lock config after failure");
|
||||
let manager = locked.get_manager(&AppType::Codex).expect("codex manager");
|
||||
assert!(
|
||||
manager.current.is_empty(),
|
||||
|
||||
Reference in New Issue
Block a user