kismet_cache/
stack.rs

1//! We expect most callers to interact with Kismet via the [`Cache`]
2//! struct defined here.  A [`Cache`] hides the difference in
3//! behaviour between [`crate::plain::Cache`] and
4//! [`crate::sharded::Cache`] via late binding, and lets callers
5//! transparently handle misses by looking in a series of secondary
6//! cache directories.
7use std::borrow::Cow;
8use std::fs::File;
9use std::io::Error;
10use std::io::ErrorKind;
11use std::io::Result;
12use std::path::Path;
13use std::sync::Arc;
14
15use derivative::Derivative;
16use tempfile::NamedTempFile;
17
18use crate::plain::Cache as PlainCache;
19use crate::sharded::Cache as ShardedCache;
20use crate::Key;
21use crate::ReadOnlyCache;
22use crate::ReadOnlyCacheBuilder;
23
24/// A `ConsistencyChecker` function compares cached values for the
25/// same key and returns `Err` when the values are incompatible.
26type ConsistencyChecker = Arc<
27    dyn Fn(&mut File, &mut File) -> Result<()>
28        + Sync
29        + Send
30        + std::panic::RefUnwindSafe
31        + std::panic::UnwindSafe,
32>;
33
34/// The `FullCache` trait exposes both read and write operations as
35/// implemented by sharded and plain caches.
36trait FullCache:
37    std::fmt::Debug + Sync + Send + std::panic::RefUnwindSafe + std::panic::UnwindSafe
38{
39    /// Returns a read-only file for `key` in the cache directory if
40    /// it exists, or None if there is no such file.
41    ///
42    /// Implicitly "touches" the cached file if it exists.
43    fn get(&self, key: Key) -> Result<Option<File>>;
44
45    /// Returns a temporary directory suitable for temporary files
46    /// that will be published as `key`.
47    fn temp_dir(&self, key: Key) -> Result<Cow<Path>>;
48
49    /// Inserts or overwrites the file at `value` as `key` in the
50    /// sharded cache directory.
51    ///
52    /// Always consumes the file at `value` on success; may consume it
53    /// on error.
54    fn set(&self, key: Key, value: &Path) -> Result<()>;
55
56    /// Inserts the file at `value` as `key` in the cache directory if
57    /// there is no such cached entry already, or touches the cached
58    /// file if it already exists.
59    ///
60    /// Always consumes the file at `value` on success; may consume it
61    /// on error.
62    fn put(&self, key: Key, value: &Path) -> Result<()>;
63
64    /// Marks the cached file `key` as newly used, if it exists.
65    ///
66    /// Returns whether a file for `key` exists in the cache.
67    fn touch(&self, key: Key) -> Result<bool>;
68}
69
70impl FullCache for PlainCache {
71    fn get(&self, key: Key) -> Result<Option<File>> {
72        PlainCache::get(self, key.name)
73    }
74
75    fn temp_dir(&self, _key: Key) -> Result<Cow<Path>> {
76        PlainCache::temp_dir(self)
77    }
78
79    fn set(&self, key: Key, value: &Path) -> Result<()> {
80        PlainCache::set(self, key.name, value)
81    }
82
83    fn put(&self, key: Key, value: &Path) -> Result<()> {
84        PlainCache::put(self, key.name, value)
85    }
86
87    fn touch(&self, key: Key) -> Result<bool> {
88        PlainCache::touch(self, key.name)
89    }
90}
91
92impl FullCache for ShardedCache {
93    fn get(&self, key: Key) -> Result<Option<File>> {
94        ShardedCache::get(self, key)
95    }
96
97    fn temp_dir(&self, key: Key) -> Result<Cow<Path>> {
98        ShardedCache::temp_dir(self, Some(key))
99    }
100
101    fn set(&self, key: Key, value: &Path) -> Result<()> {
102        ShardedCache::set(self, key, value)
103    }
104
105    fn put(&self, key: Key, value: &Path) -> Result<()> {
106        ShardedCache::put(self, key, value)
107    }
108
109    fn touch(&self, key: Key) -> Result<bool> {
110        ShardedCache::touch(self, key)
111    }
112}
113
114/// Construct a [`Cache`] with this builder.  The resulting cache will
115/// always first access its write-side cache (if defined), and, on
116/// misses, will attempt to service [`Cache::get`] and
117/// [`Cache::touch`] calls by iterating over the read-only caches.
118#[derive(Derivative)]
119#[derivative(Debug)]
120pub struct CacheBuilder {
121    write_side: Option<Arc<dyn FullCache>>,
122    auto_sync: bool,
123
124    #[derivative(Debug = "ignore")]
125    consistency_checker: Option<ConsistencyChecker>,
126
127    read_side: ReadOnlyCacheBuilder,
128}
129
130impl Default for CacheBuilder {
131    fn default() -> CacheBuilder {
132        CacheBuilder {
133            write_side: None,
134            auto_sync: true,
135            consistency_checker: None,
136            read_side: Default::default(),
137        }
138    }
139}
140
141/// A [`Cache`] wraps either up to one plain or sharded read-write
142/// cache in a convenient interface, and may optionally fulfill read
143/// operations by deferring to a list of read-only cache when the
144/// read-write cache misses.
145///
146/// The default cache has no write-side and an empty stack of backup
147/// read-only caches.
148///
149/// [`Cache`] objects are cheap to clone and lock-free; don't put an
150/// [`Arc`] on them.  Avoid opening multiple caches for the same set
151/// of directories: using the same [`Cache`] object improves the
152/// accuracy of the write cache's lock-free in-memory statistics, when
153/// it's a sharded cache.
154#[derive(Clone, Derivative)]
155#[derivative(Debug)]
156pub struct Cache {
157    // The write-side cache services writes and is the cache of first
158    // resort for `get` and `touch`.
159    write_side: Option<Arc<dyn FullCache>>,
160    // Whether to automatically sync file contents before publishing
161    // them to the write-side cache.
162    auto_sync: bool,
163
164    // If provided, `Kismet` will compare results to make sure all
165    // cache levels that have a value for a given key agree ( the
166    // checker function returns `Ok(())`).
167    #[derivative(Debug = "ignore")]
168    consistency_checker: Option<ConsistencyChecker>,
169
170    // The read-side cache (a list of read-only caches) services `get`
171    // and `touch` calls when we fail to find something in the
172    // write-side cache.
173    read_side: ReadOnlyCache,
174}
175
176impl Default for Cache {
177    fn default() -> Cache {
178        Cache {
179            write_side: None,
180            auto_sync: true,
181            consistency_checker: None,
182            read_side: Default::default(),
183        }
184    }
185}
186
187/// Where does a cache hit come from: the primary read-write cache, or
188/// one of the secondary read-only caches?
189pub enum CacheHit<'a> {
190    /// The file was found in the primary read-write cache; promoting
191    /// it is a no-op.
192    Primary(&'a mut File),
193    /// The file was found in one of the secondary read-only caches.
194    /// Promoting it to the primary cache will require a full copy.
195    Secondary(&'a mut File),
196}
197
198/// What to do with a cache hit in a [`Cache::get_or_update`] call?
199pub enum CacheHitAction {
200    /// Return the cache hit as is.
201    Accept,
202    /// Return the cache hit after promoting it to the current write
203    /// cache directory, if necessary.
204    Promote,
205    /// Replace with and return a new file.
206    Replace,
207}
208
209impl CacheBuilder {
210    /// Returns a fresh empty builder.
211    pub fn new() -> Self {
212        Self::default()
213    }
214
215    /// Sets the consistency checker function: when the function is
216    /// provided, the `ReadOnlyCache` will keep searching after the
217    /// first cache hit, and compare subsequent hits with the first
218    /// one by calling `checker`.  The `checker` function should
219    /// return `Ok(())` if the two files are compatible (identical),
220    /// and `Err` otherwise.
221    ///
222    /// Kismet will propagate the error on mismatch.
223    pub fn consistency_checker(
224        &mut self,
225        checker: impl Fn(&mut File, &mut File) -> Result<()>
226            + Sync
227            + Send
228            + std::panic::RefUnwindSafe
229            + std::panic::UnwindSafe
230            + Sized
231            + 'static,
232    ) -> &mut Self {
233        self.arc_consistency_checker(Some(Arc::new(checker)))
234    }
235
236    /// Sets the consistency checker function to
237    /// [`crate::byte_equality_checker`]: the contents of all cache
238    /// hits must be bytewise identical, without considering any
239    /// metadata.
240    pub fn byte_equality_checker(&mut self) -> &mut Self {
241        self.consistency_checker(crate::byte_equality_checker)
242    }
243
244    /// Sets the consistency checker function to
245    /// [`crate::panicking_byte_equality_checker`]: the contents of
246    /// all cache hits must be bytewise identical, without considering
247    /// any metadata, and the call will panic on mismatch.
248    pub fn panicking_byte_equality_checker(&mut self) -> &mut Self {
249        self.consistency_checker(crate::panicking_byte_equality_checker)
250    }
251
252    /// Removes the consistency checker function, if any.
253    pub fn clear_consistency_checker(&mut self) -> &mut Self {
254        self.arc_consistency_checker(None)
255    }
256
257    /// Sets the consistency checker function.  `None` clears the
258    /// checker function.  See [`CacheBuilder::consistency_checker`].
259    #[allow(clippy::type_complexity)] // We want the public type to be transparent
260    pub fn arc_consistency_checker(
261        &mut self,
262        checker: Option<
263            Arc<
264                dyn Fn(&mut File, &mut File) -> Result<()>
265                    + Sync
266                    + Send
267                    + std::panic::RefUnwindSafe
268                    + std::panic::UnwindSafe,
269            >,
270        >,
271    ) -> &mut Self {
272        self.consistency_checker = checker.clone();
273        self.read_side.arc_consistency_checker(checker);
274        self
275    }
276
277    /// Sets the read-write cache directory to `path`.
278    ///
279    /// The read-write cache will be a plain cache directory if
280    /// `num_shards <= 1`, and a sharded directory otherwise.
281    pub fn writer(
282        &mut self,
283        path: impl AsRef<Path>,
284        num_shards: usize,
285        total_capacity: usize,
286    ) -> &mut Self {
287        if num_shards <= 1 {
288            self.plain_writer(path, total_capacity)
289        } else {
290            self.sharded_writer(path, num_shards, total_capacity)
291        }
292    }
293
294    /// Sets the read-write cache directory to a plain directory at
295    /// `path`, with a target file count of up to `capacity`.
296    pub fn plain_writer(&mut self, path: impl AsRef<Path>, capacity: usize) -> &mut Self {
297        let _ = self.write_side.insert(Arc::new(PlainCache::new(
298            path.as_ref().to_owned(),
299            capacity,
300        )));
301        self
302    }
303
304    /// Sets the read-write cache directory to a sharded directory at
305    /// `path`, with `num_shards` subdirectories and a target file
306    /// count of up to `capacity` for the entire cache.
307    pub fn sharded_writer(
308        &mut self,
309        path: impl AsRef<Path>,
310        num_shards: usize,
311        total_capacity: usize,
312    ) -> &mut Self {
313        let _ = self.write_side.insert(Arc::new(ShardedCache::new(
314            path.as_ref().to_owned(),
315            num_shards,
316            total_capacity,
317        )));
318        self
319    }
320
321    /// Sets whether files published read-write cache will be
322    /// automatically flushed to disk with [`File::sync_all`]
323    /// before sending them to the cache directory.
324    ///
325    /// Defaults to true, for safety.  Even when `auto_sync` is
326    /// enabled, Kismet does not `fsync` cache directories; after a
327    /// kernel or hardware crash, caches may partially revert to an
328    /// older state, but should not contain incomplete files.
329    ///
330    /// An application may want to disable `auto_sync` because it
331    /// already synchronises files, or because the cache directories
332    /// do not survive crashes: they might be erased after each boot,
333    /// e.g., via
334    /// [tmpfiles.d](https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html),
335    /// or tagged with a [boot id](https://man7.org/linux/man-pages/man3/sd_id128_get_machine.3.html).
336    pub fn auto_sync(&mut self, sync: bool) -> &mut Self {
337        self.auto_sync = sync;
338        self
339    }
340
341    /// Adds a new read-only cache directory at `path` to the end of the
342    /// cache builder's search list.
343    ///
344    /// Adds a plain cache directory if `num_shards <= 1`, and a sharded
345    /// directory otherwise.
346    pub fn reader(&mut self, path: impl AsRef<Path>, num_shards: usize) -> &mut Self {
347        self.read_side.cache(path, num_shards);
348        self
349    }
350
351    /// Adds a new plain (unsharded) read-only cache directory at
352    /// `path` to the end of the cache builder's search list.
353    pub fn plain_reader(&mut self, path: impl AsRef<Path>) -> &mut Self {
354        self.read_side.plain(path);
355        self
356    }
357
358    /// Adds a new plain cache read-only directory for each path in
359    /// `paths`.  The caches are appended in order to the end of the
360    /// cache builder's search list.
361    pub fn plain_readers(
362        &mut self,
363        paths: impl IntoIterator<Item = impl AsRef<Path>>,
364    ) -> &mut Self {
365        self.read_side.plain_caches(paths);
366        self
367    }
368
369    /// Adds a new sharded read-only cache directory at `path` to the
370    /// end of the cache builder's search list.
371    pub fn sharded_reader(&mut self, path: impl AsRef<Path>, num_shards: usize) -> &mut Self {
372        self.read_side.sharded(path, num_shards);
373        self
374    }
375
376    /// Returns the contents of `self` as a fresh value; `self` is
377    /// reset to the default empty builder state.  This makes it
378    /// possible to declare simple configurations in a single
379    /// expression, with `.take().build()`.
380    pub fn take(&mut self) -> Self {
381        std::mem::take(self)
382    }
383
384    /// Returns a fresh [`Cache`] for the builder's write cache and
385    /// additional search list of read-only cache directories.
386    pub fn build(self) -> Cache {
387        Cache {
388            write_side: self.write_side,
389            auto_sync: self.auto_sync,
390            consistency_checker: self.consistency_checker,
391            read_side: self.read_side.build(),
392        }
393    }
394}
395
396/// Attempts to set the permissions on `file` to `0444`: the tempfile
397/// crate always overrides to 0600 when possible, but that doesn't
398/// really make sense for kismet: we don't want cache entries we can
399/// tell exist, but can't access.  Access control should happen via
400/// permissions on the cache directory.
401#[cfg(target_family = "unix")]
402fn fix_tempfile_permissions(file: &NamedTempFile) -> Result<()> {
403    use std::fs::Permissions;
404    use std::os::unix::fs::PermissionsExt;
405
406    file.as_file()
407        .set_permissions(Permissions::from_mode(0o444))
408}
409
410#[cfg(not(target_family = "unix"))]
411fn fix_tempfile_permissions(_: &NamedTempFile) -> Result<()> {
412    Ok(())
413}
414
415impl Cache {
416    /// Calls [`File::sync_all`] on `file` if `Cache::auto_sync`
417    /// is true.
418    #[inline]
419    fn maybe_sync(&self, file: &File) -> Result<()> {
420        if self.auto_sync {
421            file.sync_all()
422        } else {
423            Ok(())
424        }
425    }
426
427    /// Opens `path` and calls [`File::sync_all`] on the resulting
428    /// file, if `Cache::auto_sync` is true.
429    ///
430    /// Panics when [`File::sync_all`] fails. See
431    /// https://wiki.postgresql.org/wiki/Fsync_Errors or
432    /// Rebello et al's "Can Applications Recover from fsync Failures?"
433    /// (https://www.usenix.org/system/files/atc20-rebello.pdf)
434    /// for an idea of the challenges associated with handling
435    /// fsync failures on persistent files.
436    fn maybe_sync_path(&self, path: &Path) -> Result<()> {
437        if self.auto_sync {
438            // It's really not clear what happens to a file's content
439            // if we open it just before fsync, and fsync fails.  It
440            // should be safe to just unlink the file
441            std::fs::File::open(path)?
442                .sync_all()
443                .expect("auto_sync failed, and failure semantics are unclear for fsync");
444        }
445
446        Ok(())
447    }
448
449    /// Attempts to open a read-only file for `key`.  The `Cache` will
450    /// query each its write cache (if any), followed by the list of
451    /// additional read-only cache, in definition order, and return a
452    /// read-only file for the first hit.
453    ///
454    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is invalid
455    /// (empty, or starts with a dot or a forward or back slash).
456    ///
457    /// Returns [`None`] if no file for `key` can be found in any of the
458    /// constituent caches, and bubbles up the first I/O error
459    /// encountered, if any.
460    ///
461    /// In the worst case, each call to `get` attempts to open two
462    /// files for the [`Cache`]'s read-write directory and for each
463    /// read-only backup directory.
464    pub fn get<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<File>> {
465        fn doit(
466            write_side: Option<&dyn FullCache>,
467            checker: Option<&ConsistencyChecker>,
468            read_side: &ReadOnlyCache,
469            key: Key,
470        ) -> Result<Option<File>> {
471            use std::io::Seek;
472            use std::io::SeekFrom;
473
474            if let Some(write) = write_side {
475                if let Some(mut ret) = write.get(key)? {
476                    if let Some(checker) = checker {
477                        if let Some(mut read_hit) = read_side.get(key)? {
478                            checker(&mut ret, &mut read_hit)?;
479                            ret.seek(SeekFrom::Start(0))?;
480                        }
481                    }
482
483                    return Ok(Some(ret));
484                }
485            }
486
487            read_side.get(key)
488        }
489
490        doit(
491            self.write_side.as_ref().map(AsRef::as_ref),
492            self.consistency_checker.as_ref(),
493            &self.read_side,
494            key.into(),
495        )
496    }
497
498    /// Attempts to find a cache entry for `key`.  If there is none,
499    /// populates the cache with a file filled by `populate`.  Returns
500    /// a file in all cases (unless the call fails with an error).
501    ///
502    /// Always invokes `populate` for a consistency check when a
503    /// consistency check function is provided.  The `populate`
504    /// function can return `ErrorKind::NotFound` to skip the
505    /// comparison without failing the whole call.
506    ///
507    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is
508    /// invalid (empty, or starts with a dot or a forward or back slash).
509    ///
510    /// See [`Cache::get_or_update`] for more control over the operation.
511    pub fn ensure<'a>(
512        &self,
513        key: impl Into<Key<'a>>,
514        populate: impl FnOnce(&mut File) -> Result<()>,
515    ) -> Result<File> {
516        fn judge(_: CacheHit) -> CacheHitAction {
517            CacheHitAction::Promote
518        }
519
520        self.get_or_update(key, judge, |dst, _| populate(dst))
521    }
522
523    /// Attempts to find a cache entry for `key`.  If there is none,
524    /// populates the write cache (if possible) with a file, once
525    /// filled by `populate`; otherwise obeys the value returned by
526    /// `judge` to determine what to do with the hit.
527    ///
528    /// Always invokes `populate` for a consistency check when a
529    /// consistency check function is provided.  The `populate`
530    /// function can return `ErrorKind::NotFound` to skip the
531    /// comparison without failing the whole call.
532    ///
533    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is
534    /// invalid (empty, or starts with a dot or a forward or back slash).
535    ///
536    /// When we need to populate a new file, `populate` is called with
537    /// a mutable reference to the destination file, and the old
538    /// cached file (in whatever state `judge` left it), if available.
539    ///
540    /// See [`Cache::ensure`] for a simpler interface.
541    ///
542    /// In the worst case, each call to `get_or_update` attempts to
543    /// open two files for the [`Cache`]'s read-write directory and
544    /// for each read-only backup directory, and fails to find
545    /// anything.  `get_or_update` then publishes a new cached file
546    /// (in a constant number of file operations), but not before
547    /// triggering a second chance maintenance (time linearithmic in
548    /// the number of files in the directory chosen for maintenance,
549    /// but amortised to logarithmic).
550    pub fn get_or_update<'a>(
551        &self,
552        key: impl Into<Key<'a>>,
553        judge: impl FnOnce(CacheHit) -> CacheHitAction,
554        populate: impl FnOnce(&mut File, Option<File>) -> Result<()>,
555    ) -> Result<File> {
556        use std::io::Seek;
557        use std::io::SeekFrom;
558
559        // Promotes `file` to `cache`.
560        fn promote(cache: &dyn FullCache, sync: bool, key: Key, mut file: File) -> Result<File> {
561            let mut tmp = NamedTempFile::new_in(cache.temp_dir(key)?)?;
562            std::io::copy(&mut file, tmp.as_file_mut())?;
563            fix_tempfile_permissions(&tmp)?;
564
565            // Force the destination file's contents to disk before
566            // adding it to the read-write cache, if we're supposed to
567            // sync files automatically.
568            if sync {
569                tmp.as_file().sync_all()?;
570            }
571
572            cache.put(key, tmp.path())?;
573
574            // We got a read-only file.  Rewind it before returning.
575            file.seek(SeekFrom::Start(0))?;
576            Ok(file)
577        }
578
579        let cache_or = self.write_side.as_ref().map(Arc::as_ref);
580        let key: Key = key.into();
581
582        let get_tempfile = || {
583            if let Some(cache) = cache_or {
584                tempfile::tempfile_in(cache.temp_dir(key)?)
585            } else {
586                tempfile::tempfile()
587            }
588        };
589
590        let mut old = None; // Overwritten with `Some(file)` when replacing `file`.
591        if let Some(mut file) = cache_or
592            .and_then(|cache| cache.get(key).transpose())
593            .transpose()?
594        {
595            if let Some(checker) = self.consistency_checker.as_ref() {
596                if let Some(mut read) = self.read_side.get(key)? {
597                    checker(&mut file, &mut read)?;
598                    file.seek(SeekFrom::Start(0))?;
599                }
600            }
601
602            match judge(CacheHit::Primary(&mut file)) {
603                // Promote is a no-op if the file is already in the write cache.
604                CacheHitAction::Accept | CacheHitAction::Promote => {
605                    file.seek(SeekFrom::Start(0))?;
606
607                    if let Some(checker) = self.consistency_checker.as_ref() {
608                        let mut tmp = get_tempfile()?;
609                        match populate(&mut tmp, None) {
610                            Err(e) if e.kind() == ErrorKind::NotFound => {
611                                return Ok(file);
612                            }
613                            ret => ret?,
614                        };
615                        tmp.seek(SeekFrom::Start(0))?;
616                        checker(&mut file, &mut tmp)?;
617                        file.seek(SeekFrom::Start(0))?;
618                    }
619
620                    return Ok(file);
621                }
622                CacheHitAction::Replace => old = Some(file),
623            }
624        } else if let Some(mut file) = self.read_side.get(key)? {
625            match judge(CacheHit::Secondary(&mut file)) {
626                j @ CacheHitAction::Accept | j @ CacheHitAction::Promote => {
627                    file.seek(SeekFrom::Start(0))?;
628
629                    if let Some(checker) = self.consistency_checker.as_ref() {
630                        let mut tmp = get_tempfile()?;
631
632                        match populate(&mut tmp, None) {
633                            Err(e) if e.kind() == ErrorKind::NotFound => {
634                                return Ok(file);
635                            }
636                            ret => ret?,
637                        };
638
639                        tmp.seek(SeekFrom::Start(0))?;
640                        checker(&mut file, &mut tmp)?;
641                        file.seek(SeekFrom::Start(0))?;
642                    }
643
644                    return if matches!(j, CacheHitAction::Accept) {
645                        Ok(file)
646                    } else if let Some(cache) = cache_or {
647                        promote(cache, self.auto_sync, key, file)
648                    } else {
649                        Ok(file)
650                    };
651                }
652                CacheHitAction::Replace => old = Some(file),
653            }
654        }
655
656        let cache = match cache_or {
657            Some(cache) => cache,
658            None => {
659                // If there's no write-side cache, satisfy the cache miss
660                // without saving the result anywhere.
661                let mut tmp = tempfile::tempfile()?;
662                populate(&mut tmp, old)?;
663
664                tmp.seek(SeekFrom::Start(0))?;
665                return Ok(tmp);
666            }
667        };
668
669        let replace = old.is_some();
670        // We either have to replace or ensure there is a cache entry.
671        // Either way, start by populating a temporary file.
672        let mut tmp = NamedTempFile::new_in(cache.temp_dir(key)?)?;
673        populate(tmp.as_file_mut(), old)?;
674        fix_tempfile_permissions(&tmp)?;
675        self.maybe_sync(tmp.as_file())?;
676
677        // Grab a read-only return value before publishing the file.
678        let path = tmp.path();
679        let mut ret = File::open(path)?;
680        if replace {
681            cache.set(key, path)?;
682        } else {
683            cache.put(key, path)?;
684            // Return the now-cached file, if we can get it.
685            if let Ok(Some(file)) = cache.get(key) {
686                ret = file;
687            }
688        }
689
690        Ok(ret)
691    }
692
693    fn set_impl(&self, key: Key, value: &Path) -> Result<()> {
694        match self.write_side.as_ref() {
695            Some(write) => write.set(key, value),
696            None => Err(Error::new(
697                ErrorKind::Unsupported,
698                "no kismet write cache defined",
699            )),
700        }
701    }
702
703    /// Inserts or overwrites the file at `value` as `key` in the
704    /// write cache directory.  This will always fail with
705    /// [`ErrorKind::Unsupported`] if no write cache was defined.
706    /// The path at `value` must be in the same filesystem as the
707    /// write cache directory: we rely on atomic file renames.
708    ///
709    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is invalid
710    /// (empty, or starts with a dot or a forward or back slash).
711    ///
712    /// Always consumes the file at `value` on success; may consume it
713    /// on error.
714    ///
715    /// When `auto_sync` is enabled (the default), the file at `value`
716    /// will always be [`File::sync_all`]ed before publishing to the
717    /// cache.  Kismet will **panic** when the [`File::sync_all`] call
718    /// itself fails: retrying the same call to [`Cache::set`] could
719    /// erroneously succeed, since some filesystems clear internal I/O
720    /// failure flag after the first `fsync`.
721    ///
722    /// Executes in a bounded number of file operations, except for
723    /// the lock-free maintenance, which needs time linearithmic in
724    /// the number of files in the directory chosen for maintenance,
725    /// amortised to logarithmic, and constant number of file operations.
726    pub fn set<'a>(&self, key: impl Into<Key<'a>>, value: impl AsRef<Path>) -> Result<()> {
727        fn doit(this: &Cache, key: Key, value: &Path) -> Result<()> {
728            this.maybe_sync_path(value)?;
729            this.set_impl(key, value)
730        }
731
732        doit(self, key.into(), value.as_ref())
733    }
734
735    /// Invokes [`Cache::set`] on a [`tempfile::NamedTempFile`].
736    ///
737    /// See [`Cache::set`] for more details.  The only difference is
738    /// that `set_temp_file` does not panic when `auto_sync` is enabled
739    /// and we fail to [`File::sync_all`] the [`NamedTempFile`] value.
740    pub fn set_temp_file<'a>(&self, key: impl Into<Key<'a>>, value: NamedTempFile) -> Result<()> {
741        fn doit(this: &Cache, key: Key, value: NamedTempFile) -> Result<()> {
742            fix_tempfile_permissions(&value)?;
743            this.maybe_sync(value.as_file())?;
744            this.set_impl(key, value.path())
745        }
746
747        doit(self, key.into(), value)
748    }
749
750    fn put_impl(&self, key: Key, value: &Path) -> Result<()> {
751        match self.write_side.as_ref() {
752            Some(write) => write.put(key, value),
753            None => Err(Error::new(
754                ErrorKind::Unsupported,
755                "no kismet write cache defined",
756            )),
757        }
758    }
759
760    /// Inserts the file at `value` as `key` in the cache directory if
761    /// there is no such cached entry already, or touches the cached
762    /// file if it already exists.  This will always fail with
763    /// [`ErrorKind::Unsupported`] if no write cache was defined.
764    /// The path at `value` must be in the same filesystem as the
765    /// write cache directory: we rely on atomic file hard linkage.
766    ///
767    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is invalid
768    /// (empty, or starts with a dot or a forward or back slash).
769    ///
770    /// Always consumes the file at `value` on success; may consume it
771    /// on error.
772    ///
773    /// When `auto_sync` is enabled (the default), the file at `value`
774    /// will always be [`File::sync_all`]ed before publishing to the
775    /// cache.  Kismet will **panic** when the [`File::sync_all`] call
776    /// itself fails: retrying the same call to [`Cache::put`] could
777    /// erroneously succeed, since some filesystems clear internal I/O
778    /// failure flag after the first `fsync`.
779    ///
780    /// Executes in a bounded number of file operations, except for
781    /// the lock-free maintenance, which needs time linearithmic in
782    /// the number of files in the directory chosen for maintenance,
783    /// amortised to logarithmic, and constant number of file operations.
784    pub fn put<'a>(&self, key: impl Into<Key<'a>>, value: impl AsRef<Path>) -> Result<()> {
785        fn doit(this: &Cache, key: Key, value: &Path) -> Result<()> {
786            this.maybe_sync_path(value)?;
787            this.put_impl(key, value)
788        }
789
790        doit(self, key.into(), value.as_ref())
791    }
792
793    /// Invokes [`Cache::put`] on a [`tempfile::NamedTempFile`].
794    ///
795    /// See [`Cache::put`] for more details.  The only difference is
796    /// that `put_temp_file` does not panic when `auto_sync` is enabled
797    /// and we fail to [`File::sync_all`] the [`NamedTempFile`] value.
798    pub fn put_temp_file<'a>(&self, key: impl Into<Key<'a>>, value: NamedTempFile) -> Result<()> {
799        fn doit(this: &Cache, key: Key, value: NamedTempFile) -> Result<()> {
800            fix_tempfile_permissions(&value)?;
801            this.maybe_sync(value.as_file())?;
802            this.put_impl(key, value.path())
803        }
804
805        doit(self, key.into(), value)
806    }
807
808    /// Marks a cache entry for `key` as accessed (read).  The [`Cache`]
809    /// will touch the same file that would be returned by `get`.
810    ///
811    /// Fails with [`ErrorKind::InvalidInput`] if `key.name` is invalid
812    /// (empty, or starts with a dot or a forward or back slash).
813    ///
814    /// Returns whether a file for `key` could be found, and bubbles
815    /// up the first I/O error encountered, if any.
816    ///
817    /// In the worst case, each call to `touch` attempts to update the
818    /// access time on two files for each cache directory in the
819    /// `ReadOnlyCache` stack.
820    pub fn touch<'a>(&self, key: impl Into<Key<'a>>) -> Result<bool> {
821        fn doit(
822            write_side: Option<&dyn FullCache>,
823            read_side: &ReadOnlyCache,
824            key: Key,
825        ) -> Result<bool> {
826            if let Some(write) = write_side {
827                if write.touch(key)? {
828                    return Ok(true);
829                }
830            }
831
832            read_side.touch(key)
833        }
834
835        doit(
836            self.write_side.as_ref().map(AsRef::as_ref),
837            &self.read_side,
838            key.into(),
839        )
840    }
841}
842
843#[cfg(test)]
844mod test {
845    use std::fs::File;
846    use std::io::ErrorKind;
847    use std::sync::atomic::AtomicU64;
848    use std::sync::atomic::Ordering;
849    use std::sync::Arc;
850
851    use crate::plain::Cache as PlainCache;
852    use crate::sharded::Cache as ShardedCache;
853    use crate::Cache;
854    use crate::CacheBuilder;
855    use crate::CacheHit;
856    use crate::CacheHitAction;
857    use crate::Key;
858
859    struct TestKey {
860        key: String,
861    }
862
863    impl TestKey {
864        fn new(key: &str) -> TestKey {
865            TestKey {
866                key: key.to_string(),
867            }
868        }
869    }
870
871    impl<'a> From<&'a TestKey> for Key<'a> {
872        fn from(x: &'a TestKey) -> Key<'a> {
873            Key::new(&x.key, 0, 1)
874        }
875    }
876
877    fn byte_equality_checker(
878        counter: Arc<AtomicU64>,
879    ) -> impl 'static + Fn(&mut File, &mut File) -> std::io::Result<()> {
880        move |x: &mut File, y: &mut File| {
881            counter.fetch_add(1, Ordering::Relaxed);
882            crate::byte_equality_checker(x, y)
883        }
884    }
885
886    // No cache defined -> read calls should successfully do nothing,
887    // write calls should fail.
888    #[test]
889    fn empty() {
890        use test_dir::{DirBuilder, FileType, TestDir};
891
892        let temp = TestDir::temp().create("foo", FileType::RandomFile(10));
893        let cache: Cache = Default::default();
894
895        assert!(matches!(cache.get(&TestKey::new("foo")), Ok(None)));
896        // Ensure also succeeds: the temporary value is recreated for
897        // each miss.
898        assert!(matches!(
899            cache.ensure(&TestKey::new("foo"), |_| Ok(())),
900            Ok(_)
901        ));
902
903        assert!(matches!(cache.set(&TestKey::new("foo"), &temp.path("foo")),
904                         Err(e) if e.kind() == ErrorKind::Unsupported));
905        assert!(matches!(cache.put(&TestKey::new("foo"), &temp.path("foo")),
906                         Err(e) if e.kind() == ErrorKind::Unsupported));
907        assert!(matches!(cache.touch(&TestKey::new("foo")), Ok(false)));
908    }
909
910    // Disable autosync; we should get an `Unsupported` error even if the
911    // input file does not exist.
912    #[test]
913    fn empty_no_auto_sync() {
914        let cache = CacheBuilder::new().auto_sync(false).take().build();
915
916        assert!(matches!(cache.get(&TestKey::new("foo")), Ok(None)));
917        assert!(matches!(
918            cache.ensure(&TestKey::new("foo"), |_| Ok(())),
919            Ok(_)
920        ));
921
922        assert!(
923            matches!(cache.set(&TestKey::new("foo"), "/no-such-tmp/foo"),
924                     Err(e) if e.kind() == ErrorKind::Unsupported)
925        );
926        assert!(
927            matches!(cache.put(&TestKey::new("foo"), "/no-such-tmp/foo"),
928                     Err(e) if e.kind() == ErrorKind::Unsupported)
929        );
930        assert!(matches!(cache.touch(&TestKey::new("foo")), Ok(false)));
931    }
932
933    /// Populate two plain caches and set a consistency checker.  We
934    /// should access both.
935    #[test]
936    fn consistency_checker_success() {
937        use std::io::Error;
938        use std::io::ErrorKind;
939        use std::io::Read;
940        use std::io::Write;
941        use test_dir::{DirBuilder, FileType, TestDir};
942
943        let temp = TestDir::temp()
944            .create("first", FileType::Dir)
945            .create("second", FileType::Dir)
946            .create("first/0", FileType::ZeroFile(2))
947            .create("second/0", FileType::ZeroFile(2))
948            .create("first/1", FileType::ZeroFile(1))
949            .create("second/2", FileType::ZeroFile(3))
950            .create("second/3", FileType::ZeroFile(3))
951            .create("second/4", FileType::ZeroFile(4));
952
953        let counter = Arc::new(AtomicU64::new(0));
954
955        let cache = CacheBuilder::new()
956            .plain_writer(temp.path("first"), 100)
957            .plain_reader(temp.path("second"))
958            .consistency_checker(byte_equality_checker(counter.clone()))
959            .take()
960            .build();
961
962        // Find a hit in both caches. The checker should be invoked.
963        {
964            let mut hit = cache
965                .get(&TestKey::new("0"))
966                .expect("must succeed")
967                .expect("must exist");
968
969            assert_eq!(counter.load(Ordering::Relaxed), 1);
970
971            let mut contents = Vec::new();
972            hit.read_to_end(&mut contents).expect("read should succeed");
973            assert_eq!(contents, "00".as_bytes());
974        }
975
976        // Do the same via `ensure`.
977        {
978            counter.store(0, Ordering::Relaxed);
979            let mut populated = cache
980                .ensure(&TestKey::new("0"), |dst| {
981                    dst.write_all("00".as_bytes())?;
982                    Ok(())
983                })
984                .expect("ensure must succeed");
985
986            assert_eq!(counter.load(Ordering::Relaxed), 2);
987
988            let mut contents = Vec::new();
989            populated
990                .read_to_end(&mut contents)
991                .expect("read should succeed");
992            assert_eq!(contents, "00".as_bytes());
993        }
994
995        // Now return `NotFound` from the `populate` callback,
996        // we should still succeed.
997        {
998            counter.store(0, Ordering::Relaxed);
999            let mut populated = cache
1000                .ensure(&TestKey::new("0"), |_| {
1001                    Err(Error::new(ErrorKind::NotFound, "not found"))
1002                })
1003                .expect("ensure must succeed");
1004
1005            assert_eq!(counter.load(Ordering::Relaxed), 1);
1006
1007            let mut contents = Vec::new();
1008            populated
1009                .read_to_end(&mut contents)
1010                .expect("read should succeed");
1011            assert_eq!(contents, "00".as_bytes());
1012        }
1013
1014        counter.store(0, Ordering::Relaxed);
1015        let _ = cache
1016            .get(&TestKey::new("1"))
1017            .expect("must succeed")
1018            .expect("must exist");
1019        // Only found in the writer, there's nothing to check.
1020        assert_eq!(counter.load(Ordering::Relaxed), 0);
1021
1022        // Do the same via `ensure`.
1023        {
1024            let mut populated = cache
1025                .ensure(&TestKey::new("1"), |dst| {
1026                    dst.write_all("0".as_bytes())?;
1027                    Ok(())
1028                })
1029                .expect("ensure must succeed");
1030
1031            assert_eq!(counter.load(Ordering::Relaxed), 1);
1032
1033            let mut contents = Vec::new();
1034            populated
1035                .read_to_end(&mut contents)
1036                .expect("read should succeed");
1037            assert_eq!(contents, "0".as_bytes());
1038        }
1039
1040        counter.store(0, Ordering::Relaxed);
1041        let _ = cache
1042            .get(&TestKey::new("2"))
1043            .expect("must succeed")
1044            .expect("must exist");
1045        // Only found in the read cache, there's nothing to check.
1046        assert_eq!(counter.load(Ordering::Relaxed), 0);
1047
1048        // Do the same via `ensure`.
1049        {
1050            counter.store(0, Ordering::Relaxed);
1051            let mut populated = cache
1052                .ensure(&TestKey::new("2"), |dst| {
1053                    dst.write_all("000".as_bytes())?;
1054                    Ok(())
1055                })
1056                .expect("ensure must succeed");
1057
1058            assert_eq!(counter.load(Ordering::Relaxed), 1);
1059
1060            let mut contents = Vec::new();
1061            populated
1062                .read_to_end(&mut contents)
1063                .expect("read should succeed");
1064            assert_eq!(contents, "000".as_bytes());
1065        }
1066
1067        {
1068            counter.store(0, Ordering::Relaxed);
1069            let mut populated = cache
1070                .get_or_update(
1071                    &TestKey::new("3"),
1072                    |_| CacheHitAction::Accept,
1073                    |dst, _| {
1074                        dst.write_all("000".as_bytes())?;
1075                        Ok(())
1076                    },
1077                )
1078                .expect("get_or_update must succeed");
1079
1080            assert_eq!(counter.load(Ordering::Relaxed), 1);
1081
1082            let mut contents = Vec::new();
1083            populated
1084                .read_to_end(&mut contents)
1085                .expect("read should succeed");
1086            assert_eq!(contents, "000".as_bytes());
1087        }
1088
1089        // Again, but now the `populate` callback returns `NotFound`.
1090        {
1091            counter.store(0, Ordering::Relaxed);
1092            let mut populated = cache
1093                .get_or_update(
1094                    &TestKey::new("4"),
1095                    |_| CacheHitAction::Accept,
1096                    |_, _| Err(Error::new(ErrorKind::NotFound, "not found")),
1097                )
1098                .expect("get_or_update must succeed");
1099
1100            assert_eq!(counter.load(Ordering::Relaxed), 0);
1101
1102            let mut contents = Vec::new();
1103            populated
1104                .read_to_end(&mut contents)
1105                .expect("read should succeed");
1106            assert_eq!(contents, "0000".as_bytes());
1107        }
1108
1109        // Make sure we succeed on plain misses.
1110        {
1111            counter.store(0, Ordering::Relaxed);
1112            let mut populated = cache
1113                .get_or_update(
1114                    &TestKey::new("no-such-key"),
1115                    |_| CacheHitAction::Accept,
1116                    |dst, _| {
1117                        dst.write_all("fresh data".as_bytes())?;
1118                        Ok(())
1119                    },
1120                )
1121                .expect("get_or_update must succeed");
1122
1123            assert_eq!(counter.load(Ordering::Relaxed), 0);
1124
1125            let mut contents = Vec::new();
1126            populated
1127                .read_to_end(&mut contents)
1128                .expect("read should succeed");
1129            assert_eq!(contents, "fresh data".as_bytes());
1130        }
1131    }
1132
1133    /// Populate two plain caches and set a consistency checker.  We
1134    /// should error on mismatch.
1135    #[test]
1136    fn consistency_checker_failure() {
1137        use std::io::Write;
1138        use test_dir::{DirBuilder, FileType, TestDir};
1139
1140        let temp = TestDir::temp()
1141            .create("first", FileType::Dir)
1142            .create("second", FileType::Dir)
1143            .create("first/0", FileType::ZeroFile(2))
1144            .create("second/0", FileType::ZeroFile(3))
1145            .create("first/1", FileType::ZeroFile(1))
1146            .create("second/2", FileType::ZeroFile(4));
1147
1148        let counter = Arc::new(AtomicU64::new(0));
1149        let cache = CacheBuilder::new()
1150            .plain_writer(temp.path("first"), 100)
1151            .plain_reader(temp.path("second"))
1152            .consistency_checker(byte_equality_checker(counter))
1153            .take()
1154            .build();
1155
1156        // This call should error.
1157        assert!(cache.get(&TestKey::new("0")).is_err());
1158
1159        // The call should also error through `ensure`.
1160        assert!(cache
1161            .ensure(&TestKey::new("0"), |_| {
1162                unreachable!("should detect read-cache mismatch first");
1163            })
1164            .is_err());
1165
1166        // Do the same for the files that are only in one of the two
1167        // caches.
1168        assert!(cache
1169            .ensure(&TestKey::new("1"), |dst| {
1170                dst.write_all("0000".as_bytes())?;
1171                Ok(())
1172            })
1173            .is_err());
1174
1175        assert!(cache
1176            .ensure(&TestKey::new("2"), |dst| {
1177                dst.write_all("0".as_bytes())?;
1178                Ok(())
1179            })
1180            .is_err());
1181
1182        // Same with `get_or_update`.
1183        assert!(cache
1184            .get_or_update(
1185                &TestKey::new("2"),
1186                |_| CacheHitAction::Accept,
1187                |dst, _| {
1188                    dst.write_all("0".as_bytes())?;
1189                    Ok(())
1190                }
1191            )
1192            .is_err());
1193    }
1194
1195    /// Populate two plain caches and unset the consistency checker.  We
1196    /// should not error.
1197    #[test]
1198    fn consistency_checker_silent_failure() {
1199        use test_dir::{DirBuilder, FileType, TestDir};
1200
1201        let temp = TestDir::temp()
1202            .create("first", FileType::Dir)
1203            .create("second", FileType::Dir)
1204            .create("first/0", FileType::ZeroFile(2))
1205            .create("second/0", FileType::ZeroFile(3))
1206            .create("first/1", FileType::ZeroFile(1))
1207            .create("second/2", FileType::ZeroFile(4));
1208
1209        let counter = Arc::new(AtomicU64::new(0));
1210
1211        let cache = CacheBuilder::new()
1212            .plain_writer(temp.path("first"), 100)
1213            .plain_reader(temp.path("second"))
1214            .consistency_checker(byte_equality_checker(counter.clone()))
1215            .clear_consistency_checker()
1216            .take()
1217            .build();
1218
1219        // This call should not error.
1220        let _ = cache
1221            .get(&TestKey::new("0"))
1222            .expect("must succeed")
1223            .expect("must exist");
1224
1225        // And same for `ensure` calls.
1226        let _ = cache
1227            .ensure(&TestKey::new("0"), |_| {
1228                unreachable!("should not be called");
1229            })
1230            .expect("must succeed");
1231
1232        let _ = cache
1233            .ensure(&TestKey::new("1"), |_| {
1234                unreachable!("should not be called");
1235            })
1236            .expect("must succeed");
1237
1238        let _ = cache
1239            .ensure(&TestKey::new("2"), |_| {
1240                unreachable!("should not be called");
1241            })
1242            .expect("must succeed");
1243
1244        let _ = cache
1245            .get_or_update(
1246                &TestKey::new("2"),
1247                |_| CacheHitAction::Accept,
1248                |_, _| {
1249                    unreachable!("should not be called");
1250                },
1251            )
1252            .expect("must succeed");
1253        // There should be no call to the checker function.
1254        assert_eq!(counter.load(Ordering::Relaxed), 0);
1255    }
1256
1257    /// Populate two plain caches.  We should read from both.
1258    #[test]
1259    fn two_plain_caches() {
1260        use test_dir::{DirBuilder, FileType, TestDir};
1261
1262        let temp = TestDir::temp()
1263            .create("first", FileType::Dir)
1264            .create("second", FileType::Dir)
1265            .create("first/0", FileType::ZeroFile(2))
1266            .create("second/1", FileType::ZeroFile(3));
1267
1268        let ro = CacheBuilder::new()
1269            .plain_readers(["first", "second"].iter().map(|p| temp.path(p)))
1270            .take()
1271            .build();
1272
1273        // We should find 0 and 1.
1274        let _ = ro
1275            .get(&TestKey::new("0"))
1276            .expect("must succeed")
1277            .expect("must exist");
1278
1279        let _ = ro
1280            .get(&TestKey::new("1"))
1281            .expect("must succeed")
1282            .expect("must exist");
1283
1284        // But not 2.
1285        assert!(ro.get(&TestKey::new("2")).expect("must succeed").is_none());
1286    }
1287
1288    // Fail to find a file, ensure it, then see that we can get it.
1289    #[test]
1290    fn test_ensure() {
1291        use std::io::{Read, Write};
1292        use test_dir::{DirBuilder, TestDir};
1293
1294        let temp = TestDir::temp();
1295        // Get some coverage for no-auto_sync config.
1296        let cache = CacheBuilder::new()
1297            .writer(temp.path("."), 1, 10)
1298            .auto_sync(false)
1299            .take()
1300            .build();
1301        let key = TestKey::new("foo");
1302
1303        // The file doesn't exist initially.
1304        assert!(matches!(cache.get(&key), Ok(None)));
1305
1306        {
1307            let mut populated = cache
1308                .ensure(&key, |file| file.write_all(b"test"))
1309                .expect("ensure must succeed");
1310
1311            let mut dst = Vec::new();
1312            populated.read_to_end(&mut dst).expect("read must succeed");
1313            assert_eq!(&dst, b"test");
1314        }
1315
1316        // And now get the file again.
1317        {
1318            let mut fetched = cache
1319                .get(&key)
1320                .expect("get must succeed")
1321                .expect("file must be found");
1322
1323            let mut dst = Vec::new();
1324            fetched.read_to_end(&mut dst).expect("read must succeed");
1325            assert_eq!(&dst, b"test");
1326        }
1327
1328        // And make sure a later `ensure` call just grabs the file.
1329        {
1330            let mut populated = cache
1331                .ensure(&key, |_| {
1332                    unreachable!("should not be called for an extant file")
1333                })
1334                .expect("ensure must succeed");
1335
1336            let mut dst = Vec::new();
1337            populated.read_to_end(&mut dst).expect("read must succeed");
1338            assert_eq!(&dst, b"test");
1339        }
1340    }
1341
1342    // Use a two-level cache, and make sure `ensure` promotes copies from
1343    // the backup to the primary location.
1344    #[test]
1345    fn test_ensure_promote() {
1346        use std::io::{Read, Write};
1347        use tempfile::NamedTempFile;
1348        use test_dir::{DirBuilder, FileType, TestDir};
1349
1350        let temp = TestDir::temp()
1351            .create("cache", FileType::Dir)
1352            .create("extra_plain", FileType::Dir);
1353
1354        // Populate the plain cache in `extra_plain` with one file.
1355        {
1356            let cache = PlainCache::new(temp.path("extra_plain"), 10);
1357
1358            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1359                .expect("new temp file must succeed");
1360            tmp.as_file()
1361                .write_all(b"initial")
1362                .expect("write must succeed");
1363
1364            cache.put("foo", tmp.path()).expect("put must succeed");
1365        }
1366
1367        let cache = CacheBuilder::new()
1368            .writer(temp.path("cache"), 1, 10)
1369            .plain_reader(temp.path("extra_plain"))
1370            .take()
1371            .build();
1372        let key = TestKey::new("foo");
1373
1374        // The file is found initially.
1375        {
1376            let mut fetched = cache
1377                .get(&key)
1378                .expect("get must succeed")
1379                .expect("file must be found");
1380
1381            let mut dst = Vec::new();
1382            fetched.read_to_end(&mut dst).expect("read must succeed");
1383            assert_eq!(&dst, b"initial");
1384        }
1385
1386        {
1387            let mut populated = cache
1388                .ensure(&key, |_| {
1389                    unreachable!("should not be called for an extant file")
1390                })
1391                .expect("ensure must succeed");
1392
1393            let mut dst = Vec::new();
1394            populated.read_to_end(&mut dst).expect("read must succeed");
1395            assert_eq!(&dst, b"initial");
1396        }
1397
1398        // And now get the file again, and make sure it doesn't come from the
1399        // backup location.
1400        {
1401            let new_cache = CacheBuilder::new()
1402                .writer(temp.path("cache"), 1, 10)
1403                .take()
1404                .build();
1405            let mut fetched = new_cache
1406                .get(&key)
1407                .expect("get must succeed")
1408                .expect("file must be found");
1409
1410            let mut dst = Vec::new();
1411            fetched.read_to_end(&mut dst).expect("read must succeed");
1412            assert_eq!(&dst, b"initial");
1413        }
1414    }
1415
1416    // Use a two-level cache, get_or_update with an `Accept` judgement.
1417    // We should leave everything where it is.
1418    #[test]
1419    fn test_get_or_update_accept() {
1420        use std::io::{Read, Write};
1421        use tempfile::NamedTempFile;
1422        use test_dir::{DirBuilder, FileType, TestDir};
1423
1424        let temp = TestDir::temp()
1425            .create("cache", FileType::Dir)
1426            .create("extra_plain", FileType::Dir);
1427
1428        // Populate the plain cache in `extra_plain` with one file.
1429        {
1430            let cache = PlainCache::new(temp.path("extra_plain"), 10);
1431
1432            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1433                .expect("new temp file must succeed");
1434            tmp.as_file()
1435                .write_all(b"initial")
1436                .expect("write must succeed");
1437
1438            cache.put("foo", tmp.path()).expect("put must succeed");
1439        }
1440
1441        let cache = CacheBuilder::new()
1442            // Make it sharded, because why not?
1443            .writer(temp.path("cache"), 2, 10)
1444            .plain_reader(temp.path("extra_plain"))
1445            .take()
1446            .build();
1447        let key = TestKey::new("foo");
1448        let key2 = TestKey::new("bar");
1449
1450        // The file is found initially, in the backup cache.
1451        {
1452            let mut fetched = cache
1453                .get_or_update(
1454                    &key,
1455                    |hit| {
1456                        assert!(matches!(hit, CacheHit::Secondary(_)));
1457                        CacheHitAction::Accept
1458                    },
1459                    |_, _| unreachable!("should not have to fill an extant file"),
1460                )
1461                .expect("get_or_update must succeed");
1462
1463            let mut dst = Vec::new();
1464            fetched.read_to_end(&mut dst).expect("read must succeed");
1465            assert_eq!(&dst, b"initial");
1466        }
1467
1468        // Let's try again with a file that does not exist yet.
1469        {
1470            let mut fetched = cache
1471                .get_or_update(
1472                    &key2,
1473                    |_| unreachable!("should not be called"),
1474                    |file, old| {
1475                        assert!(old.is_none());
1476                        file.write_all(b"updated")
1477                    },
1478                )
1479                .expect("get_or_update must succeed");
1480
1481            let mut dst = Vec::new();
1482            fetched.read_to_end(&mut dst).expect("read must succeed");
1483            assert_eq!(&dst, b"updated");
1484        }
1485
1486        // The new file is now found.
1487        {
1488            let mut fetched = cache
1489                .get_or_update(
1490                    &key2,
1491                    |hit| {
1492                        assert!(matches!(hit, CacheHit::Primary(_)));
1493                        CacheHitAction::Accept
1494                    },
1495                    |_, _| unreachable!("should not have to fill an extant file"),
1496                )
1497                .expect("get_or_update must succeed");
1498
1499            let mut dst = Vec::new();
1500            fetched.read_to_end(&mut dst).expect("read must succeed");
1501            assert_eq!(&dst, b"updated");
1502        }
1503
1504        // And now get the files again, and make sure they don't
1505        // come from the backup location.
1506        {
1507            let new_cache = CacheBuilder::new()
1508                .writer(temp.path("cache"), 2, 10)
1509                .take()
1510                .build();
1511
1512            // The new cache shouldn't have the old key.
1513            assert!(matches!(new_cache.touch(&key), Ok(false)));
1514
1515            // But it should have `key2`.
1516            let mut fetched = new_cache
1517                .get(&key2)
1518                .expect("get must succeed")
1519                .expect("file must be found");
1520
1521            let mut dst = Vec::new();
1522            fetched.read_to_end(&mut dst).expect("read must succeed");
1523            assert_eq!(&dst, b"updated");
1524        }
1525    }
1526
1527    // Use a two-level cache, get_or_update with a `Replace` judgement.
1528    // We should always overwrite everything to the write cache.
1529    #[test]
1530    fn test_get_or_update_replace() {
1531        use std::io::{Read, Write};
1532        use tempfile::NamedTempFile;
1533        use test_dir::{DirBuilder, FileType, TestDir};
1534
1535        let temp = TestDir::temp()
1536            .create("cache", FileType::Dir)
1537            .create("extra_plain", FileType::Dir);
1538
1539        // Populate the plain cache in `extra_plain` with one file.
1540        {
1541            let cache = PlainCache::new(temp.path("extra_plain"), 10);
1542
1543            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1544                .expect("new temp file must succeed");
1545            tmp.as_file()
1546                .write_all(b"initial")
1547                .expect("write must succeed");
1548
1549            cache.put("foo", tmp.path()).expect("put must succeed");
1550        }
1551
1552        let cache = CacheBuilder::new()
1553            // Make it sharded, because why not?
1554            .writer(temp.path("cache"), 2, 10)
1555            .plain_reader(temp.path("extra_plain"))
1556            .take()
1557            .build();
1558        let key = TestKey::new("foo");
1559
1560        {
1561            let mut fetched = cache
1562                .get_or_update(
1563                    &key,
1564                    |hit| {
1565                        assert!(matches!(hit, CacheHit::Secondary(_)));
1566                        CacheHitAction::Replace
1567                    },
1568                    |file, old| {
1569                        // Make sure the `old` file is the "initial" file.
1570                        let mut prev = old.expect("must have old data");
1571                        let mut dst = Vec::new();
1572                        prev.read_to_end(&mut dst).expect("read must succeed");
1573                        assert_eq!(&dst, b"initial");
1574
1575                        file.write_all(b"replace1")
1576                    },
1577                )
1578                .expect("get_or_update must succeed");
1579
1580            let mut dst = Vec::new();
1581            fetched.read_to_end(&mut dst).expect("read must succeed");
1582            assert_eq!(&dst, b"replace1");
1583        }
1584
1585        // Re-read the file.
1586        {
1587            let mut fetched = cache
1588                .get(&key)
1589                .expect("get must succeed")
1590                .expect("file should be found");
1591
1592            let mut dst = Vec::new();
1593            fetched.read_to_end(&mut dst).expect("read must succeed");
1594            assert_eq!(&dst, b"replace1");
1595        }
1596
1597        // Update it again.
1598        {
1599            let mut fetched = cache
1600                .get_or_update(
1601                    &key,
1602                    |hit| {
1603                        assert!(matches!(hit, CacheHit::Primary(_)));
1604                        CacheHitAction::Replace
1605                    },
1606                    |file, old| {
1607                        // Make sure the `old` file is the "initial" file.
1608                        let mut prev = old.expect("must have old data");
1609                        let mut dst = Vec::new();
1610                        prev.read_to_end(&mut dst).expect("read must succeed");
1611                        assert_eq!(&dst, b"replace1");
1612
1613                        file.write_all(b"replace2")
1614                    },
1615                )
1616                .expect("get_or_update must succeed");
1617
1618            let mut dst = Vec::new();
1619            fetched.read_to_end(&mut dst).expect("read must succeed");
1620            assert_eq!(&dst, b"replace2");
1621        }
1622
1623        // The new file is now found.
1624        {
1625            let mut fetched = cache
1626                .get_or_update(
1627                    &key,
1628                    |hit| {
1629                        assert!(matches!(hit, CacheHit::Primary(_)));
1630                        CacheHitAction::Replace
1631                    },
1632                    |file, old| {
1633                        // Make sure the `old` file is the "replace2" file.
1634                        let mut prev = old.expect("must have old data");
1635                        let mut dst = Vec::new();
1636                        prev.read_to_end(&mut dst).expect("read must succeed");
1637                        assert_eq!(&dst, b"replace2");
1638
1639                        file.write_all(b"replace3")
1640                    },
1641                )
1642                .expect("get_or_update must succeed");
1643
1644            let mut dst = Vec::new();
1645            fetched.read_to_end(&mut dst).expect("read must succeed");
1646            assert_eq!(&dst, b"replace3");
1647        }
1648
1649        // And now get the same file again, and make sure it doesn't
1650        // come from the backup location.
1651        {
1652            let new_cache = CacheBuilder::new()
1653                .writer(temp.path("cache"), 2, 10)
1654                .take()
1655                .build();
1656
1657            // But it should have `key2`.
1658            let mut fetched = new_cache
1659                .get(&key)
1660                .expect("get must succeed")
1661                .expect("file must be found");
1662
1663            let mut dst = Vec::new();
1664            fetched.read_to_end(&mut dst).expect("read must succeed");
1665            assert_eq!(&dst, b"replace3");
1666        }
1667    }
1668
1669    // Use a one-level cache, without any write-side cache.  We should
1670    // still be able to `ensure` (or `get_or_update`), by calling the
1671    // `populate` function for all misses.
1672    #[test]
1673    fn test_ensure_no_write_side() {
1674        use std::io::{Read, Write};
1675        use tempfile::NamedTempFile;
1676        use test_dir::{DirBuilder, FileType, TestDir};
1677
1678        let temp = TestDir::temp().create("extra_plain", FileType::Dir);
1679
1680        // Populate the plain cache in `extra_plain` with one file.
1681        {
1682            let cache = PlainCache::new(temp.path("extra_plain"), 10);
1683
1684            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1685                .expect("new temp file must succeed");
1686            tmp.as_file()
1687                .write_all(b"initial")
1688                .expect("write must succeed");
1689
1690            cache.put("foo", tmp.path()).expect("put must succeed");
1691        }
1692
1693        let cache = CacheBuilder::new()
1694            .plain_reader(temp.path("extra_plain"))
1695            .take()
1696            .build();
1697        let key = TestKey::new("foo");
1698        let key2 = TestKey::new("bar");
1699
1700        // The file is found initially, in the backup cache.
1701        {
1702            let mut fetched = cache
1703                .get_or_update(
1704                    &key,
1705                    |hit| {
1706                        assert!(matches!(hit, CacheHit::Secondary(_)));
1707                        CacheHitAction::Accept
1708                    },
1709                    |_, _| unreachable!("should not have to fill an extant file"),
1710                )
1711                .expect("get_or_update must succeed");
1712
1713            let mut dst = Vec::new();
1714            fetched.read_to_end(&mut dst).expect("read must succeed");
1715            assert_eq!(&dst, b"initial");
1716        }
1717
1718        // Let's try again with a file that does not exist yet.
1719        {
1720            let mut fetched = cache
1721                .get_or_update(
1722                    &key2,
1723                    |_| unreachable!("should not be called"),
1724                    |file, old| {
1725                        assert!(old.is_none());
1726                        file.write_all(b"updated")
1727                    },
1728                )
1729                .expect("get_or_update must succeed");
1730
1731            let mut dst = Vec::new();
1732            fetched.read_to_end(&mut dst).expect("read must succeed");
1733            assert_eq!(&dst, b"updated");
1734        }
1735
1736        // Let's try again with the same file file.
1737        {
1738            let mut fetched = cache
1739                .get_or_update(
1740                    &key2,
1741                    |_| unreachable!("should not be called"),
1742                    |file, old| {
1743                        assert!(old.is_none());
1744                        file.write_all(b"updated2")
1745                    },
1746                )
1747                .expect("get_or_update must succeed");
1748
1749            let mut dst = Vec::new();
1750            fetched.read_to_end(&mut dst).expect("read must succeed");
1751            assert_eq!(&dst, b"updated2");
1752        }
1753    }
1754
1755    /// Use a byte equality checker with two different cache files for
1756    /// the same key.  We should find an error.
1757    #[test]
1758    fn test_byte_equality_checker() {
1759        use test_dir::{DirBuilder, FileType, TestDir};
1760
1761        let temp = TestDir::temp()
1762            .create("first", FileType::Dir)
1763            .create("second", FileType::Dir)
1764            .create("first/0", FileType::ZeroFile(2))
1765            .create("second/0", FileType::ZeroFile(3));
1766
1767        let cache = CacheBuilder::new()
1768            .plain_readers(["first", "second"].iter().map(|p| temp.path(p)))
1769            .byte_equality_checker()
1770            .take()
1771            .build();
1772
1773        assert!(cache.get(&TestKey::new("0")).is_err());
1774    }
1775
1776    /// Use a panicking byte equality checker with two different cache
1777    /// files for the same key.  We should find an error.
1778    #[test]
1779    #[should_panic(expected = "file contents do not match")]
1780    fn test_panicking_byte_equality_checker() {
1781        use test_dir::{DirBuilder, FileType, TestDir};
1782
1783        let temp = TestDir::temp()
1784            .create("first", FileType::Dir)
1785            .create("second", FileType::Dir)
1786            .create("first/0", FileType::ZeroFile(2))
1787            .create("second/0", FileType::ZeroFile(3));
1788
1789        let cache = CacheBuilder::new()
1790            .plain_readers(["first", "second"].iter().map(|p| temp.path(p)))
1791            .panicking_byte_equality_checker()
1792            .take()
1793            .build();
1794
1795        // We should fail before returning Err.
1796        assert!(cache.get(&TestKey::new("0")).is_ok());
1797    }
1798
1799    // Smoke test a wrapped plain cache.
1800    #[test]
1801    fn smoke_test_plain() {
1802        use std::io::{Read, Write};
1803        use tempfile::NamedTempFile;
1804        use test_dir::{DirBuilder, FileType, TestDir};
1805
1806        let temp = TestDir::temp()
1807            .create("cache", FileType::Dir)
1808            .create("extra", FileType::Dir);
1809
1810        // Populate the plain cache in `extra` with two files, "b" and "c".
1811        {
1812            let cache = PlainCache::new(temp.path("extra"), 10);
1813
1814            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1815                .expect("new temp file must succeed");
1816            tmp.as_file()
1817                .write_all(b"extra")
1818                .expect("write must succeed");
1819
1820            cache.put("b", tmp.path()).expect("put must succeed");
1821
1822            let tmp2 = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1823                .expect("new temp file must succeed");
1824            tmp2.as_file()
1825                .write_all(b"extra2")
1826                .expect("write must succeed");
1827
1828            cache.put("c", tmp2.path()).expect("put must succeed");
1829        }
1830
1831        let cache = CacheBuilder::new()
1832            .writer(temp.path("cache"), 1, 10)
1833            .reader(temp.path("extra"), 1)
1834            .take()
1835            .build();
1836
1837        // There shouldn't be anything for "a"
1838        assert!(matches!(cache.get(&TestKey::new("a")), Ok(None)));
1839        assert!(matches!(cache.touch(&TestKey::new("a")), Ok(false)));
1840
1841        // We should be able to touch "b"
1842        assert!(matches!(cache.touch(&TestKey::new("b")), Ok(true)));
1843
1844        // And its contents should match that of the "extra" cache dir.
1845        {
1846            let mut b_file = cache
1847                .get(&TestKey::new("b"))
1848                .expect("must succeed")
1849                .expect("must exist");
1850            let mut dst = Vec::new();
1851            b_file.read_to_end(&mut dst).expect("read must succeed");
1852            assert_eq!(&dst, b"extra");
1853        }
1854
1855        // Now populate "a" and "b" in the cache.
1856        {
1857            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
1858
1859            tmp.as_file()
1860                .write_all(b"write")
1861                .expect("write must succeed");
1862            cache
1863                .put(&TestKey::new("a"), tmp.path())
1864                .expect("put must succeed");
1865        }
1866
1867        {
1868            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
1869
1870            tmp.as_file()
1871                .write_all(b"write2")
1872                .expect("write must succeed");
1873            // Exercise put_temp_file as well.
1874            cache
1875                .put_temp_file(&TestKey::new("b"), tmp)
1876                .expect("put must succeed");
1877        }
1878
1879        // And overwrite "a"
1880        {
1881            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
1882
1883            tmp.as_file()
1884                .write_all(b"write3")
1885                .expect("write must succeed");
1886            cache
1887                .set(&TestKey::new("a"), tmp.path())
1888                .expect("set must succeed");
1889        }
1890
1891        // We should find:
1892        // a => write3
1893        // b => write2
1894        // c => extra2
1895
1896        // So we should be able to touch everything.
1897        assert!(matches!(cache.touch(&TestKey::new("a")), Ok(true)));
1898        assert!(matches!(cache.touch(&TestKey::new("b")), Ok(true)));
1899        assert!(matches!(cache.touch(&TestKey::new("c")), Ok(true)));
1900
1901        // And read the expected contents.
1902        {
1903            let mut a_file = cache
1904                .get(&TestKey::new("a"))
1905                .expect("must succeed")
1906                .expect("must exist");
1907            let mut dst = Vec::new();
1908            a_file.read_to_end(&mut dst).expect("read must succeed");
1909            assert_eq!(&dst, b"write3");
1910        }
1911
1912        {
1913            let mut b_file = cache
1914                .get(&TestKey::new("b"))
1915                .expect("must succeed")
1916                .expect("must exist");
1917            let mut dst = Vec::new();
1918            b_file.read_to_end(&mut dst).expect("read must succeed");
1919            assert_eq!(&dst, b"write2");
1920        }
1921
1922        {
1923            let mut c_file = cache
1924                .get(&TestKey::new("c"))
1925                .expect("must succeed")
1926                .expect("must exist");
1927            let mut dst = Vec::new();
1928            c_file.read_to_end(&mut dst).expect("read must succeed");
1929            assert_eq!(&dst, b"extra2");
1930        }
1931    }
1932
1933    // Smoke test a wrapped sharded cache.
1934    #[test]
1935    fn smoke_test_sharded() {
1936        use std::io::{Read, Write};
1937        use tempfile::NamedTempFile;
1938        use test_dir::{DirBuilder, FileType, TestDir};
1939
1940        let temp = TestDir::temp()
1941            .create("cache", FileType::Dir)
1942            .create("extra_plain", FileType::Dir)
1943            .create("extra_sharded", FileType::Dir);
1944
1945        // Populate the plain cache in `extra_plain` with one file, "b".
1946        {
1947            let cache = PlainCache::new(temp.path("extra_plain"), 10);
1948
1949            let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
1950                .expect("new temp file must succeed");
1951            tmp.as_file()
1952                .write_all(b"extra_plain")
1953                .expect("write must succeed");
1954
1955            cache.put("b", tmp.path()).expect("put must succeed");
1956        }
1957
1958        // And now add "c" in the sharded `extra_sharded` cache.
1959        {
1960            let cache = ShardedCache::new(temp.path("extra_sharded"), 10, 10);
1961
1962            let tmp = NamedTempFile::new_in(cache.temp_dir(None).expect("temp_dir must succeed"))
1963                .expect("new temp file must succeed");
1964            tmp.as_file()
1965                .write_all(b"extra_sharded")
1966                .expect("write must succeed");
1967
1968            cache
1969                .put((&TestKey::new("c")).into(), tmp.path())
1970                .expect("put must succeed");
1971        }
1972
1973        let cache = CacheBuilder::new()
1974            .plain_writer(temp.path("cache"), 10)
1975            // Override the writer with a sharded cache
1976            .writer(temp.path("cache"), 10, 10)
1977            .plain_reader(temp.path("extra_plain"))
1978            .sharded_reader(temp.path("extra_sharded"), 10)
1979            .take()
1980            .build();
1981
1982        // There shouldn't be anything for "a"
1983        assert!(matches!(cache.get(&TestKey::new("a")), Ok(None)));
1984        assert!(matches!(cache.touch(&TestKey::new("a")), Ok(false)));
1985
1986        // We should be able to touch "b"
1987        assert!(matches!(cache.touch(&TestKey::new("b")), Ok(true)));
1988
1989        // And its contents should match that of the "extra" cache dir.
1990        {
1991            let mut b_file = cache
1992                .get(&TestKey::new("b"))
1993                .expect("must succeed")
1994                .expect("must exist");
1995            let mut dst = Vec::new();
1996            b_file.read_to_end(&mut dst).expect("read must succeed");
1997            assert_eq!(&dst, b"extra_plain");
1998        }
1999
2000        // Similarly for "c"
2001        {
2002            let mut c_file = cache
2003                .get(&TestKey::new("c"))
2004                .expect("must succeed")
2005                .expect("must exist");
2006            let mut dst = Vec::new();
2007            c_file.read_to_end(&mut dst).expect("read must succeed");
2008            assert_eq!(&dst, b"extra_sharded");
2009        }
2010
2011        // Now populate "a" and "b" in the cache.
2012        {
2013            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
2014
2015            tmp.as_file()
2016                .write_all(b"write")
2017                .expect("write must succeed");
2018            cache
2019                .set(&TestKey::new("a"), tmp.path())
2020                .expect("set must succeed");
2021        }
2022
2023        {
2024            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
2025
2026            tmp.as_file()
2027                .write_all(b"write2")
2028                .expect("write must succeed");
2029            // Exercise set_temp_file.
2030            cache
2031                .set_temp_file(&TestKey::new("b"), tmp)
2032                .expect("set must succeed");
2033        }
2034
2035        // And fail to update "a" with a put.
2036        {
2037            let tmp = NamedTempFile::new_in(temp.path(".")).expect("new temp file must succeed");
2038
2039            tmp.as_file()
2040                .write_all(b"write3")
2041                .expect("write must succeed");
2042            cache
2043                .put(&TestKey::new("a"), tmp.path())
2044                .expect("put must succeed");
2045        }
2046
2047        // We should find:
2048        // a => write
2049        // b => write2
2050        // c => extra_sharded
2051
2052        // So we should be able to touch everything.
2053        assert!(matches!(cache.touch(&TestKey::new("a")), Ok(true)));
2054        assert!(matches!(cache.touch(&TestKey::new("b")), Ok(true)));
2055        assert!(matches!(cache.touch(&TestKey::new("c")), Ok(true)));
2056
2057        // And read the expected contents.
2058        {
2059            let mut a_file = cache
2060                .get(&TestKey::new("a"))
2061                .expect("must succeed")
2062                .expect("must exist");
2063            let mut dst = Vec::new();
2064            a_file.read_to_end(&mut dst).expect("read must succeed");
2065            assert_eq!(&dst, b"write");
2066        }
2067
2068        {
2069            let mut b_file = cache
2070                .get(&TestKey::new("b"))
2071                .expect("must succeed")
2072                .expect("must exist");
2073            let mut dst = Vec::new();
2074            b_file.read_to_end(&mut dst).expect("read must succeed");
2075            assert_eq!(&dst, b"write2");
2076        }
2077
2078        {
2079            let mut c_file = cache
2080                .get(&TestKey::new("c"))
2081                .expect("must succeed")
2082                .expect("must exist");
2083            let mut dst = Vec::new();
2084            c_file.read_to_end(&mut dst).expect("read must succeed");
2085            assert_eq!(&dst, b"extra_sharded");
2086        }
2087    }
2088}