1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 54 Interprocess Communication */
10
11 #include "squid.h"
12 #include "ipc/StoreMap.h"
13 #include "sbuf/SBuf.h"
14 #include "Store.h"
15 #include "store/Controller.h"
16 #include "store_key_md5.h"
17 #include "tools.h"
18
19 static SBuf
StoreMapSlicesId(const SBuf & path)20 StoreMapSlicesId(const SBuf &path)
21 {
22 return Ipc::Mem::Segment::Name(path, "slices");
23 }
24
25 static SBuf
StoreMapAnchorsId(const SBuf & path)26 StoreMapAnchorsId(const SBuf &path)
27 {
28 return Ipc::Mem::Segment::Name(path, "anchors");
29 }
30
31 static SBuf
StoreMapFileNosId(const SBuf & path)32 StoreMapFileNosId(const SBuf &path)
33 {
34 return Ipc::Mem::Segment::Name(path, "filenos");
35 }
36
37 Ipc::StoreMap::Owner *
Init(const SBuf & path,const int sliceLimit)38 Ipc::StoreMap::Init(const SBuf &path, const int sliceLimit)
39 {
40 assert(sliceLimit > 0); // we should not be created otherwise
41 const int anchorLimit = min(sliceLimit, static_cast<int>(SwapFilenMax));
42 Owner *owner = new Owner;
43 owner->fileNos = shm_new(FileNos)(StoreMapFileNosId(path).c_str(), anchorLimit);
44 owner->anchors = shm_new(Anchors)(StoreMapAnchorsId(path).c_str(), anchorLimit);
45 owner->slices = shm_new(Slices)(StoreMapSlicesId(path).c_str(), sliceLimit);
46 debugs(54, 5, "created " << path << " with " << anchorLimit << '+' << sliceLimit);
47 return owner;
48 }
49
StoreMap(const SBuf & aPath)50 Ipc::StoreMap::StoreMap(const SBuf &aPath): cleaner(NULL), path(aPath),
51 fileNos(shm_old(FileNos)(StoreMapFileNosId(path).c_str())),
52 anchors(shm_old(Anchors)(StoreMapAnchorsId(path).c_str())),
53 slices(shm_old(Slices)(StoreMapSlicesId(path).c_str()))
54 {
55 debugs(54, 5, "attached " << path << " with " <<
56 fileNos->capacity << '+' <<
57 anchors->capacity << '+' << slices->capacity);
58 assert(entryLimit() > 0); // key-to-position mapping requires this
59 assert(entryLimit() <= sliceLimit()); // at least one slice per entry
60 }
61
62 int
compareVersions(const sfileno fileno,time_t newVersion) const63 Ipc::StoreMap::compareVersions(const sfileno fileno, time_t newVersion) const
64 {
65 const Anchor &inode = anchorAt(fileno);
66
67 // note: we do not lock, so comparison may be inacurate
68
69 if (inode.empty())
70 return +2;
71
72 if (const time_t diff = newVersion - inode.basics.timestamp)
73 return diff < 0 ? -1 : +1;
74
75 return 0;
76 }
77
78 void
forgetWritingEntry(sfileno fileno)79 Ipc::StoreMap::forgetWritingEntry(sfileno fileno)
80 {
81 Anchor &inode = anchorAt(fileno);
82
83 assert(inode.writing());
84
85 // we do not iterate slices because we were told to forget about
86 // them; the caller is responsible for freeing them (most likely
87 // our slice list is incomplete or has holes)
88
89 inode.rewind();
90
91 inode.lock.unlockExclusive();
92 --anchors->count;
93
94 debugs(54, 8, "closed entry " << fileno << " for writing " << path);
95 }
96
97 Ipc::StoreMap::Anchor *
openForWriting(const cache_key * const key,sfileno & fileno)98 Ipc::StoreMap::openForWriting(const cache_key *const key, sfileno &fileno)
99 {
100 debugs(54, 5, "opening entry with key " << storeKeyText(key)
101 << " for writing " << path);
102 const int idx = fileNoByKey(key);
103
104 if (Anchor *anchor = openForWritingAt(idx)) {
105 fileno = idx;
106 return anchor;
107 }
108
109 return NULL;
110 }
111
112 Ipc::StoreMap::Anchor *
openForWritingAt(const sfileno fileno,bool overwriteExisting)113 Ipc::StoreMap::openForWritingAt(const sfileno fileno, bool overwriteExisting)
114 {
115 Anchor &s = anchorAt(fileno);
116 ReadWriteLock &lock = s.lock;
117
118 if (lock.lockExclusive()) {
119 assert(s.writing() && !s.reading());
120
121 // bail if we cannot empty this position
122 if (!s.waitingToBeFreed && !s.empty() && !overwriteExisting) {
123 lock.unlockExclusive();
124 debugs(54, 5, "cannot open existing entry " << fileno <<
125 " for writing " << path);
126 return NULL;
127 }
128
129 // free if the entry was used, keeping the entry locked
130 if (s.waitingToBeFreed || !s.empty())
131 freeChain(fileno, s, true);
132
133 assert(s.empty());
134 s.start = -1; // we have not allocated any slices yet
135 s.splicingPoint = -1;
136 ++anchors->count;
137
138 //s.setKey(key); // XXX: the caller should do that
139 debugs(54, 5, "opened entry " << fileno << " for writing " << path);
140 return &s; // and keep the entry locked
141 }
142
143 debugs(54, 5, "cannot open busy entry " << fileno <<
144 " for writing " << path);
145 return NULL;
146 }
147
148 void
startAppending(const sfileno fileno)149 Ipc::StoreMap::startAppending(const sfileno fileno)
150 {
151 Anchor &s = anchorAt(fileno);
152 assert(s.writing());
153 s.lock.startAppending();
154 debugs(54, 5, "restricted entry " << fileno << " to appending " << path);
155 }
156
157 void
closeForWriting(const sfileno fileno)158 Ipc::StoreMap::closeForWriting(const sfileno fileno)
159 {
160 Anchor &s = anchorAt(fileno);
161 assert(s.writing());
162 // TODO: assert(!s.empty()); // i.e., unlocked s becomes s.complete()
163 s.lock.unlockExclusive();
164 debugs(54, 5, "closed entry " << fileno << " for writing " << path);
165 // cannot assert completeness here because we have no lock
166 }
167
168 void
switchWritingToReading(const sfileno fileno)169 Ipc::StoreMap::switchWritingToReading(const sfileno fileno)
170 {
171 debugs(54, 5, "switching entry " << fileno << " from writing to reading " << path);
172 Anchor &s = anchorAt(fileno);
173 assert(s.writing());
174 s.lock.switchExclusiveToShared();
175 assert(s.complete());
176 }
177
178 Ipc::StoreMap::Slice &
writeableSlice(const AnchorId anchorId,const SliceId sliceId)179 Ipc::StoreMap::writeableSlice(const AnchorId anchorId, const SliceId sliceId)
180 {
181 assert(anchorAt(anchorId).writing());
182 assert(validSlice(sliceId));
183 return sliceAt(sliceId);
184 }
185
186 const Ipc::StoreMap::Slice &
readableSlice(const AnchorId anchorId,const SliceId sliceId) const187 Ipc::StoreMap::readableSlice(const AnchorId anchorId, const SliceId sliceId) const
188 {
189 assert(anchorAt(anchorId).reading());
190 assert(validSlice(sliceId));
191 return sliceAt(sliceId);
192 }
193
194 Ipc::StoreMap::Anchor &
writeableEntry(const AnchorId anchorId)195 Ipc::StoreMap::writeableEntry(const AnchorId anchorId)
196 {
197 assert(anchorAt(anchorId).writing());
198 return anchorAt(anchorId);
199 }
200
201 const Ipc::StoreMap::Anchor &
readableEntry(const AnchorId anchorId) const202 Ipc::StoreMap::readableEntry(const AnchorId anchorId) const
203 {
204 assert(anchorAt(anchorId).reading());
205 return anchorAt(anchorId);
206 }
207
208 void
abortWriting(const sfileno fileno)209 Ipc::StoreMap::abortWriting(const sfileno fileno)
210 {
211 debugs(54, 5, "aborting entry " << fileno << " for writing " << path);
212 Anchor &s = anchorAt(fileno);
213 assert(s.writing());
214 s.lock.appending = false; // locks out any new readers
215 if (!s.lock.readers) {
216 freeChain(fileno, s, false);
217 debugs(54, 5, "closed clean entry " << fileno << " for writing " << path);
218 } else {
219 s.waitingToBeFreed = true;
220 s.writerHalted = true;
221 s.lock.unlockExclusive();
222 debugs(54, 5, "closed dirty entry " << fileno << " for writing " << path);
223 }
224 }
225
226 void
abortUpdating(Update & update)227 Ipc::StoreMap::abortUpdating(Update &update)
228 {
229 const sfileno fileno = update.stale.fileNo;
230 debugs(54, 5, "aborting entry " << fileno << " for updating " << path);
231 if (update.stale) {
232 AssertFlagIsSet(update.stale.anchor->lock.updating);
233 update.stale.anchor->lock.unlockHeaders();
234 closeForReading(update.stale.fileNo);
235 update.stale = Update::Edition();
236 }
237 if (update.fresh) {
238 abortWriting(update.fresh.fileNo);
239 update.fresh = Update::Edition();
240 }
241 debugs(54, 5, "aborted entry " << fileno << " for updating " << path);
242 }
243
244 const Ipc::StoreMap::Anchor *
peekAtReader(const sfileno fileno) const245 Ipc::StoreMap::peekAtReader(const sfileno fileno) const
246 {
247 const Anchor &s = anchorAt(fileno);
248 if (s.reading())
249 return &s; // immediate access by lock holder so no locking
250 if (s.writing())
251 return NULL; // the caller is not a read lock holder
252 assert(false); // must be locked for reading or writing
253 return NULL;
254 }
255
256 const Ipc::StoreMap::Anchor &
peekAtEntry(const sfileno fileno) const257 Ipc::StoreMap::peekAtEntry(const sfileno fileno) const
258 {
259 return anchorAt(fileno);
260 }
261
262 bool
freeEntry(const sfileno fileno)263 Ipc::StoreMap::freeEntry(const sfileno fileno)
264 {
265 debugs(54, 5, "marking entry " << fileno << " to be freed in " << path);
266
267 Anchor &s = anchorAt(fileno);
268
269 if (s.lock.lockExclusive()) {
270 const bool result = !s.waitingToBeFreed && !s.empty();
271 freeChain(fileno, s, false);
272 return result;
273 }
274
275 uint8_t expected = false;
276 // mark to free the locked entry later (if not already marked)
277 return s.waitingToBeFreed.compare_exchange_strong(expected, true);
278 }
279
280 void
freeEntryByKey(const cache_key * const key)281 Ipc::StoreMap::freeEntryByKey(const cache_key *const key)
282 {
283 debugs(54, 5, "marking entry with key " << storeKeyText(key)
284 << " to be freed in " << path);
285
286 const int idx = fileNoByKey(key);
287 Anchor &s = anchorAt(idx);
288 if (s.lock.lockExclusive()) {
289 if (s.sameKey(key))
290 freeChain(idx, s, true);
291 s.lock.unlockExclusive();
292 } else if (s.lock.lockShared()) {
293 if (s.sameKey(key))
294 s.waitingToBeFreed = true; // mark to free it later
295 s.lock.unlockShared();
296 } else {
297 // we cannot be sure that the entry we found is ours because we do not
298 // have a lock on it, but we still check to minimize false deletions
299 if (s.sameKey(key))
300 s.waitingToBeFreed = true; // mark to free it later
301 }
302 }
303
304 bool
markedForDeletion(const cache_key * const key)305 Ipc::StoreMap::markedForDeletion(const cache_key *const key)
306 {
307 const int idx = fileNoByKey(key);
308 const Anchor &s = anchorAt(idx);
309 return s.sameKey(key) ? bool(s.waitingToBeFreed) : false;
310 }
311
312 bool
hasReadableEntry(const cache_key * const key)313 Ipc::StoreMap::hasReadableEntry(const cache_key *const key)
314 {
315 sfileno index;
316 if (openForReading(reinterpret_cast<const cache_key*>(key), index)) {
317 closeForReading(index);
318 return true;
319 }
320 return false;
321 }
322
323 /// unconditionally frees an already locked chain of slots, unlocking if needed
324 void
freeChain(const sfileno fileno,Anchor & inode,const bool keepLocked)325 Ipc::StoreMap::freeChain(const sfileno fileno, Anchor &inode, const bool keepLocked)
326 {
327 debugs(54, 7, "freeing entry " << fileno <<
328 " in " << path);
329 if (!inode.empty())
330 freeChainAt(inode.start, inode.splicingPoint);
331 inode.rewind();
332
333 if (!keepLocked)
334 inode.lock.unlockExclusive();
335 --anchors->count;
336 debugs(54, 5, "freed entry " << fileno << " in " << path);
337 }
338
339 /// unconditionally frees an already locked chain of slots; no anchor maintenance
340 void
freeChainAt(SliceId sliceId,const SliceId splicingPoint)341 Ipc::StoreMap::freeChainAt(SliceId sliceId, const SliceId splicingPoint)
342 {
343 static uint64_t ChainId = 0; // to pair freeing/freed calls in debugs()
344 const uint64_t chainId = ++ChainId;
345 debugs(54, 7, "freeing chain #" << chainId << " starting at " << sliceId << " in " << path);
346 while (sliceId >= 0) {
347 Slice &slice = sliceAt(sliceId);
348 const SliceId nextId = slice.next;
349 slice.clear();
350 if (cleaner)
351 cleaner->noteFreeMapSlice(sliceId); // might change slice state
352 if (sliceId == splicingPoint) {
353 debugs(54, 5, "preserving chain #" << chainId << " in " << path <<
354 " suffix after slice " << splicingPoint);
355 break; // do not free the rest of the chain
356 }
357 sliceId = nextId;
358 }
359 debugs(54, 7, "freed chain #" << chainId << " in " << path);
360 }
361
362 void
prepFreeSlice(const SliceId sliceId)363 Ipc::StoreMap::prepFreeSlice(const SliceId sliceId)
364 {
365 // TODO: Move freeSlots here, along with reserveSlotForWriting() logic.
366 assert(validSlice(sliceId));
367 sliceAt(sliceId).clear();
368 }
369
370 Ipc::StoreMap::SliceId
sliceContaining(const sfileno fileno,const uint64_t bytesNeeded) const371 Ipc::StoreMap::sliceContaining(const sfileno fileno, const uint64_t bytesNeeded) const
372 {
373 const Anchor &anchor = anchorAt(fileno);
374 Must(anchor.reading());
375 uint64_t bytesSeen = 0;
376 SliceId lastSlice = anchor.start;
377 while (lastSlice >= 0) {
378 const Slice &slice = sliceAt(lastSlice);
379 bytesSeen += slice.size;
380 if (bytesSeen >= bytesNeeded)
381 break;
382 lastSlice = slice.next;
383 }
384 debugs(54, 7, "entry " << fileno << " has " << bytesNeeded << '/' << bytesSeen <<
385 " bytes at slice " << lastSlice << " in " << path);
386 return lastSlice; // may be negative
387 }
388
389 const Ipc::StoreMap::Anchor *
openForReading(const cache_key * const key,sfileno & fileno)390 Ipc::StoreMap::openForReading(const cache_key *const key, sfileno &fileno)
391 {
392 debugs(54, 5, "opening entry with key " << storeKeyText(key)
393 << " for reading " << path);
394 const int idx = fileNoByKey(key);
395 if (const Anchor *slot = openForReadingAt(idx)) {
396 if (slot->sameKey(key)) {
397 fileno = idx;
398 return slot; // locked for reading
399 }
400 slot->lock.unlockShared();
401 debugs(54, 7, "closed wrong-key entry " << idx << " for reading " << path);
402 }
403 return NULL;
404 }
405
406 const Ipc::StoreMap::Anchor *
openForReadingAt(const sfileno fileno)407 Ipc::StoreMap::openForReadingAt(const sfileno fileno)
408 {
409 debugs(54, 5, "opening entry " << fileno << " for reading " << path);
410 Anchor &s = anchorAt(fileno);
411
412 if (!s.lock.lockShared()) {
413 debugs(54, 5, "cannot open busy entry " << fileno <<
414 " for reading " << path);
415 return NULL;
416 }
417
418 if (s.empty()) {
419 s.lock.unlockShared();
420 debugs(54, 7, "cannot open empty entry " << fileno <<
421 " for reading " << path);
422 return NULL;
423 }
424
425 if (s.waitingToBeFreed) {
426 s.lock.unlockShared();
427 debugs(54, 7, "cannot open marked entry " << fileno <<
428 " for reading " << path);
429 return NULL;
430 }
431
432 debugs(54, 5, "opened entry " << fileno << " for reading " << path);
433 return &s;
434 }
435
436 void
closeForReading(const sfileno fileno)437 Ipc::StoreMap::closeForReading(const sfileno fileno)
438 {
439 Anchor &s = anchorAt(fileno);
440 assert(s.reading());
441 s.lock.unlockShared();
442 debugs(54, 5, "closed entry " << fileno << " for reading " << path);
443 }
444
445 bool
openForUpdating(Update & update,const sfileno fileNoHint)446 Ipc::StoreMap::openForUpdating(Update &update, const sfileno fileNoHint)
447 {
448 Must(update.entry);
449 const StoreEntry &entry = *update.entry;
450 const cache_key *const key = reinterpret_cast<const cache_key*>(entry.key);
451 update.stale.name = nameByKey(key);
452
453 if (!validEntry(fileNoHint)) {
454 debugs(54, 5, "opening entry with key " << storeKeyText(key) <<
455 " for updating " << path);
456 update.stale.fileNo = fileNoByName(update.stale.name);
457 } else {
458 update.stale.fileNo = fileNoHint;
459 }
460
461 debugs(54, 5, "opening entry " << update.stale.fileNo << " of " << entry << " for updating " << path);
462
463 // Unreadable entries cannot (e.g., empty and otherwise problematic entries)
464 // or should not (e.g., entries still forming their metadata) be updated.
465 if (const Anchor *anchor = openForReadingAt(update.stale.fileNo)) {
466 if (!anchor->sameKey(key)) {
467 closeForReading(update.stale.fileNo);
468 debugs(54, 5, "cannot open wrong-key entry " << update.stale.fileNo << " for updating " << path);
469 return false;
470 }
471 } else {
472 debugs(54, 5, "cannot open unreadable entry " << update.stale.fileNo << " for updating " << path);
473 return false;
474 }
475
476 update.stale.anchor = &anchorAt(update.stale.fileNo);
477 if (update.stale.anchor->writing()) {
478 // TODO: Support updating appending entries.
479 // For example, MemStore::updateHeaders() would not know how
480 // many old prefix body bytes to copy to the new prefix if the last old
481 // prefix slice has not been formed yet (i.e., still gets more bytes).
482 debugs(54, 5, "cannot open appending entry " << update.stale.fileNo <<
483 " for updating " << path);
484 closeForReading(update.stale.fileNo);
485 return false;
486 }
487
488 if (!update.stale.anchor->lock.lockHeaders()) {
489 debugs(54, 5, "cannot open updating entry " << update.stale.fileNo <<
490 " for updating " << path);
491 closeForReading(update.stale.fileNo);
492 return false;
493 }
494
495 /* stale anchor is properly locked; we can now use abortUpdating() if needed */
496
497 if (!openKeyless(update.fresh)) {
498 debugs(54, 5, "cannot open freshchainless entry " << update.stale.fileNo <<
499 " for updating " << path);
500 abortUpdating(update);
501 return false;
502 }
503
504 Must(update.stale);
505 Must(update.fresh);
506 update.fresh.anchor->set(entry);
507 debugs(54, 5, "opened entry " << update.stale.fileNo << " for updating " << path <<
508 " using entry " << update.fresh.fileNo << " of " << entry);
509
510 return true;
511 }
512
513 /// finds an anchor that is currently not associated with any entry key and
514 /// locks it for writing so ensure exclusive access during updates
515 bool
openKeyless(Update::Edition & edition)516 Ipc::StoreMap::openKeyless(Update::Edition &edition)
517 {
518 return visitVictims([&](const sfileno name) {
519 Update::Edition temp;
520 temp.name = name;
521 temp.fileNo = fileNoByName(temp.name);
522 if ((temp.anchor = openForWritingAt(temp.fileNo))) {
523 debugs(54, 5, "created entry " << temp.fileNo <<
524 " for updating " << path);
525 Must(temp);
526 edition = temp;
527 return true;
528 }
529 return false;
530 });
531 }
532
533 void
closeForUpdating(Update & update)534 Ipc::StoreMap::closeForUpdating(Update &update)
535 {
536 Must(update.stale.anchor);
537 Must(update.fresh.anchor);
538 AssertFlagIsSet(update.stale.anchor->lock.updating);
539 Must(update.stale.splicingPoint >= 0);
540 Must(update.fresh.splicingPoint >= 0);
541
542 /* the stale prefix cannot overlap with the fresh one (a weak check) */
543 Must(update.stale.anchor->start != update.fresh.anchor->start);
544 Must(update.stale.anchor->start != update.fresh.splicingPoint);
545 Must(update.stale.splicingPoint != update.fresh.anchor->start);
546 Must(update.stale.splicingPoint != update.fresh.splicingPoint);
547
548 /* the relative order of most operations is significant here */
549
550 /* splice the fresh chain prefix with the stale chain suffix */
551 Slice &freshSplicingSlice = sliceAt(update.fresh.splicingPoint);
552 const SliceId suffixStart = sliceAt(update.stale.splicingPoint).next; // may be negative
553 // the fresh chain is either properly terminated or already spliced
554 if (freshSplicingSlice.next < 0)
555 freshSplicingSlice.next = suffixStart;
556 else
557 Must(freshSplicingSlice.next == suffixStart);
558 // either way, fresh chain uses the stale chain suffix now
559
560 // make the fresh anchor/chain readable for everybody
561 update.fresh.anchor->lock.switchExclusiveToShared();
562 // but the fresh anchor is still invisible to anybody but us
563
564 // This freeEntry() code duplicates the code below to minimize the time when
565 // the freeEntry() race condition (see the Race: comment below) might occur.
566 if (update.stale.anchor->waitingToBeFreed)
567 freeEntry(update.fresh.fileNo);
568
569 /* any external changes were applied to the stale anchor/chain until now */
570 relocate(update.stale.name, update.fresh.fileNo);
571 /* any external changes will apply to the fresh anchor/chain from now on */
572
573 // Race: If the stale entry was deleted by some kid during the assignment,
574 // then we propagate that event to the fresh anchor and chain. Since this
575 // update is not atomically combined with the assignment above, another kid
576 // might get a fresh entry just before we have a chance to free it. However,
577 // such deletion races are always possible even without updates.
578 if (update.stale.anchor->waitingToBeFreed)
579 freeEntry(update.fresh.fileNo);
580
581 /* free the stale chain prefix except for the shared suffix */
582 update.stale.anchor->splicingPoint = update.stale.splicingPoint;
583 freeEntry(update.stale.fileNo);
584
585 // make the stale anchor/chain reusable, reachable via its new location
586 relocate(update.fresh.name, update.stale.fileNo);
587
588 const Update updateSaved = update; // for post-close debugging below
589
590 /* unlock the stale anchor/chain */
591 update.stale.anchor->lock.unlockHeaders();
592 closeForReading(update.stale.fileNo);
593 update.stale = Update::Edition();
594
595 // finally, unlock the fresh entry
596 closeForReading(update.fresh.fileNo);
597 update.fresh = Update::Edition();
598
599 debugs(54, 5, "closed entry " << updateSaved.stale.fileNo << " of " << *updateSaved.entry <<
600 " named " << updateSaved.stale.name << " for updating " << path <<
601 " to fresh entry " << updateSaved.fresh.fileNo << " named " << updateSaved.fresh.name <<
602 " with [" << updateSaved.fresh.anchor->start << ',' << updateSaved.fresh.splicingPoint <<
603 "] prefix containing at least " << freshSplicingSlice.size << " bytes");
604 }
605
606 /// Visits entries until either
607 /// * the `visitor` returns true (indicating its satisfaction with the offer);
608 /// * we give up finding a suitable entry because it already took "too long"; or
609 /// * we have offered all entries.
610 bool
visitVictims(const NameFilter visitor)611 Ipc::StoreMap::visitVictims(const NameFilter visitor)
612 {
613 // Hopefully, we find a usable entry much sooner (TODO: use time?).
614 // The min() will protect us from division by zero inside the loop.
615 const int searchLimit = min(10000, entryLimit());
616 int tries = 0;
617 for (; tries < searchLimit; ++tries) {
618 const sfileno name = static_cast<sfileno>(++anchors->victim % entryLimit());
619 if (visitor(name))
620 return true;
621 }
622
623 debugs(54, 5, "no victims found in " << path << "; tried: " << tries);
624 return false;
625 }
626
627 bool
purgeOne()628 Ipc::StoreMap::purgeOne()
629 {
630 return visitVictims([&](const sfileno name) {
631 const sfileno fileno = fileNoByName(name);
632 Anchor &s = anchorAt(fileno);
633 if (s.lock.lockExclusive()) {
634 // the caller wants a free slice; empty anchor is not enough
635 if (!s.empty() && s.start >= 0) {
636 // this entry may be marked for deletion, and that is OK
637 freeChain(fileno, s, false);
638 debugs(54, 5, "purged entry " << fileno << " from " << path);
639 return true;
640 }
641 s.lock.unlockExclusive();
642 }
643 return false;
644 });
645 }
646
647 void
importSlice(const SliceId sliceId,const Slice & slice)648 Ipc::StoreMap::importSlice(const SliceId sliceId, const Slice &slice)
649 {
650 // Slices are imported into positions that should not be available via
651 // "get free slice" API. This is not something we can double check
652 // reliably because the anchor for the imported slice may not have been
653 // imported yet.
654 assert(validSlice(sliceId));
655 sliceAt(sliceId) = slice;
656 }
657
658 int
entryLimit() const659 Ipc::StoreMap::entryLimit() const
660 {
661 return min(sliceLimit(), static_cast<int>(SwapFilenMax+1));
662 }
663
664 int
entryCount() const665 Ipc::StoreMap::entryCount() const
666 {
667 return anchors->count;
668 }
669
670 int
sliceLimit() const671 Ipc::StoreMap::sliceLimit() const
672 {
673 return slices->capacity;
674 }
675
676 void
updateStats(ReadWriteLockStats & stats) const677 Ipc::StoreMap::updateStats(ReadWriteLockStats &stats) const
678 {
679 for (int i = 0; i < anchors->capacity; ++i)
680 anchorAt(i).lock.updateStats(stats);
681 }
682
683 bool
validEntry(const int pos) const684 Ipc::StoreMap::validEntry(const int pos) const
685 {
686 return 0 <= pos && pos < entryLimit();
687 }
688
689 bool
validSlice(const int pos) const690 Ipc::StoreMap::validSlice(const int pos) const
691 {
692 return 0 <= pos && pos < sliceLimit();
693 }
694
695 Ipc::StoreMap::Anchor&
anchorAt(const sfileno fileno)696 Ipc::StoreMap::anchorAt(const sfileno fileno)
697 {
698 assert(validEntry(fileno));
699 return anchors->items[fileno];
700 }
701
702 const Ipc::StoreMap::Anchor&
anchorAt(const sfileno fileno) const703 Ipc::StoreMap::anchorAt(const sfileno fileno) const
704 {
705 return const_cast<StoreMap&>(*this).anchorAt(fileno);
706 }
707
708 sfileno
nameByKey(const cache_key * const key) const709 Ipc::StoreMap::nameByKey(const cache_key *const key) const
710 {
711 assert(key);
712 const uint64_t *const k = reinterpret_cast<const uint64_t *>(key);
713 // TODO: use a better hash function
714 const int hash = (k[0] + k[1]) % entryLimit();
715 return hash;
716 }
717
718 sfileno
fileNoByName(const sfileno name) const719 Ipc::StoreMap::fileNoByName(const sfileno name) const
720 {
721 // fileNos->items are initialized to zero, which we treat as "name is fileno";
722 // a positive value means the entry anchor got moved to a new fileNo
723 if (const int item = fileNos->items[name])
724 return item-1;
725 return name;
726 }
727
728 /// map `name` to `fileNo`
729 void
relocate(const sfileno name,const sfileno fileno)730 Ipc::StoreMap::relocate(const sfileno name, const sfileno fileno)
731 {
732 // preserve special meaning for zero; see fileNoByName
733 fileNos->items[name] = fileno+1;
734 }
735
736 sfileno
fileNoByKey(const cache_key * const key) const737 Ipc::StoreMap::fileNoByKey(const cache_key *const key) const
738 {
739 const int name = nameByKey(key);
740 return fileNoByName(name);
741 }
742
743 Ipc::StoreMap::Anchor &
anchorByKey(const cache_key * const key)744 Ipc::StoreMap::anchorByKey(const cache_key *const key)
745 {
746 return anchorAt(fileNoByKey(key));
747 }
748
749 Ipc::StoreMap::Slice&
sliceAt(const SliceId sliceId)750 Ipc::StoreMap::sliceAt(const SliceId sliceId)
751 {
752 assert(validSlice(sliceId));
753 return slices->items[sliceId];
754 }
755
756 const Ipc::StoreMap::Slice&
sliceAt(const SliceId sliceId) const757 Ipc::StoreMap::sliceAt(const SliceId sliceId) const
758 {
759 return const_cast<StoreMap&>(*this).sliceAt(sliceId);
760 }
761
762 /* Ipc::StoreMapAnchor */
763
StoreMapAnchor()764 Ipc::StoreMapAnchor::StoreMapAnchor(): start(0), splicingPoint(-1)
765 {
766 // keep in sync with rewind()
767 }
768
769 void
setKey(const cache_key * const aKey)770 Ipc::StoreMapAnchor::setKey(const cache_key *const aKey)
771 {
772 memcpy(key, aKey, sizeof(key));
773 waitingToBeFreed = Store::Root().markedForDeletion(aKey);
774 }
775
776 bool
sameKey(const cache_key * const aKey) const777 Ipc::StoreMapAnchor::sameKey(const cache_key *const aKey) const
778 {
779 const uint64_t *const k = reinterpret_cast<const uint64_t *>(aKey);
780 return k[0] == key[0] && k[1] == key[1];
781 }
782
783 void
set(const StoreEntry & from,const cache_key * aKey)784 Ipc::StoreMapAnchor::set(const StoreEntry &from, const cache_key *aKey)
785 {
786 assert(writing() && !reading());
787 setKey(reinterpret_cast<const cache_key*>(aKey ? aKey : from.key));
788 basics.timestamp = from.timestamp;
789 basics.lastref = from.lastref;
790 basics.expires = from.expires;
791 basics.lastmod = from.lastModified();
792 basics.swap_file_sz = from.swap_file_sz;
793 basics.refcount = from.refcount;
794
795 // do not copy key bit if we are not using from.key
796 // TODO: Replace KEY_PRIVATE with a nil StoreEntry::key!
797 uint16_t cleanFlags = from.flags;
798 if (aKey)
799 EBIT_CLR(cleanFlags, KEY_PRIVATE);
800 basics.flags = cleanFlags;
801 }
802
803 void
exportInto(StoreEntry & into) const804 Ipc::StoreMapAnchor::exportInto(StoreEntry &into) const
805 {
806 assert(reading());
807 into.timestamp = basics.timestamp;
808 into.lastref = basics.lastref;
809 into.expires = basics.expires;
810 into.lastModified(basics.lastmod);
811 into.swap_file_sz = basics.swap_file_sz;
812 into.refcount = basics.refcount;
813 into.flags = basics.flags;
814 }
815
816 void
rewind()817 Ipc::StoreMapAnchor::rewind()
818 {
819 assert(writing());
820 start = 0;
821 splicingPoint = -1;
822 memset(&key, 0, sizeof(key));
823 basics.clear();
824 waitingToBeFreed = false;
825 writerHalted = false;
826 // but keep the lock
827 }
828
829 /* Ipc::StoreMapUpdate */
830
StoreMapUpdate(StoreEntry * anEntry)831 Ipc::StoreMapUpdate::StoreMapUpdate(StoreEntry *anEntry):
832 entry(anEntry)
833 {
834 entry->lock("Ipc::StoreMapUpdate1");
835 }
836
StoreMapUpdate(const StoreMapUpdate & other)837 Ipc::StoreMapUpdate::StoreMapUpdate(const StoreMapUpdate &other):
838 entry(other.entry),
839 stale(other.stale),
840 fresh(other.fresh)
841 {
842 entry->lock("Ipc::StoreMapUpdate2");
843 }
844
~StoreMapUpdate()845 Ipc::StoreMapUpdate::~StoreMapUpdate()
846 {
847 entry->unlock("Ipc::StoreMapUpdate");
848 }
849
850 /* Ipc::StoreMap::Owner */
851
Owner()852 Ipc::StoreMap::Owner::Owner():
853 fileNos(nullptr),
854 anchors(nullptr),
855 slices(nullptr)
856 {
857 }
858
~Owner()859 Ipc::StoreMap::Owner::~Owner()
860 {
861 delete fileNos;
862 delete anchors;
863 delete slices;
864 }
865
866 /* Ipc::StoreMapAnchors */
867
StoreMapAnchors(const int aCapacity)868 Ipc::StoreMapAnchors::StoreMapAnchors(const int aCapacity):
869 count(0),
870 victim(0),
871 capacity(aCapacity),
872 items(aCapacity)
873 {
874 }
875
876 size_t
sharedMemorySize() const877 Ipc::StoreMapAnchors::sharedMemorySize() const
878 {
879 return SharedMemorySize(capacity);
880 }
881
882 size_t
SharedMemorySize(const int capacity)883 Ipc::StoreMapAnchors::SharedMemorySize(const int capacity)
884 {
885 return sizeof(StoreMapAnchors) + capacity * sizeof(StoreMapAnchor);
886 }
887
888