GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/kvstore.cc
Date: 2025-12-21 02:39:23
Exec Total Coverage
Lines: 193 249 77.5%
Branches: 112 258 43.4%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "kvstore.h"
6
7 #include <assert.h>
8 #include <errno.h>
9 #include <limits.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 #include <algorithm>
14
15 #include "util/async.h"
16 #include "util/concurrency.h"
17 #include "util/logging.h"
18
19 using namespace std; // NOLINT
20
21 namespace {
22
23 3503131 static inline uint32_t hasher_any(const shash::Any &key) {
24 // We'll just do the same thing as hasher_md5, since every hash is at
25 // least as large.
26 return static_cast<uint32_t>(
27 3503131 *(reinterpret_cast<const uint32_t *>(key.digest) + 1));
28 }
29
30 } // anonymous namespace
31
32 const double MemoryKvStore::kCompactThreshold = 0.8;
33
34
35 2800 MemoryKvStore::MemoryKvStore(unsigned int cache_entries,
36 MemoryAllocator alloc,
37 unsigned alloc_size,
38 2800 perf::StatisticsTemplate statistics)
39 2800 : allocator_(alloc)
40 2800 , used_bytes_(0)
41 2800 , entry_count_(0)
42 2800 , max_entries_(cache_entries)
43
2/4
✓ Branch 1 taken 2800 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2800 times.
✗ Branch 5 not taken.
2800 , entries_(cache_entries, shash::Any(), hasher_any,
44
2/4
✓ Branch 2 taken 2800 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2800 times.
✗ Branch 6 not taken.
5600 perf::StatisticsTemplate("lru", statistics))
45 2800 , heap_(NULL)
46
2/4
✓ Branch 2 taken 2800 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2800 times.
✗ Branch 6 not taken.
5600 , counters_(statistics) {
47 2800 const int retval = pthread_rwlock_init(&rwlock_, NULL);
48
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2800 times.
2800 assert(retval == 0);
49
2/2
✓ Branch 0 taken 656 times.
✓ Branch 1 taken 2144 times.
2800 switch (alloc) {
50 656 case kMallocHeap:
51 656 heap_ = new MallocHeap(
52
3/6
✓ Branch 1 taken 656 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 656 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 656 times.
✗ Branch 8 not taken.
656 alloc_size, this->MakeCallback(&MemoryKvStore::OnBlockMove, this));
53 656 break;
54 2144 default:
55 2144 break;
56 }
57 2800 }
58
59
60 2800 MemoryKvStore::~MemoryKvStore() {
61
2/2
✓ Branch 0 taken 656 times.
✓ Branch 1 taken 2144 times.
2800 delete heap_;
62 2800 pthread_rwlock_destroy(&rwlock_);
63 2800 }
64
65
66 void MemoryKvStore::OnBlockMove(const MallocHeap::BlockPtr &ptr) {
67 bool ok;
68 struct AllocHeader a;
69 MemoryBuffer buf;
70
71 // must be locked by caller
72 assert(ptr.pointer);
73 memcpy(&a, ptr.pointer, sizeof(a));
74 LogCvmfs(kLogKvStore, kLogDebug, "compaction moved %s to %p",
75 a.id.ToString().c_str(), ptr.pointer);
76 assert(a.version == 0);
77 const bool update_lru = false;
78 ok = entries_.Lookup(a.id, &buf, update_lru);
79 assert(ok);
80 buf.address = static_cast<char *>(ptr.pointer) + sizeof(a);
81 ok = entries_.UpdateValue(buf.id, buf);
82 assert(ok);
83 }
84
85
86 2120 bool MemoryKvStore::Contains(const shash::Any &id) {
87
1/2
✓ Branch 1 taken 2120 times.
✗ Branch 2 not taken.
2120 MemoryBuffer buf;
88 // LogCvmfs(kLogKvStore, kLogDebug, "check buffer %s", id.ToString().c_str());
89 2120 const bool update_lru = false;
90
1/2
✓ Branch 1 taken 2120 times.
✗ Branch 2 not taken.
4240 return entries_.Lookup(id, &buf, update_lru);
91 }
92
93
94 6633 int MemoryKvStore::DoMalloc(MemoryBuffer *buf) {
95
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
6633 MemoryBuffer tmp;
96
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
6633 AllocHeader a;
97
98
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6633 times.
6633 assert(buf);
99 6633 memcpy(&tmp, buf, sizeof(tmp));
100
101 6633 tmp.address = NULL;
102
1/2
✓ Branch 0 taken 6633 times.
✗ Branch 1 not taken.
6633 if (tmp.size > 0) {
103
1/3
✓ Branch 0 taken 6633 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
6633 switch (allocator_) {
104 6633 case kMallocLibc:
105 6633 tmp.address = malloc(tmp.size);
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6633 times.
6633 if (!tmp.address)
107 return -errno;
108 6633 break;
109 case kMallocHeap:
110 assert(heap_);
111 a.id = tmp.id;
112 tmp.address = heap_->Allocate(tmp.size + sizeof(a), &a, sizeof(a));
113 if (!tmp.address)
114 return -ENOMEM;
115 tmp.address = static_cast<char *>(tmp.address) + sizeof(a);
116 break;
117 default:
118 abort();
119 }
120 }
121
122 6633 memcpy(buf, &tmp, sizeof(*buf));
123 6633 return 0;
124 }
125
126
127 5119 void MemoryKvStore::DoFree(MemoryBuffer *buf) {
128
1/2
✓ Branch 1 taken 5119 times.
✗ Branch 2 not taken.
5119 const AllocHeader a;
129
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5119 times.
5119 assert(buf);
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5119 times.
5119 if (!buf->address)
132 return;
133
1/3
✓ Branch 0 taken 5119 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
5119 switch (allocator_) {
134 5119 case kMallocLibc:
135 5119 free(buf->address);
136 5119 return;
137 case kMallocHeap:
138 heap_->MarkFree(static_cast<char *>(buf->address) - sizeof(a));
139 return;
140 default:
141 abort();
142 }
143 }
144
145
146 6633 bool MemoryKvStore::CompactMemory() {
147 double utilization;
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6633 times.
6633 switch (allocator_) {
149 case kMallocHeap:
150 utilization = heap_->utilization();
151 LogCvmfs(kLogKvStore, kLogDebug, "compact requested (%f)", utilization);
152 if (utilization < kCompactThreshold) {
153 LogCvmfs(kLogKvStore, kLogDebug, "compacting heap");
154 heap_->Compact();
155 if (heap_->utilization() > utilization)
156 return true;
157 }
158 return false;
159 6633 default:
160 // the others can't do any compact, so just ignore
161 6633 LogCvmfs(kLogKvStore, kLogDebug, "compact requested");
162 6633 return false;
163 }
164 }
165
166
167 362 int64_t MemoryKvStore::GetSize(const shash::Any &id) {
168
1/2
✓ Branch 1 taken 362 times.
✗ Branch 2 not taken.
362 MemoryBuffer mem;
169 362 perf::Inc(counters_.n_getsize);
170 362 const bool update_lru = false;
171
3/4
✓ Branch 1 taken 362 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 316 times.
✓ Branch 4 taken 46 times.
362 if (entries_.Lookup(id, &mem, update_lru)) {
172 // LogCvmfs(kLogKvStore, kLogDebug, "%s is %u B", id.ToString().c_str(),
173 // mem.size);
174 316 return mem.size;
175 } else {
176
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on GetSize",
177
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
92 id.ToString().c_str());
178 46 return -ENOENT;
179 }
180 }
181
182
183 230 int64_t MemoryKvStore::GetRefcount(const shash::Any &id) {
184
1/2
✓ Branch 1 taken 230 times.
✗ Branch 2 not taken.
230 MemoryBuffer mem;
185 230 perf::Inc(counters_.n_getrefcount);
186 230 const bool update_lru = false;
187
2/4
✓ Branch 1 taken 230 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 230 times.
✗ Branch 4 not taken.
230 if (entries_.Lookup(id, &mem, update_lru)) {
188 // LogCvmfs(kLogKvStore, kLogDebug, "%s has refcount %u",
189 // id.ToString().c_str(), mem.refcount);
190 230 return mem.refcount;
191 } else {
192 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on GetRefcount",
193 id.ToString().c_str());
194 return -ENOENT;
195 }
196 }
197
198
199 541186 bool MemoryKvStore::IncRef(const shash::Any &id) {
200 541186 perf::Inc(counters_.n_incref);
201 541186 const WriteLockGuard guard(rwlock_);
202
1/2
✓ Branch 1 taken 541186 times.
✗ Branch 2 not taken.
541186 MemoryBuffer mem;
203
3/4
✓ Branch 1 taken 541186 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 541140 times.
✓ Branch 4 taken 46 times.
541186 if (entries_.Lookup(id, &mem)) {
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 541140 times.
541140 assert(mem.refcount < UINT_MAX);
205 541140 ++mem.refcount;
206
1/2
✓ Branch 1 taken 541140 times.
✗ Branch 2 not taken.
541140 entries_.Insert(id, mem);
207
1/2
✓ Branch 2 taken 541140 times.
✗ Branch 3 not taken.
541140 LogCvmfs(kLogKvStore, kLogDebug, "increased refcount of %s to %u",
208
1/2
✓ Branch 1 taken 541140 times.
✗ Branch 2 not taken.
1082280 id.ToString().c_str(), mem.refcount);
209 541140 return true;
210 } else {
211
1/2
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
46 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on IncRef",
212
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
92 id.ToString().c_str());
213 46 return false;
214 }
215 541186 }
216
217
218 540892 bool MemoryKvStore::Unref(const shash::Any &id) {
219 540892 perf::Inc(counters_.n_unref);
220 540892 const WriteLockGuard guard(rwlock_);
221
1/2
✓ Branch 1 taken 540892 times.
✗ Branch 2 not taken.
540892 MemoryBuffer mem;
222
3/4
✓ Branch 1 taken 540892 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 540846 times.
✓ Branch 4 taken 46 times.
540892 if (entries_.Lookup(id, &mem)) {
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 540846 times.
540846 assert(mem.refcount > 0);
224 540846 --mem.refcount;
225
1/2
✓ Branch 1 taken 540846 times.
✗ Branch 2 not taken.
540846 entries_.Insert(id, mem);
226
1/2
✓ Branch 2 taken 540846 times.
✗ Branch 3 not taken.
540846 LogCvmfs(kLogKvStore, kLogDebug, "decreased refcount of %s to %u",
227
1/2
✓ Branch 1 taken 540846 times.
✗ Branch 2 not taken.
1081692 id.ToString().c_str(), mem.refcount);
228 540846 return true;
229 } else {
230
2/4
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 46 times.
✗ Branch 6 not taken.
46 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on Unref", id.ToString().c_str());
231 46 return false;
232 }
233 540892 }
234
235
236 310 int64_t MemoryKvStore::Read(const shash::Any &id,
237 void *buf,
238 size_t size,
239 size_t offset) {
240
1/2
✓ Branch 1 taken 310 times.
✗ Branch 2 not taken.
310 MemoryBuffer mem;
241 310 perf::Inc(counters_.n_read);
242 310 const ReadLockGuard guard(rwlock_);
243
2/4
✓ Branch 1 taken 310 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 310 times.
310 if (!entries_.Lookup(id, &mem)) {
244 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on Read", id.ToString().c_str());
245 return -ENOENT;
246 }
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 310 times.
310 if (offset > mem.size) {
248 LogCvmfs(kLogKvStore, kLogDebug, "out of bounds read (%zu>%zu) on %s",
249 offset, mem.size, id.ToString().c_str());
250 return 0;
251 }
252 310 const uint64_t copy_size = std::min(mem.size - offset, size);
253 // LogCvmfs(kLogKvStore, kLogDebug, "copy %u B from offset %u of %s",
254 // copy_size, offset, id.ToString().c_str());
255 310 memcpy(buf, static_cast<char *>(mem.address) + offset, copy_size);
256 310 perf::Xadd(counters_.sz_read, copy_size);
257 310 return copy_size;
258 310 }
259
260
261 6633 int MemoryKvStore::Commit(const MemoryBuffer &buf) {
262 6633 const WriteLockGuard guard(rwlock_);
263
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
13266 return DoCommit(buf);
264 6633 }
265
266
267 6633 int MemoryKvStore::DoCommit(const MemoryBuffer &buf) {
268 // we need to be careful about refcounts. If another thread wants to read
269 // a cache entry while it's being written (OpenFromTxn put partial data in
270 // the kvstore, will be committed again later) the refcount in the kvstore
271 // will differ from the refcount in the cache transaction. To avoid leaks,
272 // either the caller needs to fetch the cache entry before every write to
273 // find the current refcount, or the kvstore can ignore the passed-in
274 // refcount if the entry already exists. This implementation does the latter,
275 // and as a result it's not possible to directly modify the refcount
276 // without a race condition. This is a hint that callers should use the
277 // refcount like a lock and not directly modify the numeric value.
278
279
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
6633 CompactMemory();
280
281
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
6633 MemoryBuffer mem;
282 6633 perf::Inc(counters_.n_commit);
283
2/4
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 6633 times.
✗ Branch 6 not taken.
6633 LogCvmfs(kLogKvStore, kLogDebug, "commit %s", buf.id.ToString().c_str());
284
3/4
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 136 times.
✓ Branch 4 taken 6497 times.
6633 if (entries_.Lookup(buf.id, &mem)) {
285
1/2
✓ Branch 1 taken 136 times.
✗ Branch 2 not taken.
136 LogCvmfs(kLogKvStore, kLogDebug, "commit overwrites existing entry");
286 136 const size_t old_size = mem.size;
287
1/2
✓ Branch 1 taken 136 times.
✗ Branch 2 not taken.
136 DoFree(&mem);
288 136 used_bytes_ -= old_size;
289 136 counters_.sz_size->Set(used_bytes_);
290 136 --entry_count_;
291 } else {
292 // since this is a new entry, the caller can choose the starting
293 // refcount (starting at 1 for pinning, for example)
294 6497 mem.refcount = buf.refcount;
295 }
296 6633 mem.object_flags = buf.object_flags;
297 6633 mem.id = buf.id;
298 6633 mem.size = buf.size;
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6633 times.
6633 if (entry_count_ == max_entries_) {
300 LogCvmfs(kLogKvStore, kLogDebug, "too many entries in kvstore");
301 return -ENFILE;
302 }
303
2/4
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6633 times.
6633 if (DoMalloc(&mem) < 0) {
304 LogCvmfs(kLogKvStore, kLogDebug, "failed to allocate %s",
305 buf.id.ToString().c_str());
306 return -EIO;
307 }
308
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6633 times.
6633 assert(SSIZE_MAX - mem.size > used_bytes_);
309 6633 memcpy(mem.address, buf.address, mem.size);
310
1/2
✓ Branch 1 taken 6633 times.
✗ Branch 2 not taken.
6633 entries_.Insert(buf.id, mem);
311 6633 ++entry_count_;
312 6633 used_bytes_ += mem.size;
313 6633 counters_.sz_size->Set(used_bytes_);
314 6633 perf::Xadd(counters_.sz_committed, mem.size);
315 6633 return 0;
316 }
317
318
319 276 bool MemoryKvStore::Delete(const shash::Any &id) {
320 276 perf::Inc(counters_.n_delete);
321 276 const WriteLockGuard guard(rwlock_);
322
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
552 return DoDelete(id);
323 276 }
324
325
326 276 bool MemoryKvStore::DoDelete(const shash::Any &id) {
327
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 MemoryBuffer buf;
328
3/4
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 92 times.
✓ Branch 4 taken 184 times.
276 if (!entries_.Lookup(id, &buf)) {
329
1/2
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
92 LogCvmfs(kLogKvStore, kLogDebug, "miss %s on Delete",
330
1/2
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
184 id.ToString().c_str());
331 92 return false;
332 }
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 184 times.
184 if (buf.refcount > 0) {
334 LogCvmfs(kLogKvStore, kLogDebug, "can't delete %s, nonzero refcount",
335 id.ToString().c_str());
336 return false;
337 }
338
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 184 times.
184 assert(entry_count_ > 0);
339 184 --entry_count_;
340 184 used_bytes_ -= buf.size;
341 184 counters_.sz_size->Set(used_bytes_);
342 184 perf::Xadd(counters_.sz_deleted, buf.size);
343
1/2
✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
184 DoFree(&buf);
344
1/2
✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
184 entries_.Forget(id);
345
2/4
✓ Branch 1 taken 184 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 184 times.
✗ Branch 6 not taken.
184 LogCvmfs(kLogKvStore, kLogDebug, "deleted %s", id.ToString().c_str());
346 184 return true;
347 }
348
349
350 815 bool MemoryKvStore::ShrinkTo(size_t size) {
351 815 perf::Inc(counters_.n_shrinkto);
352 815 const WriteLockGuard guard(rwlock_);
353
1/2
✓ Branch 1 taken 815 times.
✗ Branch 2 not taken.
815 shash::Any key;
354
1/2
✓ Branch 1 taken 815 times.
✗ Branch 2 not taken.
815 MemoryBuffer buf;
355
356
2/2
✓ Branch 0 taken 337 times.
✓ Branch 1 taken 478 times.
815 if (used_bytes_ <= size) {
357
1/2
✓ Branch 1 taken 337 times.
✗ Branch 2 not taken.
337 LogCvmfs(kLogKvStore, kLogDebug, "no need to shrink");
358 337 return true;
359 }
360
361
1/2
✓ Branch 1 taken 478 times.
✗ Branch 2 not taken.
478 LogCvmfs(kLogKvStore, kLogDebug, "shrinking to %zu B", size);
362
1/2
✓ Branch 1 taken 478 times.
✗ Branch 2 not taken.
478 entries_.FilterBegin();
363
3/4
✓ Branch 1 taken 5755 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5513 times.
✓ Branch 4 taken 242 times.
5755 while (entries_.FilterNext()) {
364
2/2
✓ Branch 0 taken 236 times.
✓ Branch 1 taken 5277 times.
5513 if (used_bytes_ <= size)
365 236 break;
366
1/2
✓ Branch 1 taken 5277 times.
✗ Branch 2 not taken.
5277 entries_.FilterGet(&key, &buf);
367
2/2
✓ Branch 0 taken 478 times.
✓ Branch 1 taken 4799 times.
5277 if (buf.refcount > 0) {
368
1/2
✓ Branch 2 taken 478 times.
✗ Branch 3 not taken.
478 LogCvmfs(kLogKvStore, kLogDebug, "skip %s, nonzero refcount",
369
1/2
✓ Branch 1 taken 478 times.
✗ Branch 2 not taken.
956 key.ToString().c_str());
370 478 continue;
371 }
372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4799 times.
4799 assert(entry_count_ > 0);
373 4799 --entry_count_;
374
1/2
✓ Branch 1 taken 4799 times.
✗ Branch 2 not taken.
4799 entries_.FilterDelete();
375 4799 used_bytes_ -= buf.size;
376 4799 perf::Xadd(counters_.sz_shrunk, buf.size);
377 4799 counters_.sz_size->Set(used_bytes_);
378
1/2
✓ Branch 1 taken 4799 times.
✗ Branch 2 not taken.
4799 DoFree(&buf);
379
2/4
✓ Branch 1 taken 4799 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 4799 times.
✗ Branch 6 not taken.
4799 LogCvmfs(kLogKvStore, kLogDebug, "delete %s", key.ToString().c_str());
380 }
381
1/2
✓ Branch 1 taken 478 times.
✗ Branch 2 not taken.
478 entries_.FilterEnd();
382
1/2
✓ Branch 1 taken 478 times.
✗ Branch 2 not taken.
478 LogCvmfs(kLogKvStore, kLogDebug, "shrunk to %zu B", used_bytes_);
383 478 return used_bytes_ <= size;
384 815 }
385