Skip to content

Commit ab8d3ce

Browse files
committed
Do not need to store zero-length filesystem files
1 parent daea037 commit ab8d3ce

File tree

2 files changed

+111
-36
lines changed

2 files changed

+111
-36
lines changed

nativelink-store/src/filesystem_store.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,17 @@ impl<Fe: FileEntry> FilesystemStore<Fe> {
704704
}
705705

706706
pub async fn get_file_entry_for_digest(&self, digest: &DigestInfo) -> Result<Arc<Fe>, Error> {
707+
if is_zero_digest(digest) {
708+
return Ok(Arc::new(Fe::create(
709+
0,
710+
0,
711+
RwLock::new(EncodedFilePath {
712+
shared_context: self.shared_context.clone(),
713+
path_type: PathType::Content,
714+
key: digest.into(),
715+
}),
716+
)));
717+
}
707718
self.evicting_map
708719
.get(&digest.into())
709720
.await
@@ -860,6 +871,11 @@ impl<Fe: FileEntry> StoreDriver for FilesystemStore<Fe> {
860871
mut reader: DropCloserReadHalf,
861872
_upload_size: UploadSizeInfo,
862873
) -> Result<(), Error> {
874+
if is_zero_digest(key.borrow()) {
875+
// don't need to add, because zero length files are just assumed to exist
876+
return Ok(());
877+
}
878+
863879
let temp_key = make_temp_key(&key);
864880

865881
// There's a possibility of deadlock here where we take all of the
@@ -910,6 +926,10 @@ impl<Fe: FileEntry> StoreDriver for FilesystemStore<Fe> {
910926
.err_tip(|| format!("While reading metadata for {}", path.display()))?
911927
.len(),
912928
};
929+
if file_size == 0 {
930+
// don't need to add, because zero length files are just assumed to exist
931+
return Ok(None);
932+
}
913933
let entry = Fe::create(
914934
file_size,
915935
self.block_size,

nativelink-store/tests/filesystem_store_test.rs

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ async fn wait_for_no_open_files() -> Result<(), Error> {
226226
Ok(())
227227
}
228228

229-
/// Helper function to ensure there are no temporary files left.
230-
async fn check_temp_empty(temp_path: &str) -> Result<(), Error> {
231-
let (_permit, temp_dir_handle) = fs::read_dir(format!("{temp_path}/{DIGEST_FOLDER}"))
229+
/// Helper function to ensure there are no temporary or content files left.
230+
async fn check_storage_dir_empty(storage_path: &str) -> Result<(), Error> {
231+
let (_permit, temp_dir_handle) = fs::read_dir(format!("{storage_path}/{DIGEST_FOLDER}"))
232232
.await
233233
.err_tip(|| "Failed opening temp directory")?
234234
.into_inner();
@@ -243,7 +243,7 @@ async fn check_temp_empty(temp_path: &str) -> Result<(), Error> {
243243
);
244244
}
245245

246-
let (_permit, temp_dir_handle) = fs::read_dir(format!("{temp_path}/{STR_FOLDER}"))
246+
let (_permit, temp_dir_handle) = fs::read_dir(format!("{storage_path}/{STR_FOLDER}"))
247247
.await
248248
.err_tip(|| "Failed opening temp directory")?
249249
.into_inner();
@@ -380,7 +380,7 @@ async fn temp_files_get_deleted_on_replace_test() -> Result<(), Error> {
380380
"Dropped a filesystem_delete_file current_active_drop_spawns=0"
381381
));
382382

383-
check_temp_empty(&temp_path).await
383+
check_storage_dir_empty(&temp_path).await
384384
}
385385

386386
// This test ensures that if a file is overridden and an open stream to the file already
@@ -487,7 +487,7 @@ async fn file_continues_to_stream_on_content_replace_test() -> Result<(), Error>
487487
}
488488

489489
// Now ensure our temp file was cleaned up.
490-
check_temp_empty(&temp_path).await
490+
check_storage_dir_empty(&temp_path).await
491491
}
492492

493493
// Eviction has a different code path than a file replacement, so we check that if a
@@ -583,7 +583,7 @@ async fn file_gets_cleans_up_on_cache_eviction() -> Result<(), Error> {
583583
}
584584

585585
// Now ensure our temp file was cleaned up.
586-
check_temp_empty(&temp_path).await
586+
check_storage_dir_empty(&temp_path).await
587587
}
588588

589589
// Test to ensure that if we are holding a reference to `FileEntry` and the contents are
@@ -805,7 +805,7 @@ async fn rename_on_insert_fails_due_to_filesystem_error_proper_cleanup_happens()
805805

806806
// Now it should have cleaned up its temp files.
807807
{
808-
check_temp_empty(&temp_path).await?;
808+
check_storage_dir_empty(&temp_path).await?;
809809
}
810810

811811
// Finally ensure that our entry is not in the store.
@@ -907,32 +907,6 @@ async fn get_part_is_zero_digest() -> Result<(), Error> {
907907

908908
#[nativelink_test]
909909
async fn has_with_results_on_zero_digests() -> Result<(), Error> {
910-
async fn wait_for_empty_content_file<
911-
Fut: Future<Output = Result<(), Error>>,
912-
F: Fn() -> Fut,
913-
>(
914-
content_path: &str,
915-
digest: DigestInfo,
916-
yield_fn: F,
917-
) -> Result<(), Error> {
918-
loop {
919-
yield_fn().await?;
920-
921-
let empty_digest_file_name =
922-
OsString::from(format!("{content_path}/{DIGEST_FOLDER}/{digest}"));
923-
924-
let file_metadata = fs::metadata(empty_digest_file_name)
925-
.await
926-
.err_tip(|| "Failed to open content file")?;
927-
928-
// Test that the empty digest file is created and contains an empty length.
929-
if file_metadata.is_file() && file_metadata.len() == 0 {
930-
return Ok(());
931-
}
932-
}
933-
// Unreachable.
934-
}
935-
936910
let digest = DigestInfo::new(Sha256::new().finalize().into(), 0);
937911
let content_path = make_temp_path("content_path");
938912
let temp_path = make_temp_path("temp_path");
@@ -960,12 +934,93 @@ async fn has_with_results_on_zero_digests() -> Result<(), Error> {
960934
);
961935
assert_eq!(results, vec![Some(0)]);
962936

963-
wait_for_empty_content_file(&content_path, digest, || async move {
964-
tokio::task::yield_now().await;
937+
check_storage_dir_empty(&content_path).await?;
938+
939+
Ok(())
940+
}
941+
942+
async fn wrap_update_zero_digest<F>(updater: F) -> Result<(), Error>
943+
where
944+
F: AsyncFnOnce(DigestInfo, Arc<FilesystemStore>) -> Result<(), Error>,
945+
{
946+
let digest = DigestInfo::new(Sha256::new().finalize().into(), 0);
947+
let content_path = make_temp_path("content_path");
948+
let temp_path = make_temp_path("temp_path");
949+
950+
let store = FilesystemStore::<FileEntryImpl>::new_with_timeout_and_rename_fn(
951+
&FilesystemSpec {
952+
content_path: content_path.clone(),
953+
temp_path: temp_path.clone(),
954+
read_buffer_size: 1,
955+
..Default::default()
956+
},
957+
|from, to| std::fs::rename(from, to),
958+
)
959+
.await?;
960+
updater(digest, store).await?;
961+
check_storage_dir_empty(&content_path).await?;
962+
check_storage_dir_empty(&temp_path).await?;
963+
Ok(())
964+
}
965+
966+
#[nativelink_test]
967+
async fn update_whole_file_with_zero_digest() -> Result<(), Error> {
968+
wrap_update_zero_digest(async |digest, store| {
969+
let temp_file_dir = make_temp_path("update_with_zero_digest");
970+
std::fs::create_dir_all(&temp_file_dir)?;
971+
let temp_file_path = Path::new(&temp_file_dir).join("zero-length-file");
972+
std::fs::write(&temp_file_path, b"")
973+
.err_tip(|| format!("Writing to {temp_file_path:?}"))?;
974+
let file_slot = fs::open_file(&temp_file_path, 0, 0).await?.into_inner();
975+
store
976+
.update_with_whole_file(
977+
digest,
978+
temp_file_path.into(),
979+
file_slot,
980+
UploadSizeInfo::ExactSize(0),
981+
)
982+
.await?;
965983
Ok(())
966984
})
985+
.await
986+
}
987+
988+
#[nativelink_test]
989+
async fn update_oneshot_with_zero_digest() -> Result<(), Error> {
990+
wrap_update_zero_digest(async |digest, store| store.update_oneshot(digest, Bytes::new()).await)
991+
.await
992+
}
993+
994+
#[nativelink_test]
995+
async fn update_with_zero_digest() -> Result<(), Error> {
996+
wrap_update_zero_digest(async |digest, store| {
997+
let (_writer, reader) = make_buf_channel_pair();
998+
store
999+
.update(digest, reader, UploadSizeInfo::ExactSize(0))
1000+
.await
1001+
})
1002+
.await
1003+
}
1004+
1005+
#[nativelink_test]
1006+
async fn get_file_entry_for_zero_digest() -> Result<(), Error> {
1007+
let digest = DigestInfo::new(Sha256::new().finalize().into(), 0);
1008+
let content_path = make_temp_path("content_path");
1009+
let temp_path = make_temp_path("temp_path");
1010+
1011+
let store = FilesystemStore::<FileEntryImpl>::new_with_timeout_and_rename_fn(
1012+
&FilesystemSpec {
1013+
content_path: content_path.clone(),
1014+
temp_path: temp_path.clone(),
1015+
read_buffer_size: 1,
1016+
..Default::default()
1017+
},
1018+
|from, to| std::fs::rename(from, to),
1019+
)
9671020
.await?;
9681021

1022+
let file_entry = store.get_file_entry_for_digest(&digest).await?;
1023+
assert!(file_entry.is_empty());
9691024
Ok(())
9701025
}
9711026

0 commit comments

Comments
 (0)