GCC Code Coverage Report


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