Directory: | cvmfs/ |
---|---|
File: | cvmfs/fuse_evict.cc |
Date: | 2024-04-28 02:33:07 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 107 | 149 | 71.8% |
Branches: | 64 | 130 | 49.2% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | */ | ||
4 | |||
5 | #define __STDC_FORMAT_MACROS | ||
6 | |||
7 | #include "cvmfs_config.h" | ||
8 | #include "fuse_evict.h" | ||
9 | |||
10 | #include <inttypes.h> | ||
11 | #include <stdint.h> | ||
12 | |||
13 | #include <cassert> | ||
14 | #include <cstdlib> | ||
15 | #include <cstring> | ||
16 | |||
17 | #include "glue_buffer.h" | ||
18 | #include "mountpoint.h" | ||
19 | #include "shortstring.h" | ||
20 | #include "util/logging.h" | ||
21 | #include "util/platform.h" | ||
22 | #include "util/posix.h" | ||
23 | #include "util/smalloc.h" | ||
24 | |||
25 | using namespace std; // NOLINT | ||
26 | |||
27 | 4 | FuseInvalidator::Handle::Handle(unsigned timeout_s) | |
28 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
|
4 | : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec)) |
29 | { | ||
30 | 4 | status_ = reinterpret_cast<atomic_int32 *>(smalloc(sizeof(atomic_int32))); | |
31 | 4 | atomic_init32(status_); | |
32 | 4 | } | |
33 | |||
34 | |||
35 | 4 | FuseInvalidator::Handle::~Handle() { | |
36 | 4 | free(status_); | |
37 | 4 | } | |
38 | |||
39 | |||
40 | 5 | void FuseInvalidator::Handle::WaitFor() { | |
41 |
2/2✓ Branch 2 taken 6 times.
✓ Branch 3 taken 5 times.
|
11 | while (!IsDone()) SafeSleepMs(FuseInvalidator::kCheckTimeoutFreqMs); |
42 | 5 | } | |
43 | |||
44 | |||
45 | //------------------------------------------------------------------------------ | ||
46 | |||
47 | |||
48 | const unsigned FuseInvalidator::kTimeoutSafetyMarginSec = 1; | ||
49 | const unsigned FuseInvalidator::kCheckTimeoutFreqMs = 100; | ||
50 | const unsigned FuseInvalidator::kCheckTimeoutFreqOps = 256; | ||
51 | |||
52 | bool FuseInvalidator::g_fuse_notify_invalidation_ = true; | ||
53 | |||
54 | 3 | bool FuseInvalidator::HasFuseNotifyInval() { | |
55 | /** | ||
56 | * Technically, also libfuse 2.8 has support. Libfuse 2.8 comes with EL6, | ||
57 | * which had bugs reported related to the fuse_notify_inval_...() functions. | ||
58 | * Since just waiting for the timeout works perfectly fine, there is no reason | ||
59 | * to optimize for forced cache eviction too aggressively. | ||
60 | * | ||
61 | * TODO(jblomer): could we have libfuse 2.9 or higher with a very old kernel | ||
62 | * that doesn't support active invalidation? How old does the kernel need | ||
63 | * to be? Probably that situation is never triggered in practice. | ||
64 | */ | ||
65 | 3 | return FuseInvalidator::g_fuse_notify_invalidation_ && (FUSE_VERSION >= 29); | |
66 | } | ||
67 | |||
68 | |||
69 | ✗ | FuseInvalidator::FuseInvalidator( | |
70 | MountPoint *mount_point, | ||
71 | void **fuse_channel_or_session, | ||
72 | ✗ | bool fuse_notify_invalidation) | |
73 | ✗ | : mount_point_(mount_point) | |
74 | ✗ | , inode_tracker_(mount_point->inode_tracker()) | |
75 | ✗ | , dentry_tracker_(mount_point->dentry_tracker()) | |
76 | ✗ | , fuse_channel_or_session_(fuse_channel_or_session) | |
77 | ✗ | , spawned_(false) | |
78 | { | ||
79 | ✗ | g_fuse_notify_invalidation_ = fuse_notify_invalidation; | |
80 | ✗ | MakePipe(pipe_ctrl_); | |
81 | ✗ | memset(&thread_invalidator_, 0, sizeof(thread_invalidator_)); | |
82 | ✗ | atomic_init32(&terminated_); | |
83 | } | ||
84 | |||
85 | 5 | FuseInvalidator::FuseInvalidator( | |
86 | glue::InodeTracker *inode_tracker, | ||
87 | glue::DentryTracker *dentry_tracker, | ||
88 | void **fuse_channel_or_session, | ||
89 | 5 | bool fuse_notify_invalidation) | |
90 | 5 | : mount_point_(NULL) | |
91 | 5 | , inode_tracker_(inode_tracker) | |
92 | 5 | , dentry_tracker_(dentry_tracker) | |
93 | 5 | , fuse_channel_or_session_(fuse_channel_or_session) | |
94 | 5 | , spawned_(false) | |
95 | { | ||
96 | 5 | g_fuse_notify_invalidation_ = fuse_notify_invalidation; | |
97 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | MakePipe(pipe_ctrl_); |
98 | 5 | memset(&thread_invalidator_, 0, sizeof(thread_invalidator_)); | |
99 | 5 | atomic_init32(&terminated_); | |
100 | 5 | } | |
101 | |||
102 | |||
103 | 5 | FuseInvalidator::~FuseInvalidator() { | |
104 | 5 | atomic_cas32(&terminated_, 0, 1); | |
105 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (spawned_) { |
106 | 4 | char c = 'Q'; | |
107 | 4 | WritePipe(pipe_ctrl_[1], &c, 1); | |
108 | 4 | pthread_join(thread_invalidator_, NULL); | |
109 | } | ||
110 | 5 | ClosePipe(pipe_ctrl_); | |
111 | 5 | } | |
112 | |||
113 | |||
114 | 5 | void FuseInvalidator::InvalidateInodes(Handle *handle) { | |
115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | assert(handle != NULL); |
116 | 5 | char c = 'I'; | |
117 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | WritePipe(pipe_ctrl_[1], &c, 1); |
118 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | WritePipe(pipe_ctrl_[1], &handle, sizeof(handle)); |
119 | 5 | } | |
120 | |||
121 | ✗ | void FuseInvalidator::InvalidateDentry( | |
122 | uint64_t parent_ino, const NameString &name) | ||
123 | { | ||
124 | ✗ | char c = 'D'; | |
125 | ✗ | WritePipe(pipe_ctrl_[1], &c, 1); | |
126 | ✗ | WritePipe(pipe_ctrl_[1], &parent_ino, sizeof(parent_ino)); | |
127 | ✗ | unsigned len = name.GetLength(); | |
128 | ✗ | WritePipe(pipe_ctrl_[1], &len, sizeof(len)); | |
129 | ✗ | WritePipe(pipe_ctrl_[1], name.GetChars(), len); | |
130 | } | ||
131 | |||
132 | 4 | void *FuseInvalidator::MainInvalidator(void *data) { | |
133 | 4 | FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data); | |
134 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread"); |
135 | |||
136 | 4 | bool reported_missing_inval_support = false; | |
137 | char c; | ||
138 | Handle *handle; | ||
139 | while (true) { | ||
140 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | ReadPipe(invalidator->pipe_ctrl_[0], &c, 1); |
141 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
|
9 | if (c == 'Q') |
142 | 4 | break; | |
143 | |||
144 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (c == 'D') { |
145 | uint64_t parent_ino; | ||
146 | unsigned len; | ||
147 | ✗ | ReadPipe(invalidator->pipe_ctrl_[0], &parent_ino, sizeof(parent_ino)); | |
148 | ✗ | ReadPipe(invalidator->pipe_ctrl_[0], &len, sizeof(len)); | |
149 | ✗ | char *name = static_cast<char *>(smalloc(len + 1)); | |
150 | ✗ | ReadPipe(invalidator->pipe_ctrl_[0], name, len); | |
151 | ✗ | name[len] = '\0'; | |
152 | ✗ | if (invalidator->fuse_channel_or_session_ == NULL) { | |
153 | ✗ | if (!reported_missing_inval_support) { | |
154 | ✗ | LogCvmfs(kLogCvmfs, kLogSyslogWarn, | |
155 | "missing fuse support for dentry invalidation (%lu/%s)", | ||
156 | parent_ino, name); | ||
157 | ✗ | reported_missing_inval_support = true; | |
158 | } | ||
159 | ✗ | free(name); | |
160 | ✗ | continue; | |
161 | } | ||
162 | ✗ | LogCvmfs(kLogCvmfs, kLogDebug, "evicting single dentry %" PRIu64 "/%s", | |
163 | parent_ino, name); | ||
164 | #if CVMFS_USE_LIBFUSE == 2 | ||
165 | ✗ | fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_chan**>( | |
166 | ✗ | invalidator->fuse_channel_or_session_), parent_ino, name, len); | |
167 | #else | ||
168 | ✗ | fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_session**>( | |
169 | ✗ | invalidator->fuse_channel_or_session_), parent_ino, name, len); | |
170 | #endif | ||
171 | ✗ | free(name); | |
172 | ✗ | continue; | |
173 | } | ||
174 | |||
175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | assert(c == 'I'); |
176 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | ReadPipe(invalidator->pipe_ctrl_[0], &handle, sizeof(handle)); |
177 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u", |
178 | handle->timeout_s_); | ||
179 | |||
180 | 5 | uint64_t deadline = platform_monotonic_time() + handle->timeout_s_; | |
181 | |||
182 | // Fallback: drainout by timeout | ||
183 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
|
8 | if ((invalidator->fuse_channel_or_session_ == NULL) || |
184 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | !HasFuseNotifyInval()) |
185 | { | ||
186 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
|
2 | while (platform_monotonic_time() < deadline) { |
187 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | SafeSleepMs(kCheckTimeoutFreqMs); |
188 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (atomic_read32(&invalidator->terminated_) == 1) { |
189 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | LogCvmfs(kLogCvmfs, kLogDebug, |
190 | "cancel cache eviction due to termination"); | ||
191 | 1 | break; | |
192 | } | ||
193 | } | ||
194 | 2 | handle->SetDone(); | |
195 | 2 | continue; | |
196 | } | ||
197 | |||
198 | // We must not hold a lock when calling fuse_lowlevel_notify_inval_entry. | ||
199 | // Therefore, we first copy all the inodes into a temporary data structure. | ||
200 | glue::InodeTracker::Cursor inode_cursor( | ||
201 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | invalidator->inode_tracker_->BeginEnumerate()); |
202 | uint64_t inode; | ||
203 |
3/4✓ Branch 1 taken 3075 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3072 times.
✓ Branch 4 taken 3 times.
|
3075 | while (invalidator->inode_tracker_->NextInode(&inode_cursor, &inode)) |
204 | { | ||
205 |
1/2✓ Branch 1 taken 3072 times.
✗ Branch 2 not taken.
|
3072 | invalidator->evict_list_.PushBack(inode); |
206 | } | ||
207 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | invalidator->inode_tracker_->EndEnumerate(&inode_cursor); |
208 | |||
209 | 3 | unsigned i = 0; | |
210 | 3 | unsigned N = invalidator->evict_list_.size(); | |
211 |
2/2✓ Branch 0 taken 1536 times.
✓ Branch 1 taken 1 times.
|
1537 | while (i < N) { |
212 | 1536 | uint64_t inode = invalidator->evict_list_.At(i); | |
213 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1536 times.
|
1536 | if (inode == 0) |
214 | ✗ | inode = FUSE_ROOT_ID; | |
215 | // Can fail, e.g. the inode might be already evicted | ||
216 | |||
217 | int dbg_retval; | ||
218 | |||
219 | #if CVMFS_USE_LIBFUSE == 2 | ||
220 | 1536 | dbg_retval = fuse_lowlevel_notify_inval_inode( | |
221 | *reinterpret_cast<struct fuse_chan**>( | ||
222 |
0/2✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
1536 | invalidator->fuse_channel_or_session_), inode, 0, 0); |
223 | #else | ||
224 | ✗ | dbg_retval = fuse_lowlevel_notify_inval_inode( | |
225 | *reinterpret_cast<struct fuse_session**>( | ||
226 | ✗ | invalidator->fuse_channel_or_session_), inode, 0, 0); | |
227 | #endif | ||
228 |
1/2✓ Branch 1 taken 1536 times.
✗ Branch 2 not taken.
|
1536 | LogCvmfs(kLogCvmfs, kLogDebug, |
229 | "evicting inode %" PRIu64 " with retval: %d", | ||
230 | inode, dbg_retval); | ||
231 | |||
232 | (void) dbg_retval; // prevent compiler complaining | ||
233 | |||
234 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1530 times.
|
1536 | if ((++i % kCheckTimeoutFreqOps) == 0) { |
235 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 5 times.
|
6 | if (platform_monotonic_time() >= deadline) { |
236 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | LogCvmfs(kLogCvmfs, kLogDebug, |
237 | "cancel cache eviction after %u entries due to timeout", i); | ||
238 | 1 | break; | |
239 | } | ||
240 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
|
5 | if (atomic_read32(&invalidator->terminated_) == 1) { |
241 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | LogCvmfs(kLogCvmfs, kLogDebug, |
242 | "cancel cache eviction due to termination"); | ||
243 | 1 | break; | |
244 | } | ||
245 | } | ||
246 | } | ||
247 | |||
248 | // Do the dentry tracker last to increase the effectiveness of pruning | ||
249 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | invalidator->dentry_tracker_->Prune(); |
250 | // Copy and empty the dentry tracker in a single atomic operation | ||
251 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move(); |
252 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate(); |
253 | uint64_t entry_parent; | ||
254 | 3 | NameString entry_name; | |
255 | 3 | i = 0; | |
256 | |||
257 | #if CVMFS_USE_LIBFUSE == 2 | ||
258 | int (*notify_func)(struct fuse_chan*, fuse_ino_t, const char*, size_t); | ||
259 | 3 | notify_func = &fuse_lowlevel_notify_inval_entry; | |
260 | #else | ||
261 | int (*notify_func)(struct fuse_session*, fuse_ino_t, const char*, size_t); | ||
262 | ✗ | notify_func = &fuse_lowlevel_notify_inval_entry; | |
263 | #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 16) | ||
264 | // must be libfuse >= 3.16, otherwise the signature is wrong and it | ||
265 | // will fail building | ||
266 | // mount_point can only be NULL for unittests | ||
267 | if (invalidator->mount_point_ != NULL && | ||
268 | invalidator->mount_point_->fuse_expire_entry()) { | ||
269 | notify_func = &fuse_lowlevel_notify_expire_entry; | ||
270 | } | ||
271 | #endif | ||
272 | #endif | ||
273 | |||
274 |
3/4✓ Branch 1 taken 1282 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1280 times.
✓ Branch 4 taken 2 times.
|
1282 | while (dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name)) |
275 | { | ||
276 |
1/2✓ Branch 2 taken 1280 times.
✗ Branch 3 not taken.
|
1280 | LogCvmfs(kLogCvmfs, kLogDebug, "evicting dentry %lu --> %s", |
277 | entry_parent, entry_name.c_str()); | ||
278 | // Can fail, e.g. the entry might be already evicted | ||
279 | #if CVMFS_USE_LIBFUSE == 2 | ||
280 | 1280 | struct fuse_chan* channel_or_session = | |
281 | *reinterpret_cast<struct fuse_chan**>( | ||
282 | 1280 | invalidator->fuse_channel_or_session_); | |
283 | #else | ||
284 | ✗ | struct fuse_session* channel_or_session = | |
285 | *reinterpret_cast<struct fuse_session**>( | ||
286 | ✗ | invalidator->fuse_channel_or_session_); | |
287 | #endif | ||
288 | |||
289 |
1/2✓ Branch 2 taken 1280 times.
✗ Branch 3 not taken.
|
1280 | notify_func(channel_or_session, entry_parent, entry_name.GetChars(), |
290 | 1280 | entry_name.GetLength()); | |
291 | |||
292 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1275 times.
|
1280 | if ((++i % kCheckTimeoutFreqOps) == 0) { |
293 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
|
5 | if (atomic_read32(&invalidator->terminated_) == 1) { |
294 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | LogCvmfs(kLogCvmfs, kLogDebug, |
295 | "cancel cache eviction due to termination"); | ||
296 | 1 | break; | |
297 | } | ||
298 | } | ||
299 | } | ||
300 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | dentries_copy->EndEnumerate(&dentry_cursor); |
301 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | delete dentries_copy; |
302 | |||
303 | 3 | handle->SetDone(); | |
304 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | invalidator->evict_list_.Clear(); |
305 | 8 | } | |
306 | |||
307 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread"); |
308 | 4 | return NULL; | |
309 | } | ||
310 | |||
311 | |||
312 | 4 | void FuseInvalidator::Spawn() { | |
313 | int retval; | ||
314 | 4 | retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this); | |
315 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | assert(retval == 0); |
316 | 4 | spawned_ = true; | |
317 | 4 | } | |
318 |