GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2026-04-19 02:41:37
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 128 FuseInvalidator::Handle::Handle(unsigned timeout_s)
29
2/2
✓ Branch 1 taken 64 times.
✓ Branch 2 taken 64 times.
128 : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec)) {
30 128 status_ = reinterpret_cast<atomic_int32 *>(smalloc(sizeof(atomic_int32)));
31 128 atomic_init32(status_);
32 128 }
33
34
35 128 FuseInvalidator::Handle::~Handle() { free(status_); }
36
37
38 160 void FuseInvalidator::Handle::WaitFor() {
39
2/2
✓ Branch 1 taken 192 times.
✓ Branch 2 taken 160 times.
352 while (!IsDone())
40 192 SafeSleepMs(FuseInvalidator::kCheckTimeoutFreqMs);
41 160 }
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 96 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 96 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 160 FuseInvalidator::FuseInvalidator(glue::InodeTracker *inode_tracker,
82 glue::DentryTracker *dentry_tracker,
83 void **fuse_channel_or_session,
84 160 bool fuse_notify_invalidation)
85 160 : mount_point_(NULL)
86 160 , inode_tracker_(inode_tracker)
87 160 , dentry_tracker_(dentry_tracker)
88 160 , fuse_channel_or_session_(fuse_channel_or_session)
89
1/2
✓ Branch 3 taken 160 times.
✗ Branch 4 not taken.
160 , spawned_(false) {
90 160 g_fuse_notify_invalidation_ = fuse_notify_invalidation;
91 160 memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
92 160 atomic_init32(&terminated_);
93 160 }
94
95
96 160 FuseInvalidator::~FuseInvalidator() {
97 160 atomic_cas32(&terminated_, 0, 1);
98
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 32 times.
160 if (spawned_) {
99 128 QuitCommand *cmd = new (smalloc(sizeof(QuitCommand))) QuitCommand();
100 128 channel_.PushBack(cmd);
101 128 pthread_join(thread_invalidator_, NULL);
102 }
103 160 }
104
105
106 160 void FuseInvalidator::InvalidateInodes(Handle *handle) {
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
160 assert(handle != NULL);
108 InvalInodesCommand *inval_inodes_command = new (
109 160 smalloc(sizeof(InvalInodesCommand))) InvalInodesCommand();
110 160 inval_inodes_command->handle = handle;
111 160 channel_.PushBack(inval_inodes_command);
112 160 }
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 128 void *FuseInvalidator::MainInvalidator(void *data) {
139 128 FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
140 128 LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
141
142 128 bool reported_missing_inval_support = false;
143 while (true) {
144
1/2
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
288 Command *command = invalidator->channel_.PopFront();
145
146
3/4
✓ Branch 0 taken 288 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✓ Branch 3 taken 160 times.
288 if (dynamic_cast<QuitCommand *>(command)) {
147 128 command->~Command();
148 128 free(command);
149 128 break;
150 }
151
152 InvalDentryCommand
153
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 *inval_dentry_command = dynamic_cast<InvalDentryCommand *>(command);
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
160 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 64 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 160 times.
✗ Branch 1 not taken.
160 *inval_inodes_command = dynamic_cast<InvalInodesCommand *>(command);
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
160 assert(inval_inodes_command);
194
195 160 Handle *handle = inval_inodes_command->handle;
196
1/2
✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
160 LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
197 handle->timeout_s_);
198 160 inval_inodes_command->~InvalInodesCommand();
199 160 free(inval_inodes_command);
200
201 160 const uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
202
203 // Fallback: drainout by timeout
204 384 if ((invalidator->fuse_channel_or_session_ == NULL)
205
5/6
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 96 times.
✓ Branch 5 taken 64 times.
✓ Branch 6 taken 96 times.
160 || !HasFuseNotifyInval()) {
206
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 32 times.
64 while (platform_monotonic_time() < deadline) {
207
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 SafeSleepMs(kCheckTimeoutFreqMs);
208
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 if (atomic_read32(&invalidator->terminated_) == 1) {
209
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 LogCvmfs(kLogCvmfs, kLogDebug,
210 "cancel cache eviction due to termination");
211 32 break;
212 }
213 }
214 64 handle->SetDone();
215 64 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 96 times.
✗ Branch 2 not taken.
96 invalidator->inode_tracker_->BeginEnumerate());
222 uint64_t inode;
223
3/4
✓ Branch 1 taken 98400 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 98304 times.
✓ Branch 4 taken 96 times.
98400 while (invalidator->inode_tracker_->NextInode(&inode_cursor, &inode)) {
224
1/2
✓ Branch 1 taken 98304 times.
✗ Branch 2 not taken.
98304 invalidator->evict_list_.PushBack(inode);
225 }
226
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 invalidator->inode_tracker_->EndEnumerate(&inode_cursor);
227
228 96 unsigned i = 0;
229 96 const unsigned N = invalidator->evict_list_.size();
230
2/2
✓ Branch 0 taken 49152 times.
✓ Branch 1 taken 32 times.
49184 while (i < N) {
231 49152 uint64_t inode = invalidator->evict_list_.At(i);
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49152 times.
49152 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 49152 dbg_retval = fuse_lowlevel_notify_inval_inode(
240 *reinterpret_cast<struct fuse_chan **>(
241 49152 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 49152 times.
✗ Branch 2 not taken.
49152 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 192 times.
✓ Branch 1 taken 48960 times.
49152 if ((++i % kCheckTimeoutFreqOps) == 0) {
255
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 160 times.
192 if (platform_monotonic_time() >= deadline) {
256
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 LogCvmfs(kLogCvmfs, kLogDebug,
257 "cancel cache eviction after %u entries due to timeout", i);
258 32 break;
259 }
260
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 128 times.
160 if (atomic_read32(&invalidator->terminated_) == 1) {
261
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 LogCvmfs(kLogCvmfs, kLogDebug,
262 "cancel cache eviction due to termination");
263 32 break;
264 }
265 }
266 }
267
268 // Do the dentry tracker last to increase the effectiveness of pruning
269
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 invalidator->dentry_tracker_->Prune();
270 // Copy and empty the dentry tracker in a single atomic operation
271
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move();
272
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate();
273 uint64_t entry_parent;
274 96 NameString entry_name;
275 96 i = 0;
276
277 #if CVMFS_USE_LIBFUSE == 2
278 int (*notify_func)(struct fuse_chan *, fuse_ino_t, const char *, size_t);
279 96 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 96 while (
295
3/4
✓ Branch 1 taken 41024 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40960 times.
✓ Branch 4 taken 64 times.
41024 dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name)) {
296
1/2
✓ Branch 2 taken 40960 times.
✗ Branch 3 not taken.
40960 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 40960 *channel_or_session = *reinterpret_cast<struct fuse_chan **>(
302 40960 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 40960 times.
✗ Branch 3 not taken.
40960 notify_func(channel_or_session, entry_parent, entry_name.GetChars(),
310 40960 entry_name.GetLength());
311
312
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 40800 times.
40960 if ((++i % kCheckTimeoutFreqOps) == 0) {
313
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 128 times.
160 if (atomic_read32(&invalidator->terminated_) == 1) {
314
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 LogCvmfs(kLogCvmfs, kLogDebug,
315 "cancel cache eviction due to termination");
316 32 break;
317 }
318 }
319 }
320
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 dentries_copy->EndEnumerate(&dentry_cursor);
321
1/2
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
96 delete dentries_copy;
322
323 96 handle->SetDone();
324
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 invalidator->evict_list_.Clear();
325 256 }
326
327 128 LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
328 128 return NULL;
329 }
330
331
332 128 void FuseInvalidator::Spawn() {
333 int retval;
334 128 retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
335
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 assert(retval == 0);
336 128 spawned_ = true;
337 128 }
338