GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/glue_buffer.cc
Date: 2024-04-21 02:33:16
Exec Total Coverage
Lines: 209 282 74.1%
Branches: 77 184 41.8%

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 "glue_buffer.h"
9
10 #include <errno.h>
11 #include <poll.h>
12 #include <unistd.h>
13
14 #include <cassert>
15 #include <cstdlib>
16 #include <cstring>
17
18 #include <string>
19 #include <vector>
20
21 #include "util/exception.h"
22 #include "util/logging.h"
23 #include "util/mutex.h"
24 #include "util/platform.h"
25 #include "util/posix.h"
26 #include "util/smalloc.h"
27
28 using namespace std; // NOLINT
29
30 namespace glue {
31
32 PathStore &PathStore::operator= (const PathStore &other) {
33 if (&other == this)
34 return *this;
35
36 delete string_heap_;
37 CopyFrom(other);
38 return *this;
39 }
40
41
42 PathStore::PathStore(const PathStore &other) {
43 CopyFrom(other);
44 }
45
46
47 void PathStore::CopyFrom(const PathStore &other) {
48 map_ = other.map_;
49
50 string_heap_ = new StringHeap(other.string_heap_->used());
51 shash::Md5 empty_path = map_.empty_key();
52 for (unsigned i = 0; i < map_.capacity(); ++i) {
53 if (map_.keys()[i] != empty_path) {
54 (map_.values() + i)->name =
55 string_heap_->AddString(map_.values()[i].name.length(),
56 map_.values()[i].name.data());
57 }
58 }
59 }
60
61
62 //------------------------------------------------------------------------------
63
64
65 30 void InodeTracker::InitLock() {
66 30 lock_ =
67 30 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
68 30 int retval = pthread_mutex_init(lock_, NULL);
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 assert(retval == 0);
70 30 }
71
72
73 void InodeTracker::CopyFrom(const InodeTracker &other) {
74 assert(other.version_ == kVersion);
75 version_ = kVersion;
76 path_map_ = other.path_map_;
77 inode_ex_map_ = other.inode_ex_map_;
78 inode_references_ = other.inode_references_;
79 statistics_ = other.statistics_;
80 }
81
82
83
2/4
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
30 InodeTracker::InodeTracker() {
84 30 version_ = kVersion;
85 30 InitLock();
86 30 }
87
88
89 InodeTracker::InodeTracker(const InodeTracker &other) {
90 CopyFrom(other);
91 InitLock();
92 }
93
94
95 InodeTracker &InodeTracker::operator= (const InodeTracker &other) {
96 if (&other == this)
97 return *this;
98
99 CopyFrom(other);
100 return *this;
101 }
102
103
104 30 InodeTracker::~InodeTracker() {
105 30 pthread_mutex_destroy(lock_);
106 30 free(lock_);
107 30 }
108
109
110 //------------------------------------------------------------------------------
111
112 22 DentryTracker::DentryTracker() : version_(kVersion), is_active_(true) {
113 22 pipe_terminate_[0] = pipe_terminate_[1] = -1;
114 22 cleaning_interval_ms_ = -1;
115 22 InitLock();
116 22 }
117
118
119 25 DentryTracker::~DentryTracker() {
120
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
25 if (pipe_terminate_[1] >= 0) {
121 1 char t = 'T';
122 1 WritePipe(pipe_terminate_[1], &t, 1);
123 1 pthread_join(thread_cleaner_, NULL);
124 1 ClosePipe(pipe_terminate_);
125 }
126 25 pthread_mutex_destroy(lock_);
127 25 free(lock_);
128 25 }
129
130
131 4 DentryTracker::DentryTracker(const DentryTracker &other) {
132
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 CopyFrom(other);
133 4 pipe_terminate_[0] = pipe_terminate_[1] = -1;
134 4 cleaning_interval_ms_ = -1;
135 4 InitLock();
136 4 }
137
138
139 DentryTracker &DentryTracker::operator= (const DentryTracker &other) {
140 if (&other == this)
141 return *this;
142
143 Lock();
144 CopyFrom(other);
145 Unlock();
146 return *this;
147 }
148
149
150 4 void DentryTracker::CopyFrom(const DentryTracker &other) {
151
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 assert(other.version_ == kVersion);
152
153 4 version_ = kVersion;
154 4 statistics_ = other.statistics_;
155 4 is_active_ = other.is_active_;
156 4 entries_ = other.entries_;
157 4 }
158
159
160 4 DentryTracker *DentryTracker::Move() {
161 4 Lock();
162
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 DentryTracker *new_tracker = new DentryTracker(*this);
163 4 statistics_.num_remove += entries_.size();
164 4 entries_.Clear();
165 4 Unlock();
166 4 return new_tracker;
167 }
168
169
170 1 void DentryTracker::SpawnCleaner(unsigned interval_s) {
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(pipe_terminate_[0] == -1);
172 1 cleaning_interval_ms_ = interval_s * 1000;
173
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (cleaning_interval_ms_ == 0) cleaning_interval_ms_ = -1;
174 1 MakePipe(pipe_terminate_);
175 1 int retval = pthread_create(&thread_cleaner_, NULL, MainCleaner, this);
176
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(retval == 0);
177 1 }
178
179
180 1 void *DentryTracker::MainCleaner(void *data) {
181 1 DentryTracker *tracker = reinterpret_cast<DentryTracker *>(data);
182
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug, "starting negative entry cache cleaner");
183
184 struct pollfd watch_term;
185 1 watch_term.fd = tracker->pipe_terminate_[0];
186 1 watch_term.events = POLLIN | POLLPRI;
187 1 int timeout_ms = tracker->cleaning_interval_ms_;;
188 1 uint64_t deadline = platform_monotonic_time() + timeout_ms / 1000;
189 while (true) {
190 4 watch_term.revents = 0;
191
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 int retval = poll(&watch_term, 1, timeout_ms);
192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (retval < 0) {
193 if (errno == EINTR) {
194 if (timeout_ms >= 0) {
195 uint64_t now = platform_monotonic_time();
196 timeout_ms = (now > deadline) ? 0 : (deadline - now) * 1000;
197 }
198 3 continue;
199 }
200 abort();
201 }
202 4 timeout_ms = tracker->cleaning_interval_ms_;
203 4 deadline = platform_monotonic_time() + timeout_ms / 1000;
204
205
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 if (retval == 0) {
206
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 LogCvmfs(kLogCvmfs, kLogDebug, "negative entry cleaner: pruning");
207
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 tracker->Prune();
208 3 continue;
209 }
210
211
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(watch_term.revents != 0);
212
213 1 char c = 0;
214
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ReadPipe(tracker->pipe_terminate_[0], &c, 1);
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 assert(c == 'T');
216 1 break;
217 3 }
218
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 LogCvmfs(kLogCvmfs, kLogDebug, "stopping negative entry cache cleaner");
219 1 return NULL;
220 }
221
222
223 26 void DentryTracker::InitLock() {
224 26 lock_ =
225 26 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
226 26 int retval = pthread_mutex_init(lock_, NULL);
227
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 assert(retval == 0);
228 26 }
229
230
231 7 void DentryTracker::Prune() {
232 7 Lock();
233 7 DoPrune(platform_monotonic_time());
234 7 Unlock();
235 7 }
236
237
238 10 DentryTracker::Cursor DentryTracker::BeginEnumerate() {
239 10 Entry *head = NULL;
240 10 Lock();
241
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 entries_.Peek(&head);
242 10 return Cursor(head);
243 }
244
245
246 1292 bool DentryTracker::NextEntry(Cursor *cursor,
247 uint64_t *inode_parent, NameString *name)
248 {
249
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1286 times.
1292 if (cursor->head == NULL)
250 6 return false;
251
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1283 times.
1286 if (cursor->pos >= entries_.size())
252 3 return false;
253 1283 Entry *e = cursor->head + cursor->pos;
254 1283 *inode_parent = e->inode_parent;
255 1283 *name = e->name;
256 1283 cursor->pos++;
257 1283 return true;
258 }
259
260
261 10 void DentryTracker::EndEnumerate(Cursor * /* cursor */) {
262 10 Unlock();
263 10 }
264
265
266 //------------------------------------------------------------------------------
267
268
269
1/2
✓ Branch 3 taken 19 times.
✗ Branch 4 not taken.
19 PageCacheTracker::PageCacheTracker() : version_(kVersion), is_active_(true) {
270
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 map_.Init(16, 0, hasher_inode);
271 19 InitLock();
272 19 }
273
274
275 19 PageCacheTracker::~PageCacheTracker() {
276 19 pthread_mutex_destroy(lock_);
277 19 free(lock_);
278 19 }
279
280
281 PageCacheTracker::PageCacheTracker(const PageCacheTracker &other) {
282 CopyFrom(other);
283 InitLock();
284 }
285
286
287 PageCacheTracker &PageCacheTracker::operator= (const PageCacheTracker &other) {
288 if (&other == this)
289 return *this;
290
291 MutexLockGuard guard(lock_);
292 CopyFrom(other);
293 return *this;
294 }
295
296
297 void PageCacheTracker::CopyFrom(const PageCacheTracker &other) {
298 assert(other.version_ == kVersion);
299
300 version_ = kVersion;
301 is_active_ = other.is_active_;
302 statistics_ = other.statistics_;
303
304 map_.Init(16, 0, hasher_inode);
305 map_ = other.map_;
306 stat_store_ = other.stat_store_;
307 }
308
309
310 19 void PageCacheTracker::InitLock() {
311 19 lock_ =
312 19 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
313 19 int retval = pthread_mutex_init(lock_, NULL);
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 assert(retval == 0);
315 19 }
316
317 10 PageCacheTracker::OpenDirectives PageCacheTracker::Open(
318 uint64_t inode, const shash::Any &hash, const struct stat &info)
319 {
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 assert(inode == info.st_ino);
321
322 10 OpenDirectives open_directives;
323 // Old behavior: always flush page cache on open
324
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (!is_active_)
325 1 return open_directives;
326
327
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (inode != info.st_ino) {
328 PANIC(kLogStderr | kLogDebug,
329 "invalid entry on open: %" PRIu64 " with st_ino=%" PRIu64,
330 " hash=%s size=%" PRIu64,
331 inode, info.st_ino, hash.ToString().c_str(), info.st_size);
332 }
333
334 9 MutexLockGuard guard(lock_);
335
336
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 Entry entry;
337
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 bool retval = map_.Lookup(inode, &entry);
338
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (!retval) {
339 3 open_directives.keep_cache = true;
340 3 open_directives.direct_io = false;
341 3 statistics_.n_insert++;
342 3 statistics_.n_open_cached++;
343
344 3 entry.nopen = 1;
345
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 entry.idx_stat = stat_store_.Add(info);
346 3 entry.hash = hash;
347
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 map_.Insert(inode, entry);
348 3 return open_directives;
349 }
350
351
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 if (entry.hash == hash) {
352 4 open_directives.direct_io = false;
353
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (entry.nopen < 0) {
354 // The page cache is still in the transition phase and may contain old
355 // content. So trigger a flush of the cache in any case.
356 1 open_directives.keep_cache = false;
357 1 statistics_.n_open_flush++;
358 1 entry.nopen--;
359
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 map_.Insert(inode, entry);
360 1 return open_directives;
361 } else {
362 3 open_directives.keep_cache = true;
363 3 statistics_.n_open_cached++;
364
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (entry.nopen++ == 0)
365 entry.idx_stat = stat_store_.Add(info);
366
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 map_.Insert(inode, entry);
367 3 return open_directives;
368 }
369 }
370
371 // Page cache mismatch and old data has still open file attached to it,
372 // circumvent the page cache entirely and use direct I/O. In this case,
373 // cvmfs_close() will _not_ call Close().
374
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (entry.nopen != 0) {
375 1 open_directives.keep_cache = true;
376 1 open_directives.direct_io = true;
377 1 statistics_.n_open_direct++;
378 1 return open_directives;
379 }
380
381 // Stale data in the page cache, start the transition phase in which newly
382 // opened files flush the page cache and re-populate it with the new hash.
383 // The first file to reach Close() will finish the transition phase and
384 // mark the new hash as committed.
385 1 open_directives.direct_io = false;
386 1 open_directives.keep_cache = false;
387 1 statistics_.n_open_flush++;
388 1 entry.hash = hash;
389
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 entry.idx_stat = stat_store_.Add(info);
390 1 entry.nopen = -1;
391
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 map_.Insert(inode, entry);
392 1 return open_directives;
393 9 }
394
395 PageCacheTracker::OpenDirectives PageCacheTracker::OpenDirect() {
396 OpenDirectives open_directives(true, true);
397 // Old behavior: always flush page cache on open
398 if (!is_active_)
399 return open_directives;
400
401 MutexLockGuard guard(lock_);
402 statistics_.n_open_direct++;
403 return open_directives;
404 }
405
406 8 void PageCacheTracker::Close(uint64_t inode) {
407
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (!is_active_)
408 1 return;
409
410 7 MutexLockGuard guard(lock_);
411
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 Entry entry;
412
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 bool retval = map_.Lookup(inode, &entry);
413
414 7 if (!AssertOrLog(retval, kLogCvmfs, kLogSyslogWarn | kLogDebug,
415 "PageCacheTracker::Close Race condition? "
416 "Did not find inode %lu",
417 inode)
418
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
7 || !AssertOrLog(entry.nopen != 0, kLogCvmfs, kLogSyslogWarn | kLogDebug,
419 "PageCacheTracker::Close Race condition? "
420 "Inode %lu has no open entries",
421 inode)) {
422 return;
423 }
424
425 7 const int32_t old_open = entry.nopen;
426
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (entry.nopen < 0) {
427 // At this point we know that any stale data has been flushed from the
428 // cache and only data related to the currently booked content hash
429 // can be present. So clear the transition bit (sign bit).
430 1 entry.nopen = -entry.nopen;
431 }
432 7 entry.nopen--;
433
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (entry.nopen == 0) {
434 // File closed, remove struct stat information
435
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (entry.idx_stat < 0) {
436 PANIC(kLogSyslogErr | kLogDebug,
437 "page cache tracker: missing stat entry! Entry info: inode %" PRIu64
438 " - open counter %d - hash %s",
439 inode, old_open, entry.hash.ToString().c_str());
440 }
441
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 uint64_t inode_update = stat_store_.Erase(entry.idx_stat);
442
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 Entry entry_update;
443
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 retval = map_.Lookup(inode_update, &entry_update);
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!retval) {
445 PANIC(kLogSyslogErr | kLogDebug,
446 "invalid inode in page cache tracker: inode %" PRIu64
447 ", replacing %" PRIu64, inode_update, inode);
448 }
449
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval);
450 3 entry_update.idx_stat = entry.idx_stat;
451
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 map_.Insert(inode_update, entry_update);
452 3 entry.idx_stat = -1;
453 }
454
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 map_.Insert(inode, entry);
455
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 }
456
457 2 PageCacheTracker::EvictRaii::EvictRaii(PageCacheTracker *t)
458 2 : tracker_(t)
459 {
460 2 int retval = pthread_mutex_lock(tracker_->lock_);
461
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 assert(retval == 0);
462 2 }
463
464 2 PageCacheTracker::EvictRaii::~EvictRaii() {
465 2 int retval = pthread_mutex_unlock(tracker_->lock_);
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 assert(retval == 0);
467 2 }
468
469 2 void PageCacheTracker::EvictRaii::Evict(uint64_t inode) {
470
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (!tracker_->is_active_)
471 1 return;
472
473 1 bool contained_inode = tracker_->map_.Erase(inode);
474
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (contained_inode)
475 1 tracker_->statistics_.n_remove++;
476 }
477
478 } // namespace glue
479