GCC Code Coverage Report


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