perf: eliminate loop string allocations and proxy mem spikes
This commit is contained in:
1 parent
d5a05f8416
commit
dc59aa9238
5 files changed
+30
-24
No files matched your search
@@ -6,8 +6,9 @@ pub async fn read_mcp_message<R: tokio::io::AsyncRead + Unpin>(
|
|||||||
stdin: &mut BufReader<R>,
|
stdin: &mut BufReader<R>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
loop {
|
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
|
loop {
|
||||||
|
line.clear();
|
||||||
if stdin.read_line(&mut line).await.unwrap_or(0) == 0 {
|
if stdin.read_line(&mut line).await.unwrap_or(0) == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ pub async fn send_response(response: JsonRpcResponse) {
|
|||||||
// The MCP StdioTransport MUST use Newline-Delimited JSON (NDJSON).
|
// The MCP StdioTransport MUST use Newline-Delimited JSON (NDJSON).
|
||||||
// Do NOT use LSP-style HTTP headers (e.g. Content-Length).
|
// Do NOT use LSP-style HTTP headers (e.g. Content-Length).
|
||||||
// See MCP protocol spec (SEP-2575) and mcp-go-sdk bufio.Scanner implementation.
|
// See MCP protocol spec (SEP-2575) and mcp-go-sdk bufio.Scanner implementation.
|
||||||
let payload = format!("{}\n", msg);
|
|
||||||
let mut stdout = tokio::io::stdout();
|
let mut stdout = tokio::io::stdout();
|
||||||
let _ = stdout.write_all(payload.as_bytes()).await;
|
let _ = stdout.write_all(msg.as_bytes()).await;
|
||||||
|
let _ = stdout.write_all(b"\n").await;
|
||||||
let _ = stdout.flush().await;
|
let _ = stdout.flush().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-15
@@ -2,6 +2,11 @@ use crate::models::*;
|
|||||||
use crate::state::MemoryState;
|
use crate::state::MemoryState;
|
||||||
use crate::tools::*;
|
use crate::tools::*;
|
||||||
|
|
||||||
|
fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
|
||||||
|
if needle.is_empty() { return true; }
|
||||||
|
haystack.as_bytes().windows(needle.len()).any(|w| w.eq_ignore_ascii_case(needle.as_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! parse_tool {
|
macro_rules! parse_tool {
|
||||||
($args:expr, $id:expr, $type:ty) => {
|
($args:expr, $id:expr, $type:ty) => {
|
||||||
match parse_args::<$type>($args) {
|
match parse_args::<$type>($args) {
|
||||||
@@ -605,8 +610,8 @@ impl MemoryHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if query.is_empty()
|
if query.is_empty()
|
||||||
|| name.to_lowercase().contains(&query)
|
|| contains_ignore_ascii_case(&name, &query)
|
||||||
|| e.entity_type.to_lowercase().contains(&query)
|
|| contains_ignore_ascii_case(&e.entity_type, &query)
|
||||||
{
|
{
|
||||||
included.insert(name.clone());
|
included.insert(name.clone());
|
||||||
}
|
}
|
||||||
@@ -1014,9 +1019,9 @@ impl MemoryHandler {
|
|||||||
let snippets = self.state.snippets.read();
|
let snippets = self.state.snippets.read();
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for s in snippets {
|
for s in snippets {
|
||||||
if s.name.to_lowercase().contains(&query)
|
if contains_ignore_ascii_case(&s.name, &query)
|
||||||
|| s.description.to_lowercase().contains(&query)
|
|| contains_ignore_ascii_case(&s.description, &query)
|
||||||
|| s.language.to_lowercase().contains(&query)
|
|| contains_ignore_ascii_case(&s.language, &query)
|
||||||
{
|
{
|
||||||
results.push(s);
|
results.push(s);
|
||||||
}
|
}
|
||||||
@@ -1073,9 +1078,9 @@ impl MemoryHandler {
|
|||||||
if let Some(q) = req.query {
|
if let Some(q) = req.query {
|
||||||
let q = q.to_lowercase();
|
let q = q.to_lowercase();
|
||||||
adrs.retain(|a| {
|
adrs.retain(|a| {
|
||||||
a.title.to_lowercase().contains(&q)
|
contains_ignore_ascii_case(&a.title, &q)
|
||||||
|| a.context.to_lowercase().contains(&q)
|
|| contains_ignore_ascii_case(&a.context, &q)
|
||||||
|| a.decision.to_lowercase().contains(&q)
|
|| contains_ignore_ascii_case(&a.decision, &q)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let data = serde_json::to_string(&adrs).unwrap_or_default();
|
let data = serde_json::to_string(&adrs).unwrap_or_default();
|
||||||
@@ -1165,8 +1170,8 @@ impl MemoryHandler {
|
|||||||
let q = req.query.to_lowercase();
|
let q = req.query.to_lowercase();
|
||||||
let mut fixes = self.state.error_fixes.read();
|
let mut fixes = self.state.error_fixes.read();
|
||||||
fixes.retain(|f| {
|
fixes.retain(|f| {
|
||||||
f.signature.to_lowercase().contains(&q)
|
contains_ignore_ascii_case(&f.signature, &q)
|
||||||
|| f.solution.to_lowercase().contains(&q)
|
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||||
});
|
});
|
||||||
let data = serde_json::to_string(&fixes).unwrap_or_default();
|
let data = serde_json::to_string(&fixes).unwrap_or_default();
|
||||||
Ok(data.to_string())
|
Ok(data.to_string())
|
||||||
@@ -1562,8 +1567,8 @@ impl MemoryHandler {
|
|||||||
.filter(|d| {
|
.filter(|d| {
|
||||||
(req.namespace.is_none()
|
(req.namespace.is_none()
|
||||||
|| d.namespace == *req.namespace.as_ref().unwrap())
|
|| d.namespace == *req.namespace.as_ref().unwrap())
|
||||||
&& (d.description.to_lowercase().contains(&q)
|
&& (contains_ignore_ascii_case(&d.description, &q)
|
||||||
|| d.ideal_solution.to_lowercase().contains(&q))
|
|| contains_ignore_ascii_case(&d.ideal_solution, &q))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let memos: Vec<_> = self
|
let memos: Vec<_> = self
|
||||||
@@ -1574,7 +1579,7 @@ impl MemoryHandler {
|
|||||||
.filter(|m| {
|
.filter(|m| {
|
||||||
(req.namespace.is_none()
|
(req.namespace.is_none()
|
||||||
|| m.namespace == *req.namespace.as_ref().unwrap())
|
|| m.namespace == *req.namespace.as_ref().unwrap())
|
||||||
&& m.content.to_lowercase().contains(&q)
|
&& contains_ignore_ascii_case(&m.content, &q)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let error_fixes: Vec<_> = self
|
let error_fixes: Vec<_> = self
|
||||||
@@ -1583,8 +1588,8 @@ impl MemoryHandler {
|
|||||||
.read()
|
.read()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|f| {
|
.filter(|f| {
|
||||||
f.signature.to_lowercase().contains(&q)
|
contains_ignore_ascii_case(&f.signature, &q)
|
||||||
|| f.solution.to_lowercase().contains(&q)
|
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ mod tests {
|
|||||||
namespace: "global".to_string(),
|
namespace: "global".to_string(),
|
||||||
git_branch: None,
|
git_branch: None,
|
||||||
};
|
};
|
||||||
index.index_entity(&entity).await.unwrap();
|
let _ = index.index_entity(&entity).await.unwrap();
|
||||||
|
|
||||||
let task = Task {
|
let task = Task {
|
||||||
id: "task-1".to_string(),
|
id: "task-1".to_string(),
|
||||||
@@ -302,7 +302,7 @@ mod tests {
|
|||||||
dependencies: vec![],
|
dependencies: vec![],
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
};
|
};
|
||||||
index.index_task(&task).await.unwrap();
|
let _ = index.index_task(&task).await.unwrap();
|
||||||
|
|
||||||
let snippet = Snippet {
|
let snippet = Snippet {
|
||||||
name: "test_snippet".to_string(),
|
name: "test_snippet".to_string(),
|
||||||
@@ -311,7 +311,7 @@ mod tests {
|
|||||||
description: "A test snippet".to_string(),
|
description: "A test snippet".to_string(),
|
||||||
updated_at: 0,
|
updated_at: 0,
|
||||||
};
|
};
|
||||||
index.index_snippet(&snippet).await.unwrap();
|
let _ = index.index_snippet(&snippet).await.unwrap();
|
||||||
|
|
||||||
let adr = Adr {
|
let adr = Adr {
|
||||||
id: "adr-1".to_string(),
|
id: "adr-1".to_string(),
|
||||||
@@ -321,7 +321,7 @@ mod tests {
|
|||||||
consequence: "Test consequence".to_string(),
|
consequence: "Test consequence".to_string(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
};
|
};
|
||||||
index.index_adr(&adr).await.unwrap();
|
let _ = index.index_adr(&adr).await.unwrap();
|
||||||
|
|
||||||
index.commit().await.unwrap();
|
index.commit().await.unwrap();
|
||||||
index.reader.reload().unwrap();
|
index.reader.reload().unwrap();
|
||||||
|
|||||||
+2
-2
@@ -96,10 +96,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
if let tokio_tungstenite::tungstenite::Message::Text(text) = msg {
|
if let tokio_tungstenite::tungstenite::Message::Text(text) = msg {
|
||||||
let log_prefix = logger::extract_log_prefix(&text, true);
|
let log_prefix = logger::extract_log_prefix(&text, true);
|
||||||
tracing::info!("<<< [Stub] Received {} from server (length: {}): {}", log_prefix, text.len(), if text.len() > 1000 { format!("{}...", &text[..1000]) } else { text.clone() });
|
tracing::info!("<<< [Stub] Received {} from server (length: {}): {}", log_prefix, text.len(), if text.len() > 1000 { format!("{}...", &text[..1000]) } else { text.clone() });
|
||||||
let payload = format!("{}\n", text);
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
let mut stdout = tokio::io::stdout();
|
let mut stdout = tokio::io::stdout();
|
||||||
let _ = stdout.write_all(payload.as_bytes()).await;
|
let _ = stdout.write_all(text.as_bytes()).await;
|
||||||
|
let _ = stdout.write_all(b"\n").await;
|
||||||
let _ = stdout.flush().await;
|
let _ = stdout.flush().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user