Skip to content

Commit b96d81c

Browse files
committed
perf(pm): parse version manifests from vec buffers
1 parent 14d38c9 commit b96d81c

1 file changed

Lines changed: 75 additions & 11 deletions

File tree

crates/ruborist/src/service/manifest.rs

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@ use super::fetch::{
1414
use super::http::get_client;
1515
use crate::model::manifest::{CoreVersionManifest, FullManifest};
1616

17-
/// Parse JSON bytes on rayon's CPU thread pool (native) or inline
18-
/// (wasm32). Keeps the tokio runtime free of `simd_json` work so other
19-
/// in-flight manifest fetches keep driving network IO while this one
20-
/// parses.
21-
pub(crate) async fn parse_json_off_runtime<T>(bytes: Bytes) -> Result<T, anyhow::Error>
17+
/// Parse a JSON buffer on rayon's CPU thread pool (native) or inline
18+
/// (wasm32). The buffer is consumed because `simd_json` mutates it in-place.
19+
/// Keeps the tokio runtime free of `simd_json` work so other in-flight
20+
/// manifest fetches keep driving network IO while this one parses.
21+
pub(crate) async fn parse_json_vec_off_runtime<T>(
22+
mut parse_buf: Vec<u8>,
23+
) -> Result<T, anyhow::Error>
2224
where
2325
T: serde::de::DeserializeOwned + Send + 'static,
2426
{
2527
#[cfg(not(target_arch = "wasm32"))]
2628
{
2729
let (tx, rx) = tokio::sync::oneshot::channel();
2830
rayon::spawn(move || {
29-
let mut parse_buf = bytes.to_vec();
3031
let result = simd_json::serde::from_slice::<T>(&mut parse_buf)
3132
.map_err(|e| anyhow!("JSON parse error: {e}"));
3233
let _ = tx.send(result);
@@ -36,7 +37,6 @@ where
3637
}
3738
#[cfg(target_arch = "wasm32")]
3839
{
39-
let mut parse_buf = bytes.to_vec();
4040
simd_json::serde::from_slice::<T>(&mut parse_buf)
4141
.map_err(|e| anyhow!("JSON parse error: {e}"))
4242
}
@@ -71,7 +71,8 @@ pub(crate) async fn parse_full_manifest_off_runtime(
7171
}
7272
#[cfg(target_arch = "wasm32")]
7373
{
74-
let mut manifest: FullManifest = parse_json_off_runtime(raw_bytes.clone()).await?;
74+
let mut manifest: FullManifest =
75+
parse_json_vec_off_runtime(raw_bytes.clone().to_vec()).await?;
7576
manifest.raw = raw_bytes;
7677
Ok(manifest)
7778
}
@@ -220,6 +221,28 @@ pub async fn fetch_full_manifest_fresh(
220221
}
221222
}
222223

224+
#[cfg(not(target_arch = "wasm32"))]
225+
async fn read_body_vec(mut response: reqwest::Response) -> Result<Vec<u8>, FetchError> {
226+
let capacity = response
227+
.content_length()
228+
.and_then(|len| usize::try_from(len).ok())
229+
.unwrap_or(0);
230+
let mut body = Vec::with_capacity(capacity);
231+
while let Some(chunk) = response.chunk().await.map_err(classify_reqwest_error)? {
232+
body.extend_from_slice(&chunk);
233+
}
234+
Ok(body)
235+
}
236+
237+
#[cfg(target_arch = "wasm32")]
238+
async fn read_body_vec(response: reqwest::Response) -> Result<Vec<u8>, FetchError> {
239+
response
240+
.bytes()
241+
.await
242+
.map(|bytes| bytes.to_vec())
243+
.map_err(classify_reqwest_error)
244+
}
245+
223246
/// Options for fetching a version manifest.
224247
pub struct FetchVersionManifestOptions<'a> {
225248
pub registry_url: &'a str,
@@ -230,6 +253,17 @@ pub struct FetchVersionManifestOptions<'a> {
230253

231254
/// Fetch version manifest bytes with retry, without parsing.
232255
pub async fn fetch_version_manifest_bytes(opts: FetchVersionManifestOptions<'_>) -> Result<Bytes> {
256+
fetch_version_manifest_vec(opts).await.map(Bytes::from)
257+
}
258+
259+
/// Fetch version manifest into a mutable parse buffer with retry.
260+
///
261+
/// Unlike full manifests, exact-version manifests do not need to keep raw
262+
/// response bytes for later extraction. Reading directly into `Vec<u8>` avoids
263+
/// the hot-path `Bytes -> Vec` copy before `simd_json` parses in place.
264+
pub(crate) async fn fetch_version_manifest_vec(
265+
opts: FetchVersionManifestOptions<'_>,
266+
) -> Result<Vec<u8>> {
233267
let url = format!("{}/{}/{}", opts.registry_url, opts.name, opts.spec);
234268

235269
let accept = match opts.format {
@@ -251,7 +285,7 @@ pub async fn fetch_version_manifest_bytes(opts: FetchVersionManifestOptions<'_>)
251285
.map_err(classify_reqwest_error)?;
252286

253287
if response.status().is_success() {
254-
response.bytes().await.map_err(classify_reqwest_error)
288+
read_body_vec(response).await
255289
} else {
256290
Err(classify_status(response.status(), &url))
257291
}
@@ -271,6 +305,36 @@ pub async fn fetch_version_manifest_bytes(opts: FetchVersionManifestOptions<'_>)
271305
pub async fn fetch_version_manifest(
272306
opts: FetchVersionManifestOptions<'_>,
273307
) -> Result<CoreVersionManifest> {
274-
let bytes = fetch_version_manifest_bytes(opts).await?;
275-
parse_json_off_runtime::<CoreVersionManifest>(bytes).await
308+
let bytes = fetch_version_manifest_vec(opts).await?;
309+
parse_json_vec_off_runtime::<CoreVersionManifest>(bytes).await
310+
}
311+
312+
#[cfg(test)]
313+
mod tests {
314+
use serde::Deserialize;
315+
316+
use super::*;
317+
318+
#[derive(Debug, Deserialize, PartialEq)]
319+
struct TinyManifest {
320+
name: String,
321+
version: String,
322+
}
323+
324+
#[tokio::test]
325+
async fn parse_json_vec_off_runtime_consumes_mutable_buffer() {
326+
let parsed = parse_json_vec_off_runtime::<TinyManifest>(
327+
br#"{"name":"demo","version":"1.0.0"}"#.to_vec(),
328+
)
329+
.await
330+
.unwrap();
331+
332+
assert_eq!(
333+
parsed,
334+
TinyManifest {
335+
name: "demo".to_string(),
336+
version: "1.0.0".to_string(),
337+
}
338+
);
339+
}
276340
}

0 commit comments

Comments
 (0)