diff --git a/Cargo.lock b/Cargo.lock index b4896f4c5219e0e1dc878e1619b8c705799d1fbd..916b872d69e76025efe70c103ce2ac8181840ad4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,6 +372,56 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -852,6 +902,7 @@ dependencies = [ "async-lsp", "async-process", "clap", + "crossbeam", "futures", "rmcp", "serde", diff --git a/Cargo.toml b/Cargo.toml index ff5193e292c1f4053ccece4b5633157abd5b63c3..e188d3c73f4c6bf569514d56e25846d9d112e0dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ async-gen = "0.2.3" async-lsp = "0.2.2" async-process = "2.3" clap = { version = "4.5.48", features = ["derive"] } +crossbeam = "0.8" futures = "0.3.31" rmcp = { version = "0.8.0", features = ["server", "transport-io"] } serde = { version = "1.0", features = ["derive"] } diff --git a/src/buf_pool.rs b/src/buf_pool.rs new file mode 100644 index 0000000000000000000000000000000000000000..6c9b5308d05cb132a63bf4a80a6f785062d5afaf --- /dev/null +++ b/src/buf_pool.rs @@ -0,0 +1,74 @@ +use crossbeam::queue::SegQueue; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +/// A thread-safe, lock-free pool of reusable byte buffers. +#[derive(Clone)] +pub struct BufPool { + queue: Arc>>, +} + +impl BufPool { + /// Creates a pool pre-populated with `capacity` empty buffers. + pub fn with_capacity(capacity: usize) -> Self { + let queue = Arc::new(SegQueue::new()); + for _ in 0..capacity { + queue.push(Vec::new()); + } + Self { queue } + } + + /// Checks out a buffer from the pool. If the pool is empty, allocates a new buffer. + pub fn checkout(&self) -> BufGuard { + let buf = self.queue.pop().unwrap_or_else(|| Vec::new()); + BufGuard { + buf, + pool: self.queue.clone(), + } + } +} + +/// RAII guard that automatically returns the buffer to the pool when dropped. +pub struct BufGuard { + buf: Vec, + pool: Arc>>, +} + +impl BufGuard { + /// Extracts the buffer, preventing automatic return to the pool. + /// Useful if you need to move the buffer elsewhere. + pub fn into_inner(mut self) -> Vec { + let buf = std::mem::take(&mut self.buf); + // SAFETY: + // 1) We forget `self`, preventing it from + // double-free'ing `pool` + // 2) The 'pointer' is really a &mut, so + // it is aligned, type-valid, and valid for reads. + let pool = unsafe { std::ptr::read(&self.pool) }; + std::mem::forget(self); + drop(pool); + buf + } +} + +impl Drop for BufGuard { + fn drop(&mut self) { + let mut buf = std::mem::take(&mut self.buf); + buf.clear(); + self.pool.push(buf); + } +} + +impl Deref for BufGuard { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.buf + } +} + +impl DerefMut for BufGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf + } +} diff --git a/src/main.rs b/src/main.rs index 3e54ac612d12824caf4ca8cb4e92c991c17373c6..e7563ba8e2881af126ebebfd96f67a2bd8519aad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod buf_pool; mod config; mod lsp; mod mcp; diff --git a/src/mcp/server.rs b/src/mcp/server.rs index f37be84e3a8311522c3af25c92ba30ab0c7c17e6..3793c818f8dfa8f48a7f499ca0948a534653b3ec 100644 --- a/src/mcp/server.rs +++ b/src/mcp/server.rs @@ -11,6 +11,7 @@ use rmcp::{tool_handler, tool_router}; use async_lsp::ServerSocket as LSPServerSocket; +use crate::buf_pool::BufPool; use crate::config::Config; use crate::lsp::LSPClient; pub use crate::mcp::tools::read::*; @@ -19,6 +20,7 @@ pub struct MCPServer { pub(crate) tool_router: ToolRouter, pub(crate) config: Arc, pub(crate) lsp_server: LSPServerSocket, + pub(crate) buf_pool: BufPool, } pub async fn setup(config: Arc) -> anyhow::Result<(MCPServer, LSPClient)> { @@ -35,6 +37,7 @@ impl MCPServer { config, lsp_server, tool_router: Self::tool_router(), + buf_pool: BufPool::with_capacity(8), } } diff --git a/src/mcp/tools/read.rs b/src/mcp/tools/read.rs index 5acc33a9f4d4e8cee4d8f09ba066af1d110a2c96..25753bc0a7def4658dbbf58ac1caec10175614b1 100644 --- a/src/mcp/tools/read.rs +++ b/src/mcp/tools/read.rs @@ -29,6 +29,15 @@ pub async fn call( server: &MCPServer, Parameters(args): Parameters, ) -> Result { + let file_path = server + .config + .path_in_project(&args.path) + .await + .map_err(|err| MCPError::invalid_request(format!("{:?}", err), None))? + .ok_or_else(|| { + MCPError::invalid_params(format!("Path not in project: {:?}", &args.path), None) + })?; + Err(MCPError::internal_error("Not yet implemented", None)) // let content = tokio::fs::read_to_string(&file_path) // .await