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