GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/fuse_evict.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 107 149 71.8%
Branches: 64 130 49.2%

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