GCC Code Coverage Report


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