1pub mod fs;
4
5use crate::{
6 config::common::CacheSize,
7 heap::{Heap, HeapDeps, HeapIndex},
8 root::{Root, RootBuf},
9};
10use anyhow::{bail, Error, Result};
11use bytesize::ByteSize;
12use derive_more::{with_trait::Debug, Deref, DerefMut};
13use fs::{FileType, Fs, Metadata};
14use maelstrom_base::{JobId, Sha256Digest};
15use slog::{debug, info, warn, Logger};
16use std::{
17 cmp::Ordering,
18 collections::{hash_map::Entry as HashEntry, HashMap, HashSet},
19 ffi::{OsStr, OsString},
20 hash::Hash,
21 iter::IntoIterator,
22 mem,
23 num::NonZeroU32,
24 path::{Path, PathBuf},
25 result,
26 str::FromStr as _,
27 string::ToString,
28};
29
30const CACHEDIR_TAG: &str = "CACHEDIR.TAG";
31const CACHEDIR_TAG_CONTENTS: [u8; 43] = *b"Signature: 8a477f597d28d172789f06886806bc55";
32const CACHEDIR_TAG_CONTENTS_LEN: usize = CACHEDIR_TAG_CONTENTS.len();
33const CACHEDIR_TAG_CONTENTS_LEN_U64: u64 = CACHEDIR_TAG_CONTENTS_LEN as u64;
34const LOCK_FILE: &str = "lock";
35const REMOVING: &str = "removing";
36const SHA256: &str = "sha256";
37
38#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
40pub enum GetArtifact {
41 Success,
45
46 Wait,
50
51 Get,
56}
57
58#[derive(Clone, Debug)]
60#[debug(bound(FsT::TempFile: Debug, FsT::TempDir: Debug))]
61pub enum GotArtifact<FsT: Fs> {
62 Symlink { target: PathBuf },
63 File { source: FsT::TempFile },
64 Directory { source: FsT::TempDir, size: u64 },
65}
66
67impl<FsT: Fs> GotArtifact<FsT> {
68 pub fn symlink(target: PathBuf) -> Self {
69 Self::Symlink { target }
70 }
71
72 pub fn file(source: FsT::TempFile) -> Self {
73 Self::File { source }
74 }
75
76 pub fn directory(source: FsT::TempDir, size: u64) -> Self {
77 Self::Directory { source, size }
78 }
79}
80
81impl<FsT: Fs> Eq for GotArtifact<FsT>
82where
83 FsT::TempFile: Eq,
84 FsT::TempDir: Eq,
85{
86}
87
88impl<FsT: Fs> Ord for GotArtifact<FsT>
89where
90 FsT::TempFile: Ord,
91 FsT::TempDir: Ord,
92{
93 fn cmp(&self, other: &Self) -> Ordering {
94 match (self, other) {
95 (
96 Self::Symlink {
97 target: self_target,
98 },
99 Self::Symlink {
100 target: other_target,
101 },
102 ) => self_target.cmp(other_target),
103 (
104 Self::File {
105 source: self_source,
106 },
107 Self::File {
108 source: other_source,
109 },
110 ) => self_source.cmp(other_source),
111 (
112 Self::Directory {
113 source: self_source,
114 size: self_size,
115 },
116 Self::Directory {
117 source: other_source,
118 size: other_size,
119 },
120 ) => self_source
121 .cmp(other_source)
122 .then(self_size.cmp(other_size)),
123 (Self::Symlink { .. }, _) => Ordering::Less,
124 (Self::File { .. }, Self::Directory { .. }) => Ordering::Less,
125 (Self::File { .. }, Self::Symlink { .. }) => Ordering::Greater,
126 (Self::Directory { .. }, _) => Ordering::Greater,
127 }
128 }
129}
130
131impl<FsT: Fs> PartialEq for GotArtifact<FsT>
132where
133 FsT::TempFile: PartialEq,
134 FsT::TempDir: PartialEq,
135{
136 fn eq(&self, other: &Self) -> bool {
137 match (self, other) {
138 (
139 Self::Symlink {
140 target: self_target,
141 },
142 Self::Symlink {
143 target: other_target,
144 },
145 ) => self_target.eq(other_target),
146 (
147 Self::File {
148 source: self_source,
149 },
150 Self::File {
151 source: other_source,
152 },
153 ) => self_source.eq(other_source),
154 (
155 Self::Directory {
156 source: self_source,
157 size: self_size,
158 },
159 Self::Directory {
160 source: other_source,
161 size: other_size,
162 },
163 ) => self_source.eq(other_source) && self_size.eq(other_size),
164 _ => false,
165 }
166 }
167}
168
169impl<FsT: Fs> PartialOrd for GotArtifact<FsT>
170where
171 GotArtifact<FsT>: Ord,
172{
173 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
174 Some(self.cmp(other))
175 }
176}
177
178pub trait Key: Clone + Debug + Eq + Hash {
179 fn kinds() -> impl Iterator<Item = &'static str>;
180 fn from_kind_and_digest(kind: &'static str, digest: Sha256Digest) -> Self;
181 fn kind(&self) -> &'static str;
182 fn digest(&self) -> &Sha256Digest;
183}
184
185pub trait GetStrategy {
186 type Getter: Eq + Hash;
187 fn getter_from_job_id(jid: JobId) -> Self::Getter;
188}
189
190enum Entry<GetStrategyT: GetStrategy> {
194 Getting {
196 jobs: Vec<JobId>,
197 getters: HashSet<GetStrategyT::Getter>,
198 },
199
200 InUse {
203 file_type: FileType,
204 bytes_used: u64,
205 ref_count: NonZeroU32,
206 },
207
208 InHeap {
212 file_type: FileType,
213 bytes_used: u64,
214 priority: u64,
215 heap_index: HeapIndex,
216 },
217}
218
219#[derive(Deref, DerefMut)]
222struct Map<KeyT: Key, GetStrategyT: GetStrategy>(HashMap<KeyT, Entry<GetStrategyT>>);
223
224impl<KeyT: Key, GetStrategyT: GetStrategy> Default for Map<KeyT, GetStrategyT> {
225 fn default() -> Self {
226 Self(HashMap::default())
227 }
228}
229
230impl<KeyT: Key, GetStrategyT: GetStrategy> HeapDeps for Map<KeyT, GetStrategyT> {
231 type Element = KeyT;
232
233 fn is_element_less_than(&self, lhs: &Self::Element, rhs: &Self::Element) -> bool {
234 let lhs_priority = match self.get(lhs) {
235 Some(Entry::InHeap { priority, .. }) => *priority,
236 _ => panic!("Element should be in heap"),
237 };
238 let rhs_priority = match self.get(rhs) {
239 Some(Entry::InHeap { priority, .. }) => *priority,
240 _ => panic!("Element should be in heap"),
241 };
242 lhs_priority.cmp(&rhs_priority) == Ordering::Less
243 }
244
245 fn update_index(&mut self, elem: &Self::Element, idx: HeapIndex) {
246 match self.get_mut(elem) {
247 Some(Entry::InHeap { heap_index, .. }) => *heap_index = idx,
248 _ => panic!("Element should be in heap"),
249 };
250 }
251}
252
253#[derive(Clone)]
254pub struct TempFileFactory<FsT> {
255 fs: FsT,
256 tmp: RootBuf<TmpDir>,
257}
258
259impl<FsT: Fs> TempFileFactory<FsT> {
260 pub fn temp_dir(&self) -> Result<FsT::TempDir> {
261 Ok(self.fs.temp_dir(&self.tmp)?)
262 }
263
264 pub fn temp_file(&self) -> Result<FsT::TempFile> {
265 Ok(self.fs.temp_file(&self.tmp)?)
266 }
267}
268
269pub struct CacheDir;
270
271pub struct EntryPath;
272
273struct CachedirTagPath;
274struct LockFilePath;
275struct RemovingRoot;
276struct RemovingPath;
277struct TmpDir;
278struct Sha256Dir;
279struct KindDir;
280struct SizeFile;
281
282pub struct Cache<FsT: Fs, KeyT: Key, GetStrategyT: GetStrategy> {
286 fs: FsT,
287 root: RootBuf<CacheDir>,
288 removing: RootBuf<RemovingRoot>,
289 sha256: RootBuf<Sha256Dir>,
290 entries: Map<KeyT, GetStrategyT>,
291 heap: Heap<Map<KeyT, GetStrategyT>>,
292 next_priority: u64,
293 bytes_used: u64,
294 bytes_used_target: u64,
295 getting: usize,
296 log: Logger,
297 _lock_file: FsT::FileLock,
298}
299
300impl<FsT: Fs, KeyT: Key, GetStrategyT: GetStrategy> Cache<FsT, KeyT, GetStrategyT> {
301 pub fn new(
310 fs: FsT,
311 root: RootBuf<CacheDir>,
312 size: CacheSize,
313 log: Logger,
314 log_initial_message_at_info: bool,
315 ) -> Result<(Self, TempFileFactory<FsT>)> {
316 let cachedir_tag = root.join::<CachedirTagPath>(CACHEDIR_TAG);
317 let lock_file_path = root.join::<LockFilePath>(LOCK_FILE);
318 let removing = root.join::<RemovingRoot>(REMOVING);
319 let sha256 = root.join::<Sha256Dir>(SHA256);
320 let tmp = root.join::<TmpDir>("tmp");
321
322 fs.mkdir_recursively(&root)?;
324
325 let Some(lock_file) = fs.possibly_create_file_and_try_exclusive_lock(&lock_file_path)?
327 else {
328 bail!("lock file {lock_file_path:?} is held by a different process");
329 };
330
331 let preserve_directory_contents = match fs.metadata(&cachedir_tag)? {
336 Some(Metadata {
337 type_: FileType::File,
338 size: CACHEDIR_TAG_CONTENTS_LEN_U64,
339 }) => {
340 let mut contents = [0u8; CACHEDIR_TAG_CONTENTS_LEN];
341 fs.read_file(&cachedir_tag, &mut contents[..])?;
342 contents == CACHEDIR_TAG_CONTENTS
343 }
344 _ => false,
345 };
346
347 let (cache, temp_file_factory) = if preserve_directory_contents {
348 Self::new_preserve_directory_contents(
349 fs, lock_file, root, removing, tmp, sha256, size, log,
350 )
351 } else {
352 Self::new_clear_directory_contents(
353 fs,
354 lock_file,
355 root,
356 cachedir_tag,
357 removing,
358 tmp,
359 sha256,
360 size,
361 log,
362 )
363 }?;
364
365 if log_initial_message_at_info {
366 info!(cache.log, "cache initialized";
367 "entries" => %cache.entries.len(),
368 "bytes_used" => %ByteSize::b(cache.bytes_used),
369 "byte_used_target" => %ByteSize::b(cache.bytes_used_target)
370 );
371 } else {
372 debug!(cache.log, "cache initialized";
373 "entries" => %cache.entries.len(),
374 "bytes_used" => %ByteSize::b(cache.bytes_used),
375 "byte_used_target" => %ByteSize::b(cache.bytes_used_target)
376 );
377 }
378
379 Ok((cache, temp_file_factory))
380 }
381
382 #[allow(clippy::too_many_arguments)]
383 fn new_clear_directory_contents(
384 fs: FsT,
385 lock_file: FsT::FileLock,
386 root: RootBuf<CacheDir>,
387 cachedir_tag: RootBuf<CachedirTagPath>,
388 removing: RootBuf<RemovingRoot>,
389 tmp: RootBuf<TmpDir>,
390 sha256: RootBuf<Sha256Dir>,
391 size: CacheSize,
392 log: Logger,
393 ) -> Result<(Self, TempFileFactory<FsT>)> {
394 ensure_removing_directory(&fs, &root, &removing)?;
396
397 remove_all_from_directory_except(&fs, &removing, &root, [LOCK_FILE, REMOVING])?;
399
400 fs.create_file(&cachedir_tag, &CACHEDIR_TAG_CONTENTS)?;
402 fs.mkdir(&tmp)?;
403 fs.mkdir(&sha256)?;
404 for kind in KeyT::kinds() {
405 fs.mkdir(&kind_dir(&sha256, kind))?;
406 }
407
408 let cache = Cache {
409 fs: fs.clone(),
410 root,
411 removing,
412 sha256,
413 entries: Map::default(),
414 heap: Heap::default(),
415 getting: 0,
416 next_priority: 0,
417 bytes_used: 0,
418 bytes_used_target: size.into(),
419 log,
420 _lock_file: lock_file,
421 };
422 let temp_file_factory = TempFileFactory { fs, tmp };
423 Ok((cache, temp_file_factory))
424 }
425
426 #[allow(clippy::too_many_arguments)]
427 fn new_preserve_directory_contents(
428 fs: FsT,
429 lock_file: FsT::FileLock,
430 root: RootBuf<CacheDir>,
431 removing: RootBuf<RemovingRoot>,
432 tmp: RootBuf<TmpDir>,
433 sha256: RootBuf<Sha256Dir>,
434 size: CacheSize,
435 log: Logger,
436 ) -> Result<(Self, TempFileFactory<FsT>)> {
437 ensure_removing_directory(&fs, &root, &removing)?;
439
440 remove_all_from_directory_except(
442 &fs,
443 &removing,
444 &root,
445 [LOCK_FILE, REMOVING, SHA256, CACHEDIR_TAG],
446 )?;
447
448 fs.mkdir(&tmp)?;
450 fs.mkdir_recursively(&sha256)?;
451
452 remove_all_from_directory_except(&fs, &removing, &sha256, KeyT::kinds())?;
454 for kind in KeyT::kinds() {
455 fs.mkdir_recursively(&kind_dir(&sha256, kind))?;
456 }
457
458 let mut entries = Map::<KeyT, GetStrategyT>::default();
459 let mut heap = Heap::<Map<KeyT, GetStrategyT>>::default();
460 let mut next_priority = 0u64;
461 let mut bytes_used = 0u64;
462
463 for kind in KeyT::kinds() {
465 let kind_dir: RootBuf<KindDir> = sha256.join(kind);
466 let mut directory_sizes = HashMap::<Sha256Digest, (bool, Option<u64>)>::new();
467 for entry in fs.read_dir(&kind_dir)? {
468 let (name, metadata) = entry?;
469 match cache_file_name_type(&fs, &kind_dir, &name, metadata) {
470 CacheFileNameType::File(digest, size) => {
471 let key = KeyT::from_kind_and_digest(kind, digest);
472 entries.insert(
473 key.clone(),
474 Entry::InHeap {
475 file_type: metadata.type_,
476 bytes_used: metadata.size,
477 priority: next_priority,
478 heap_index: Default::default(),
479 },
480 );
481 heap.push(&mut entries, key);
482 next_priority = next_priority.checked_add(1).unwrap();
483 bytes_used = bytes_used.checked_add(size).unwrap();
484 }
485 CacheFileNameType::Directory(digest) => {
486 directory_sizes.entry(digest).or_default().0 = true;
487 }
488 CacheFileNameType::DirectorySize(digest, size) => {
489 directory_sizes.entry(digest).or_default().1 = Some(size);
490 }
491 CacheFileNameType::Other => {
492 let path: RootBuf<EntryPath> = kind_dir.join(name);
493 if metadata.is_directory() {
494 rmdir_in_background(&fs, &removing, &path)?;
495 } else {
496 fs.remove(&path)?;
497 }
498 }
499 }
500 }
501
502 for (digest, entry) in directory_sizes {
503 match entry {
504 (true, Some(size)) => {
505 let key = KeyT::from_kind_and_digest(kind, digest);
506 entries.insert(
507 key.clone(),
508 Entry::InHeap {
509 file_type: FileType::Directory,
510 bytes_used: size,
511 priority: next_priority,
512 heap_index: Default::default(),
513 },
514 );
515 heap.push(&mut entries, key);
516 next_priority = next_priority.checked_add(1).unwrap();
517 bytes_used = bytes_used.checked_add(size).unwrap();
518 }
519 (false, Some(_)) => {
520 fs.remove(&size_file_name(&cache_file_name(&kind_dir, &digest)))?;
521 }
522 (true, None) => {
523 rmdir_in_background(&fs, &removing, &cache_file_name(&kind_dir, &digest))?;
524 }
525 _ => {
526 unreachable!();
527 }
528 }
529 }
530 }
531
532 let mut cache = Cache {
533 fs: fs.clone(),
534 root,
535 removing,
536 sha256,
537 entries,
538 heap,
539 getting: 0,
540 next_priority,
541 bytes_used,
542 bytes_used_target: size.into(),
543 log,
544 _lock_file: lock_file,
545 };
546
547 cache.possibly_remove_some_can_error()?;
548 let temp_file_factory = TempFileFactory { fs, tmp };
549 Ok((cache, temp_file_factory))
550 }
551
552 pub fn get_artifact(&mut self, key: KeyT, jid: JobId) -> GetArtifact {
555 match self.entries.entry(key) {
556 HashEntry::Vacant(entry) => {
557 entry.insert(Entry::Getting {
558 jobs: vec![jid],
559 getters: HashSet::from([GetStrategyT::getter_from_job_id(jid)]),
560 });
561 self.getting = self.getting.checked_add(1).unwrap();
562 GetArtifact::Get
563 }
564 HashEntry::Occupied(entry) => {
565 let entry = entry.into_mut();
566 match entry {
567 Entry::Getting { jobs, getters } => {
568 jobs.push(jid);
569 let getter = GetStrategyT::getter_from_job_id(jid);
570 if getters.insert(getter) {
571 GetArtifact::Get
572 } else {
573 GetArtifact::Wait
574 }
575 }
576 Entry::InUse { ref_count, .. } => {
577 *ref_count = ref_count.checked_add(1).unwrap();
578 GetArtifact::Success
579 }
580 Entry::InHeap {
581 file_type,
582 bytes_used,
583 heap_index,
584 ..
585 } => {
586 let heap_index = *heap_index;
587 *entry = Entry::InUse {
588 file_type: *file_type,
589 ref_count: NonZeroU32::new(1).unwrap(),
590 bytes_used: *bytes_used,
591 };
592 self.heap.remove(&mut self.entries, heap_index);
593 GetArtifact::Success
594 }
595 }
596 }
597 }
598 }
599
600 pub fn got_artifact_failure(&mut self, key: &KeyT) -> Vec<JobId> {
603 if let Some(Entry::Getting { jobs, .. }) = self.entries.remove(key) {
604 self.getting = self.getting.checked_sub(1).unwrap();
605 jobs
606 } else {
607 vec![]
608 }
609 }
610
611 pub fn got_artifact_success(
614 &mut self,
615 key: &KeyT,
616 artifact: GotArtifact<FsT>,
617 ) -> result::Result<Vec<JobId>, (Error, Vec<JobId>)> {
618 fn update_cache_directory<FsT: Fs>(
619 fs: &FsT,
620 path: &Root<EntryPath>,
621 artifact: GotArtifact<FsT>,
622 ) -> Result<(FileType, u64)> {
623 match artifact {
624 GotArtifact::Directory { source, size } => {
625 write_size_file(fs, path, size)?;
626 fs.persist_temp_dir(source, path)?;
627 Ok((FileType::Directory, size))
628 }
629 GotArtifact::File { source } => {
630 fs.persist_temp_file(source, path)?;
631 let metadata = fs.metadata(path)?.unwrap();
632 assert!(matches!(metadata.type_, FileType::File));
633 Ok((FileType::File, metadata.size))
634 }
635 GotArtifact::Symlink { target } => {
636 fs.symlink(&target, path)?;
637 let metadata = fs.metadata(path)?.unwrap();
638 assert!(matches!(metadata.type_, FileType::Symlink));
639 Ok((FileType::Symlink, metadata.size))
640 }
641 }
642 }
643
644 fn dispose_of_artifact<FsT: Fs>(
645 fs: &FsT,
646 removing: &Root<RemovingRoot>,
647 artifact: GotArtifact<FsT>,
648 ) -> result::Result<(), FsT::Error> {
649 match artifact {
650 GotArtifact::Directory { source, .. } => {
651 let target = get_unused_removing_path(fs, removing)?;
652 fs.persist_temp_dir(source, &target)?;
653 fs.rmdir_recursively_on_thread(target.into_path_buf())?;
654 }
655 GotArtifact::File { source } => {
656 let target = get_unused_removing_path(fs, removing)?;
657 fs.persist_temp_file(source, &target)?;
658 fs.remove(&target)?;
659 }
660 GotArtifact::Symlink { .. } => {}
661 }
662 Ok(())
663 }
664
665 fn dispose_of_artifact_or_warn<FsT: Fs>(
666 fs: &FsT,
667 log: &Logger,
668 removing: &Root<RemovingRoot>,
669 artifact: GotArtifact<FsT>,
670 ) {
671 if let Err(err) = dispose_of_artifact(fs, removing, artifact) {
672 warn!(log, "error encountered disposing of unnecessary artifact"; "error" => %err);
673 }
674 }
675
676 let cache_path = self.cache_path(key);
677
678 let Some(entry) = self.entries.get_mut(key) else {
679 debug!(self.log, "cache disposing of added artifact because it is no longer required";
680 "key" => ?key,
681 );
682 dispose_of_artifact_or_warn(&self.fs, &self.log, &self.removing, artifact);
683 return Ok(vec![]);
684 };
685
686 let Entry::Getting { jobs, .. } = entry else {
687 debug!(self.log, "cache disposing of added artifact because it is no longer required";
688 "key" => ?key,
689 );
690 dispose_of_artifact_or_warn(&self.fs, &self.log, &self.removing, artifact);
691 return Ok(vec![]);
692 };
693
694 let (file_type, bytes_used) = match update_cache_directory(&self.fs, &cache_path, artifact)
695 {
696 Err(err) => {
697 return Err((err, self.got_artifact_failure(key)));
698 }
699 Ok((file_type, bytes_used)) => (file_type, bytes_used),
700 };
701
702 let ref_count = NonZeroU32::new(jobs.len().try_into().unwrap()).unwrap();
704 let jobs = mem::take(jobs);
705 *entry = Entry::InUse {
706 file_type,
707 bytes_used,
708 ref_count,
709 };
710 self.getting = self.getting.checked_sub(1).unwrap();
711 self.bytes_used = self.bytes_used.checked_add(bytes_used).unwrap();
712 debug!(self.log, "cache added artifact";
713 "key" => ?key,
714 "artifact_bytes_used" => %ByteSize::b(bytes_used),
715 "entries" => %(self.entries.len() - self.getting),
716 "file_type" => %file_type,
717 "bytes_used" => %ByteSize::b(self.bytes_used),
718 "byte_used_target" => %ByteSize::b(self.bytes_used_target)
719 );
720 self.possibly_remove_some_ignore_error();
721 Ok(jobs)
722 }
723
724 #[must_use]
728 pub fn try_increment_ref_count(&mut self, key: &KeyT) -> Option<u64> {
729 if let Some(Entry::InUse {
730 ref_count,
731 bytes_used,
732 ..
733 }) = self.entries.get_mut(key)
734 {
735 *ref_count = ref_count.checked_add(1).unwrap();
736 Some(*bytes_used)
737 } else {
738 None
739 }
740 }
741
742 #[must_use]
745 pub fn try_get_size(&mut self, key: &KeyT) -> Option<u64> {
746 if let Some(Entry::InUse { bytes_used, .. }) = self.entries.get_mut(key) {
747 Some(*bytes_used)
748 } else {
749 None
750 }
751 }
752
753 pub fn decrement_ref_count(&mut self, key: &KeyT) {
755 let entry = self
756 .entries
757 .get_mut(key)
758 .expect("got decrement_ref_count for key with no entry");
759 let Entry::InUse {
760 file_type,
761 bytes_used,
762 ref_count,
763 } = entry
764 else {
765 panic!("got decrement_ref_count with for key with zero reference count");
766 };
767 match NonZeroU32::new(ref_count.get() - 1) {
768 Some(new_ref_count) => *ref_count = new_ref_count,
769 None => {
770 *entry = Entry::InHeap {
771 file_type: *file_type,
772 bytes_used: *bytes_used,
773 priority: self.next_priority,
774 heap_index: HeapIndex::default(),
775 };
776 self.heap.push(&mut self.entries, key.clone());
777 self.next_priority = self.next_priority.checked_add(1).unwrap();
778 self.possibly_remove_some_ignore_error();
779 }
780 }
781 }
782
783 pub fn getter_disconnected(&mut self, getter: GetStrategyT::Getter) {
784 self.entries.retain(|_, e| {
785 let Entry::Getting { jobs, getters } = e else {
786 return true;
787 };
788 jobs.retain(|jid| GetStrategyT::getter_from_job_id(*jid) != getter);
789 getters.remove(&getter);
790 let keep = !jobs.is_empty();
791 if !keep {
792 self.getting = self.getting.checked_sub(1).unwrap();
793 }
794 keep
795 });
796 }
797
798 pub fn root(&self) -> &Root<CacheDir> {
799 &self.root
800 }
801
802 pub fn cache_path(&self, key: &KeyT) -> RootBuf<EntryPath> {
804 cache_file_name(self.sha256.join(key.kind()).as_root(), key.digest())
805 }
806
807 fn possibly_remove_some_ignore_error(&mut self) {
810 if let Err(err) = self.possibly_remove_some_can_error() {
811 warn!(self.log, "error removing unused entries from cache to reclaim space"; "error" => %err);
812 }
813 }
814
815 fn possibly_remove_some_can_error(&mut self) -> Result<()> {
816 while self.bytes_used > self.bytes_used_target {
817 let Some(key) = self.heap.pop(&mut self.entries) else {
818 break;
819 };
820 let Some(Entry::InHeap {
821 file_type,
822 bytes_used,
823 ..
824 }) = self.entries.remove(&key)
825 else {
826 panic!("Entry popped off of heap was in unexpected state");
827 };
828 let cache_path = self.cache_path(&key);
829 if file_type == FileType::Directory {
830 rmdir_in_background(&self.fs, &self.removing, &cache_path)?;
831 self.fs.remove(&size_file_name(&cache_path))?;
832 } else {
833 self.fs.remove(&cache_path)?;
834 }
835 self.bytes_used = self.bytes_used.checked_sub(bytes_used).unwrap();
836 debug!(self.log, "cache removed unused artifact";
837 "key" => ?key,
838 "artifact_bytes_used" => %ByteSize::b(bytes_used),
839 "entries" => %(self.entries.len() - self.getting),
840 "bytes_used" => %ByteSize::b(self.bytes_used),
841 "byte_used_target" => %ByteSize::b(self.bytes_used_target)
842 );
843 }
844 Ok(())
845 }
846}
847
848enum CacheFileNameType {
849 Other,
850 File(Sha256Digest, u64),
851 Directory(Sha256Digest),
852 DirectorySize(Sha256Digest, u64),
853}
854
855fn remove_all_from_directory_except<S>(
856 fs: &impl Fs,
857 removing: &Root<RemovingRoot>,
858 dir: &Path,
859 except: impl IntoIterator<Item = S>,
860) -> Result<()>
861where
862 S: ToString,
863{
864 let except = except
865 .into_iter()
866 .map(|e| OsString::from(e.to_string()))
867 .collect::<HashSet<_>>();
868 for entry in fs.read_dir(dir)? {
869 let (entry_name, metadata) = entry?;
870 if !except.contains(&entry_name) {
871 let path = dir.join(entry_name);
872 if metadata.is_directory() {
873 rmdir_in_background(fs, removing, &path)?;
874 } else {
875 fs.remove(&path)?;
876 }
877 }
878 }
879 Ok(())
880}
881
882fn get_unused_removing_path<FsT: Fs>(
884 fs: &FsT,
885 removing: &Root<RemovingRoot>,
886) -> result::Result<RootBuf<RemovingPath>, FsT::Error> {
887 loop {
888 let target = removing.join(format!("{:016x}", fs.rand_u64()));
889 if fs.metadata(&target)?.is_none() {
890 break Ok(target);
891 }
892 }
893}
894
895fn rmdir_in_background(fs: &impl Fs, removing: &Root<RemovingRoot>, source: &Path) -> Result<()> {
897 let target = get_unused_removing_path(fs, removing)?;
898 fs.rename(source, &target)?;
899 fs.rmdir_recursively_on_thread(target.into_path_buf())?;
900 Ok(())
901}
902
903fn cache_file_name(dir: &Root<KindDir>, digest: &Sha256Digest) -> RootBuf<EntryPath> {
904 dir.join(digest.to_string())
905}
906
907fn size_file_name(base: &Root<EntryPath>) -> RootBuf<SizeFile> {
908 let mut result = base.to_owned().into_path_buf();
909 result.set_extension("size");
910 RootBuf::new(result)
911}
912
913fn read_size_file(
914 fs: &impl Fs,
915 kind_dir: &Root<KindDir>,
916 digest: &Sha256Digest,
917 metadata: Metadata,
918) -> Option<u64> {
919 if metadata.size != 8 {
920 return None;
921 }
922 let mut contents = [0u8; 8];
923 let bytes_read = fs
924 .read_file(
925 &size_file_name(&cache_file_name(kind_dir, digest)),
926 &mut contents,
927 )
928 .ok()?;
929 (bytes_read == 8).then_some(u64::from_be_bytes(contents))
930}
931
932fn cache_file_name_type(
933 fs: &impl Fs,
934 dir: &Root<KindDir>,
935 name: &OsStr,
936 metadata: Metadata,
937) -> CacheFileNameType {
938 if let Some(name) = name.to_str() {
939 if let Ok(digest) = Sha256Digest::from_str(name) {
940 return match metadata.type_ {
941 FileType::File => CacheFileNameType::File(digest, metadata.size),
942 FileType::Directory => CacheFileNameType::Directory(digest),
943 _ => CacheFileNameType::Other,
944 };
945 }
946 if metadata.type_ == FileType::File {
947 if let Some(name) = name.strip_suffix(".size") {
948 if let Ok(digest) = Sha256Digest::from_str(name) {
949 if let Some(size) = read_size_file(fs, dir, &digest, metadata) {
950 return CacheFileNameType::DirectorySize(digest, size);
951 }
952 }
953 }
954 }
955 }
956
957 CacheFileNameType::Other
958}
959
960fn write_size_file(fs: &impl Fs, base: &Root<EntryPath>, size: u64) -> Result<()> {
961 Ok(fs.create_file(&size_file_name(base), &size.to_be_bytes())?)
962}
963
964fn ensure_removing_directory(
965 fs: &impl Fs,
966 root: &Root<CacheDir>,
967 removing: &Root<RemovingRoot>,
968) -> Result<()> {
969 match fs.metadata(removing)? {
970 Some(metadata) if metadata.is_directory() => {
971 let removing_tmp = loop {
972 let target: RootBuf<()> = root.join(format!("removing.{:016x}", fs.rand_u64()));
973 if fs.metadata(&target)?.is_none() {
974 break target;
975 }
976 };
977 fs.rename(removing, &removing_tmp)?;
978 }
979 Some(_) => {
980 fs.remove(removing)?;
981 }
982 None => {}
983 }
984 fs.mkdir(removing)?;
985 Ok(())
986}
987
988fn kind_dir(sha256: &Root<Sha256Dir>, kind: &'static str) -> RootBuf<KindDir> {
989 sha256.join(kind)
990}
991
992#[cfg(test)]
1001mod tests {
1002 use super::{
1003 fs::{
1004 test::{self, Entry},
1005 Metadata, TempDir as _, TempFile as _,
1006 },
1007 Debug, *,
1008 };
1009 use crate::fs;
1010 use maelstrom_base::digest;
1011 use maelstrom_test::*;
1012 use slog::{o, Discard};
1013 use strum::{EnumString, IntoStaticStr, VariantNames};
1014
1015 #[derive(Clone, Copy, Debug, EnumString, Eq, Hash, IntoStaticStr, PartialEq, VariantNames)]
1016 #[strum(serialize_all = "snake_case")]
1017 enum TestKeyKind {
1018 Apple,
1019 Orange,
1020 }
1021
1022 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
1023 struct TestKey {
1024 kind: TestKeyKind,
1025 digest: Sha256Digest,
1026 }
1027
1028 impl Key for TestKey {
1029 fn kinds() -> impl Iterator<Item = &'static str> {
1030 TestKeyKind::VARIANTS.iter().copied()
1031 }
1032
1033 fn from_kind_and_digest(kind: &'static str, digest: Sha256Digest) -> Self {
1034 Self {
1035 kind: kind.try_into().unwrap(),
1036 digest,
1037 }
1038 }
1039
1040 fn kind(&self) -> &'static str {
1041 self.kind.into()
1042 }
1043
1044 fn digest(&self) -> &Sha256Digest {
1045 &self.digest
1046 }
1047 }
1048
1049 macro_rules! apple {
1050 ($digest:expr) => {
1051 TestKey {
1052 kind: TestKeyKind::Apple,
1053 digest: digest!($digest),
1054 }
1055 };
1056 }
1057
1058 macro_rules! orange {
1059 ($digest:expr) => {
1060 TestKey {
1061 kind: TestKeyKind::Orange,
1062 digest: digest!($digest),
1063 }
1064 };
1065 }
1066
1067 #[derive(Eq, Hash, PartialEq)]
1068 enum TestGetter {
1069 Small,
1070 Large,
1071 }
1072
1073 struct TestGetStrategy;
1074
1075 impl GetStrategy for TestGetStrategy {
1076 type Getter = TestGetter;
1077
1078 fn getter_from_job_id(job_id: JobId) -> Self::Getter {
1079 if job_id.cid.as_u32() < 100 {
1080 TestGetter::Small
1081 } else {
1082 TestGetter::Large
1083 }
1084 }
1085 }
1086
1087 struct Fixture {
1088 fs: test::Fs,
1089 cache: Cache<test::Fs, TestKey, TestGetStrategy>,
1090 temp_file_factory: TempFileFactory<test::Fs>,
1091 }
1092
1093 impl Fixture {
1094 fn new(bytes_used_target: u64, root: Entry) -> Self {
1095 let fs = test::Fs::new(root);
1096 let (cache, temp_file_factory) = Cache::new(
1097 fs.clone(),
1098 "/z".parse().unwrap(),
1099 ByteSize::b(bytes_used_target).into(),
1100 Logger::root(Discard, o!()),
1101 false,
1102 )
1103 .unwrap();
1104 Fixture {
1105 fs,
1106 cache,
1107 temp_file_factory,
1108 }
1109 }
1110
1111 #[track_caller]
1112 fn assert_fs(&self, expected_fs: Entry) {
1113 self.fs.assert_tree(expected_fs);
1114 }
1115
1116 #[track_caller]
1117 fn assert_fs_entry(&self, path: &str, expected_entry: Entry) {
1118 self.fs.assert_entry(Path::new(path), expected_entry);
1119 }
1120
1121 #[track_caller]
1122 fn assert_pending_recursive_rmdirs<const N: usize>(&self, paths: [&str; N]) {
1123 self.fs.assert_recursive_rmdirs(HashSet::from_iter(
1124 paths.into_iter().map(ToString::to_string),
1125 ))
1126 }
1127
1128 #[track_caller]
1129 fn assert_file_exists(&self, key: TestKey, expected_metadata: Metadata) {
1130 let cache_path = self.cache.cache_path(&key);
1131 assert_eq!(
1132 self.fs.metadata(&cache_path).unwrap().unwrap(),
1133 expected_metadata
1134 );
1135 }
1136
1137 #[track_caller]
1138 fn assert_file_does_not_exist(&self, key: TestKey) {
1139 let cache_path = self.cache.cache_path(&key);
1140 assert!(self.fs.metadata(&cache_path).unwrap().is_none());
1141 }
1142
1143 #[track_caller]
1144 fn assert_bytes_used(&self, expected_bytes_used: u64) {
1145 assert_eq!(self.cache.bytes_used, expected_bytes_used);
1146 }
1147
1148 #[track_caller]
1149 fn get_artifact(&mut self, key: TestKey, jid: JobId, expected: GetArtifact) {
1150 let result = self.cache.get_artifact(key, jid);
1151 assert_eq!(result, expected);
1152 }
1153
1154 #[track_caller]
1155 fn got_artifact_success_directory(
1156 &mut self,
1157 key: TestKey,
1158 contents: Entry,
1159 size: u64,
1160 expected: Vec<JobId>,
1161 ) {
1162 let source = self.temp_file_factory.temp_dir().unwrap();
1163 self.fs.graft(source.path(), contents);
1164 self.got_artifact_success(key, GotArtifact::directory(source, size), expected)
1165 }
1166
1167 #[track_caller]
1168 fn got_artifact_success_file(
1169 &mut self,
1170 key: TestKey,
1171 contents: &[u8],
1172 expected: Vec<JobId>,
1173 ) {
1174 let source = self.temp_file_factory.temp_file().unwrap();
1175 self.fs.graft(source.path(), Entry::file(contents));
1176 self.got_artifact_success(key, GotArtifact::file(source), expected)
1177 }
1178
1179 #[track_caller]
1180 fn got_artifact_success_symlink(
1181 &mut self,
1182 key: TestKey,
1183 target: &str,
1184 expected: Vec<JobId>,
1185 ) {
1186 let target = target.into();
1187 self.got_artifact_success(key, GotArtifact::symlink(target), expected)
1188 }
1189
1190 #[track_caller]
1191 fn got_artifact_success(
1192 &mut self,
1193 key: TestKey,
1194 artifact: GotArtifact<test::Fs>,
1195 expected: Vec<JobId>,
1196 ) {
1197 let result = self.cache.got_artifact_success(&key, artifact).unwrap();
1198 assert_eq!(result, expected);
1199 }
1200
1201 #[track_caller]
1202 fn got_artifact_failure(&mut self, key: TestKey, expected: Vec<JobId>) {
1203 let result = self.cache.got_artifact_failure(&key);
1204 assert_eq!(result, expected);
1205 }
1206
1207 fn try_increment_ref_count(&mut self, key: TestKey, expected: Option<u64>) {
1208 assert_eq!(self.cache.try_increment_ref_count(&key), expected);
1209 }
1210
1211 fn decrement_ref_count(&mut self, key: TestKey) {
1212 self.cache.decrement_ref_count(&key);
1213 }
1214 }
1215
1216 #[test]
1217 fn success_flow_with_single_getter() {
1218 let mut fixture = Fixture::new(10, fs! {});
1219 fixture.assert_bytes_used(0);
1220
1221 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1222 fixture.get_artifact(apple!(42), jid!(2), GetArtifact::Wait);
1223 fixture.get_artifact(apple!(42), jid!(3), GetArtifact::Wait);
1224 fixture.assert_file_does_not_exist(apple!(42));
1225 fixture.assert_bytes_used(0);
1226
1227 fixture.got_artifact_success_file(apple!(42), b"a", vec![jid!(1), jid!(2), jid!(3)]);
1228 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1229 fixture.assert_bytes_used(1);
1230
1231 fixture.get_artifact(apple!(42), jid!(4), GetArtifact::Success);
1232 fixture.get_artifact(apple!(42), jid!(5), GetArtifact::Success);
1233 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1234 fixture.assert_bytes_used(1);
1235 }
1236
1237 #[test]
1238 fn success_flow_with_multiple_getters() {
1239 let mut fixture = Fixture::new(10, fs! {});
1240 fixture.assert_bytes_used(0);
1241
1242 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1243 fixture.get_artifact(apple!(42), jid!(2), GetArtifact::Wait);
1244 fixture.get_artifact(apple!(42), jid!(100), GetArtifact::Get);
1245 fixture.get_artifact(apple!(42), jid!(101), GetArtifact::Wait);
1246 fixture.assert_file_does_not_exist(apple!(42));
1247 fixture.assert_bytes_used(0);
1248
1249 fixture.got_artifact_success_file(
1250 apple!(42),
1251 b"a",
1252 vec![jid!(1), jid!(2), jid!(100), jid!(101)],
1253 );
1254 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1255 fixture.assert_bytes_used(1);
1256
1257 fixture.get_artifact(apple!(42), jid!(4), GetArtifact::Success);
1258 fixture.get_artifact(apple!(42), jid!(5), GetArtifact::Success);
1259 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1260 fixture.assert_bytes_used(1);
1261 }
1262
1263 #[test]
1264 fn success_flow_with_files() {
1265 let mut fixture = Fixture::new(1, fs! {});
1266 fixture.assert_bytes_used(0);
1267
1268 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1269 fixture.get_artifact(apple!(1), jid!(2), GetArtifact::Wait);
1270 fixture.assert_bytes_used(0);
1271 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1272 fixture.assert_pending_recursive_rmdirs([]);
1273
1274 fixture.got_artifact_success_file(apple!(1), b"123456", vec![jid!(1), jid!(2)]);
1275 fixture.assert_bytes_used(6);
1276 fixture.assert_fs_entry(
1277 "/z/sha256",
1278 fs! {
1279 apple {
1280 "0000000000000000000000000000000000000000000000000000000000000001"(b"123456"),
1281 },
1282 orange {},
1283 },
1284 );
1285 fixture.assert_pending_recursive_rmdirs([]);
1286
1287 fixture.decrement_ref_count(apple!(1));
1288 fixture.decrement_ref_count(apple!(1));
1289 fixture.assert_bytes_used(0);
1290 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1291 fixture.assert_pending_recursive_rmdirs([]);
1292 }
1293
1294 #[test]
1295 fn success_flow_with_symlinks() {
1296 let mut fixture = Fixture::new(1, fs! {});
1297 fixture.assert_bytes_used(0);
1298
1299 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1300 fixture.get_artifact(apple!(1), jid!(2), GetArtifact::Wait);
1301 fixture.assert_bytes_used(0);
1302 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1303 fixture.assert_pending_recursive_rmdirs([]);
1304
1305 fixture.got_artifact_success_symlink(apple!(1), "target", vec![jid!(1), jid!(2)]);
1306 fixture.assert_bytes_used(6);
1307 fixture.assert_fs_entry(
1308 "/z/sha256",
1309 fs! {
1310 apple {
1311 "0000000000000000000000000000000000000000000000000000000000000001" -> "target",
1312 },
1313 orange {},
1314 },
1315 );
1316 fixture.assert_pending_recursive_rmdirs([]);
1317
1318 fixture.decrement_ref_count(apple!(1));
1319 fixture.decrement_ref_count(apple!(1));
1320 fixture.assert_bytes_used(0);
1321 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1322 fixture.assert_pending_recursive_rmdirs([]);
1323 }
1324
1325 #[test]
1326 fn success_flow_with_directories() {
1327 let mut fixture = Fixture::new(1, fs! {});
1328 fixture.assert_bytes_used(0);
1329
1330 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1331 fixture.get_artifact(apple!(1), jid!(2), GetArtifact::Wait);
1332 fixture.assert_bytes_used(0);
1333 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1334 fixture.assert_pending_recursive_rmdirs([]);
1335
1336 fixture.got_artifact_success_directory(
1337 apple!(1),
1338 fs! {
1339 foo(b"bar"),
1340 symlink -> "foo",
1341 },
1342 6,
1343 vec![jid!(1), jid!(2)],
1344 );
1345 fixture.assert_bytes_used(6);
1346 fixture.assert_fs_entry(
1347 "/z/sha256",
1348 fs! {
1349 apple {
1350 "0000000000000000000000000000000000000000000000000000000000000001" {
1351 foo(b"bar"),
1352 symlink -> "foo",
1353 },
1354 "0000000000000000000000000000000000000000000000000000000000000001.size"(&6u64.to_be_bytes()),
1355 },
1356 orange {},
1357 },
1358 );
1359 fixture.assert_pending_recursive_rmdirs([]);
1360
1361 fixture.decrement_ref_count(apple!(1));
1362 fixture.decrement_ref_count(apple!(1));
1363 fixture.assert_bytes_used(0);
1364 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1365 fixture.assert_pending_recursive_rmdirs(["/z/removing/0000000000000001"]);
1366 }
1367
1368 #[test]
1369 fn success_flow_with_failure_persisting() {
1370 let mut fixture = Fixture::new(1, fs! {});
1371 fixture.assert_bytes_used(0);
1372
1373 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1374 fixture.get_artifact(apple!(1), jid!(2), GetArtifact::Wait);
1375 fixture.assert_bytes_used(0);
1376 fixture.assert_fs_entry("/z/sha256", fs! { apple {}, orange {} });
1377 fixture.assert_pending_recursive_rmdirs([]);
1378
1379 let source = fixture.temp_file_factory.temp_dir().unwrap();
1380 fixture.fs.graft(source.path(), fs! { foo(b"bar") });
1381 fixture.fs.set_failure(true);
1382 let (err, jids) = fixture
1383 .cache
1384 .got_artifact_success(&apple!(1), GotArtifact::directory(source, 6))
1385 .unwrap_err();
1386 assert_eq!(jids, vec![jid!(1), jid!(2)]);
1387 assert_eq!(err.to_string(), "Test");
1388
1389 fixture.assert_bytes_used(0);
1390 fixture.assert_fs_entry(
1391 "/z/sha256",
1392 fs! {
1393 apple {},
1394 orange {},
1395 },
1396 );
1397 fixture.assert_pending_recursive_rmdirs([]);
1398 }
1399
1400 #[test]
1401 fn artifact_stays_in_cache() {
1402 let mut fixture = Fixture::new(10, fs! {});
1403
1404 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1405 fixture.got_artifact_success_file(apple!(42), b"a", vec![jid!(1)]);
1406 fixture.decrement_ref_count(apple!(42));
1407
1408 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1409 fixture.assert_bytes_used(1);
1410
1411 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Success);
1412 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1413 fixture.assert_bytes_used(1);
1414 }
1415
1416 #[test]
1417 fn large_artifact_does_not_stay_in_cache() {
1418 let mut fixture = Fixture::new(1, fs! {});
1419
1420 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1421 fixture.got_artifact_success_file(apple!(42), b"123456789", vec![jid!(1)]);
1422 fixture.assert_bytes_used(9);
1423 fixture.decrement_ref_count(apple!(42));
1424
1425 fixture.assert_file_does_not_exist(apple!(42));
1426 fixture.assert_bytes_used(0);
1427 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1428 }
1429
1430 #[test]
1431 fn large_artifact_stays_in_cache_while_referenced() {
1432 let mut fixture = Fixture::new(1, fs! {});
1433
1434 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1435 fixture.got_artifact_success_file(apple!(42), b"123456789", vec![jid!(1)]);
1436
1437 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Success);
1438
1439 fixture.decrement_ref_count(apple!(42));
1440 fixture.assert_file_exists(apple!(42), Metadata::file(9));
1441 fixture.assert_bytes_used(9);
1442
1443 fixture.decrement_ref_count(apple!(42));
1444 fixture.assert_file_does_not_exist(apple!(42));
1445 fixture.assert_bytes_used(0);
1446 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1447 }
1448
1449 #[test]
1450 fn different_key_kinds_are_independent() {
1451 let mut fixture = Fixture::new(1, fs! {});
1452
1453 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1454 fixture.get_artifact(apple!(42), jid!(2), GetArtifact::Wait);
1455 fixture.get_artifact(apple!(42), jid!(3), GetArtifact::Wait);
1456 fixture.assert_file_does_not_exist(apple!(42));
1457 fixture.assert_bytes_used(0);
1458
1459 fixture.get_artifact(orange!(42), jid!(4), GetArtifact::Get);
1460 fixture.get_artifact(orange!(42), jid!(5), GetArtifact::Wait);
1461 fixture.assert_file_does_not_exist(orange!(42));
1462 fixture.assert_bytes_used(0);
1463
1464 fixture.got_artifact_success_file(apple!(42), b"abc", vec![jid!(1), jid!(2), jid!(3)]);
1465 fixture.assert_file_exists(apple!(42), Metadata::file(3));
1466 fixture.assert_file_does_not_exist(orange!(42));
1467 fixture.assert_bytes_used(3);
1468
1469 fixture.get_artifact(orange!(42), jid!(6), GetArtifact::Wait);
1470
1471 fixture.got_artifact_success_file(orange!(42), b"1234", vec![jid!(4), jid!(5), jid!(6)]);
1472 fixture.assert_file_exists(apple!(42), Metadata::file(3));
1473 fixture.assert_file_exists(orange!(42), Metadata::file(4));
1474 fixture.assert_bytes_used(7);
1475
1476 fixture.decrement_ref_count(apple!(42));
1477 fixture.decrement_ref_count(apple!(42));
1478 fixture.decrement_ref_count(apple!(42));
1479 fixture.assert_file_does_not_exist(apple!(42));
1480 fixture.assert_file_exists(orange!(42), Metadata::file(4));
1481 fixture.assert_bytes_used(4);
1482
1483 fixture.decrement_ref_count(orange!(42));
1484 fixture.decrement_ref_count(orange!(42));
1485 fixture.decrement_ref_count(orange!(42));
1486 fixture.assert_file_does_not_exist(apple!(42));
1487 fixture.assert_file_does_not_exist(orange!(42));
1488 fixture.assert_bytes_used(0);
1489 }
1490
1491 #[test]
1492 fn entries_are_removed_in_lru_order() {
1493 let mut fixture = Fixture::new(3, fs! {});
1494
1495 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1496 fixture.get_artifact(orange!(2), jid!(2), GetArtifact::Get);
1497 fixture.get_artifact(apple!(3), jid!(3), GetArtifact::Get);
1498 fixture.get_artifact(orange!(4), jid!(4), GetArtifact::Get);
1499 fixture.assert_bytes_used(0);
1500
1501 fixture.got_artifact_success_file(apple!(1), b"a", vec![jid!(1)]);
1502 fixture.got_artifact_success_file(orange!(2), b"b", vec![jid!(2)]);
1503 fixture.got_artifact_success_file(apple!(3), b"c", vec![jid!(3)]);
1504 fixture.got_artifact_success_file(orange!(4), b"d", vec![jid!(4)]);
1505
1506 fixture.assert_file_exists(apple!(1), Metadata::file(1));
1507 fixture.assert_file_exists(orange!(2), Metadata::file(1));
1508 fixture.assert_file_exists(apple!(3), Metadata::file(1));
1509 fixture.assert_file_exists(orange!(4), Metadata::file(1));
1510 fixture.assert_bytes_used(4);
1511
1512 fixture.decrement_ref_count(orange!(4));
1513
1514 fixture.assert_file_exists(apple!(1), Metadata::file(1));
1515 fixture.assert_file_exists(orange!(2), Metadata::file(1));
1516 fixture.assert_file_exists(apple!(3), Metadata::file(1));
1517 fixture.assert_file_does_not_exist(orange!(4));
1518 fixture.assert_bytes_used(3);
1519 fixture.get_artifact(orange!(4), jid!(14), GetArtifact::Get);
1520
1521 fixture.decrement_ref_count(apple!(3));
1522 fixture.decrement_ref_count(orange!(2));
1523 fixture.decrement_ref_count(apple!(1));
1524
1525 fixture.assert_file_exists(apple!(1), Metadata::file(1));
1526 fixture.assert_file_exists(orange!(2), Metadata::file(1));
1527 fixture.assert_file_exists(apple!(3), Metadata::file(1));
1528 fixture.assert_file_does_not_exist(orange!(4));
1529 fixture.assert_bytes_used(3);
1530
1531 fixture.get_artifact(apple!(5), jid!(5), GetArtifact::Get);
1532 fixture.got_artifact_success_file(apple!(5), b"e", vec![jid!(5)]);
1533
1534 fixture.assert_file_exists(apple!(1), Metadata::file(1));
1535 fixture.assert_file_exists(orange!(2), Metadata::file(1));
1536 fixture.assert_file_does_not_exist(apple!(3));
1537 fixture.assert_file_does_not_exist(orange!(4));
1538 fixture.assert_file_exists(apple!(5), Metadata::file(1));
1539 fixture.assert_bytes_used(3);
1540 fixture.get_artifact(apple!(3), jid!(13), GetArtifact::Get);
1541
1542 fixture.get_artifact(orange!(2), jid!(12), GetArtifact::Success);
1543 fixture.decrement_ref_count(orange!(2));
1544
1545 fixture.get_artifact(orange!(6), jid!(6), GetArtifact::Get);
1546 fixture.got_artifact_success_file(orange!(6), b"f", vec![jid!(6)]);
1547
1548 fixture.assert_file_does_not_exist(apple!(1));
1549 fixture.assert_file_exists(orange!(2), Metadata::file(1));
1550 fixture.assert_file_does_not_exist(apple!(3));
1551 fixture.assert_file_does_not_exist(orange!(4));
1552 fixture.assert_file_exists(apple!(5), Metadata::file(1));
1553 fixture.assert_file_exists(orange!(6), Metadata::file(1));
1554 fixture.assert_bytes_used(3);
1555 fixture.get_artifact(apple!(1), jid!(11), GetArtifact::Get);
1556 }
1557
1558 #[test]
1559 fn failure_flow() {
1560 let mut fixture = Fixture::new(10, fs! {});
1561 fixture.assert_bytes_used(0);
1562
1563 fixture.get_artifact(apple!(42), jid!(1), GetArtifact::Get);
1564 fixture.get_artifact(apple!(42), jid!(2), GetArtifact::Wait);
1565 fixture.get_artifact(apple!(42), jid!(3), GetArtifact::Wait);
1566 fixture.assert_file_does_not_exist(apple!(42));
1567 fixture.assert_bytes_used(0);
1568
1569 fixture.got_artifact_failure(apple!(42), vec![jid!(1), jid!(2), jid!(3)]);
1570 fixture.assert_file_does_not_exist(apple!(42));
1571 fixture.assert_bytes_used(0);
1572
1573 fixture.get_artifact(apple!(42), jid!(4), GetArtifact::Get);
1574 fixture.get_artifact(apple!(42), jid!(5), GetArtifact::Wait);
1575 fixture.get_artifact(apple!(42), jid!(6), GetArtifact::Wait);
1576
1577 fixture.got_artifact_success_file(apple!(42), b"a", vec![jid!(4), jid!(5), jid!(6)]);
1578 fixture.assert_file_exists(apple!(42), Metadata::file(1));
1579 fixture.assert_bytes_used(1);
1580 }
1581
1582 #[test]
1583 fn try_increment_ref_count() {
1584 let mut fixture = Fixture::new(10, fs! {});
1585
1586 fixture.get_artifact(apple!(2), jid!(1), GetArtifact::Get);
1587
1588 fixture.get_artifact(apple!(3), jid!(1), GetArtifact::Get);
1589 fixture.got_artifact_success_file(apple!(3), b"abc", vec![jid!(1)]);
1590 fixture.decrement_ref_count(apple!(3));
1591
1592 fixture.get_artifact(apple!(4), jid!(1), GetArtifact::Get);
1593 fixture.got_artifact_success_file(apple!(4), b"def", vec![jid!(1)]);
1594
1595 fixture.try_increment_ref_count(apple!(1), None);
1596 fixture.try_increment_ref_count(apple!(2), None);
1597 fixture.try_increment_ref_count(apple!(3), None);
1598 fixture.try_increment_ref_count(apple!(4), Some(3));
1599
1600 fixture.get_artifact(apple!(5), jid!(1), GetArtifact::Get);
1601 fixture.got_artifact_success_file(apple!(5), b"0123456789", vec![jid!(1)]);
1602 fixture.assert_bytes_used(13);
1603
1604 fixture.decrement_ref_count(apple!(4));
1605 fixture.assert_bytes_used(13);
1606
1607 fixture.decrement_ref_count(apple!(4));
1608 fixture.assert_bytes_used(10);
1609 }
1610
1611 #[test]
1612 fn got_artifact_failure_no_entry() {
1613 let mut fixture = Fixture::new(1, fs! {});
1614
1615 fixture.got_artifact_failure(apple!(1), vec![]);
1616 }
1617
1618 #[test]
1619 fn got_artifact_failure_in_use() {
1620 let mut fixture = Fixture::new(1, fs! {});
1621
1622 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1623 fixture.got_artifact_success_file(apple!(1), b"abc", vec![jid!(1)]);
1624
1625 fixture.got_artifact_failure(apple!(1), vec![]);
1626 }
1627
1628 #[test]
1629 fn got_artifact_failure_in_cache() {
1630 let mut fixture = Fixture::new(100, fs! {});
1631
1632 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1633 fixture.got_artifact_success_file(apple!(1), b"abc", vec![jid!(1)]);
1634 fixture.decrement_ref_count(apple!(1));
1635
1636 fixture.got_artifact_failure(apple!(1), vec![]);
1637 }
1638
1639 #[test]
1640 fn got_artifact_success_directory_no_entry() {
1641 let mut fixture = Fixture::new(10, fs! {});
1642
1643 fixture.got_artifact_success_directory(apple!(1), fs! { foo(b"foo") }, 1, vec![]);
1644 fixture.assert_fs(fs! {
1645 z {
1646 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1647 "lock"(b""),
1648 removing {
1649 "0000000000000001" {
1650 foo(b"foo"),
1651 },
1652 },
1653 sha256 {
1654 apple {},
1655 orange {},
1656 },
1657 tmp {},
1658 },
1659 });
1660 fixture.assert_pending_recursive_rmdirs(["/z/removing/0000000000000001"]);
1661 }
1662
1663 #[test]
1664 fn got_artifact_success_file_no_entry() {
1665 let mut fixture = Fixture::new(1, fs! {});
1666
1667 fixture.got_artifact_success_file(apple!(1), b"abc", vec![]);
1668 fixture.assert_fs(fs! {
1669 z {
1670 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1671 "lock"(b""),
1672 removing {
1673 },
1674 sha256 {
1675 apple {},
1676 orange {},
1677 },
1678 tmp {},
1679 },
1680 });
1681 fixture.assert_pending_recursive_rmdirs([]);
1682 }
1683
1684 #[test]
1685 fn got_artifact_success_symlink_no_entry() {
1686 let mut fixture = Fixture::new(1, fs! {});
1687
1688 fixture.got_artifact_success_symlink(apple!(1), "/target", vec![]);
1689 fixture.assert_fs(fs! {
1690 z {
1691 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1692 "lock"(b""),
1693 removing {
1694 },
1695 sha256 {
1696 apple {},
1697 orange {},
1698 },
1699 tmp {},
1700 },
1701 });
1702 fixture.assert_pending_recursive_rmdirs([]);
1703 }
1704
1705 #[test]
1706 fn got_artifact_success_directory_in_use() {
1707 let mut fixture = Fixture::new(10, fs! {});
1708
1709 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1710 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1711 fixture.got_artifact_success_directory(
1712 apple!(1),
1713 fs! { foo(b"foo") },
1714 1,
1715 vec![jid!(1), jid!(100)],
1716 );
1717 fixture.got_artifact_success_directory(apple!(1), fs! { foo(b"bar") }, 1, vec![]);
1718 fixture.assert_fs(fs! {
1719 z {
1720 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1721 "lock"(b""),
1722 removing {
1723 "0000000000000001" {
1724 foo(b"bar"),
1725 },
1726 },
1727 sha256 {
1728 apple {
1729 "0000000000000000000000000000000000000000000000000000000000000001" {
1730 foo(b"foo"),
1731 },
1732 "0000000000000000000000000000000000000000000000000000000000000001.size"(b"\0\0\0\0\0\0\0\x01"),
1733 },
1734 orange {},
1735 },
1736 tmp {},
1737 },
1738 });
1739 fixture.assert_pending_recursive_rmdirs(["/z/removing/0000000000000001"]);
1740 }
1741
1742 #[test]
1743 fn got_artifact_success_file_in_use() {
1744 let mut fixture = Fixture::new(1, fs! {});
1745
1746 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1747 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1748 fixture.got_artifact_success_file(apple!(1), b"foo", vec![jid!(1), jid!(100)]);
1749 fixture.got_artifact_success_file(apple!(1), b"bar", vec![]);
1750 fixture.assert_fs(fs! {
1751 z {
1752 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1753 "lock"(b""),
1754 removing {
1755 },
1756 sha256 {
1757 apple {
1758 "0000000000000000000000000000000000000000000000000000000000000001"(b"foo"),
1759 },
1760 orange {},
1761 },
1762 tmp {},
1763 },
1764 });
1765 fixture.assert_pending_recursive_rmdirs([]);
1766 }
1767
1768 #[test]
1769 fn got_artifact_success_symlink_in_use() {
1770 let mut fixture = Fixture::new(1, fs! {});
1771
1772 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1773 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1774 fixture.got_artifact_success_symlink(apple!(1), "/foo", vec![jid!(1), jid!(100)]);
1775 fixture.got_artifact_success_symlink(apple!(1), "/bar", vec![]);
1776 fixture.assert_fs(fs! {
1777 z {
1778 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1779 "lock"(b""),
1780 removing {
1781 },
1782 sha256 {
1783 apple {
1784 "0000000000000000000000000000000000000000000000000000000000000001" -> "/foo",
1785 },
1786 orange {},
1787 },
1788 tmp {},
1789 },
1790 });
1791 fixture.assert_pending_recursive_rmdirs([]);
1792 }
1793
1794 #[test]
1795 fn got_artifact_success_directory_in_cache() {
1796 let mut fixture = Fixture::new(10, fs! {});
1797
1798 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1799 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1800 fixture.got_artifact_success_directory(
1801 apple!(1),
1802 fs! { foo(b"foo") },
1803 1,
1804 vec![jid!(1), jid!(100)],
1805 );
1806 fixture.decrement_ref_count(apple!(1));
1807 fixture.decrement_ref_count(apple!(1));
1808 fixture.got_artifact_success_directory(apple!(1), fs! { foo(b"bar") }, 1, vec![]);
1809 fixture.assert_fs(fs! {
1810 z {
1811 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1812 "lock"(b""),
1813 removing {
1814 "0000000000000001" {
1815 foo(b"bar"),
1816 },
1817 },
1818 sha256 {
1819 apple {
1820 "0000000000000000000000000000000000000000000000000000000000000001" {
1821 foo(b"foo"),
1822 },
1823 "0000000000000000000000000000000000000000000000000000000000000001.size"(b"\0\0\0\0\0\0\0\x01"),
1824 },
1825 orange {},
1826 },
1827 tmp {},
1828 },
1829 });
1830 fixture.assert_pending_recursive_rmdirs(["/z/removing/0000000000000001"]);
1831 }
1832
1833 #[test]
1834 fn got_artifact_success_file_in_cache() {
1835 let mut fixture = Fixture::new(10, fs! {});
1836
1837 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1838 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1839 fixture.got_artifact_success_file(apple!(1), b"foo", vec![jid!(1), jid!(100)]);
1840 fixture.decrement_ref_count(apple!(1));
1841 fixture.decrement_ref_count(apple!(1));
1842 fixture.got_artifact_success_file(apple!(1), b"bar", vec![]);
1843 fixture.assert_fs(fs! {
1844 z {
1845 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1846 "lock"(b""),
1847 removing {
1848 },
1849 sha256 {
1850 apple {
1851 "0000000000000000000000000000000000000000000000000000000000000001"(b"foo"),
1852 },
1853 orange {},
1854 },
1855 tmp {},
1856 },
1857 });
1858 fixture.assert_pending_recursive_rmdirs([]);
1859 }
1860
1861 #[test]
1862 fn got_artifact_success_symlink_in_cache() {
1863 let mut fixture = Fixture::new(10, fs! {});
1864
1865 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1866 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1867 fixture.got_artifact_success_symlink(apple!(1), "/foo", vec![jid!(1), jid!(100)]);
1868 fixture.decrement_ref_count(apple!(1));
1869 fixture.decrement_ref_count(apple!(1));
1870 fixture.got_artifact_success_symlink(apple!(1), "/bar", vec![]);
1871 fixture.assert_fs(fs! {
1872 z {
1873 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1874 "lock"(b""),
1875 removing {
1876 },
1877 sha256 {
1878 apple {
1879 "0000000000000000000000000000000000000000000000000000000000000001" -> "/foo",
1880 },
1881 orange {},
1882 },
1883 tmp {},
1884 },
1885 });
1886 fixture.assert_pending_recursive_rmdirs([]);
1887 }
1888
1889 #[test]
1890 fn getter_disconnected_getting() {
1891 let mut fixture = Fixture::new(1, fs! {});
1892
1893 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1894 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1895
1896 fixture.cache.getter_disconnected(TestGetter::Large);
1897
1898 fixture.got_artifact_success_file(apple!(1), b"foo", vec![jid!(1)]);
1900
1901 fixture.assert_bytes_used(3);
1902 fixture.decrement_ref_count(apple!(1));
1903 fixture.assert_bytes_used(0);
1904 }
1905
1906 #[test]
1907 fn getter_disconnected_in_use() {
1908 let mut fixture = Fixture::new(1, fs! {});
1909
1910 fixture.get_artifact(apple!(1), jid!(1), GetArtifact::Get);
1911 fixture.get_artifact(apple!(1), jid!(100), GetArtifact::Get);
1912
1913 fixture.got_artifact_success_file(apple!(1), b"foo", vec![jid!(1), jid!(100)]);
1914
1915 fixture.cache.getter_disconnected(TestGetter::Large);
1917
1918 fixture.assert_bytes_used(3);
1919 fixture.decrement_ref_count(apple!(1));
1920 fixture.assert_bytes_used(3);
1921 fixture.decrement_ref_count(apple!(1));
1922 fixture.assert_bytes_used(0);
1923 }
1924
1925 #[test]
1926 fn cache_path() {
1927 let fixture = Fixture::new(10, fs! {});
1928 assert_eq!(
1929 fixture.cache.cache_path(&apple!(42)).into_path_buf(),
1930 PathBuf::from(format!("/z/sha256/apple/{:0>64x}", 42)),
1931 );
1932 }
1933
1934 #[test]
1935 fn temp_file() {
1936 let fixture = Fixture::new(10, fs! {});
1937 let temp_file = fixture.temp_file_factory.temp_file().unwrap();
1938 assert_eq!(temp_file.path(), Path::new("/z/tmp/000"));
1939 assert_eq!(
1940 fixture.fs.metadata(temp_file.path()).unwrap().unwrap(),
1941 Metadata::file(0)
1942 );
1943 }
1944
1945 #[test]
1946 fn temp_dir() {
1947 let fixture = Fixture::new(10, fs! {});
1948 let temp_dir = fixture.temp_file_factory.temp_dir().unwrap();
1949 assert_eq!(temp_dir.path(), Path::new("/z/tmp/000"));
1950 assert_eq!(
1951 fixture.fs.metadata(temp_dir.path()).unwrap().unwrap(),
1952 Metadata::directory(0)
1953 );
1954 }
1955
1956 #[test]
1957 fn new_with_no_cache_directory() {
1958 let fixture = Fixture::new(1000, fs! {});
1959 fixture.assert_fs(fs! {
1960 z {
1961 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
1962 "lock"(b""),
1963 removing {
1964 },
1965 sha256 {
1966 apple {},
1967 orange {},
1968 },
1969 tmp {},
1970 },
1971 });
1972 fixture.assert_pending_recursive_rmdirs([]);
1973 }
1974
1975 #[test]
1976 fn new_without_cachedir_tag_does_proper_cleanup_with_removing_dir() {
1977 let fixture = Fixture::new(
1978 1000,
1979 fs! {
1980 z {
1981 removing {
1982 "0000000000000001" { a(b"a contents") },
1983 "0000000000000002"(b"2 contents"),
1984 "0000000000000003" -> "foo",
1985 },
1986 sha256 {
1987 apple {
1988 bad_apple {
1989 foo(b"foo contents"),
1990 symlink -> "bad_apple",
1991 more_bad_apple {
1992 bar(b"bar contents"),
1993 },
1994 },
1995 },
1996 banana {
1997 bread(b"bread contents"),
1998 },
1999 },
2000 garbage {
2001 foo(b"more foo contents"),
2002 symlink -> "garbage",
2003 more_garbage {
2004 bar(b"more bar contents"),
2005 },
2006 },
2007 foo(b"foo contents"),
2008 symlink -> "garbage",
2009 },
2010 },
2011 );
2012 fixture.assert_fs(fs! {
2013 z {
2014 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2015 "lock"(b""),
2016 removing {
2017 "0000000000000002" {
2018 foo(b"more foo contents"),
2019 symlink -> "garbage",
2020 more_garbage {
2021 bar(b"more bar contents"),
2022 },
2023 },
2024 "0000000000000003" {
2025 "0000000000000001" { a(b"a contents") },
2026 "0000000000000002"(b"2 contents"),
2027 "0000000000000003" -> "foo",
2028 },
2029 "0000000000000004" {
2030 apple {
2031 bad_apple {
2032 foo(b"foo contents"),
2033 symlink -> "bad_apple",
2034 more_bad_apple {
2035 bar(b"bar contents"),
2036 },
2037 },
2038 },
2039 banana {
2040 bread(b"bread contents"),
2041 },
2042 },
2043 },
2044 sha256 {
2045 apple {},
2046 orange {},
2047 },
2048 tmp {},
2049 },
2050 });
2051 fixture.assert_pending_recursive_rmdirs([
2052 "/z/removing/0000000000000002",
2053 "/z/removing/0000000000000003",
2054 "/z/removing/0000000000000004",
2055 ]);
2056 }
2057
2058 #[test]
2059 fn new_without_cachedir_tag_does_proper_cleanup_with_removing_file() {
2060 let fixture = Fixture::new(
2061 1000,
2062 fs! {
2063 z {
2064 removing (b"removing"),
2065 sha256 {
2066 apple {
2067 bad_apple {
2068 foo(b"foo contents"),
2069 symlink -> "bad_apple",
2070 more_bad_apple {
2071 bar(b"bar contents"),
2072 },
2073 },
2074 },
2075 banana {
2076 bread(b"bread contents"),
2077 },
2078 },
2079 garbage {
2080 foo(b"more foo contents"),
2081 symlink -> "garbage",
2082 more_garbage {
2083 bar(b"more bar contents"),
2084 },
2085 },
2086 foo(b"foo contents"),
2087 symlink -> "garbage",
2088 },
2089 },
2090 );
2091 fixture.assert_fs(fs! {
2092 z {
2093 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2094 "lock"(b""),
2095 removing {
2096 "0000000000000001" {
2097 foo(b"more foo contents"),
2098 symlink -> "garbage",
2099 more_garbage {
2100 bar(b"more bar contents"),
2101 },
2102 },
2103 "0000000000000002" {
2104 apple {
2105 bad_apple {
2106 foo(b"foo contents"),
2107 symlink -> "bad_apple",
2108 more_bad_apple {
2109 bar(b"bar contents"),
2110 },
2111 },
2112 },
2113 banana {
2114 bread(b"bread contents"),
2115 },
2116 },
2117 },
2118 sha256 {
2119 apple {},
2120 orange {},
2121 },
2122 tmp {},
2123 },
2124 });
2125 fixture.assert_pending_recursive_rmdirs([
2126 "/z/removing/0000000000000001",
2127 "/z/removing/0000000000000002",
2128 ]);
2129 }
2130
2131 #[test]
2132 fn new_without_cachedir_tag_does_proper_cleanup_with_no_removing_file_or_dir() {
2133 let fixture = Fixture::new(
2134 1000,
2135 fs! {
2136 z {
2137 sha256 {
2138 apple {
2139 bad_apple {
2140 foo(b"foo contents"),
2141 symlink -> "bad_apple",
2142 more_bad_apple {
2143 bar(b"bar contents"),
2144 },
2145 },
2146 },
2147 banana {
2148 bread(b"bread contents"),
2149 },
2150 },
2151 garbage {
2152 foo(b"more foo contents"),
2153 symlink -> "garbage",
2154 more_garbage {
2155 bar(b"more bar contents"),
2156 },
2157 },
2158 foo(b"foo contents"),
2159 symlink -> "garbage",
2160 },
2161 },
2162 );
2163 fixture.assert_fs(fs! {
2164 z {
2165 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2166 "lock"(b""),
2167 removing {
2168 "0000000000000001" {
2169 foo(b"more foo contents"),
2170 symlink -> "garbage",
2171 more_garbage {
2172 bar(b"more bar contents"),
2173 },
2174 },
2175 "0000000000000002" {
2176 apple {
2177 bad_apple {
2178 foo(b"foo contents"),
2179 symlink -> "bad_apple",
2180 more_bad_apple {
2181 bar(b"bar contents"),
2182 },
2183 },
2184 },
2185 banana {
2186 bread(b"bread contents"),
2187 },
2188 },
2189 },
2190 sha256 {
2191 apple {},
2192 orange {},
2193 },
2194 tmp {},
2195 },
2196 });
2197 fixture.assert_pending_recursive_rmdirs([
2198 "/z/removing/0000000000000001",
2199 "/z/removing/0000000000000002",
2200 ]);
2201 }
2202
2203 #[test]
2204 fn new_with_cachedir() {
2205 let mut fixture = Fixture::new(
2206 1000,
2207 fs! {
2208 z {
2209 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2210 removing {
2211 "0000000000000001" { a(b"a contents") },
2212 "0000000000000002"(b"2 contents"),
2213 "0000000000000003" -> "foo",
2214 },
2215 sha256 {
2216 apple {
2217 "0000000000000000000000000000000000000000000000000000000000000001" -> "target",
2218 "0000000000000000000000000000000000000000000000000000000000000002"(b"02"),
2219 "0000000000000000000000000000000000000000000000000000000000000003" {
2220 foo(b"foo contents"),
2221 symlink -> "bad_apple",
2222 more_bad_apple {
2223 bar(b"bar contents"),
2224 },
2225 },
2226 "0000000000000000000000000000000000000000000000000000000000000004.size"(b"ab"),
2227 garbage_file(b"ab"),
2228 garbage_directory {
2229 blah(b"blah contents"),
2230 },
2231 "0000000000000000000000000000000000000000000000000000000000000005" {
2232 keep(b"keep"),
2233 },
2234 "0000000000000000000000000000000000000000000000000000000000000005.size"(b"\0\0\0\0\0\0\0\xff"),
2235 },
2236 banana {
2237 bread(b"bread contents"),
2238 },
2239 },
2240 garbage {
2241 foo(b"more foo contents"),
2242 symlink -> "garbage",
2243 more_garbage {
2244 bar(b"more bar contents"),
2245 },
2246 },
2247 foo(b"foo contents"),
2248 symlink -> "garbage",
2249 tmp {
2250 "tmp.1"(b"tmp.1"),
2251 },
2252 },
2253 },
2254 );
2255 fixture.assert_fs(fs! {
2256 z {
2257 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2258 "lock"(b""),
2259 removing {
2260 "0000000000000002" {
2261 foo(b"more foo contents"),
2262 symlink -> "garbage",
2263 more_garbage {
2264 bar(b"more bar contents"),
2265 },
2266 },
2267 "0000000000000003" {
2268 "0000000000000001" { a(b"a contents") },
2269 "0000000000000002"(b"2 contents"),
2270 "0000000000000003" -> "foo",
2271 },
2272 "0000000000000004" {
2273 "tmp.1"(b"tmp.1"),
2274 },
2275 "0000000000000005" {
2276 bread(b"bread contents"),
2277 },
2278 "0000000000000006" {
2279 blah(b"blah contents"),
2280 },
2281 "0000000000000007" {
2282 foo(b"foo contents"),
2283 symlink -> "bad_apple",
2284 more_bad_apple {
2285 bar(b"bar contents"),
2286 },
2287 },
2288 },
2289 sha256 {
2290 apple {
2291 "0000000000000000000000000000000000000000000000000000000000000002"(b"02"),
2292 "0000000000000000000000000000000000000000000000000000000000000005" {
2293 keep(b"keep"),
2294 },
2295 "0000000000000000000000000000000000000000000000000000000000000005.size"(b"\0\0\0\0\0\0\0\xff"),
2296 },
2297 orange {},
2298 },
2299 tmp {},
2300 },
2301 });
2302 fixture.assert_pending_recursive_rmdirs([
2303 "/z/removing/0000000000000002",
2304 "/z/removing/0000000000000003",
2305 "/z/removing/0000000000000004",
2306 "/z/removing/0000000000000005",
2307 "/z/removing/0000000000000006",
2308 "/z/removing/0000000000000007",
2309 ]);
2310
2311 fixture.assert_bytes_used(257);
2312 fixture.get_artifact(apple!(2), jid!(1), GetArtifact::Success);
2313 fixture.get_artifact(apple!(5), jid!(1), GetArtifact::Success);
2314 }
2315
2316 #[test]
2317 fn new_without_cachedir_tag_with_lock_file_contends() {
2318 let fixture = Fixture::new(
2319 1000,
2320 fs! {
2321 z {
2322 "lock"(b""),
2323 },
2324 },
2325 );
2326 fixture.assert_fs(fs! {
2327 z {
2328 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2329 "lock"(b""),
2330 removing {},
2331 sha256 {
2332 apple {},
2333 orange {},
2334 },
2335 tmp {},
2336 },
2337 });
2338 fixture.assert_pending_recursive_rmdirs([]);
2339
2340 let Err(err) = Cache::<test::Fs, TestKey, TestGetStrategy>::new(
2341 fixture.fs.clone(),
2342 "/z".parse().unwrap(),
2343 ByteSize::b(1).into(),
2344 Logger::root(Discard, o!()),
2345 false,
2346 ) else {
2347 panic!("expected error");
2348 };
2349
2350 assert_eq!(
2351 err.to_string(),
2352 "lock file \"/z/lock\" is held by a different process"
2353 );
2354 }
2355
2356 #[test]
2357 fn new_without_cachedir_tag_without_lock_file_contends() {
2358 let fixture = Fixture::new(1000, fs! {});
2359 fixture.assert_fs(fs! {
2360 z {
2361 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2362 "lock"(b""),
2363 removing {},
2364 sha256 {
2365 apple {},
2366 orange {},
2367 },
2368 tmp {},
2369 },
2370 });
2371 fixture.assert_pending_recursive_rmdirs([]);
2372
2373 let Err(err) = Cache::<test::Fs, TestKey, TestGetStrategy>::new(
2374 fixture.fs.clone(),
2375 "/z".parse().unwrap(),
2376 ByteSize::b(1).into(),
2377 Logger::root(Discard, o!()),
2378 false,
2379 ) else {
2380 panic!("expected error");
2381 };
2382
2383 assert_eq!(
2384 err.to_string(),
2385 "lock file \"/z/lock\" is held by a different process"
2386 );
2387 }
2388
2389 #[test]
2390 fn new_with_cachedir_tag_with_lock_file_contends() {
2391 let fixture = Fixture::new(
2392 1000,
2393 fs! {
2394 z {
2395 "CACHEDIR.TAG"(&CACHEDIR_TAG_CONTENTS),
2396 "lock"(b""),
2397 removing {},
2398 sha256 {
2399 apple {},
2400 orange {},
2401 },
2402 tmp {},
2403 },
2404 },
2405 );
2406 fixture.assert_pending_recursive_rmdirs([
2407 "/z/removing/0000000000000002",
2408 "/z/removing/0000000000000003",
2409 ]);
2410
2411 let Err(err) = Cache::<test::Fs, TestKey, TestGetStrategy>::new(
2412 fixture.fs.clone(),
2413 "/z".parse().unwrap(),
2414 ByteSize::b(1).into(),
2415 Logger::root(Discard, o!()),
2416 false,
2417 ) else {
2418 panic!("expected error");
2419 };
2420
2421 assert_eq!(
2422 err.to_string(),
2423 "lock file \"/z/lock\" is held by a different process"
2424 );
2425 }
2426}