gix_fs/
snapshot.rs

1// TODO: tests
2use std::ops::Deref;
3
4use gix_features::threading::{get_mut, get_ref, MutableOnDemand, OwnShared};
5
6/// A structure holding enough information to reload a value if its on-disk representation changes as determined by its modified time.
7#[derive(Debug)]
8pub struct FileSnapshot<T: std::fmt::Debug> {
9    value: T,
10    modified: std::time::SystemTime,
11}
12
13/// Lifecycle
14impl<T: std::fmt::Debug> FileSnapshot<T> {
15    /// A way for users to create 'fake' snapshot from `value` that isn't actually linked to a file on disk.
16    ///
17    /// This is useful if there are alternative ways of obtaining the contained instance as fallback to trying
18    /// to read it from disk.
19    pub fn new(value: T) -> Self {
20        FileSnapshot {
21            value,
22            modified: std::time::UNIX_EPOCH,
23        }
24    }
25}
26
27impl<T: std::fmt::Debug> From<T> for FileSnapshot<T> {
28    fn from(value: T) -> Self {
29        FileSnapshot::new(value)
30    }
31}
32
33impl<T: Clone + std::fmt::Debug> Clone for FileSnapshot<T> {
34    fn clone(&self) -> Self {
35        Self {
36            value: self.value.clone(),
37            modified: self.modified,
38        }
39    }
40}
41
42impl<T: Clone + std::fmt::Debug> FileSnapshot<T> {
43    /// Return the contained instance if nobody else is holding it, or clone it otherwise.
44    pub fn into_owned_or_cloned(self: OwnShared<Self>) -> T {
45        match OwnShared::try_unwrap(self) {
46            Ok(this) => this.value,
47            Err(this) => this.value.clone(),
48        }
49    }
50}
51
52/// A snapshot of a resource which is up-to-date in the moment it is retrieved.
53pub type SharedFileSnapshot<T> = OwnShared<FileSnapshot<T>>;
54
55/// Use this type for fields in structs that are to store the [`FileSnapshot`], typically behind an [`OwnShared`].
56///
57/// Note that the resource itself is behind another [`OwnShared`] to allow it to be used without holding any kind of lock, hence
58/// without blocking updates while it is used.
59#[derive(Debug, Default)]
60pub struct SharedFileSnapshotMut<T: std::fmt::Debug>(pub MutableOnDemand<Option<SharedFileSnapshot<T>>>);
61
62impl<T: std::fmt::Debug> Deref for FileSnapshot<T> {
63    type Target = T;
64
65    fn deref(&self) -> &Self::Target {
66        &self.value
67    }
68}
69
70impl<T: std::fmt::Debug> std::ops::DerefMut for FileSnapshot<T> {
71    fn deref_mut(&mut self) -> &mut Self::Target {
72        &mut self.value
73    }
74}
75
76impl<T: std::fmt::Debug> Deref for SharedFileSnapshotMut<T> {
77    type Target = MutableOnDemand<Option<SharedFileSnapshot<T>>>;
78
79    fn deref(&self) -> &Self::Target {
80        &self.0
81    }
82}
83
84impl<T: std::fmt::Debug> SharedFileSnapshotMut<T> {
85    /// Create a new instance of this type.
86    ///
87    /// Useful in case `Default::default()` isn't working for some reason.
88    pub fn new() -> Self {
89        SharedFileSnapshotMut(MutableOnDemand::new(None))
90    }
91
92    /// Refresh `state` forcefully by re-`open`ing the resource. Note that `open()` returns `None` if the resource isn't
93    /// present on disk, and that it's critical that the modified time is obtained _before_ opening the resource.
94    pub fn force_refresh<E>(
95        &self,
96        open: impl FnOnce() -> Result<Option<(std::time::SystemTime, T)>, E>,
97    ) -> Result<(), E> {
98        let mut state = get_mut(&self.0);
99        *state = open()?.map(|(modified, value)| OwnShared::new(FileSnapshot { value, modified }));
100        Ok(())
101    }
102
103    /// Assure that the resource in `state` is up-to-date by comparing the `current_modification_time` with the one we know in `state`
104    /// and by acting accordingly.
105    /// Returns the potentially updated/reloaded resource if it is still present on disk, which then represents a snapshot that is up-to-date
106    /// in that very moment, or `None` if the underlying file doesn't exist.
107    ///
108    /// Note that even though this is racy, each time a request is made there is a chance to see the actual state.
109    pub fn recent_snapshot<E>(
110        &self,
111        mut current_modification_time: impl FnMut() -> Option<std::time::SystemTime>,
112        open: impl FnOnce() -> Result<Option<T>, E>,
113    ) -> Result<Option<SharedFileSnapshot<T>>, E> {
114        let state = get_ref(self);
115        let recent_modification = current_modification_time();
116        let buffer = match (&*state, recent_modification) {
117            (None, None) => (*state).clone(),
118            (Some(_), None) => {
119                drop(state);
120                let mut state = get_mut(self);
121                *state = None;
122                (*state).clone()
123            }
124            (Some(snapshot), Some(modified_time)) => {
125                if snapshot.modified < modified_time {
126                    drop(state);
127                    let mut state = get_mut(self);
128
129                    if let (Some(_snapshot), Some(modified_time)) = (&*state, current_modification_time()) {
130                        *state = open()?.map(|value| {
131                            OwnShared::new(FileSnapshot {
132                                value,
133                                modified: modified_time,
134                            })
135                        });
136                    }
137
138                    (*state).clone()
139                } else {
140                    // Note that this relies on sub-section precision or else is a race when the packed file was just changed.
141                    // It's nothing we can know though, so… up to the caller unfortunately.
142                    Some(snapshot.clone())
143                }
144            }
145            (None, Some(_modified_time)) => {
146                drop(state);
147                let mut state = get_mut(self);
148                // Still in the same situation? If so, load the buffer. This compensates for the trampling herd
149                // during lazy-loading at the expense of another mtime check.
150                if let (None, Some(modified_time)) = (&*state, current_modification_time()) {
151                    *state = open()?.map(|value| {
152                        OwnShared::new(FileSnapshot {
153                            value,
154                            modified: modified_time,
155                        })
156                    });
157                }
158                (*state).clone()
159            }
160        };
161        Ok(buffer)
162    }
163}