GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/cache_plugin/cvmfs_cache_ram.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 0 401 0.0%
Branches: 0 174 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * A cache plugin that stores all data in a fixed-size memory chunk.
5 */
6
7 #include <alloca.h>
8 #include <fcntl.h>
9 #include <inttypes.h>
10 #include <signal.h>
11 #include <stdint.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15
16 #include <algorithm>
17 #include <cassert>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <string>
22 #include <vector>
23
24 #include "cache_plugin/libcvmfs_cache.h"
25 #include "lru.h"
26 #include "malloc_heap.h"
27 #include "smallhash.h"
28 #include "util/concurrency.h"
29 #include "util/logging.h"
30 #include "util/murmur.hxx"
31 #include "util/platform.h"
32 #include "util/smalloc.h"
33 #include "util/string.h"
34
35 using namespace std; // NOLINT
36
37 /**
38 * Header of the data pieces in the cache. After the object header, the
39 * zero-terminated description and the object data follows.
40 */
41 struct ObjectHeader {
42 ✗ ObjectHeader() {
43 ✗ txn_id = uint64_t(-1);
44 ✗ size_data = 0;
45 ✗ size_desc = 0;
46 ✗ refcnt = 0;
47 ✗ type = CVMCACHE_OBJECT_REGULAR;
48 ✗ memset(&id, 0, sizeof(id));
49 }
50
51 ✗ char *GetDescription() {
52 ✗ if (size_desc == 0)
53 ✗ return NULL;
54 ✗ return reinterpret_cast<char *>(this) + sizeof(ObjectHeader);
55 }
56
57 ✗ void SetDescription(char *description) {
58 ✗ if (description == NULL)
59 ✗ return;
60 ✗ memcpy(reinterpret_cast<char *>(this) + sizeof(ObjectHeader), description,
61 ✗ strlen(description) + 1);
62 }
63
64 ✗ unsigned char *GetData() {
65 return reinterpret_cast<unsigned char *>(this) + sizeof(ObjectHeader)
66 ✗ + size_desc;
67 }
68
69 /**
70 * Set during a running transaction so that we know where to look for pointers
71 * when the memory block gets compacted. Once committed, this is
72 * uint64_t(-1).
73 */
74 uint64_t txn_id;
75 /**
76 * Can be zero.
77 */
78 uint32_t size_data;
79 /**
80 * String length + 1 (null terminated) or null if the description is NULL.
81 */
82 uint32_t size_desc;
83 /**
84 * During a transaction, neg_nbytes_written is used to track the number of
85 * already written bytes. On commit, refcnt is set to 1.
86 */
87 union {
88 int32_t refcnt;
89 int32_t neg_nbytes_written;
90 };
91 cvmcache_object_type type;
92 struct cvmcache_hash id;
93 };
94
95
96 /**
97 * Listings are generated and cached during the entire life time of a listing
98 * id. Not very memory efficient but we don't optimize for listings.
99 */
100 struct Listing {
101 ✗ Listing() : pos(0) { }
102 uint64_t pos;
103 vector<struct cvmcache_object_info> elems;
104 };
105
106
107 /**
108 * Allows us to use a cvmcache_hash in (hash) maps.
109 */
110 struct ComparableHash {
111 ✗ ComparableHash() { memset(&hash, 0, sizeof(hash)); }
112 ✗ explicit ComparableHash(const struct cvmcache_hash &h) : hash(h) { }
113 ✗ bool operator==(const ComparableHash &other) const {
114 ✗ return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
115 ✗ const_cast<cvmcache_hash *>(&(other.hash)))
116 ✗ == 0;
117 }
118 bool operator!=(const ComparableHash &other) const {
119 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
120 const_cast<cvmcache_hash *>(&(other.hash)))
121 != 0;
122 }
123 bool operator<(const ComparableHash &other) const {
124 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
125 const_cast<cvmcache_hash *>(&(other.hash)))
126 < 0;
127 }
128 bool operator>(const ComparableHash &other) const {
129 return cvmcache_hash_cmp(const_cast<cvmcache_hash *>(&(this->hash)),
130 const_cast<cvmcache_hash *>(&(other.hash)))
131 > 0;
132 }
133
134 struct cvmcache_hash hash;
135 };
136
137
138 namespace {
139
140 ✗ static inline uint32_t hasher_uint64(const uint64_t &key) {
141 ✗ return MurmurHash2(&key, sizeof(key), 0x07387a4f);
142 }
143
144 ✗ static inline uint32_t hasher_any(const ComparableHash &key) {
145 return static_cast<uint32_t>(
146 ✗ *(reinterpret_cast<const uint32_t *>(&key.hash)));
147 }
148
149 } // anonymous namespace
150
151
152 /**
153 * Used in the PluginRamCache when detaching nested catalogs.
154 */
155 struct cvmcache_context *ctx;
156
157
158 /**
159 * Implements all the cache plugin callbacks. Singleton.
160 */
161 class PluginRamCache : public Callbackable<MallocHeap::BlockPtr> {
162 public:
163 ✗ static PluginRamCache *Create(const string &mem_size_str) {
164 ✗ assert(instance_ == NULL);
165
166 uint64_t mem_size_bytes;
167 ✗ if (HasSuffix(mem_size_str, "%", false)) {
168 ✗ mem_size_bytes = platform_memsize() * String2Uint64(mem_size_str) / 100;
169 } else {
170 ✗ mem_size_bytes = String2Uint64(mem_size_str) * 1024 * 1024;
171 }
172 ✗ instance_ = new PluginRamCache(mem_size_bytes);
173 ✗ return instance_;
174 }
175
176 ✗ static PluginRamCache *GetInstance() {
177 ✗ assert(instance_ != NULL);
178 ✗ return instance_;
179 }
180
181 ~PluginRamCache() {
182 delete storage_;
183 delete objects_all_;
184 delete objects_volatile_;
185 instance_ = NULL;
186 }
187
188 ✗ void DropBreadcrumbs() { breadcrumbs_.clear(); }
189
190 ✗ static int ram_chrefcnt(struct cvmcache_hash *id, int32_t change_by) {
191 ✗ const ComparableHash h(*id);
192 ObjectHeader *object;
193 ✗ if (!Me()->objects_all_->Lookup(h, &object))
194 ✗ return CVMCACHE_STATUS_NOENTRY;
195
196 ✗ if (object->type == CVMCACHE_OBJECT_VOLATILE)
197 ✗ Me()->objects_volatile_->Update(h);
198
199 ✗ if (change_by == 0)
200 ✗ return CVMCACHE_STATUS_OK;
201 ✗ if ((object->refcnt + change_by) < 0)
202 ✗ return CVMCACHE_STATUS_BADCOUNT;
203
204 ✗ if (object->refcnt == 0) {
205 ✗ Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(object);
206 ✗ Me()->CheckHighPinWatermark();
207 }
208 ✗ object->refcnt += change_by;
209 ✗ if (object->refcnt == 0) {
210 ✗ Me()->cache_info_.pinned_bytes -= Me()->storage_->GetSize(object);
211 ✗ Me()->in_danger_zone_ = Me()->IsInDangerZone();
212 }
213 ✗ return CVMCACHE_STATUS_OK;
214 }
215
216
217 ✗ static int ram_obj_info(struct cvmcache_hash *id,
218 struct cvmcache_object_info *info) {
219 ✗ const ComparableHash h(*id);
220 ObjectHeader *object;
221 ✗ if (!Me()->objects_all_->Lookup(h, &object, false))
222 ✗ return CVMCACHE_STATUS_NOENTRY;
223
224 ✗ info->size = object->size_data;
225 ✗ info->type = object->type;
226 ✗ info->pinned = object->refcnt > 0;
227 ✗ info->description = (object->GetDescription() == NULL)
228 ✗ ? NULL
229 ✗ : strdup(object->GetDescription());
230 ✗ return CVMCACHE_STATUS_OK;
231 }
232
233
234 ✗ static int ram_pread(struct cvmcache_hash *id,
235 uint64_t offset,
236 uint32_t *size,
237 unsigned char *buffer) {
238 ✗ const ComparableHash h(*id);
239 ObjectHeader *object;
240 ✗ const bool retval = Me()->objects_all_->Lookup(h, &object, false);
241 ✗ assert(retval);
242 ✗ if (offset > object->size_data)
243 ✗ return CVMCACHE_STATUS_OUTOFBOUNDS;
244 ✗ const unsigned nbytes = std::min(
245 ✗ *size, static_cast<uint32_t>(object->size_data - offset));
246 ✗ memcpy(buffer, object->GetData() + offset, nbytes);
247 ✗ *size = nbytes;
248 ✗ return CVMCACHE_STATUS_OK;
249 }
250
251
252 ✗ static int ram_start_txn(struct cvmcache_hash *id,
253 uint64_t txn_id,
254 struct cvmcache_object_info *info) {
255 ✗ ObjectHeader object_header;
256 ✗ object_header.txn_id = txn_id;
257 ✗ if (info->size != CVMCACHE_SIZE_UNKNOWN)
258 ✗ object_header.size_data = info->size;
259 else
260 ✗ object_header.size_data = 4096;
261 ✗ if (info->description != NULL)
262 ✗ object_header.size_desc = strlen(info->description) + 1;
263 ✗ object_header.refcnt = 1;
264 ✗ object_header.type = info->type;
265 ✗ object_header.id = *id;
266
267 ✗ const uint32_t total_size = sizeof(object_header) + object_header.size_desc
268 ✗ + object_header.size_data;
269 ✗ Me()->TryFreeSpace(total_size);
270 ObjectHeader *allocd_object = reinterpret_cast<ObjectHeader *>(
271 ✗ Me()->storage_->Allocate(total_size, &object_header,
272 ✗ sizeof(object_header)));
273 ✗ if (allocd_object == NULL)
274 ✗ return CVMCACHE_STATUS_NOSPACE;
275
276 ✗ allocd_object->SetDescription(info->description);
277 ✗ Me()->transactions_.Insert(txn_id, allocd_object);
278 ✗ return CVMCACHE_STATUS_OK;
279 }
280
281
282 ✗ static int ram_write_txn(uint64_t txn_id,
283 unsigned char *buffer,
284 uint32_t size) {
285 ObjectHeader *txn_object;
286 ✗ int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
287 ✗ assert(retval);
288 ✗ assert(size > 0);
289
290 ✗ if (txn_object->neg_nbytes_written > 0)
291 ✗ txn_object->neg_nbytes_written = 0;
292 ✗ if ((size - txn_object->neg_nbytes_written) > txn_object->size_data) {
293 ✗ const uint32_t current_size = Me()->storage_->GetSize(txn_object);
294 ✗ const uint32_t header_size = current_size - txn_object->size_data;
295 ✗ const uint32_t new_size = std::max(
296 ✗ header_size + size - txn_object->neg_nbytes_written,
297 ✗ uint32_t(current_size * kObjectExpandFactor));
298 ✗ const bool did_compact = Me()->TryFreeSpace(new_size);
299 ✗ if (did_compact) {
300 ✗ retval = Me()->transactions_.Lookup(txn_id, &txn_object);
301 ✗ assert(retval);
302 }
303 ✗ txn_object = reinterpret_cast<ObjectHeader *>(
304 ✗ Me()->storage_->Expand(txn_object, new_size));
305 ✗ if (txn_object == NULL)
306 ✗ return CVMCACHE_STATUS_NOSPACE;
307 ✗ txn_object->size_data = new_size - header_size;
308 ✗ Me()->transactions_.Insert(txn_id, txn_object);
309 }
310
311 ✗ memcpy(txn_object->GetData() - txn_object->neg_nbytes_written, buffer,
312 size);
313 ✗ txn_object->neg_nbytes_written -= size;
314 ✗ return CVMCACHE_STATUS_OK;
315 }
316
317
318 ✗ static int ram_commit_txn(uint64_t txn_id) {
319 ✗ Me()->TryFreeSpace(0);
320 ✗ if (Me()->objects_all_->IsFull())
321 ✗ return CVMCACHE_STATUS_NOSPACE;
322
323 ObjectHeader *txn_object;
324 ✗ const int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
325 ✗ assert(retval);
326
327 ✗ Me()->transactions_.Erase(txn_id);
328 ✗ const ComparableHash h(txn_object->id);
329 ObjectHeader *existing_object;
330 ✗ if (Me()->objects_all_->Lookup(h, &existing_object)) {
331 // Concurrent addition of same objects, drop the one at hand and
332 // increase ref count of existing copy
333 ✗ Me()->storage_->MarkFree(txn_object);
334 ✗ if (existing_object->refcnt == 0)
335 ✗ Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(
336 existing_object);
337 ✗ existing_object->refcnt++;
338 } else {
339 ✗ txn_object->txn_id = uint64_t(-1);
340 ✗ if (txn_object->neg_nbytes_written > 0)
341 ✗ txn_object->neg_nbytes_written = 0;
342 ✗ txn_object->size_data = -(txn_object->neg_nbytes_written);
343 ✗ txn_object->refcnt = 1;
344 ✗ Me()->cache_info_.used_bytes += Me()->storage_->GetSize(txn_object);
345 ✗ Me()->cache_info_.pinned_bytes += Me()->storage_->GetSize(txn_object);
346 ✗ Me()->objects_all_->Insert(h, txn_object);
347 ✗ if (txn_object->type == CVMCACHE_OBJECT_VOLATILE) {
348 ✗ assert(!Me()->objects_volatile_->IsFull());
349 ✗ Me()->objects_volatile_->Insert(h, txn_object);
350 }
351 }
352 ✗ Me()->CheckHighPinWatermark();
353 ✗ return CVMCACHE_STATUS_OK;
354 }
355
356
357 ✗ static int ram_abort_txn(uint64_t txn_id) {
358 ✗ ObjectHeader *txn_object = NULL;
359 ✗ const int retval = Me()->transactions_.Lookup(txn_id, &txn_object);
360 ✗ assert(retval);
361 ✗ Me()->transactions_.Erase(txn_id);
362 ✗ Me()->storage_->MarkFree(txn_object);
363 ✗ return CVMCACHE_STATUS_OK;
364 }
365
366
367 ✗ static int ram_info(struct cvmcache_info *info) {
368 ✗ *info = Me()->cache_info_;
369 ✗ return CVMCACHE_STATUS_OK;
370 }
371
372
373 ✗ static int ram_shrink(uint64_t shrink_to, uint64_t *used) {
374 ✗ *used = Me()->cache_info_.used_bytes;
375 ✗ if (*used <= shrink_to)
376 ✗ return CVMCACHE_STATUS_OK;
377
378 ✗ Me()->DoShrink(shrink_to);
379 ✗ *used = Me()->cache_info_.used_bytes;
380 ✗ return (*used <= shrink_to) ? CVMCACHE_STATUS_OK : CVMCACHE_STATUS_PARTIAL;
381 }
382
383
384 ✗ static int ram_listing_begin(uint64_t lst_id,
385 enum cvmcache_object_type type) {
386 ✗ Listing *lst = new Listing();
387 ✗ Me()->objects_all_->FilterBegin();
388 ✗ while (Me()->objects_all_->FilterNext()) {
389 ✗ ComparableHash h;
390 ObjectHeader *object;
391 ✗ Me()->objects_all_->FilterGet(&h, &object);
392 ✗ if (object->type != type)
393 ✗ continue;
394
395 struct cvmcache_object_info item;
396 ✗ item.id = object->id;
397 ✗ item.size = object->size_data;
398 ✗ item.type = type;
399 ✗ item.pinned = object->refcnt != 0;
400 ✗ item.description = (object->size_desc > 0)
401 ✗ ? strdup(object->GetDescription())
402 : NULL;
403 ✗ lst->elems.push_back(item);
404 }
405 ✗ Me()->objects_all_->FilterEnd();
406
407 ✗ Me()->listings_.Insert(lst_id, lst);
408 ✗ return CVMCACHE_STATUS_OK;
409 }
410
411
412 ✗ static int ram_listing_next(int64_t listing_id,
413 struct cvmcache_object_info *item) {
414 Listing *lst;
415 ✗ const bool retval = Me()->listings_.Lookup(listing_id, &lst);
416 ✗ assert(retval);
417 ✗ if (lst->pos >= lst->elems.size())
418 ✗ return CVMCACHE_STATUS_OUTOFBOUNDS;
419 ✗ *item = lst->elems[lst->pos];
420 ✗ lst->pos++;
421 ✗ return CVMCACHE_STATUS_OK;
422 }
423
424
425 ✗ static int ram_listing_end(int64_t listing_id) {
426 Listing *lst;
427 ✗ const bool retval = Me()->listings_.Lookup(listing_id, &lst);
428 ✗ assert(retval);
429
430 // Don't free description strings, done by the library
431 ✗ delete lst;
432 ✗ Me()->listings_.Erase(listing_id);
433 ✗ return CVMCACHE_STATUS_OK;
434 }
435
436
437 ✗ static int ram_breadcrumb_store(const char *fqrn,
438 const cvmcache_breadcrumb *breadcrumb) {
439 ✗ Me()->breadcrumbs_[fqrn] = *breadcrumb;
440 ✗ return CVMCACHE_STATUS_OK;
441 }
442
443
444 ✗ static int ram_breadcrumb_load(const char *fqrn,
445 cvmcache_breadcrumb *breadcrumb) {
446 const map<std::string, cvmcache_breadcrumb>::const_iterator
447 ✗ itr = Me()->breadcrumbs_.find(fqrn);
448 ✗ if (itr == Me()->breadcrumbs_.end())
449 ✗ return CVMCACHE_STATUS_NOENTRY;
450 ✗ *breadcrumb = itr->second;
451 ✗ return CVMCACHE_STATUS_OK;
452 }
453
454 private:
455 static const uint64_t kMinSize; // 100 * 1024 * 1024;
456 static const double kShrinkFactor; // = 0.75;
457 static const double kObjectExpandFactor; // = 1.5;
458 static const double kSlotFraction; // = 0.04;
459 static const double kDangerZoneThreshold; // = 0.7
460
461 static PluginRamCache *instance_;
462 ✗ static PluginRamCache *Me() { return instance_; }
463 ✗ explicit PluginRamCache(uint64_t mem_size) {
464 ✗ in_danger_zone_ = false;
465
466 ✗ const uint64_t heap_size = RoundUp8(
467 ✗ std::max(kMinSize, uint64_t(mem_size * (1.0 - kSlotFraction))));
468 ✗ memset(&cache_info_, 0, sizeof(cache_info_));
469 ✗ cache_info_.size_bytes = heap_size;
470 ✗ storage_ = new MallocHeap(
471 ✗ heap_size, this->MakeCallback(&PluginRamCache::OnBlockMove, this));
472
473 struct cvmcache_hash hash_empty;
474 ✗ memset(&hash_empty, 0, sizeof(hash_empty));
475
476 ✗ transactions_.Init(64, uint64_t(-1), hasher_uint64);
477 ✗ listings_.Init(8, uint64_t(-1), hasher_uint64);
478
479 const double slot_size = lru::LruCache<ComparableHash,
480 ✗ ObjectHeader *>::GetEntrySize();
481 ✗ const uint64_t num_slots = uint64_t((heap_size * kSlotFraction)
482 ✗ / (2.0 * slot_size));
483 ✗ const unsigned mask_64 = ~((1 << 6) - 1);
484
485 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
486 "Allocating %" PRIu64 "MB of memory for up to %" PRIu64 " objects",
487 heap_size / (1024 * 1024), num_slots & mask_64);
488
489 // Number of cache entries must be a multiple of 64
490 ✗ objects_all_ = new lru::LruCache<ComparableHash, ObjectHeader *>(
491 num_slots & mask_64,
492 ✗ ComparableHash(hash_empty),
493 hasher_any,
494 ✗ perf::StatisticsTemplate("objects_all", &statistics_));
495 ✗ objects_volatile_ = new lru::LruCache<ComparableHash, ObjectHeader *>(
496 num_slots & mask_64,
497 ✗ ComparableHash(hash_empty),
498 hasher_any,
499 ✗ perf::StatisticsTemplate("objects_volatile", &statistics_));
500 }
501
502 /**
503 * Returns true if memory compaction took place and pointers might have been
504 * invalidated.
505 */
506 ✗ bool TryFreeSpace(uint64_t bytes_required) {
507 ✗ if (!objects_all_->IsFull() && storage_->HasSpaceFor(bytes_required))
508 ✗ return false;
509
510 // Free space occupied due to piecewise catalog storage
511 ✗ if (!objects_all_->IsFull()) {
512 ✗ LogCvmfs(kLogCache, kLogDebug, "compacting ram cache");
513 ✗ storage_->Compact();
514 ✗ if (storage_->HasSpaceFor(bytes_required))
515 ✗ return true;
516 }
517
518 ✗ const uint64_t shrink_to = std::min(
519 ✗ storage_->capacity() - (bytes_required + 8),
520 ✗ uint64_t(storage_->capacity() * kShrinkFactor));
521 ✗ DoShrink(shrink_to);
522 ✗ return true;
523 }
524
525 ✗ void OnBlockMove(const MallocHeap::BlockPtr &ptr) {
526 ✗ assert(ptr.pointer);
527 ✗ ObjectHeader *object = reinterpret_cast<ObjectHeader *>(ptr.pointer);
528 ✗ const ComparableHash h(object->id);
529 ✗ if (object->txn_id == uint64_t(-1)) {
530 ✗ bool retval = objects_all_->UpdateValue(h, object);
531 ✗ assert(retval);
532 ✗ if (object->type == CVMCACHE_OBJECT_VOLATILE) {
533 ✗ retval = objects_volatile_->UpdateValue(h, object);
534 ✗ assert(retval);
535 }
536 } else {
537 ✗ const uint64_t old_size = transactions_.size();
538 ✗ transactions_.Insert(object->txn_id, object);
539 ✗ assert(old_size == transactions_.size());
540 }
541 }
542
543
544 ✗ void DoShrink(uint64_t shrink_to) {
545 ✗ ComparableHash h;
546 ObjectHeader *object;
547
548 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
549 "clean up cache until at most %lu KB is used", shrink_to / 1024);
550
551 ✗ objects_volatile_->FilterBegin();
552 ✗ while (objects_volatile_->FilterNext()) {
553 ✗ objects_volatile_->FilterGet(&h, &object);
554 ✗ if (object->refcnt != 0)
555 ✗ continue;
556 ✗ cache_info_.used_bytes -= storage_->GetSize(object);
557 ✗ storage_->MarkFree(object);
558 ✗ objects_volatile_->FilterDelete();
559 ✗ objects_all_->Forget(h);
560 ✗ if (storage_->compacted_bytes() <= shrink_to)
561 ✗ break;
562 }
563 ✗ objects_volatile_->FilterEnd();
564
565 ✗ objects_all_->FilterBegin();
566 ✗ while ((storage_->compacted_bytes() > shrink_to)
567 ✗ && objects_all_->FilterNext()) {
568 ✗ objects_all_->FilterGet(&h, &object);
569 ✗ if (object->refcnt != 0)
570 ✗ continue;
571 ✗ assert(object->type != CVMCACHE_OBJECT_VOLATILE);
572 ✗ cache_info_.used_bytes -= storage_->GetSize(object);
573 ✗ storage_->MarkFree(object);
574 ✗ objects_all_->FilterDelete();
575 }
576 ✗ objects_all_->FilterEnd();
577
578 ✗ storage_->Compact();
579 ✗ cache_info_.no_shrink++;
580 }
581
582 ✗ void CheckHighPinWatermark() {
583 ✗ if (!Me()->in_danger_zone_ && Me()->IsInDangerZone()) {
584 ✗ LogCvmfs(kLogCvmfs, kLogDebug | kLogSyslog,
585 "high watermark of pinned files");
586 ✗ Me()->in_danger_zone_ = true;
587 ✗ cvmcache_ask_detach(ctx);
588 }
589 }
590
591 ✗ bool IsInDangerZone() {
592 ✗ return (static_cast<double>(cache_info_.pinned_bytes)
593 ✗ / static_cast<double>(cache_info_.size_bytes))
594 ✗ > kDangerZoneThreshold;
595 }
596
597
598 struct cvmcache_info cache_info_;
599 perf::Statistics statistics_;
600 SmallHashDynamic<uint64_t, ObjectHeader *> transactions_;
601 SmallHashDynamic<uint64_t, Listing *> listings_;
602 lru::LruCache<ComparableHash, ObjectHeader *> *objects_all_;
603 lru::LruCache<ComparableHash, ObjectHeader *> *objects_volatile_;
604 map<std::string, cvmcache_breadcrumb> breadcrumbs_;
605 MallocHeap *storage_;
606 bool in_danger_zone_;
607 }; // class PluginRamCache
608
609 PluginRamCache *PluginRamCache::instance_ = NULL;
610 const uint64_t PluginRamCache::kMinSize = 100 * 1024 * 1024;
611 const double PluginRamCache::kShrinkFactor = 0.75;
612 const double PluginRamCache::kObjectExpandFactor = 1.5;
613 const double PluginRamCache::kSlotFraction = 0.04;
614 const double PluginRamCache::kDangerZoneThreshold = 0.7;
615
616
617 ✗ static void Usage(const char *progname) {
618 ✗ LogCvmfs(kLogCache, kLogStdout, "%s <config file>", progname);
619 }
620
621
622 /**
623 * For testing and debugging purposes, the cache manager drops its
624 * breadcrumb cache upon SIGUSR2 retrieval
625 */
626 ✗ void DropBreadcrumbs(int sig, siginfo_t *siginfo, void *context) {
627 ✗ LogCvmfs(kLogCache, kLogSyslog | kLogDebug, "dropping breadcrumbs");
628 ✗ PluginRamCache::GetInstance()->DropBreadcrumbs();
629 }
630
631
632 ✗ int main(int argc, char **argv) {
633 ✗ if (argc < 2) {
634 ✗ Usage(argv[0]);
635 ✗ return 1;
636 }
637
638 ✗ SetLogDebugFile("/dev/null");
639
640 ✗ cvmcache_init_global();
641
642 ✗ cvmcache_option_map *options = cvmcache_options_init();
643 ✗ if (cvmcache_options_parse(options, argv[1]) != 0) {
644 ✗ LogCvmfs(kLogCache, kLogStderr, "cannot parse options file %s", argv[1]);
645 ✗ return 1;
646 }
647 ✗ char *debug_log = cvmcache_options_get(options,
648 "CVMFS_CACHE_PLUGIN_DEBUGLOG");
649 ✗ if (debug_log != NULL) {
650 ✗ SetLogDebugFile(debug_log);
651 ✗ cvmcache_options_free(debug_log);
652 }
653 ✗ char *locator = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_LOCATOR");
654 ✗ if (locator == NULL) {
655 ✗ LogCvmfs(kLogCache, kLogStderr, "CVMFS_CACHE_PLUGIN_LOCATOR missing");
656 ✗ cvmcache_options_fini(options);
657 ✗ return 1;
658 }
659 ✗ char *mem_size = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_SIZE");
660 ✗ if (mem_size == NULL) {
661 ✗ LogCvmfs(kLogCache, kLogStderr, "CVMFS_CACHE_PLUGIN_SIZE missing");
662 ✗ cvmcache_options_fini(options);
663 ✗ return 1;
664 }
665 ✗ char *test_mode = cvmcache_options_get(options, "CVMFS_CACHE_PLUGIN_TEST");
666
667 ✗ if (!test_mode)
668 ✗ cvmcache_spawn_watchdog(NULL);
669
670 ✗ PluginRamCache *plugin = PluginRamCache::Create(mem_size);
671 struct sigaction sa;
672 ✗ memset(&sa, 0, sizeof(sa));
673 ✗ sa.sa_sigaction = DropBreadcrumbs;
674 ✗ sa.sa_flags = SA_SIGINFO;
675 ✗ sigfillset(&sa.sa_mask);
676 ✗ int retval = sigaction(SIGUSR2, &sa, NULL);
677 ✗ assert(retval == 0);
678
679 struct cvmcache_callbacks callbacks;
680 ✗ memset(&callbacks, 0, sizeof(callbacks));
681 ✗ callbacks.cvmcache_chrefcnt = plugin->ram_chrefcnt;
682 ✗ callbacks.cvmcache_obj_info = plugin->ram_obj_info;
683 ✗ callbacks.cvmcache_pread = plugin->ram_pread;
684 ✗ callbacks.cvmcache_start_txn = plugin->ram_start_txn;
685 ✗ callbacks.cvmcache_write_txn = plugin->ram_write_txn;
686 ✗ callbacks.cvmcache_commit_txn = plugin->ram_commit_txn;
687 ✗ callbacks.cvmcache_abort_txn = plugin->ram_abort_txn;
688 ✗ callbacks.cvmcache_info = plugin->ram_info;
689 ✗ callbacks.cvmcache_shrink = plugin->ram_shrink;
690 ✗ callbacks.cvmcache_listing_begin = plugin->ram_listing_begin;
691 ✗ callbacks.cvmcache_listing_next = plugin->ram_listing_next;
692 ✗ callbacks.cvmcache_listing_end = plugin->ram_listing_end;
693 ✗ callbacks.cvmcache_breadcrumb_store = plugin->ram_breadcrumb_store;
694 ✗ callbacks.cvmcache_breadcrumb_load = plugin->ram_breadcrumb_load;
695 ✗ callbacks.capabilities = CVMCACHE_CAP_ALL_V2;
696
697 ✗ ctx = cvmcache_init(&callbacks);
698 ✗ retval = cvmcache_listen(ctx, locator);
699 ✗ if (!retval) {
700 ✗ LogCvmfs(kLogCache, kLogStderr, "failed to listen on %s", locator);
701 ✗ return 1;
702 }
703
704 ✗ if (test_mode) {
705 // Daemonize, print out PID
706 pid_t pid;
707 int statloc;
708 ✗ if ((pid = fork()) == 0) {
709 ✗ if ((pid = fork()) == 0) {
710 ✗ const int null_read = open("/dev/null", O_RDONLY);
711 ✗ const int null_write = open("/dev/null", O_WRONLY);
712 ✗ assert((null_read >= 0) && (null_write >= 0));
713 ✗ int retval = dup2(null_read, 0);
714 ✗ assert(retval == 0);
715 ✗ retval = dup2(null_write, 1);
716 ✗ assert(retval == 1);
717 ✗ retval = dup2(null_write, 2);
718 ✗ assert(retval == 2);
719 ✗ close(null_read);
720 ✗ close(null_write);
721 } else {
722 ✗ assert(pid > 0);
723 ✗ printf("%d\n", pid);
724 ✗ fflush(stdout);
725 ✗ fsync(1);
726 ✗ _exit(0);
727 }
728 } else {
729 ✗ assert(pid > 0);
730 ✗ waitpid(pid, &statloc, 0);
731 ✗ _exit(0);
732 }
733 }
734
735 ✗ LogCvmfs(kLogCache, kLogStdout,
736 "Listening for cvmfs clients on %s\n"
737 "NOTE: this process needs to run as user cvmfs\n",
738 locator);
739
740 ✗ cvmcache_process_requests(ctx, 0);
741 ✗ if (test_mode)
742 while (true)
743 ✗ sleep(1);
744 ✗ if (!cvmcache_is_supervised()) {
745 ✗ LogCvmfs(kLogCache, kLogStdout, "Press <Ctrl+D> to quit");
746 ✗ LogCvmfs(kLogCache, kLogStdout,
747 "Press <R Enter> to ask clients to release nested catalogs");
748 while (true) {
749 char buf;
750 ✗ retval = read(fileno(stdin), &buf, 1);
751 ✗ if (retval != 1)
752 ✗ break;
753 ✗ if (buf == 'R') {
754 ✗ LogCvmfs(kLogCache, kLogStdout,
755 " ... asking clients to release nested catalogs");
756 ✗ cvmcache_ask_detach(ctx);
757 }
758 }
759 ✗ cvmcache_terminate(ctx);
760 } else {
761 ✗ LogCvmfs(kLogCache, kLogDebug | kLogSyslog,
762 "CernVM-FS RAM cache plugin started in supervised mode");
763 }
764
765 ✗ cvmcache_wait_for(ctx);
766 ✗ LogCvmfs(kLogCache, kLogDebug | kLogStdout, " ... good bye");
767 ✗ cvmcache_options_free(mem_size);
768 ✗ cvmcache_options_free(locator);
769 ✗ cvmcache_options_fini(options);
770 ✗ cvmcache_terminate_watchdog();
771 ✗ cvmcache_cleanup_global();
772 ✗ return 0;
773 }
774