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