Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions crates/pm/src/util/manifest_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,27 @@ use async_trait::async_trait;
use serde::Serialize;
use utoo_ruborist::model::manifest::CoreVersionManifest;
use utoo_ruborist::service::{ManifestStore, VersionsInfo};
use utoo_ruborist::util::OnceMap;

use crate::util::json::read_json_file;

pub struct DiskManifestStore {
cache_dir: PathBuf,
/// Dedup `(name, resolved_version)` disk writes. The upstream
/// `inflight_version<(name, original_spec)>` gate keys on the original
/// spec, so two specs (`^4.17.0`, `~4.17.20`) resolving to the same
/// `4.17.21` each call `store_version_manifest` and would otherwise
/// spawn duplicate `write_json` tasks racing on truncate of the same
/// path. `OnceMap::register` is a sync, atomic "first wins" check.
inflight_version_writes: Arc<OnceMap<(String, String), ()>>,
}

impl DiskManifestStore {
pub fn new(cache_dir: PathBuf) -> Self {
Self { cache_dir }
Self {
cache_dir,
inflight_version_writes: Arc::new(OnceMap::new()),
}
}

fn versions_path(&self, name: &str) -> PathBuf {
Expand Down Expand Up @@ -67,8 +78,21 @@ impl ManifestStore for DiskManifestStore {
version: &str,
manifest: Arc<CoreVersionManifest>,
) {
let key = (name.to_string(), version.to_string());
let Some(notify) = self.inflight_version_writes.register(key.clone()) else {
// Another caller already enqueued this write — skip the
// duplicate serialize + spawn + truncate-race.
return;
};
let path = self.manifest_path(name, version);
tokio::spawn(async move { write_json(&path, &*manifest).await });
// Move an `Arc` clone of the OnceMap into the task so we can
// transition Waiting → Done after the write completes; this also
// notifies any future `wait_if_pending` callers.
let inflight = Arc::clone(&self.inflight_version_writes);
tokio::spawn(async move {
write_json(&path, &*manifest).await;
inflight.complete(key, Some(()), notify);
Comment on lines +93 to +94

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The write is currently marked as complete with Some(()) regardless of whether write_json succeeds or fails. Since OnceMap::register returns None for any key already in the Done state, a single failed write (e.g., due to a transient I/O error or disk full condition) will prevent any subsequent attempts to persist that specific version manifest for the lifetime of the process. This results in persistent cache misses for that version.

Consider updating write_json to return a success boolean and using inflight.complete(key, success.then_some(()), notify) here. This ensures that if a write fails, the key is removed from the map (via None), allowing future calls to retry the operation.

});
}
}

Expand Down
Loading