GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 112 164 68.3%
Branches: 62 131 47.3%

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