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