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