GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2026-10-11 02:40:02
Exec Total Coverage
Lines: 111 155 71.6%
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 4 FuseInvalidator::Handle::Handle(unsigned timeout_s)
26
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec)) {
27 4 status_ = reinterpret_cast<std::atomic<int32_t> *>(
28 4 smalloc(sizeof(std::atomic<int32_t>)));
29 4 status_->store(0);
30 4 }
31
32
33 4 FuseInvalidator::Handle::~Handle() { free(status_); }
34
35
36 5 void FuseInvalidator::Handle::WaitFor() {
37
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 5 times.
11 while (!IsDone())
38 6 SafeSleepMs(FuseInvalidator::kCheckTimeoutFreqMs);
39 5 }
40
41
42 //------------------------------------------------------------------------------
43
44
45 const unsigned FuseInvalidator::kTimeoutSafetyMarginSec = 1;
46 const unsigned FuseInvalidator::kCheckTimeoutFreqMs = 100;
47 const unsigned FuseInvalidator::kCheckTimeoutFreqOps = 256;
48
49 bool FuseInvalidator::g_fuse_notify_invalidation_ = true;
50
51 3 bool FuseInvalidator::HasFuseNotifyInval() {
52 3 return FuseInvalidator::g_fuse_notify_invalidation_;
53 }
54
55
56 ✗ FuseInvalidator::FuseInvalidator(MountPoint *mount_point,
57 struct fuse_session **fuse_session,
58 ✗ bool fuse_notify_invalidation)
59 ✗ : mount_point_(mount_point)
60 ✗ , inode_tracker_(mount_point->inode_tracker())
61 ✗ , dentry_tracker_(mount_point->dentry_tracker())
62 ✗ , fuse_session_(fuse_session)
63 ✗ , spawned_(false) {
64 ✗ g_fuse_notify_invalidation_ = fuse_notify_invalidation;
65 ✗ memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
66 ✗ terminated_.store(0);
67 }
68
69 5 FuseInvalidator::FuseInvalidator(glue::InodeTracker *inode_tracker,
70 glue::DentryTracker *dentry_tracker,
71 struct fuse_session **fuse_session,
72 5 bool fuse_notify_invalidation)
73 5 : mount_point_(NULL)
74 5 , inode_tracker_(inode_tracker)
75 5 , dentry_tracker_(dentry_tracker)
76 5 , fuse_session_(fuse_session)
77
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 , spawned_(false) {
78 5 g_fuse_notify_invalidation_ = fuse_notify_invalidation;
79 5 memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
80 5 terminated_.store(0);
81 5 }
82
83
84 5 FuseInvalidator::~FuseInvalidator() {
85 5 int32_t expected_val = 0;
86 5 terminated_.compare_exchange_strong(expected_val, 1);
87
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (spawned_) {
88 4 QuitCommand *cmd = new (smalloc(sizeof(QuitCommand))) QuitCommand();
89 4 channel_.PushBack(cmd);
90 4 pthread_join(thread_invalidator_, NULL);
91 }
92 5 }
93
94
95 5 void FuseInvalidator::InvalidateInodes(Handle *handle) {
96
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(handle != NULL);
97 InvalInodesCommand *inval_inodes_command = new (
98 5 smalloc(sizeof(InvalInodesCommand))) InvalInodesCommand();
99 5 inval_inodes_command->handle = handle;
100 5 channel_.PushBack(inval_inodes_command);
101 5 }
102
103 ✗ void FuseInvalidator::InvalidateDentry(uint64_t parent_ino,
104 const NameString &name) {
105 InvalDentryCommand *inval_dentry_command;
106 ✗ vector<Command *> *items = channel_.StartEnqueueing();
107 ✗ for (size_t i = 0; i < items->size(); ++i) {
108 ✗ inval_dentry_command = dynamic_cast<InvalDentryCommand *>(items->at(i));
109 ✗ if (!inval_dentry_command)
110 ✗ continue;
111 ✗ if (inval_dentry_command->parent_ino != parent_ino)
112 ✗ continue;
113 ✗ if (inval_dentry_command->name != name)
114 ✗ continue;
115 ✗ channel_.AbortEnqueueing();
116 ✗ return;
117 }
118
119 ✗ inval_dentry_command = new (smalloc(sizeof(InvalDentryCommand)))
120 ✗ InvalDentryCommand();
121 ✗ inval_dentry_command->parent_ino = parent_ino;
122 ✗ inval_dentry_command->name = name;
123 ✗ items->push_back(inval_dentry_command);
124 ✗ channel_.CommitEnqueueing();
125 }
126
127 4 void *FuseInvalidator::MainInvalidator(void *data) {
128 4 FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
129 4 LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
130
131 4 bool reported_missing_inval_support = false;
132 while (true) {
133
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 Command *command = invalidator->channel_.PopFront();
134
135
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)) {
136 4 command->~Command();
137 4 free(command);
138 4 break;
139 }
140
141 InvalDentryCommand
142
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 *inval_dentry_command = dynamic_cast<InvalDentryCommand *>(command);
143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (inval_dentry_command) {
144 ✗ if (invalidator->fuse_session_ == NULL) {
145 ✗ if (!reported_missing_inval_support) {
146 ✗ LogCvmfs(kLogCvmfs, kLogSyslogWarn,
147 "missing fuse support for dentry invalidation "
148 "(%" PRIu64 "/%s)",
149 inval_dentry_command->parent_ino,
150 ✗ inval_dentry_command->name.ToString().c_str());
151 ✗ reported_missing_inval_support = true;
152 }
153 ✗ inval_dentry_command->~InvalDentryCommand();
154 ✗ free(inval_dentry_command);
155 2 continue;
156 }
157 ✗ LogCvmfs(kLogCvmfs, kLogDebug, "evicting single dentry %" PRIu64 "/%s",
158 inval_dentry_command->parent_ino,
159 ✗ inval_dentry_command->name.ToString().c_str());
160 ✗ fuse_lowlevel_notify_inval_entry(
161 ✗ *invalidator->fuse_session_, inval_dentry_command->parent_ino,
162 inval_dentry_command->name.GetChars(),
163 ✗ inval_dentry_command->name.GetLength());
164 ✗ inval_dentry_command->~InvalDentryCommand();
165 ✗ free(inval_dentry_command);
166 ✗ continue;
167 }
168
169 InvalInodesCommand
170
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 *inval_inodes_command = dynamic_cast<InvalInodesCommand *>(command);
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(inval_inodes_command);
172
173 5 Handle *handle = inval_inodes_command->handle;
174
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
175 handle->timeout_s_);
176 5 inval_inodes_command->~InvalInodesCommand();
177 5 free(inval_inodes_command);
178
179 5 const uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
180
181 // Fallback: drainout by timeout
182
5/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 3 times.
5 if ((invalidator->fuse_session_ == NULL) || !HasFuseNotifyInval()) {
183
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 while (platform_monotonic_time() < deadline) {
184
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 SafeSleepMs(kCheckTimeoutFreqMs);
185
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 if (invalidator->terminated_.load() == 1) {
186
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug,
187 "cancel cache eviction due to termination");
188 1 break;
189 }
190 }
191 2 handle->SetDone();
192 2 continue;
193 }
194
195 // We must not hold a lock when calling fuse_lowlevel_notify_inval_entry.
196 // Therefore, we first copy all the inodes into a temporary data structure.
197 glue::InodeTracker::Cursor inode_cursor(
198
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 invalidator->inode_tracker_->BeginEnumerate());
199 uint64_t inode;
200
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)) {
201
1/2
✓ Branch 1 taken 3072 times.
✗ Branch 2 not taken.
3072 invalidator->evict_list_.PushBack(inode);
202 }
203
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 invalidator->inode_tracker_->EndEnumerate(&inode_cursor);
204
205 3 unsigned i = 0;
206 3 const unsigned N = invalidator->evict_list_.size();
207
2/2
✓ Branch 0 taken 1536 times.
✓ Branch 1 taken 1 times.
1537 while (i < N) {
208 1536 uint64_t inode = invalidator->evict_list_.At(i);
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1536 times.
1536 if (inode == 0)
210 ✗ inode = FUSE_ROOT_ID;
211 // Can fail, e.g. the inode might be already evicted
212
213 1536 const int dbg_retval = fuse_lowlevel_notify_inval_inode(
214
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
1536 *invalidator->fuse_session_, inode, 0, 0);
215
1/2
✓ Branch 1 taken 1536 times.
✗ Branch 2 not taken.
1536 LogCvmfs(kLogCvmfs, kLogDebug,
216 "evicting inode %" PRIu64 " with retval: %d", inode, dbg_retval);
217
218 (void)dbg_retval; // prevent compiler complaining
219
220
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1530 times.
1536 if ((++i % kCheckTimeoutFreqOps) == 0) {
221
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 5 times.
6 if (platform_monotonic_time() >= deadline) {
222
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug,
223 "cancel cache eviction after %u entries due to timeout", i);
224 1 break;
225 }
226
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
10 if (invalidator->terminated_.load() == 1) {
227
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug,
228 "cancel cache eviction due to termination");
229 1 break;
230 }
231 }
232 }
233
234 // Do the dentry tracker last to increase the effectiveness of pruning
235
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 invalidator->dentry_tracker_->Prune();
236 // Copy and empty the dentry tracker in a single atomic operation
237
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move();
238
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate();
239 uint64_t entry_parent;
240 3 NameString entry_name;
241 3 i = 0;
242
243 int (*notify_func)(struct fuse_session *, fuse_ino_t, const char *, size_t);
244 3 notify_func = &fuse_lowlevel_notify_inval_entry;
245 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 16)
246 // must be libfuse >= 3.16, otherwise the signature is wrong and it
247 // will fail building
248 // mount_point can only be NULL for unittests
249 if (invalidator->mount_point_ != NULL
250 && invalidator->mount_point_->fuse_expire_entry()) {
251 notify_func = &fuse_lowlevel_notify_expire_entry;
252 }
253 #endif
254
255 3 while (
256
3/4
✓ Branch 1 taken 1282 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1280 times.
✓ Branch 4 taken 2 times.
1282 dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name)) {
257
1/2
✓ Branch 2 taken 1280 times.
✗ Branch 3 not taken.
1280 LogCvmfs(kLogCvmfs, kLogDebug, "evicting dentry %lu --> %s", entry_parent,
258 entry_name.c_str());
259 // Can fail, e.g. the entry might be already evicted
260
1/2
✓ Branch 2 taken 1280 times.
✗ Branch 3 not taken.
1280 notify_func(*invalidator->fuse_session_, entry_parent,
261 1280 entry_name.GetChars(), entry_name.GetLength());
262
263
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1275 times.
1280 if ((++i % kCheckTimeoutFreqOps) == 0) {
264
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
10 if (invalidator->terminated_.load() == 1) {
265
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug,
266 "cancel cache eviction due to termination");
267 1 break;
268 }
269 }
270 }
271
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 dentries_copy->EndEnumerate(&dentry_cursor);
272
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 delete dentries_copy;
273
274 3 handle->SetDone();
275
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 invalidator->evict_list_.Clear();
276 8 }
277
278 4 LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
279 4 return NULL;
280 }
281
282
283 4 void FuseInvalidator::Spawn() {
284 int retval;
285 4 retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(retval == 0);
287 4 spawned_ = true;
288 4 }
289