GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 112 164 68.3%
Branches: 62 127 48.8%

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