Skip to content

Commit 30b8f4a

Browse files
committed
Performance optimization by use Bytes
Merge pull request #1 from richardhapb/to-bytes
2 parents e1fc70a + 5058c76 commit 30b8f4a

File tree

7 files changed

+192
-173
lines changed

7 files changed

+192
-173
lines changed

Cargo.lock

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[package]
22
name = "lspdock"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
edition = "2024"
55

66
[dependencies]
77
dirs = "6.0.0"
88
futures-core = "0.3.31"
9+
memchr = "2.7.6"
910
serde = { version = "1.0.219", features = ["derive"] }
1011
serde_json = "1.0.140"
1112
sysinfo = "0.36.1"

src/lsp/binding.rs

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use crate::{config::ProxyConfig, proxy::Pair};
2+
use memchr::memmem::{find, find_iter};
23
use serde_json::{Value, json};
34
use std::{path::PathBuf, process::Stdio, str, sync::Arc};
45
use tokio::{
56
fs::{File, create_dir_all},
67
io::AsyncWriteExt,
78
process::Command,
89
};
10+
use tokio_util::bytes::Bytes;
911
use tracing::{debug, error, trace};
1012

1113
use std::collections::HashMap;
@@ -14,27 +16,41 @@ use tokio::sync::RwLock;
1416
/// Redirect the paths from the sender pair to the receiver pair; this is used
1517
/// for matching the paths between the container and the host path.
1618
pub fn redirect_uri(
17-
raw_str: &mut String,
19+
raw_bytes: &mut Bytes,
1820
from: &Pair,
1921
config: &ProxyConfig,
2022
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
21-
let from_path_str: &str;
22-
let to_path_str: &str;
23+
let from_path: &[u8];
24+
let to_path: &[u8];
2325

2426
match from {
2527
Pair::Client => {
26-
from_path_str = &config.local_path;
27-
to_path_str = &config.docker_internal_path;
28+
from_path = &config.local_path.as_bytes();
29+
to_path = &config.docker_internal_path.as_bytes();
2830
}
2931
Pair::Server => {
30-
from_path_str = &config.docker_internal_path;
31-
to_path_str = &config.local_path;
32+
from_path = &config.docker_internal_path.as_bytes();
33+
to_path = &config.local_path.as_bytes();
3234
}
3335
}
3436

35-
trace!(%from_path_str, %to_path_str);
37+
trace!(from=?String::from_utf8(from_path.to_vec()), to=?String::from_utf8(to_path.to_vec()));
3638

37-
*raw_str = raw_str.replace(from_path_str, to_path_str);
39+
let occurrences = find_iter(&raw_bytes, from_path);
40+
let from_n = from_path.len();
41+
let mut new_bytes: Bytes = Bytes::new();
42+
let mut last = 0;
43+
44+
for occurr in occurrences {
45+
let before = &raw_bytes[last..occurr];
46+
last = occurr + from_n;
47+
// add the new text and join
48+
new_bytes = Bytes::from([&new_bytes, before, to_path].concat());
49+
}
50+
let after = &raw_bytes[last..];
51+
new_bytes = Bytes::from([&new_bytes, after].concat());
52+
53+
*raw_bytes = new_bytes;
3854

3955
Ok(())
4056
}
@@ -98,94 +114,101 @@ impl RequestTracker {
98114
self.map.write().await.insert(id, method.to_string());
99115
}
100116

101-
async fn take_if_match(&self, id: u64, expected: &str) -> bool {
117+
async fn take_if_match(&self, id: u64) -> bool {
102118
let mut map = self.map.write().await;
103-
let exists = map.get(&id).map(|m| m == expected).unwrap_or(false);
104-
if exists {
119+
if map.get(&id).is_some() {
105120
map.remove(&id);
121+
return true;
106122
}
107-
exists
123+
false
108124
}
109125

110126
pub async fn check_for_methods(
111127
&self,
112128
methods: &[&str],
113-
raw_str: &mut String,
129+
raw_bytes: &mut Bytes,
114130
pair: &Pair,
115131
) -> std::io::Result<()> {
116132
// If the LSP is not in a container, there is no need to track this.
117133
if !self.config.use_docker {
118134
return Ok(());
119135
}
120136

121-
//textDocument/declaration
122-
123137
match pair {
124138
Pair::Server => {
125-
let mut v: Value = serde_json::from_str(&raw_str)?;
139+
// Early return
140+
if self.map.read().await.is_empty() {
141+
trace!("Nothing expecting response, skipping method");
142+
return Ok(());
143+
}
144+
145+
let mut v: Value = serde_json::from_slice(raw_bytes.as_ref())?;
126146
trace!(server_response=%v, "received");
127147

128148
// Check if this is a response to a tracked request
129149
if let Some(id) = v.get("id").and_then(Value::as_u64) {
130-
for method in methods {
131-
debug!("Checking for {method} method");
132-
133-
let matches = self.take_if_match(id, *method).await;
134-
debug!(%matches);
135-
if matches {
136-
trace!(%id, "matches");
137-
if let Some(results) = v.get_mut("result").and_then(Value::as_array_mut)
138-
{
139-
trace!(?results);
140-
for result in results {
141-
if let Some(uri_val) =
142-
result.get("uri").and_then(|u| u.as_str())
143-
{
144-
if !(uri_val.contains(&self.config.local_path)) {
145-
debug!(%uri_val);
146-
let new_uri =
147-
self.bind_library(uri_val.to_string()).await?;
148-
debug!("file://{}", new_uri);
149-
150-
Self::modify_uri(result, &new_uri);
151-
}
150+
let matches = self.take_if_match(id).await;
151+
debug!(%matches);
152+
if matches {
153+
trace!(%id, "matches");
154+
if let Some(results) = v.get_mut("result").and_then(Value::as_array_mut) {
155+
trace!(?results);
156+
for result in results {
157+
if let Some(uri_val) = result.get("uri").and_then(|u| u.as_str()) {
158+
if !(uri_val.contains(&self.config.local_path)) {
159+
debug!(%uri_val);
160+
let new_uri = self.bind_library(uri_val).await?;
161+
debug!("file://{}", new_uri);
162+
163+
Self::modify_uri(result, &new_uri);
152164
}
153165
}
154-
*raw_str = v.to_string(); // write back the modified JSON
155-
} else {
156-
trace!("result content not found");
157166
}
167+
168+
*raw_bytes = Bytes::from(serde_json::to_vec(&v)?);
169+
} else {
170+
trace!("result content not found");
158171
}
159172
}
160173
}
161174
}
162175

163176
Pair::Client => {
164-
let v: Value = serde_json::from_str(&raw_str)?;
177+
// Early check to avoid parsing
178+
let mut method_found = "";
179+
for method in methods {
180+
debug!("Checking for {method} method");
181+
let expected = &[b"\"method\":\"", method.as_bytes(), b"\""].concat();
182+
if find(raw_bytes, expected).is_some() {
183+
method_found = method;
184+
break;
185+
}
186+
}
187+
188+
if method_found.is_empty() {
189+
debug!("Any method that required redirection was not found, skipping patch");
190+
return Ok(());
191+
}
192+
193+
debug!(%method_found);
194+
195+
let v: Value = serde_json::from_slice(raw_bytes.as_ref())?;
165196
trace!(client_request=%v, "received");
166197

167198
debug!("Checking for id");
168199
if let Some(id) = v.get("id").and_then(Value::as_u64) {
169200
debug!(%id);
170-
171-
if let Some(req_method) = v.get("method").and_then(Value::as_str) {
172-
trace!(%req_method);
173-
// Only track expected methods if URI matches
174-
for method in methods {
175-
if req_method == *method {
176-
debug!(%id, "Storing");
177-
self.track(id, *method).await;
178-
}
179-
}
180-
}
201+
// Only track expected methods if URI matches
202+
self.track(id, method_found).await;
203+
debug!(%id, "Storing");
181204
}
182205
}
183206
}
184207

185208
Ok(())
186209
}
187210

188-
async fn bind_library(&self, uri: String) -> std::io::Result<String> {
211+
async fn bind_library(&self, uri: &str) -> std::io::Result<String> {
189212
let temp_dir = std::env::temp_dir().join("lspdock");
190213
trace!(temp_dir=%temp_dir.to_string_lossy());
191214

@@ -200,7 +223,7 @@ impl RequestTracker {
200223
} else {
201224
let relative_path = safe_path.strip_prefix("/").unwrap_or(&safe_path);
202225
trace!(%relative_path);
203-
let tmp_file_path = relative_path.to_string();
226+
let tmp_file_path = relative_path;
204227
temp_dir.join(tmp_file_path)
205228
};
206229

@@ -216,7 +239,7 @@ impl RequestTracker {
216239
let temp_uri_path = PathBuf::from(&temp_uri);
217240
debug!(%temp_uri);
218241
if !temp_uri_path.exists() {
219-
self.copy_file(safe_path.to_string(), &temp_uri).await?;
242+
self.copy_file(&safe_path, &temp_uri).await?;
220243
} else {
221244
debug!("File already exists, skipping copy. {}", temp_uri);
222245
}
@@ -225,7 +248,7 @@ impl RequestTracker {
225248
}
226249

227250
/// Copies a file from either the local filesystem or a Docker container.
228-
async fn copy_file(&self, path: String, destination: &str) -> std::io::Result<()> {
251+
async fn copy_file(&self, path: &str, destination: &str) -> std::io::Result<()> {
229252
// Only copy the file if the LSP is in a container
230253
debug!("Starting file copy from {} to {}", path, destination);
231254
let cmd = Command::new("docker")

0 commit comments

Comments
 (0)