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)
    }
}