-
-
Notifications
You must be signed in to change notification settings - Fork 646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Skip loading of local cache data when possible #17495
Changes from 6 commits
a03782a
643c3c6
2bba68c
e5f6a4d
31b0543
0e76b70
839251f
965ff98
6188dcd
56c415f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -229,6 +229,59 @@ impl RemoteStore { | |
.await | ||
.map(|&()| ()) | ||
} | ||
|
||
/// Download the digest to the local byte store from this remote store. The function `f_remote` | ||
/// can be used to validate/transform the bytes. | ||
async fn download_digest_to_local< | ||
FRemote: Fn(Bytes) -> Result<(), String> + Send + Sync + 'static, | ||
Comment on lines
+235
to
+236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function was simply extracted from |
||
>( | ||
&self, | ||
local_store: local::ByteStore, | ||
digest: Digest, | ||
entry_type: EntryType, | ||
f_remote: FRemote, | ||
) -> Result<(), StoreError> { | ||
let remote_store = self.store.clone(); | ||
self | ||
.maybe_download(digest, async move { | ||
// TODO: Now that we always copy from the remote store to the local store before executing | ||
// the caller's logic against the local store, `remote::ByteStore::load_bytes_with` no | ||
// longer needs to accept a function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a pointer to #17065 here? I have some more ideas that I will likely put on that ticket today. |
||
let bytes = retry_call( | ||
remote_store, | ||
|remote_store| async move { remote_store.load_bytes_with(digest, Ok).await }, | ||
|err| match err { | ||
ByteStoreError::Grpc(status) => status_is_retryable(status), | ||
_ => false, | ||
}, | ||
) | ||
.await | ||
.map_err(|err| match err { | ||
ByteStoreError::Grpc(status) => status_to_str(status), | ||
ByteStoreError::Other(msg) => msg, | ||
})? | ||
.ok_or_else(|| { | ||
StoreError::MissingDigest( | ||
"Was not present in either the local or remote store".to_owned(), | ||
digest, | ||
) | ||
})?; | ||
|
||
f_remote(bytes.clone())?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI this clone seems like wasted performance in some cases, but will leave it for a followup There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let stored_digest = local_store | ||
.store_bytes(entry_type, None, bytes, true) | ||
.await?; | ||
if digest == stored_digest { | ||
Ok(()) | ||
} else { | ||
Err(StoreError::Unclassified(format!( | ||
"CAS gave wrong digest: expected {:?}, got {:?}", | ||
digest, stored_digest | ||
))) | ||
} | ||
}) | ||
.await | ||
} | ||
} | ||
|
||
/// | ||
|
@@ -674,9 +727,6 @@ impl Store { | |
f_local: FLocal, | ||
f_remote: FRemote, | ||
) -> Result<T, StoreError> { | ||
let local = self.local.clone(); | ||
let maybe_remote = self.remote.clone(); | ||
|
||
if let Some(bytes_res) = self | ||
.local | ||
.load_bytes_with(entry_type, digest, f_local.clone()) | ||
|
@@ -685,47 +735,11 @@ impl Store { | |
return Ok(bytes_res?); | ||
} | ||
|
||
let remote = maybe_remote.ok_or_else(|| { | ||
let remote = self.remote.clone().ok_or_else(|| { | ||
StoreError::MissingDigest("Was not present in the local store".to_owned(), digest) | ||
})?; | ||
let remote_store = remote.store.clone(); | ||
|
||
remote | ||
.maybe_download(digest, async move { | ||
// TODO: Now that we always copy from the remote store to the local store before executing | ||
// the caller's logic against the local store, `remote::ByteStore::load_bytes_with` no | ||
// longer needs to accept a function. | ||
let bytes = retry_call( | ||
remote_store, | ||
|remote_store| async move { remote_store.load_bytes_with(digest, Ok).await }, | ||
|err| match err { | ||
ByteStoreError::Grpc(status) => status_is_retryable(status), | ||
_ => false, | ||
}, | ||
) | ||
.await | ||
.map_err(|err| match err { | ||
ByteStoreError::Grpc(status) => status_to_str(status), | ||
ByteStoreError::Other(msg) => msg, | ||
})? | ||
.ok_or_else(|| { | ||
StoreError::MissingDigest( | ||
"Was not present in either the local or remote store".to_owned(), | ||
digest, | ||
) | ||
})?; | ||
|
||
f_remote(bytes.clone())?; | ||
let stored_digest = local.store_bytes(entry_type, None, bytes, true).await?; | ||
if digest == stored_digest { | ||
Ok(()) | ||
} else { | ||
Err(StoreError::Unclassified(format!( | ||
"CAS gave wrong digest: expected {:?}, got {:?}", | ||
digest, stored_digest | ||
))) | ||
} | ||
}) | ||
.download_digest_to_local(self.local.clone(), digest, entry_type, f_remote) | ||
.await?; | ||
|
||
Ok( | ||
|
@@ -951,55 +965,70 @@ impl Store { | |
Ok(missing.is_empty()) | ||
} | ||
|
||
/// | ||
/// Ensure that a directory is locally loadable, which will download it from the Remote store as | ||
/// a sideeffect (if one is configured). | ||
/// | ||
pub async fn ensure_local_has_recursive_directory( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was inlined into the new function |
||
/// Ensure that the files are locally loadable. This will download them from the remote store as | ||
/// a side effect, if one is configured. | ||
pub async fn ensure_local_has_files( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe rename to: Somewhat implicit in here is that we also want to guarantee that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the idea that at the end of callling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yea, either at the beginning or the end is fine. It's also possible to use But you should be able to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stuhood I want to make sure I got it right why it's safe to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right: |
||
&self, | ||
dir_digest: DirectoryDigest, | ||
mut file_digests: HashSet<Digest>, | ||
directory_digests: HashSet<DirectoryDigest>, | ||
) -> Result<(), StoreError> { | ||
let mut file_digests = Vec::new(); | ||
self | ||
.load_digest_trie(dir_digest) | ||
.await? | ||
.walk(SymlinkBehavior::Aware, &mut |_, entry| match entry { | ||
directory::Entry::File(f) => file_digests.push(f.digest()), | ||
directory::Entry::Symlink(_) | directory::Entry::Directory(_) => (), | ||
}); | ||
let file_digests_from_directories = | ||
future::try_join_all(directory_digests.into_iter().map(|dir_digest| async move { | ||
let mut maybe_file_digest = None; | ||
self | ||
.load_digest_trie(dir_digest) | ||
.await? | ||
.walk(SymlinkBehavior::Aware, &mut |_, entry| match entry { | ||
directory::Entry::File(f) => { | ||
maybe_file_digest = Some(f.digest()); | ||
} | ||
directory::Entry::Symlink(_) | directory::Entry::Directory(_) => (), | ||
}); | ||
Ok::<_, StoreError>(maybe_file_digest) | ||
})) | ||
.await?; | ||
file_digests.extend(file_digests_from_directories.into_iter().flatten()); | ||
|
||
let missing_file_digests = self | ||
.local | ||
.get_missing_digests(EntryType::File, file_digests) | ||
.await?; | ||
if missing_file_digests.is_empty() { | ||
return Ok(()); | ||
} | ||
Comment on lines
+990
to
+996
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is new |
||
|
||
let remote = &self.remote.clone().ok_or_else(|| { | ||
StoreError::MissingDigest( | ||
"Was not present in the local store".to_owned(), | ||
*missing_file_digests.iter().next().unwrap(), | ||
) | ||
})?; | ||
let _ = future::try_join_all( | ||
file_digests | ||
missing_file_digests | ||
.into_iter() | ||
.map(|file_digest| self.ensure_local_has_file(file_digest)) | ||
.collect::<Vec<_>>(), | ||
.map(|file_digest| async move { | ||
if let Err(e) = remote | ||
.download_digest_to_local(self.local.clone(), file_digest, EntryType::File, |_| Ok(())) | ||
.await | ||
Comment on lines
-974
to
+1010
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the key change. We now only download, don't load. |
||
{ | ||
log::debug!("Missing file digest from remote store: {:?}", file_digest); | ||
in_workunit!( | ||
"missing_file_counter", | ||
Level::Trace, | ||
|workunit| async move { | ||
workunit.increment_counter(Metric::RemoteStoreMissingDigest, 1); | ||
}, | ||
) | ||
.await; | ||
return Err(e); | ||
} | ||
Ok(()) | ||
}), | ||
) | ||
.await?; | ||
Ok(()) | ||
} | ||
|
||
/// Ensure that a file is locally loadable, which will download it from the Remote store as | ||
/// a side effect (if one is configured). Called only with the Digest of a File. | ||
pub async fn ensure_local_has_file(&self, file_digest: Digest) -> Result<(), StoreError> { | ||
if let Err(e) = self | ||
.load_bytes_with(EntryType::File, file_digest, |_| Ok(()), |_| Ok(())) | ||
.await | ||
{ | ||
log::debug!("Missing file digest from remote store: {:?}", file_digest); | ||
in_workunit!( | ||
"missing_file_counter", | ||
Level::Trace, | ||
|workunit| async move { | ||
workunit.increment_counter(Metric::RemoteStoreMissingDigest, 1); | ||
}, | ||
) | ||
.await; | ||
Err(e) | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Load a REv2 Tree from a remote CAS _without_ persisting the embedded Directory protos in | ||
/// the local store. Tree is used by the REv2 protocol as an optimization for encoding the | ||
/// the Directory protos that comprise the output directories from a remote execution | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bytes
is immutable, so the function can only validate.