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
42/// A snapshot of a resource which is up-to-date in the moment it is retrieved.
43pub type SharedFileSnapshot<T> = OwnShared<FileSnapshot<T>>;
44
45/// Use this type for fields in structs that are to store the [`FileSnapshot`], typically behind an [`OwnShared`].
46///
47/// Note that the resource itself is behind another [`OwnShared`] to allow it to be used without holding any kind of lock, hence
48/// without blocking updates while it is used.
49#[derive(Debug, Default)]
50pub struct SharedFileSnapshotMut<T: std::fmt::Debug>(pub MutableOnDemand<Option<SharedFileSnapshot<T>>>);
51
52impl<T: std::fmt::Debug> Deref for FileSnapshot<T> {
53    type Target = T;
54
55    fn deref(&self) -> &Self::Target {
56        &self.value
57    }
58}
59
60impl<T: std::fmt::Debug> std::ops::DerefMut for FileSnapshot<T> {
61    fn deref_mut(&mut self) -> &mut Self::Target {
62        &mut self.value
63    }
64}
65
66impl<T: std::fmt::Debug> Deref for SharedFileSnapshotMut<T> {
67    type Target = MutableOnDemand<Option<SharedFileSnapshot<T>>>;
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74impl<T: std::fmt::Debug> SharedFileSnapshotMut<T> {
75    /// Create a new instance of this type.
76    ///
77    /// Useful in case `Default::default()` isn't working for some reason.
78    pub fn new() -> Self {
79        SharedFileSnapshotMut(MutableOnDemand::new(None))
80    }
81
82    /// Refresh `state` forcefully by re-`open`ing the resource. Note that `open()` returns `None` if the resource isn't
83    /// present on disk, and that it's critical that the modified time is obtained _before_ opening the resource.
84    pub fn force_refresh<E>(
85        &self,
86        open: impl FnOnce() -> Result<Option<(std::time::SystemTime, T)>, E>,
87    ) -> Result<(), E> {
88        let mut state = get_mut(&self.0);
89        *state = open()?.map(|(modified, value)| OwnShared::new(FileSnapshot { value, modified }));
90        Ok(())
91    }
92
93    /// Assure that the resource in `state` is up-to-date by comparing the `current_modification_time` with the one we know in `state`
94    /// and by acting accordingly.
95    /// Returns the potentially updated/reloaded resource if it is still present on disk, which then represents a snapshot that is up-to-date
96    /// in that very moment, or `None` if the underlying file doesn't exist.
97    ///
98    /// Note that even though this is racy, each time a request is made there is a chance to see the actual state.
99    pub fn recent_snapshot<E>(
100        &self,
101        mut current_modification_time: impl FnMut() -> Option<std::time::SystemTime>,
102        open: impl FnOnce() -> Result<Option<T>, E>,
103    ) -> Result<Option<SharedFileSnapshot<T>>, E> {
104        let state = get_ref(self);
105        let recent_modification = current_modification_time();
106        let buffer = match (&*state, recent_modification) {
107            (None, None) => (*state).clone(),
108            (Some(_), None) => {
109                drop(state);
110                let mut state = get_mut(self);
111                *state = None;
112                (*state).clone()
113            }
114            (Some(snapshot), Some(modified_time)) => {
115                if snapshot.modified < modified_time {
116                    drop(state);
117                    let mut state = get_mut(self);
118
119                    if let (Some(_snapshot), Some(modified_time)) = (&*state, current_modification_time()) {
120                        *state = open()?.map(|value| {
121                            OwnShared::new(FileSnapshot {
122                                value,
123                                modified: modified_time,
124                            })
125                        });
126                    }
127
128                    (*state).clone()
129                } else {
130                    // Note that this relies on sub-section precision or else is a race when the packed file was just changed.
131                    // It's nothing we can know though, so… up to the caller unfortunately.
132                    Some(snapshot.clone())
133                }
134            }
135            (None, Some(_modified_time)) => {
136                drop(state);
137                let mut state = get_mut(self);
138                // Still in the same situation? If so, load the buffer. This compensates for the trampling herd
139                // during lazy-loading at the expense of another mtime check.
140                if let (None, Some(modified_time)) = (&*state, current_modification_time()) {
141                    *state = open()?.map(|value| {
142                        OwnShared::new(FileSnapshot {
143                            value,
144                            modified: modified_time,
145                        })
146                    });
147                }
148                (*state).clone()
149            }
150        };
151        Ok(buffer)
152    }
153}