perf(nvim-core): implement zero-copy BytesMut stream buffer and DashMap for lock contention

This commit is contained in:
Riz Ashraf committed 2026-09-22 22:26:00 +01:00
1 parent 37003be620
commit 61b03bc6e3
2 files changed
+21 -38

No files matched your search

+2
View File
@@ -14,4 +14,6 @@ tracing-subscriber = "0.3.23"
dirs = "7.0.0" dirs = "7.0.0"
rmcp = { version = "3.4.0", features = ["server"] } rmcp = { version = "3.4.0", features = ["server"] }
mcp-stdio = { version = "0.1.0", path = "../mcp-stdio" } mcp-stdio = { version = "0.1.0", path = "../mcp-stdio" }
bytes = "1.12.1"
dashmap = "6.2.1"
+19 -38
View File
@@ -114,7 +114,6 @@ async fn get_socket_path() -> Result<String, String> {
} }
Err("Could not find Neovim socket".to_string()) Err("Could not find Neovim socket".to_string())
} }
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::LazyLock; use std::sync::LazyLock;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
@@ -162,8 +161,8 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
let (mut read_half, mut write_half) = tokio::io::split(stream); let (mut read_half, mut write_half) = tokio::io::split(stream);
let (tx, mut rx) = mpsc::channel::<NvimRequest>(32); let (tx, mut rx) = mpsc::channel::<NvimRequest>(32);
type PendingRequestsMap = type PendingRequestsMap =
Arc<std::sync::Mutex<HashMap<u64, oneshot::Sender<Result<rmpv::Value, String>>>>>; Arc<dashmap::DashMap<u64, oneshot::Sender<Result<rmpv::Value, String>>>>;
let pending_requests: PendingRequestsMap = Arc::new(std::sync::Mutex::new(HashMap::new())); let pending_requests: PendingRequestsMap = Arc::new(dashmap::DashMap::new());
// Write task // Write task
let pending_clone = Arc::clone(&pending_requests); let pending_clone = Arc::clone(&pending_requests);
@@ -175,10 +174,7 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
continue; continue;
} }
pending_clone pending_clone.insert(req.msgid, req.reply);
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(req.msgid, req.reply);
if write_half.write_all(&buf).await.is_err() { if write_half.write_all(&buf).await.is_err() {
tracing::error!("Failed to write to Neovim socket"); tracing::error!("Failed to write to Neovim socket");
@@ -190,15 +186,15 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
// Read task // Read task
let pending_clone2 = Arc::clone(&pending_requests); let pending_clone2 = Arc::clone(&pending_requests);
tokio::spawn(async move { tokio::spawn(async move {
let mut resp_buf = Vec::new(); use bytes::{Buf, BytesMut};
let mut chunk = vec![0u8; 65536]; let mut resp_buf = BytesMut::with_capacity(65536);
let mut offset = 0;
loop { loop {
let mut cursor = std::io::Cursor::new(&resp_buf[offset..]); let mut cursor = std::io::Cursor::new(&resp_buf[..]);
match rmpv::decode::read_value(&mut cursor) { match rmpv::decode::read_value(&mut cursor) {
Ok(val) => { Ok(val) => {
offset += cursor.position() as usize; let parsed_len = cursor.position() as usize;
resp_buf.advance(parsed_len);
if let rmpv::Value::Array(ref arr) = val if let rmpv::Value::Array(ref arr) = val
&& arr.len() >= 4 && arr.len() >= 4
@@ -209,21 +205,10 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
_ => 0, _ => 0,
}; };
if let Some(reply_sender) = pending_clone2 if let Some((_, reply_sender)) = pending_clone2.remove(&msgid) {
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&msgid)
{
let _ = reply_sender.send(Ok(val)); let _ = reply_sender.send(Ok(val));
} }
} }
if offset == resp_buf.len() {
resp_buf.clear();
offset = 0;
} else if offset > 1024 * 1024 {
resp_buf.drain(..offset);
offset = 0;
}
continue; continue;
} }
Err(e) Err(e)
@@ -237,16 +222,13 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
_ => false, _ => false,
} => } =>
{ {
resp_buf.drain(..offset); match tokio::time::timeout(
offset = 0; tokio::time::Duration::from_secs(60),
read_half.read_buf(&mut resp_buf),
let read_future = read_half.read(&mut chunk); )
match tokio::time::timeout(tokio::time::Duration::from_secs(60), read_future)
.await .await
{ {
Ok(Ok(n)) if n > 0 => { Ok(Ok(n)) if n > 0 => {}
resp_buf.extend_from_slice(&chunk[..n]);
}
_ => { _ => {
tracing::error!("Neovim socket read loop closed or timeout"); tracing::error!("Neovim socket read loop closed or timeout");
break; break;
@@ -261,10 +243,12 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
} }
// Cleanup pending requests on disconnect // Cleanup pending requests on disconnect
let mut pending = pending_clone2.lock().unwrap_or_else(|e| e.into_inner()); let keys: Vec<_> = pending_clone2.iter().map(|kv| *kv.key()).collect();
for (_, sender) in pending.drain() { for k in keys {
if let Some((_, sender)) = pending_clone2.remove(&k) {
let _ = sender.send(Err("Connection closed".to_string())); let _ = sender.send(Err("Connection closed".to_string()));
} }
}
}); });
// Cleanup task for timed-out requests // Cleanup task for timed-out requests
@@ -276,10 +260,7 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
if Arc::strong_count(&pending_clone3) <= 1 { if Arc::strong_count(&pending_clone3) <= 1 {
break; // Socket closed and other tasks finished, no need to keep cleaning up break; // Socket closed and other tasks finished, no need to keep cleaning up
} }
pending_clone3 pending_clone3.retain(|_, sender| !sender.is_closed());
.lock()
.unwrap_or_else(|e| e.into_inner())
.retain(|_, sender| !sender.is_closed());
} }
}); });