GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2026-11-01 02:41:19
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 176 FuseInvalidator::Handle::Handle(unsigned timeout_s)
26
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 88 times.
176 : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec)) {
27 176 status_ = reinterpret_cast<std::atomic<int32_t> *>(
28 176 smalloc(sizeof(std::atomic<int32_t>)));
29 176 status_->store(0);
30 176 }
31
32
33 176 FuseInvalidator::Handle::~Handle() { free(status_); }
34
35
36 220 void FuseInvalidator::Handle::WaitFor() {
37
2/2
✓ Branch 1 taken 264 times.
✓ Branch 2 taken 220 times.
484 while (!IsDone())
38 264 SafeSleepMs(FuseInvalidator::kCheckTimeoutFreqMs);
39 220 }
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 132 bool FuseInvalidator::HasFuseNotifyInval() {
52 132 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 220 FuseInvalidator::FuseInvalidator(glue::InodeTracker *inode_tracker,
70 glue::DentryTracker *dentry_tracker,
71 struct fuse_session **fuse_session,
72 220 bool fuse_notify_invalidation)
73 220 : mount_point_(NULL)
74 220 , inode_tracker_(inode_tracker)
75 220 , dentry_tracker_(dentry_tracker)
76 220 , fuse_session_(fuse_session)
77
1/2
✓ Branch 2 taken 220 times.
✗ Branch 3 not taken.
220 , spawned_(false) {
78 220 g_fuse_notify_invalidation_ = fuse_notify_invalidation;
79 220 memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
80 220 terminated_.store(0);
81 220 }
82
83
84 220 FuseInvalidator::~FuseInvalidator() {
85 220 int32_t expected_val = 0;
86 220 terminated_.compare_exchange_strong(expected_val, 1);
87
2/2
✓ Branch 0 taken 176 times.
✓ Branch 1 taken 44 times.
220 if (spawned_) {
88 176 QuitCommand *cmd = new (smalloc(sizeof(QuitCommand))) QuitCommand();
89 176 channel_.PushBack(cmd);
90 176 pthread_join(thread_invalidator_, NULL);
91 }
92 220 }
93
94
95 220 void FuseInvalidator::InvalidateInodes(Handle *handle) {
96
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
220 assert(handle != NULL);
97 InvalInodesCommand *inval_inodes_command = new (
98 220 smalloc(sizeof(InvalInodesCommand))) InvalInodesCommand();
99 220 inval_inodes_command->handle = handle;
100 220 channel_.PushBack(inval_inodes_command);
101 220 }
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 176 void *FuseInvalidator::MainInvalidator(void *data) {
128 176 FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
129 176 LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
130
131 176 bool reported_missing_inval_support = false;
132 while (true) {
133
1/2
✓ Branch 1 taken 396 times.
✗ Branch 2 not taken.
396 Command *command = invalidator->channel_.PopFront();
134
135
3/4
✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 176 times.
✓ Branch 3 taken 220 times.
396 if (dynamic_cast<QuitCommand *>(command)) {
136 176 command->~Command();
137 176 free(command);
138 176 break;
139 }
140
141 InvalDentryCommand
142
1/2
✓ Branch 0 taken 220 times.
✗ Branch 1 not taken.
220 *inval_dentry_command = dynamic_cast<InvalDentryCommand *>(command);
143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
220 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 88 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 220 times.
✗ Branch 1 not taken.
220 *inval_inodes_command = dynamic_cast<InvalInodesCommand *>(command);
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
220 assert(inval_inodes_command);
172
173 220 Handle *handle = inval_inodes_command->handle;
174
1/2
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
220 LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
175 handle->timeout_s_);
176 220 inval_inodes_command->~InvalInodesCommand();
177 220 free(inval_inodes_command);
178
179 220 const uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
180
181 // Fallback: drainout by timeout
182
5/6
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 132 times.
✓ Branch 5 taken 88 times.
✓ Branch 6 taken 132 times.
220 if ((invalidator->fuse_session_ == NULL) || !HasFuseNotifyInval()) {
183
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
88 while (platform_monotonic_time() < deadline) {
184
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 SafeSleepMs(kCheckTimeoutFreqMs);
185
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
88 if (invalidator->terminated_.load() == 1) {
186
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug,
187 "cancel cache eviction due to termination");
188 44 break;
189 }
190 }
191 88 handle->SetDone();
192 88 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 132 times.
✗ Branch 2 not taken.
132 invalidator->inode_tracker_->BeginEnumerate());
199 uint64_t inode;
200
3/4
✓ Branch 1 taken 135300 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 135168 times.
✓ Branch 4 taken 132 times.
135300 while (invalidator->inode_tracker_->NextInode(&inode_cursor, &inode)) {
201
1/2
✓ Branch 1 taken 135168 times.
✗ Branch 2 not taken.
135168 invalidator->evict_list_.PushBack(inode);
202 }
203
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 invalidator->inode_tracker_->EndEnumerate(&inode_cursor);
204
205 132 unsigned i = 0;
206 132 const unsigned N = invalidator->evict_list_.size();
207
2/2
✓ Branch 0 taken 67584 times.
✓ Branch 1 taken 44 times.
67628 while (i < N) {
208 67584 uint64_t inode = invalidator->evict_list_.At(i);
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67584 times.
67584 if (inode == 0)
210 ✗ inode = FUSE_ROOT_ID;
211 // Can fail, e.g. the inode might be already evicted
212
213 67584 const int dbg_retval = fuse_lowlevel_notify_inval_inode(
214
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
67584 *invalidator->fuse_session_, inode, 0, 0);
215
1/2
✓ Branch 1 taken 67584 times.
✗ Branch 2 not taken.
67584 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 264 times.
✓ Branch 1 taken 67320 times.
67584 if ((++i % kCheckTimeoutFreqOps) == 0) {
221
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 220 times.
264 if (platform_monotonic_time() >= deadline) {
222
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug,
223 "cancel cache eviction after %u entries due to timeout", i);
224 44 break;
225 }
226
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 176 times.
440 if (invalidator->terminated_.load() == 1) {
227
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug,
228 "cancel cache eviction due to termination");
229 44 break;
230 }
231 }
232 }
233
234 // Do the dentry tracker last to increase the effectiveness of pruning
235
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 invalidator->dentry_tracker_->Prune();
236 // Copy and empty the dentry tracker in a single atomic operation
237
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move();
238
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate();
239 uint64_t entry_parent;
240 132 NameString entry_name;
241 132 i = 0;
242
243 int (*notify_func)(struct fuse_session *, fuse_ino_t, const char *, size_t);
244 132 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 132 while (
256
3/4
✓ Branch 1 taken 56408 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 56320 times.
✓ Branch 4 taken 88 times.
56408 dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name)) {
257
1/2
✓ Branch 2 taken 56320 times.
✗ Branch 3 not taken.
56320 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 56320 times.
✗ Branch 3 not taken.
56320 notify_func(*invalidator->fuse_session_, entry_parent,
261 56320 entry_name.GetChars(), entry_name.GetLength());
262
263
2/2
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 56100 times.
56320 if ((++i % kCheckTimeoutFreqOps) == 0) {
264
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 176 times.
440 if (invalidator->terminated_.load() == 1) {
265
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 LogCvmfs(kLogCvmfs, kLogDebug,
266 "cancel cache eviction due to termination");
267 44 break;
268 }
269 }
270 }
271
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 dentries_copy->EndEnumerate(&dentry_cursor);
272
1/2
✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
132 delete dentries_copy;
273
274 132 handle->SetDone();
275
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 invalidator->evict_list_.Clear();
276 352 }
277
278 176 LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
279 176 return NULL;
280 }
281
282
283 176 void FuseInvalidator::Spawn() {
284 int retval;
285 176 retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 176 times.
176 assert(retval == 0);
287 176 spawned_ = true;
288 176 }
289