Skip to content

Commit cc21d2a

Browse files
committed
Remove autosave config lock from editor hot path
Drop the runtime autosave setting and config:get socket handler so backend config can be shared immutably instead of behind a mutex. Remove autosave work from file:change, keep explicit file:save behavior, and update file/LSP handlers to read the immutable config directly. Restore per-watch git status updates instead of the batched watcher channel because the batching path did not resolve the editor responsiveness issue.
1 parent 0732954 commit cc21d2a

9 files changed

Lines changed: 37 additions & 142 deletions

File tree

anycode-backend/config.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
theme = "themes/anycode.json"
22
left_panel_width = 25
3-
autosave = true
43

54
terminal.command = "bash"
65

anycode-backend/src/app_state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tokio_util::sync::CancellationToken;
1414

1515
#[derive(Clone)]
1616
pub struct AppState {
17-
pub config: Arc<Mutex<Config>>,
17+
pub config: Arc<Config>,
1818
pub file2code: Arc<Mutex<HashMap<String, Code>>>,
1919
pub lsp_manager: Arc<Mutex<LspManager>>,
2020
pub acp_manager: Arc<Mutex<AcpManager>>,

anycode-backend/src/config.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ pub struct Config {
2222
pub theme: String,
2323
pub language: Vec<Language>,
2424
pub terminal: Option<Terminal>,
25-
#[serde(default = "default_autosave")]
26-
pub autosave: bool,
2725
}
2826

2927
impl Config {
@@ -32,15 +30,10 @@ impl Config {
3230
theme: "default".to_string(),
3331
language: vec![],
3432
terminal: None,
35-
autosave: default_autosave(),
3633
}
3734
}
3835
}
3936

40-
fn default_autosave() -> bool {
41-
true
42-
}
43-
4437
#[derive(Debug, Deserialize, Clone)]
4538
pub struct Language {
4639
pub name: String,

anycode-backend/src/handlers/io_handler.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ pub async fn handle_file_open(
9696
};
9797

9898
let mut f2c = state.file2code.lock().await;
99-
let config = state.config.lock().await;
100-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
99+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
101100
Ok(c) => c,
102101
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
103102
};
@@ -244,8 +243,7 @@ pub async fn handle_file_close(
244243

245244
let lang = {
246245
let mut f2c = state.file2code.lock().await;
247-
let config = state.config.lock().await;
248-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
246+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
249247
Ok(c) => c,
250248
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
251249
};
@@ -318,8 +316,7 @@ pub async fn handle_file_change(
318316
};
319317

320318
let mut f2c = state.file2code.lock().await;
321-
let config = state.config.lock().await;
322-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
319+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
323320
Ok(c) => c,
324321
Err(e) => {
325322
tracing::error!("Failed to get code: {:?}", e);
@@ -352,15 +349,6 @@ pub async fn handle_file_change(
352349
}
353350
}
354351

355-
let autosave = state.config.lock().await.autosave;
356-
if autosave {
357-
if let Err(e) = code.save_file() {
358-
error!("Autosave failed for {}: {:?}", abs_path, e);
359-
} else if let Some(lsp) = lsp_manager.get(&code.lang).await {
360-
lsp.did_save(&abs_path, Some(&code.text.to_string()));
361-
}
362-
}
363-
364352
// Broadcast as a single message for other clients if needed
365353
socket.broadcast().emit("file:change", &change).await.ok();
366354
}
@@ -384,8 +372,7 @@ pub async fn handle_file_save(
384372
};
385373

386374
let mut f2c = state.file2code.lock().await;
387-
let config = state.config.lock().await;
388-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
375+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
389376
Ok(c) => c,
390377
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
391378
};

anycode-backend/src/handlers/lsp_handler.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ pub async fn handle_completion(
2828
};
2929

3030
let mut f2c = state.file2code.lock().await;
31-
let config = state.config.lock().await;
32-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
31+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
3332
Ok(c) => c,
3433
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
3534
};
@@ -69,8 +68,7 @@ pub async fn handle_hover(
6968
};
7069

7170
let mut f2c = state.file2code.lock().await;
72-
let config = state.config.lock().await;
73-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
71+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
7472
Ok(c) => c,
7573
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
7674
};
@@ -111,8 +109,7 @@ pub async fn handle_definition(
111109
};
112110

113111
let mut f2c = state.file2code.lock().await;
114-
let config = state.config.lock().await;
115-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
112+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
116113
Ok(c) => c,
117114
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
118115
};
@@ -152,8 +149,7 @@ pub async fn handle_references(
152149
};
153150

154151
let mut f2c = state.file2code.lock().await;
155-
let config = state.config.lock().await;
156-
let code = match get_or_create_code(&mut f2c, &abs_path, &config) {
152+
let code = match get_or_create_code(&mut f2c, &abs_path, &state.config) {
157153
Ok(c) => c,
158154
Err(e) => error_ack!(ack, &abs_path, "{:?}", e),
159155
};

anycode-backend/src/handlers/theme_handler.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use crate::app_state::AppState;
21
use serde::{Deserialize, Serialize};
32
use serde_json::{self, json, Value};
4-
use socketioxide::extract::{AckSender, Data, SocketRef, State};
3+
use socketioxide::extract::{AckSender, Data, SocketRef};
54
use std::fs;
65
use std::path::PathBuf;
76
use tracing::info;
@@ -160,13 +159,3 @@ pub async fn handle_theme_get(
160159
let _ = ack.send(&json!({ "success": false, "error": "Theme name not found in file" }));
161160
}
162161
}
163-
164-
pub async fn handle_config_get(
165-
_socket: SocketRef,
166-
ack: AckSender,
167-
state: State<AppState>,
168-
) {
169-
info!("config:get requested");
170-
let autosave = state.config.lock().await.autosave;
171-
let _ = ack.send(&json!({ "success": true, "autosave": autosave }));
172-
}

anycode-backend/src/handlers/watch_handler.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use std::collections::HashMap;
44
use std::path::{Path, PathBuf};
55
use std::sync::Arc;
66
use std::time::Duration;
7-
use tokio::sync::{Mutex, watch, mpsc};
7+
use tokio::sync::{Mutex, watch};
88
use tokio_util::sync::CancellationToken;
99
use tracing::info;
1010

1111
use crate::app_state::SocketData;
1212
use crate::code::Code;
1313
use crate::diff::compute_text_edits;
14+
use crate::git::GitManager;
1415
use crate::handlers::io_handler::apply_edits_to_code;
1516
use crate::lsp::LspManager;
1617
use crate::search::search_file_result;
@@ -92,7 +93,7 @@ pub async fn handle_watch_event(
9293
file2code: &Arc<Mutex<HashMap<String, Code>>>,
9394
socket2data: &Arc<Mutex<HashMap<String, SocketData>>>,
9495
file_states: &Arc<Mutex<HashMap<String, FileWatchState>>>,
95-
git_update_tx: &mpsc::Sender<PathBuf>,
96+
git_manager: &Arc<Mutex<GitManager>>,
9697
lsp_manager: &Arc<Mutex<LspManager>>,
9798
) {
9899
let normalized_path = normalize_watch_path(path);
@@ -127,7 +128,7 @@ pub async fn handle_watch_event(
127128
let file2code = file2code.clone();
128129
let socket2data = socket2data.clone();
129130
let file_states = file_states.clone();
130-
let git_update_tx = git_update_tx.clone();
131+
let git_manager = git_manager.clone();
131132
let lsp_manager = lsp_manager.clone();
132133
let path_str_key = path_str.clone();
133134
let event_kind = event.kind.clone();
@@ -155,7 +156,7 @@ pub async fn handle_watch_event(
155156
.await;
156157

157158
handle_search_update(&path, &socket, &socket2data).await;
158-
let _ = git_update_tx.send(path.clone()).await;
159+
handle_changes_update(&path, &socket, &git_manager).await;
159160

160161
let mut states = file_states.lock().await;
161162
if let Some(state) = states.get_mut(&path_str_key) {
@@ -271,6 +272,24 @@ async fn handle_search_update(
271272
}
272273
}
273274

275+
async fn handle_changes_update(
276+
path: &Path,
277+
socket: &Arc<socketioxide::SocketIo>,
278+
git_manager: &Arc<Mutex<crate::git::GitManager>>
279+
) {
280+
let update = {
281+
let mut git = git_manager.lock().await;
282+
if git.should_ignore(path) {
283+
return;
284+
}
285+
git.check_status_changed_for_paths(&[path.to_path_buf()])
286+
};
287+
288+
if let Some(update) = update {
289+
let _ = socket.emit("changes:update", &update.to_json()).await;
290+
}
291+
}
292+
274293

275294

276295
async fn handle_file_modification(

anycode-backend/src/main.rs

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ async fn on_connect(socket: SocketRef, _state: State<AppState>) {
9393

9494
socket.on("theme:list", handle_theme_list);
9595
socket.on("theme:get", handle_theme_get);
96-
socket.on("config:get", handle_config_get);
97-
9896
socket.on_disconnect(on_disconnect)
9997
}
10098

@@ -192,7 +190,7 @@ fn build_app_state() -> (
192190
let file2code = Arc::new(Mutex::new(HashMap::new()));
193191
let socket2data = Arc::new(Mutex::new(HashMap::new()));
194192
let terminals = Arc::new(Mutex::new(HashMap::new()));
195-
let config = Arc::new(Mutex::new(config));
193+
let config = Arc::new(config);
196194

197195
let state = AppState {
198196
config,
@@ -261,7 +259,7 @@ async fn main() -> Result<()> {
261259
// Prepare ACP filesystem task dependencies (spawned after SocketIo creation)
262260
let acp_fs_file2code = state.file2code.clone();
263261
let acp_fs_lsp = state.lsp_manager.clone();
264-
let acp_fs_config = state.config.lock().await.clone();
262+
let acp_fs_config = state.config.as_ref().clone();
265263

266264
let (layer, io) = SocketIo::builder().with_state(state.clone()).build_layer();
267265
let cors = ServiceBuilder::new()
@@ -303,73 +301,8 @@ async fn main() -> Result<()> {
303301
let dir = std::path::Path::new(".");
304302
watcher.watch(dir, RecursiveMode::Recursive)?;
305303

306-
// Create a channel for batching/debouncing git status updates
307-
let (git_update_tx, mut git_update_rx) = mpsc::channel::<std::path::PathBuf>(1000);
308-
309-
// Spawn a task to process git status updates in a debounced, batched manner
310-
let git_manager_clone = git_manager.clone();
311-
let socket_clone = io.clone();
312-
tokio::spawn(async move {
313-
let mut paths_to_update = HashSet::new();
314-
315-
loop {
316-
// Wait for the first path to arrive
317-
let first_path = match git_update_rx.recv().await {
318-
Some(path) => path,
319-
None => break, // Channel closed
320-
};
321-
paths_to_update.insert(first_path);
322-
323-
// Debounce window: wait for 150ms of inactivity, or up to a max timeout (500ms) to prevent starvation
324-
let start_time = std::time::Instant::now();
325-
let max_wait = std::time::Duration::from_millis(500);
326-
327-
loop {
328-
let elapsed = start_time.elapsed();
329-
if elapsed >= max_wait {
330-
break;
331-
}
332-
let remaining = max_wait - elapsed;
333-
let step_timeout = std::time::Duration::from_millis(50).min(remaining);
334-
335-
match tokio::time::timeout(step_timeout, git_update_rx.recv()).await {
336-
Ok(Some(path)) => {
337-
paths_to_update.insert(path);
338-
}
339-
Ok(None) => {
340-
// Channel closed
341-
break;
342-
}
343-
Err(_) => {
344-
// Timeout hit: no events for step_timeout, flush the batch
345-
break;
346-
}
347-
}
348-
}
349-
350-
// Process batched paths under a single lock on GitManager
351-
if !paths_to_update.is_empty() {
352-
let paths: Vec<std::path::PathBuf> = paths_to_update.drain().collect();
353-
let mut git = git_manager_clone.lock().await;
354-
355-
// Filter out paths that should be ignored under the lock
356-
let paths_to_check: Vec<std::path::PathBuf> = paths
357-
.into_iter()
358-
.filter(|path| !git.should_ignore(path))
359-
.collect();
360-
361-
if !paths_to_check.is_empty() {
362-
if let Some(update) = git.check_status_changed_for_paths(&paths_to_check) {
363-
let _ = socket_clone.emit("changes:update", &update.to_json()).await;
364-
}
365-
}
366-
}
367-
}
368-
});
369-
370304
let file_states = Arc::new(Mutex::new(HashMap::new()));
371305
let socket = io.clone();
372-
let git_update_tx_clone = git_update_tx.clone();
373306
tokio::spawn(async move {
374307
while let Some(res) = watch_rx.recv().await {
375308
match res {
@@ -385,7 +318,7 @@ async fn main() -> Result<()> {
385318
&file2code,
386319
&socket2data,
387320
&file_states,
388-
&git_update_tx_clone,
321+
&git_manager,
389322
&lsp_manager,
390323
)
391324
.await

anycode/hooks/useSettings.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,3 @@
1-
import { useEffect } from 'react';
2-
import type { Socket } from 'socket.io-client';
3-
4-
type UseSettingsParams = {
5-
wsRef: React.RefObject<Socket | null>;
6-
isConnected: boolean;
7-
};
8-
9-
export const useSettings = ({ wsRef, isConnected }: UseSettingsParams) => {
10-
useEffect(() => {
11-
if (!isConnected || !wsRef.current) {
12-
return;
13-
}
14-
15-
wsRef.current.emit('config:get', (res: any) => {
16-
if (res && res.success) {
17-
// Config loaded
18-
}
19-
});
20-
}, [isConnected, wsRef]);
21-
1+
export const useSettings = () => {
222
return {};
233
};
24-

0 commit comments

Comments
 (0)