Squid Web Cache master
Loading...
Searching...
No Matches
StoreMap.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 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 "base/IoManip.h"
13#include "ipc/StoreMap.h"
14#include "sbuf/SBuf.h"
15#include "SquidConfig.h"
16#include "StatCounters.h"
17#include "Store.h"
18#include "store/Controller.h"
19#include "store_key_md5.h"
20#include "tools.h"
21
22#include <chrono>
23
24static SBuf
26{
27 return Ipc::Mem::Segment::Name(path, "slices");
28}
29
30static SBuf
32{
33 return Ipc::Mem::Segment::Name(path, "anchors");
34}
35
36static SBuf
38{
39 return Ipc::Mem::Segment::Name(path, "filenos");
40}
41
43Ipc::StoreMap::Init(const SBuf &path, const int sliceLimit)
44{
45 assert(sliceLimit > 0); // we should not be created otherwise
46 const int anchorLimit = min(sliceLimit, static_cast<int>(SwapFilenMax));
47 Owner *owner = new Owner;
48 owner->fileNos = shm_new(FileNos)(StoreMapFileNosId(path).c_str(), anchorLimit);
49 owner->anchors = shm_new(Anchors)(StoreMapAnchorsId(path).c_str(), anchorLimit);
51 debugs(54, 5, "created " << path << " with " << anchorLimit << '+' << sliceLimit);
52 return owner;
53}
54
55Ipc::StoreMap::StoreMap(const SBuf &aPath): cleaner(nullptr), path(aPath),
56 fileNos(shm_old(FileNos)(StoreMapFileNosId(path).c_str())),
57 anchors(shm_old(Anchors)(StoreMapAnchorsId(path).c_str())),
58 slices(shm_old(Slices)(StoreMapSlicesId(path).c_str())),
59 hitValidation(true)
60{
61 debugs(54, 5, "attached " << path << " with " <<
62 fileNos->capacity << '+' <<
63 anchors->capacity << '+' << slices->capacity);
64 assert(entryLimit() > 0); // key-to-position mapping requires this
65 assert(entryLimit() <= sliceLimit()); // at least one slice per entry
66}
67
68int
69Ipc::StoreMap::compareVersions(const sfileno fileno, time_t newVersion) const
70{
71 const Anchor &inode = anchorAt(fileno);
72
73 // note: we do not lock, so comparison may be inaccurate
74
75 if (inode.empty())
76 return +2;
77
78 if (const time_t diff = newVersion - inode.basics.timestamp)
79 return diff < 0 ? -1 : +1;
80
81 return 0;
82}
83
84void
86{
87 Anchor &inode = anchorAt(fileno);
88
89 assert(inode.writing());
90
91 // we do not iterate slices because we were told to forget about
92 // them; the caller is responsible for freeing them (most likely
93 // our slice list is incomplete or has holes)
94
95 inode.rewind();
96
97 inode.lock.unlockExclusive();
98 --anchors->count;
99
100 debugs(54, 8, "closed entry " << fileno << " for writing " << path);
101}
102
105{
106 debugs(54, 5, "opening/creating entry with key " << storeKeyText(key)
107 << " for reading " << path);
108
109 // start with reading so that we do not overwrite an existing unlocked entry
110 auto idx = fileNoByKey(key);
111 if (const auto anchor = openForReadingAt(idx, key)) {
112 fileno = idx;
113 return anchor;
114 }
115
116 // the competing openOrCreateForReading() workers race to create a new entry
117 idx = fileNoByKey(key);
118 if (auto anchor = openForWritingAt(idx)) {
119 anchor->setKey(key);
120 anchor->lock.switchExclusiveToShared();
121 // race ended
122 assert(anchor->complete());
123 fileno = idx;
124 debugs(54, 5, "switched entry " << fileno << " from writing to reading " << path);
125 return anchor;
126 }
127
128 // we lost the above race; see if the winner-created entry is now readable
129 // TODO: Do some useful housekeeping work here to give the winner more time.
130 idx = fileNoByKey(key);
131 if (const auto anchor = openForReadingAt(idx, key)) {
132 fileno = idx;
133 return anchor;
134 }
135
136 // slow entry creator or some other problem
137 return nullptr;
138}
139
142{
143 debugs(54, 5, "opening entry with key " << storeKeyText(key)
144 << " for writing " << path);
145 const int idx = fileNoByKey(key);
146
147 if (Anchor *anchor = openForWritingAt(idx)) {
148 fileno = idx;
149 return anchor;
150 }
151
152 return nullptr;
153}
154
156Ipc::StoreMap::openForWritingAt(const sfileno fileno, bool overwriteExisting)
157{
158 Anchor &s = anchorAt(fileno);
159 ReadWriteLock &lock = s.lock;
160
161 if (lock.lockExclusive()) {
162 assert(s.writing() && !s.reading());
163
164 // bail if we cannot empty this position
165 if (!s.waitingToBeFreed && !s.empty() && !overwriteExisting) {
166 lock.unlockExclusive();
167 debugs(54, 5, "cannot open existing entry " << fileno <<
168 " for writing " << path);
169 return nullptr;
170 }
171
172 // free if the entry was used, keeping the entry locked
173 if (s.waitingToBeFreed || !s.empty())
174 freeChain(fileno, s, true);
175
176 assert(s.empty());
177 s.start = -1; // we have not allocated any slices yet
178 s.splicingPoint = -1;
179 ++anchors->count;
180
181 //s.setKey(key); // XXX: the caller should do that
182 debugs(54, 5, "opened entry " << fileno << " for writing " << path);
183 return &s; // and keep the entry locked
184 }
185
186 debugs(54, 5, "cannot open busy entry " << fileno <<
187 " for writing " << path);
188 return nullptr;
189}
190
191void
193{
194 Anchor &s = anchorAt(fileno);
195 assert(s.writing());
197 debugs(54, 5, "restricted entry " << fileno << " to appending " << path);
198}
199
200void
202{
203 Anchor &s = anchorAt(fileno);
204 assert(s.writing());
205 // TODO: assert(!s.empty()); // i.e., unlocked s becomes s.complete()
207 debugs(54, 5, "closed entry " << fileno << " for writing " << path);
208 // cannot assert completeness here because we have no lock
209}
210
211void
213{
214 debugs(54, 5, "switching entry " << fileno << " from writing to reading " << path);
215 Anchor &s = anchorAt(fileno);
216 assert(s.writing());
218 assert(s.complete());
219}
220
222Ipc::StoreMap::writeableSlice(const AnchorId anchorId, const SliceId sliceId)
223{
224 assert(anchorAt(anchorId).writing());
225 assert(validSlice(sliceId));
226 return sliceAt(sliceId);
227}
228
230Ipc::StoreMap::readableSlice(const AnchorId anchorId, const SliceId sliceId) const
231{
232 assert(anchorAt(anchorId).reading());
233 assert(validSlice(sliceId));
234 return sliceAt(sliceId);
235}
236
239{
240 assert(anchorAt(anchorId).writing());
241 return anchorAt(anchorId);
242}
243
246{
247 assert(anchorAt(anchorId).reading());
248 return anchorAt(anchorId);
249}
250
251void
253{
254 debugs(54, 5, "aborting entry " << fileno << " for writing " << path);
255 Anchor &s = anchorAt(fileno);
256 assert(s.writing());
258 freeChain(fileno, s, false);
259 debugs(54, 5, "closed idle entry " << fileno << " for writing " << path);
260 } else {
261 s.waitingToBeFreed = true;
262 s.writerHalted = true;
264 debugs(54, 5, "closed busy entry " << fileno << " for writing " << path);
265 }
266}
267
268void
270{
271 const sfileno fileno = update.stale.fileNo;
272 debugs(54, 5, "aborting entry " << fileno << " for updating " << path);
273 if (update.stale) {
275 update.stale.anchor->lock.unlockHeaders();
276 closeForReading(update.stale.fileNo);
277 update.stale = Update::Edition();
278 }
279 if (update.fresh) {
280 abortWriting(update.fresh.fileNo);
281 update.fresh = Update::Edition();
282 }
283 debugs(54, 5, "aborted entry " << fileno << " for updating " << path);
284}
285
288{
289 const Anchor &s = anchorAt(fileno);
290 if (s.reading())
291 return &s; // immediate access by lock holder so no locking
292 assert(s.writing()); // must be locked for reading or writing
293 return nullptr;
294}
295
298{
299 const Anchor &s = anchorAt(fileno);
300 if (s.writing())
301 return &s; // immediate access by lock holder so no locking
302 assert(s.reading()); // must be locked for reading or writing
303 return nullptr;
304}
305
308{
309 return anchorAt(fileno);
310}
311
312bool
314{
315 debugs(54, 5, "marking entry " << fileno << " to be freed in " << path);
316
317 Anchor &s = anchorAt(fileno);
318
319 if (s.lock.lockExclusive()) {
320 const bool result = !s.waitingToBeFreed && !s.empty();
321 freeChain(fileno, s, false);
322 return result;
323 }
324
325 uint8_t expected = false;
326 // mark to free the locked entry later (if not already marked)
327 return s.waitingToBeFreed.compare_exchange_strong(expected, true);
328}
329
330void
332{
333 debugs(54, 5, "marking entry with key " << storeKeyText(key)
334 << " to be freed in " << path);
335
336 const int idx = fileNoByKey(key);
337 Anchor &s = anchorAt(idx);
338 if (s.lock.lockExclusive()) {
339 if (s.sameKey(key))
340 freeChain(idx, s, true);
342 } else if (s.lock.lockShared()) {
343 if (s.sameKey(key))
344 s.waitingToBeFreed = true; // mark to free it later
345 s.lock.unlockShared();
346 } else {
347 // we cannot be sure that the entry we found is ours because we do not
348 // have a lock on it, but we still check to minimize false deletions
349 if (s.sameKey(key))
350 s.waitingToBeFreed = true; // mark to free it later
351 }
352}
353
354bool
356{
357 const int idx = fileNoByKey(key);
358 const Anchor &s = anchorAt(idx);
359 return s.sameKey(key) ? bool(s.waitingToBeFreed) : false;
360}
361
362bool
364{
365 sfileno index;
366 if (openForReading(reinterpret_cast<const cache_key*>(key), index)) {
367 closeForReading(index);
368 return true;
369 }
370 return false;
371}
372
374void
375Ipc::StoreMap::freeChain(const sfileno fileno, Anchor &inode, const bool keepLocked)
376{
377 debugs(54, 7, "freeing entry " << fileno <<
378 " in " << path);
379 if (!inode.empty())
380 freeChainAt(inode.start, inode.splicingPoint);
381 inode.rewind();
382
383 if (!keepLocked)
384 inode.lock.unlockExclusive();
385 --anchors->count;
386 debugs(54, 5, "freed entry " << fileno << " in " << path);
387}
388
390void
391Ipc::StoreMap::freeChainAt(SliceId sliceId, const SliceId splicingPoint)
392{
393 static uint64_t ChainId = 0; // to pair freeing/freed calls in debugs()
394 const uint64_t chainId = ++ChainId;
395 debugs(54, 7, "freeing chain #" << chainId << " starting at " << sliceId << " in " << path);
396 while (sliceId >= 0) {
397 Slice &slice = sliceAt(sliceId);
398 const SliceId nextId = slice.next;
399 slice.clear();
400 if (cleaner)
401 cleaner->noteFreeMapSlice(sliceId); // might change slice state
402 if (sliceId == splicingPoint) {
403 debugs(54, 5, "preserving chain #" << chainId << " in " << path <<
404 " suffix after slice " << splicingPoint);
405 break; // do not free the rest of the chain
406 }
407 sliceId = nextId;
408 }
409 debugs(54, 7, "freed chain #" << chainId << " in " << path);
410}
411
412void
414{
415 // TODO: Move freeSlots here, along with reserveSlotForWriting() logic.
416 assert(validSlice(sliceId));
417 sliceAt(sliceId).clear();
418}
419
421Ipc::StoreMap::sliceContaining(const sfileno fileno, const uint64_t bytesNeeded) const
422{
423 const Anchor &anchor = anchorAt(fileno);
424 Must(anchor.reading());
425 uint64_t bytesSeen = 0;
426 SliceId lastSlice = anchor.start;
427 while (lastSlice >= 0) {
428 const Slice &slice = sliceAt(lastSlice);
429 bytesSeen += slice.size;
430 if (bytesSeen >= bytesNeeded)
431 break;
432 lastSlice = slice.next;
433 }
434 debugs(54, 7, "entry " << fileno << " has " << bytesNeeded << '/' << bytesSeen <<
435 " bytes at slice " << lastSlice << " in " << path);
436 return lastSlice; // may be negative
437}
438
441{
442 debugs(54, 5, "opening entry with key " << storeKeyText(key)
443 << " for reading " << path);
444 const int idx = fileNoByKey(key);
445 if (const auto anchor = openForReadingAt(idx, key)) {
446 fileno = idx;
447 return anchor; // locked for reading
448 }
449 return nullptr;
450}
451
453Ipc::StoreMap::openForReadingAt(const sfileno fileno, const cache_key *const key)
454{
455 debugs(54, 5, "opening entry " << fileno << " for reading " << path);
456 Anchor &s = anchorAt(fileno);
457
458 if (!s.lock.lockShared()) {
459 debugs(54, 5, "cannot open busy entry " << fileno <<
460 " for reading " << path);
461 return nullptr;
462 }
463
464 if (s.empty()) {
465 s.lock.unlockShared();
466 debugs(54, 7, "cannot open empty entry " << fileno <<
467 " for reading " << path);
468 return nullptr;
469 }
470
471 if (s.waitingToBeFreed) {
472 s.lock.unlockShared();
473 debugs(54, 7, "cannot open marked entry " << fileno <<
474 " for reading " << path);
475 return nullptr;
476 }
477
478 if (!s.sameKey(key)) {
479 s.lock.unlockShared();
480 debugs(54, 5, "cannot open wrong-key entry " << fileno <<
481 " for reading " << path);
482 return nullptr;
483 }
484
485 if (Config.paranoid_hit_validation.count() && hitValidation && !validateHit(fileno)) {
486 s.lock.unlockShared();
487 debugs(54, 5, "cannot open corrupted entry " << fileno <<
488 " for reading " << path);
489 return nullptr;
490 }
491
492 debugs(54, 5, "opened entry " << fileno << " for reading " << path);
493 return &s;
494}
495
496void
498{
499 Anchor &s = anchorAt(fileno);
500 assert(s.reading());
501 s.lock.unlockShared();
502 debugs(54, 5, "closed entry " << fileno << " for reading " << path);
503}
504
505void
507{
508 auto &s = anchorAt(fileno);
509 assert(s.reading());
510
511 if (!s.lock.unlockSharedAndSwitchToExclusive()) {
512 debugs(54, 5, "closed entry " << fileno << " for reading " << path);
513 return;
514 }
515
516 assert(s.writing());
517 assert(!s.reading());
518 freeChain(fileno, s, false);
519 debugs(54, 5, "closed idle entry " << fileno << " for reading " << path);
520}
521
522bool
524{
525 Must(update.entry);
526 const StoreEntry &entry = *update.entry;
527 const cache_key *const key = reinterpret_cast<const cache_key*>(entry.key);
528 update.stale.name = nameByKey(key);
529
530 if (!validEntry(fileNoHint)) {
531 debugs(54, 5, "opening entry with key " << storeKeyText(key) <<
532 " for updating " << path);
533 update.stale.fileNo = fileNoByName(update.stale.name);
534 } else {
535 update.stale.fileNo = fileNoHint;
536 }
537
538 debugs(54, 5, "opening entry " << update.stale.fileNo << " of " << entry << " for updating " << path);
539
540 // Unreadable entries cannot (e.g., empty and otherwise problematic entries)
541 // or should not (e.g., entries still forming their metadata) be updated.
542 if (!openForReadingAt(update.stale.fileNo, key)) {
543 debugs(54, 5, "cannot open unreadable entry " << update.stale.fileNo << " for updating " << path);
544 return false;
545 }
546
547 update.stale.anchor = &anchorAt(update.stale.fileNo);
548 if (update.stale.anchor->writing()) {
549 // TODO: Support updating appending entries.
550 // For example, MemStore::updateHeaders() would not know how
551 // many old prefix body bytes to copy to the new prefix if the last old
552 // prefix slice has not been formed yet (i.e., still gets more bytes).
553 debugs(54, 5, "cannot open appending entry " << update.stale.fileNo <<
554 " for updating " << path);
555 closeForReading(update.stale.fileNo);
556 return false;
557 }
558
559 if (!update.stale.anchor->lock.lockHeaders()) {
560 debugs(54, 5, "cannot open updating entry " << update.stale.fileNo <<
561 " for updating " << path);
562 closeForReading(update.stale.fileNo);
563 return false;
564 }
565
566 /* stale anchor is properly locked; we can now use abortUpdating() if needed */
567
568 if (!openKeyless(update.fresh)) {
569 debugs(54, 5, "cannot open freshchainless entry " << update.stale.fileNo <<
570 " for updating " << path);
571 abortUpdating(update);
572 return false;
573 }
574
575 Must(update.stale);
576 Must(update.fresh);
577 update.fresh.anchor->set(entry);
578 debugs(54, 5, "opened entry " << update.stale.fileNo << " for updating " << path <<
579 " using entry " << update.fresh.fileNo << " of " << entry);
580
581 return true;
582}
583
586bool
588{
589 return visitVictims([&](const sfileno name) {
590 Update::Edition temp;
591 temp.name = name;
592 temp.fileNo = fileNoByName(temp.name);
593 if ((temp.anchor = openForWritingAt(temp.fileNo))) {
594 debugs(54, 5, "created entry " << temp.fileNo <<
595 " for updating " << path);
596 Must(temp);
597 edition = temp;
598 return true;
599 }
600 return false;
601 });
602}
603
604void
606{
607 Must(update.stale.anchor);
608 Must(update.fresh.anchor);
610 Must(update.stale.splicingPoint >= 0);
611 Must(update.fresh.splicingPoint >= 0);
612
613 /* the stale prefix cannot overlap with the fresh one (a weak check) */
614 Must(update.stale.anchor->start != update.fresh.anchor->start);
615 Must(update.stale.anchor->start != update.fresh.splicingPoint);
616 Must(update.stale.splicingPoint != update.fresh.anchor->start);
617 Must(update.stale.splicingPoint != update.fresh.splicingPoint);
618
619 /* the relative order of most operations is significant here */
620
621 /* splice the fresh chain prefix with the stale chain suffix */
622 Slice &freshSplicingSlice = sliceAt(update.fresh.splicingPoint);
623 const SliceId suffixStart = sliceAt(update.stale.splicingPoint).next; // may be negative
624 // the fresh chain is either properly terminated or already spliced
625 if (freshSplicingSlice.next < 0)
626 freshSplicingSlice.next = suffixStart;
627 else
628 Must(freshSplicingSlice.next == suffixStart);
629 // either way, fresh chain uses the stale chain suffix now
630
631 // make the fresh anchor/chain readable for everybody
633 // but the fresh anchor is still invisible to anybody but us
634
635 // This freeEntry() code duplicates the code below to minimize the time when
636 // the freeEntry() race condition (see the Race: comment below) might occur.
637 if (update.stale.anchor->waitingToBeFreed)
638 freeEntry(update.fresh.fileNo);
639
640 /* any external changes were applied to the stale anchor/chain until now */
641 relocate(update.stale.name, update.fresh.fileNo);
642 /* any external changes will apply to the fresh anchor/chain from now on */
643
644 // Race: If the stale entry was deleted by some kid during the assignment,
645 // then we propagate that event to the fresh anchor and chain. Since this
646 // update is not atomically combined with the assignment above, another kid
647 // might get a fresh entry just before we have a chance to free it. However,
648 // such deletion races are always possible even without updates.
649 if (update.stale.anchor->waitingToBeFreed)
650 freeEntry(update.fresh.fileNo);
651
652 /* free the stale chain prefix except for the shared suffix */
654 freeEntry(update.stale.fileNo);
655
656 // Make the stale anchor/chain reusable, reachable via update.fresh.name. If
657 // update.entry->swap_filen is still update.stale.fileNo, and the entry is
658 // using store, then the entry must have a lock on update.stale.fileNo,
659 // preventing its premature reuse by others.
660 relocate(update.fresh.name, update.stale.fileNo);
661
662 const Update updateSaved = update; // for post-close debugging below
663
664 /* unlock the stale anchor/chain */
665 update.stale.anchor->lock.unlockHeaders();
666 closeForReading(update.stale.fileNo);
667 update.stale = Update::Edition();
668
669 // finally, unlock the fresh entry
670 closeForReading(update.fresh.fileNo);
671 update.fresh = Update::Edition();
672
673 debugs(54, 5, "closed entry " << updateSaved.stale.fileNo << " of " << *updateSaved.entry <<
674 " named " << updateSaved.stale.name << " for updating " << path <<
675 " to fresh entry " << updateSaved.fresh.fileNo << " named " << updateSaved.fresh.name <<
676 " with [" << updateSaved.fresh.anchor->start << ',' << updateSaved.fresh.splicingPoint <<
677 "] prefix containing at least " << freshSplicingSlice.size << " bytes");
678}
679
684bool
686{
687 // Hopefully, we find a usable entry much sooner (TODO: use time?).
688 // The min() will protect us from division by zero inside the loop.
689 const int searchLimit = min(10000, entryLimit());
690 int tries = 0;
691 for (; tries < searchLimit; ++tries) {
692 const sfileno name = static_cast<sfileno>(++anchors->victim % entryLimit());
693 if (visitor(name))
694 return true;
695 }
696
697 debugs(54, 5, "no victims found in " << path << "; tried: " << tries);
698 return false;
699}
700
701bool
703{
704 return visitVictims([&](const sfileno name) {
705 const sfileno fileno = fileNoByName(name);
706 Anchor &s = anchorAt(fileno);
707 if (s.lock.lockExclusive()) {
708 // the caller wants a free slice; empty anchor is not enough
709 if (!s.empty() && s.start >= 0) {
710 // this entry may be marked for deletion, and that is OK
711 freeChain(fileno, s, false);
712 debugs(54, 5, "purged entry " << fileno << " from " << path);
713 return true;
714 }
716 }
717 return false;
718 });
719}
720
721void
722Ipc::StoreMap::importSlice(const SliceId sliceId, const Slice &slice)
723{
724 // Slices are imported into positions that should not be available via
725 // "get free slice" API. This is not something we can double check
726 // reliably because the anchor for the imported slice may not have been
727 // imported yet.
728 assert(validSlice(sliceId));
729 sliceAt(sliceId) = slice;
730}
731
732int
734{
735 return min(sliceLimit(), static_cast<int>(SwapFilenMax+1));
736}
737
738int
740{
741 return anchors->count;
742}
743
744int
746{
747 return slices->capacity;
748}
749
750void
752{
753 for (int i = 0; i < anchors->capacity; ++i)
754 anchorAt(i).lock.updateStats(stats);
755}
756
757bool
758Ipc::StoreMap::validEntry(const int pos) const
759{
760 return 0 <= pos && pos < entryLimit();
761}
762
763bool
764Ipc::StoreMap::validSlice(const int pos) const
765{
766 return 0 <= pos && pos < sliceLimit();
767}
768
773{
774public:
775 typedef std::chrono::high_resolution_clock Clock;
776
777 explicit ConservativeTimer(const Clock::duration max):
778 startTime(Clock::now()),
779 lastTime(startTime),
780 maxTime(startTime + max) {}
781
783 bool expired() {
784 const auto currentTime = Clock::now();
785 if (currentTime < lastTime) // time went backwards
786 return true;
787 lastTime = currentTime;
788 return lastTime > maxTime;
789 }
790
791private:
793 Clock::time_point startTime;
795 Clock::time_point lastTime;
797 const Clock::time_point maxTime;
798};
799
800bool
802{
804 const auto timeIsLimited = Config.paranoid_hit_validation < std::chrono::hours(24);
805
806 const auto &anchor = anchorAt(fileno);
807
809
810 if (!anchor.basics.swap_file_sz) {
812 return true; // presume valid; cannot validate w/o known swap_file_sz
813 }
814
815 if (!anchor.lock.lockHeaders()) {
817 return true; // presume valid; cannot validate changing entry
818 }
819
820 const uint64_t expectedByteCount = anchor.basics.swap_file_sz;
821
822 size_t actualSliceCount = 0;
823 uint64_t actualByteCount = 0;
824 SliceId lastSeenSlice = anchor.start;
825 while (lastSeenSlice >= 0) {
826 ++actualSliceCount;
827 if (!validSlice(lastSeenSlice))
828 break;
829 const auto &slice = sliceAt(lastSeenSlice);
830 actualByteCount += slice.size;
831 if (actualByteCount > expectedByteCount)
832 break;
833 lastSeenSlice = slice.next;
834 if (timeIsLimited && timer.expired()) {
835 anchor.lock.unlockHeaders();
837 return true;
838 }
839 }
840
841 anchor.lock.unlockHeaders();
842
843 if (actualByteCount == expectedByteCount && lastSeenSlice < 0)
844 return true;
845
847
848 debugs(54, DBG_IMPORTANT, "ERROR: Squid BUG: purging corrupted cache entry " << fileno <<
849 " from " << path <<
850 " expected swap_file_sz=" << expectedByteCount <<
851 " actual swap_file_sz=" << actualByteCount <<
852 " actual slices=" << actualSliceCount <<
853 " last slice seen=" << lastSeenSlice << "\n" <<
854 " key=" << storeKeyText(reinterpret_cast<const cache_key*>(anchor.key)) << "\n" <<
855 " tmestmp=" << anchor.basics.timestamp << "\n" <<
856 " lastref=" << anchor.basics.lastref << "\n" <<
857 " expires=" << anchor.basics.expires << "\n" <<
858 " lastmod=" << anchor.basics.lastmod << "\n" <<
859 " refcount=" << anchor.basics.refcount << "\n" <<
860 " flags=0x" << asHex(anchor.basics.flags) << "\n" <<
861 " start=" << anchor.start << "\n" <<
862 " splicingPoint=" << anchor.splicingPoint << "\n" <<
863 " lock=" << anchor.lock << "\n" <<
864 " waitingToBeFreed=" << (anchor.waitingToBeFreed ? 1 : 0) << "\n"
865 );
866 freeEntry(fileno);
867 return false;
868}
869
872{
873 assert(validEntry(fileno));
874 return anchors->items[fileno];
875}
876
879{
880 return const_cast<StoreMap&>(*this).anchorAt(fileno);
881}
882
884Ipc::StoreMap::nameByKey(const cache_key *const key) const
885{
886 assert(key);
887 const uint64_t *const k = reinterpret_cast<const uint64_t *>(key);
888 // TODO: use a better hash function
889 const int hash = (k[0] + k[1]) % entryLimit();
890 return hash;
891}
892
895{
896 // fileNos->items are initialized to zero, which we treat as "name is fileno";
897 // a positive value means the entry anchor got moved to a new fileNo
898 if (const int item = fileNos->items[name])
899 return item-1;
900 return name;
901}
902
904void
905Ipc::StoreMap::relocate(const sfileno name, const sfileno fileno)
906{
907 // preserve special meaning for zero; see fileNoByName
908 fileNos->items[name] = fileno+1;
909}
910
913{
914 const int name = nameByKey(key);
915 return fileNoByName(name);
916}
917
920{
921 return anchorAt(fileNoByKey(key));
922}
923
926{
927 assert(validSlice(sliceId));
928 return slices->items[sliceId];
929}
930
932Ipc::StoreMap::sliceAt(const SliceId sliceId) const
933{
934 return const_cast<StoreMap&>(*this).sliceAt(sliceId);
935}
936
937/* Ipc::StoreMapAnchor */
938
939Ipc::StoreMapAnchor::StoreMapAnchor(): start(0), splicingPoint(-1)
940{
941 // keep in sync with rewind()
942}
943
944void
946{
947 memcpy(key, aKey, sizeof(key));
948 waitingToBeFreed = Store::Root().markedForDeletion(aKey);
949}
950
951bool
953{
954 const uint64_t *const k = reinterpret_cast<const uint64_t *>(aKey);
955 return k[0] == key[0] && k[1] == key[1];
956}
957
958void
960{
961 assert(writing() && !reading());
962 setKey(reinterpret_cast<const cache_key*>(aKey ? aKey : from.key));
963 basics.timestamp = from.timestamp;
964 basics.lastref = from.lastref;
965 basics.expires = from.expires;
966 basics.lastmod = from.lastModified();
967 basics.swap_file_sz = from.swap_file_sz;
968 basics.refcount = from.refcount;
969
970 // do not copy key bit if we are not using from.key
971 // TODO: Replace KEY_PRIVATE with a nil StoreEntry::key!
972 uint16_t cleanFlags = from.flags;
973 if (aKey)
974 EBIT_CLR(cleanFlags, KEY_PRIVATE);
975 basics.flags = cleanFlags;
976}
977
978void
980{
981 assert(reading());
982 into.timestamp = basics.timestamp;
983 into.lastref = basics.lastref;
984 into.expires = basics.expires;
985 into.lastModified(basics.lastmod);
986 into.swap_file_sz = basics.swap_file_sz;
987 into.refcount = basics.refcount;
988
989 // Some basics.flags are not meaningful and should not be overwritten here.
990 // ENTRY_REQUIRES_COLLAPSING is one of them. TODO: check other flags.
991 const bool collapsingRequired = into.hittingRequiresCollapsing();
992 into.flags = basics.flags;
993 // Avoid into.setCollapsingRequirement() here: We only restore the bit we
994 // just cleared in the assignment above, while that method debugging will
995 // falsely imply that the collapsing requirements have changed.
996 if (collapsingRequired)
998 else
1000}
1001
1002void
1004{
1005 assert(writing());
1006 start = 0;
1007 splicingPoint = -1;
1008 memset(&key, 0, sizeof(key));
1009 basics.clear();
1010 waitingToBeFreed = false;
1011 writerHalted = false;
1012 // but keep the lock
1013}
1014
1015/* Ipc::StoreMapUpdate */
1016
1018 entry(anEntry)
1019{
1020 entry->lock("Ipc::StoreMapUpdate1");
1021}
1022
1024 entry(other.entry),
1025 stale(other.stale),
1026 fresh(other.fresh)
1027{
1028 entry->lock("Ipc::StoreMapUpdate2");
1029}
1030
1032{
1033 entry->unlock("Ipc::StoreMapUpdate");
1034}
1035
1036/* Ipc::StoreMap::Owner */
1037
1039 fileNos(nullptr),
1040 anchors(nullptr),
1041 slices(nullptr)
1042{
1043}
1044
1046{
1047 delete fileNos;
1048 delete anchors;
1049 delete slices;
1050}
1051
1052/* Ipc::StoreMapAnchors */
1053
1055 count(0),
1056 victim(0),
1057 capacity(aCapacity),
1058 items(aCapacity)
1059{
1060}
1061
1062size_t
1064{
1065 return SharedMemorySize(capacity);
1066}
1067
1068size_t
1070{
1071 return sizeof(StoreMapAnchors) + capacity * sizeof(StoreMapAnchor);
1072}
1073
AsHex< Integer > asHex(const Integer n)
a helper to ease AsHex object creation
Definition IoManip.h:169
#define shm_new(Class)
Definition Pointer.h:200
#define shm_old(Class)
Definition Pointer.h:201
class SquidConfig Config
StatCounters statCounter
static SBuf StoreMapFileNosId(const SBuf &path)
Definition StoreMap.cc:37
static SBuf StoreMapSlicesId(const SBuf &path)
Definition StoreMap.cc:25
static SBuf StoreMapAnchorsId(const SBuf &path)
Definition StoreMap.cc:31
#define Must(condition)
#define assert(EX)
Definition assert.h:17
ConservativeTimer(const Clock::duration max)
Definition StoreMap.cc:777
bool expired()
whether the current time reached the provided maximum time
Definition StoreMap.cc:783
Clock::time_point lastTime
the time of the last expired() call, initially equals to startTime
Definition StoreMap.cc:795
const Clock::time_point maxTime
after going past this point in time, expired() becomes true
Definition StoreMap.cc:797
Clock::time_point startTime
the object creation time
Definition StoreMap.cc:793
std::chrono::high_resolution_clock Clock
Definition StoreMap.cc:775
static SBuf Name(const SBuf &prefix, const char *suffix)
concatenates parts of a name to form a complete name (or its prefix)
Definition Segment.cc:51
approximate stats of a set of ReadWriteLocks
void unlockHeaders()
undo successful lockHeaders()
bool lockHeaders()
lock for [readable] metadata update or return false
void unlockExclusive()
undo successful exclusiveLock()
bool lockExclusive()
lock for modification or return false
bool stopAppendingAndRestoreExclusive()
std::atomic_flag updating
a reader is updating metadata/headers
std::atomic< bool > appending
the writer has promised to only append
void unlockShared()
undo successful sharedLock()
bool lockShared()
lock for reading or return false
void startAppending()
writer keeps its lock but also allows reading
std::atomic< StoreMapSliceId > splicingPoint
Definition StoreMap.h:115
std::atomic< StoreMapSliceId > start
where the chain of StoreEntry slices begins [app]
Definition StoreMap.h:111
void rewind()
undo the effects of set(), setKey(), etc., but keep locks and state
Definition StoreMap.cc:1003
bool sameKey(const cache_key *const aKey) const
Definition StoreMap.cc:952
bool empty() const
Definition StoreMap.h:74
struct Ipc::StoreMapAnchor::Basics basics
bool complete() const
Definition StoreMap.h:77
std::atomic< uint8_t > writerHalted
whether StoreMap::abortWriting() was called for a read-locked entry
Definition StoreMap.h:83
bool writing() const
Definition StoreMap.h:76
void set(const StoreEntry &anEntry, const cache_key *aKey=nullptr)
store StoreEntry key and basics for an inode slot
Definition StoreMap.cc:959
bool reading() const
Definition StoreMap.h:75
void setKey(const cache_key *const aKey)
Definition StoreMap.cc:945
ReadWriteLock lock
protects slot data below
Definition StoreMap.h:80
std::atomic< uint8_t > waitingToBeFreed
Definition StoreMap.h:81
void exportInto(StoreEntry &) const
load StoreEntry basics that were previously stored with set()
Definition StoreMap.cc:979
StoreMapAnchors(const int aCapacity)
Definition StoreMap.cc:1054
size_t sharedMemorySize() const
Definition StoreMap.cc:1063
static size_t SharedMemorySize(const int anAnchorLimit)
Definition StoreMap.cc:1069
void clear()
restore default-constructed state
Definition StoreMap.h:46
std::atomic< StoreMapSliceId > next
ID of the next entry slice.
Definition StoreMap.h:49
std::atomic< Size > size
slice contents size
Definition StoreMap.h:48
During an update, the stored entry has two editions: stale and fresh.
Definition StoreMap.h:186
sfileno name
StoreEntry position in StoreMap::fileNos, for swapping Editions.
Definition StoreMap.h:195
sfileno fileNo
StoreMap::fileNos[name], for convenience/speed.
Definition StoreMap.h:194
StoreMapSliceId splicingPoint
the last slice in the chain still containing metadata/headers
Definition StoreMap.h:198
StoreMapAnchor * anchor
StoreMap::anchors[fileNo], for convenience/speed.
Definition StoreMap.h:193
Aggregates information required for updating entry metadata and headers.
Definition StoreMap.h:182
Edition fresh
new anchor and the updated chain prefix
Definition StoreMap.h:209
Edition stale
old anchor and chain
Definition StoreMap.h:208
StoreEntry * entry
the store entry being updated
Definition StoreMap.h:207
StoreMapUpdate(StoreEntry *anEntry)
Definition StoreMap.cc:1017
aggregates anchor and slice owners for Init() caller convenience
Definition StoreMap.h:233
Slices::Owner * slices
Definition StoreMap.h:239
Anchors::Owner * anchors
Definition StoreMap.h:238
FileNos::Owner * fileNos
Definition StoreMap.h:237
Anchor * openForWriting(const cache_key *const key, sfileno &fileno)
Definition StoreMap.cc:141
const Slice & readableSlice(const AnchorId anchorId, const SliceId sliceId) const
readable slice within an entry chain opened by openForReading()
Definition StoreMap.cc:230
const Anchor * openForReadingAt(const sfileno, const cache_key *const)
opens entry (identified by sfileno) for reading, increments read level
Definition StoreMap.cc:453
bool validateHit(const sfileno)
Definition StoreMap.cc:801
bool openForUpdating(Update &update, sfileno fileNoHint)
finds and locks the Update entry for an exclusive metadata update
Definition StoreMap.cc:523
Anchor * openForWritingAt(sfileno fileno, bool overwriteExisting=true)
Definition StoreMap.cc:156
bool visitVictims(const NameFilter filter)
Definition StoreMap.cc:685
bool markedForDeletion(const cache_key *const)
Definition StoreMap.cc:355
Anchor & writeableEntry(const AnchorId anchorId)
writeable anchor for the entry created by openForWriting()
Definition StoreMap.cc:238
sfileno AnchorId
Definition StoreMap.h:224
Mem::Pointer< StoreMapAnchors > anchors
entry inodes (starting blocks)
Definition StoreMap.h:366
const Anchor * peekAtReader(const sfileno fileno) const
Definition StoreMap.cc:287
bool validEntry(const int n) const
whether n is a valid slice coordinate
Definition StoreMap.cc:758
const Anchor & readableEntry(const AnchorId anchorId) const
readable anchor for the entry created by openForReading()
Definition StoreMap.cc:245
Anchor & anchorByKey(const cache_key *const key)
Definition StoreMap.cc:919
int entryCount() const
number of writeable and readable entries
Definition StoreMap.cc:739
static Owner * Init(const SBuf &path, const int slotLimit)
initialize shared memory
Definition StoreMap.cc:43
void relocate(const sfileno name, const sfileno fileno)
map name to fileNo
Definition StoreMap.cc:905
void freeChain(const sfileno fileno, Anchor &inode, const bool keepLock)
unconditionally frees an already locked chain of slots, unlocking if needed
Definition StoreMap.cc:375
Slice & sliceAt(const SliceId sliceId)
Definition StoreMap.cc:925
void closeForWriting(const sfileno fileno)
successfully finish creating or updating the entry at fileno pos
Definition StoreMap.cc:201
const Anchor * openOrCreateForReading(const cache_key *, sfileno &)
openForReading() but creates a new entry if there is no old one
Definition StoreMap.cc:104
void abortUpdating(Update &update)
undoes partial update, unlocks, and cleans up
Definition StoreMap.cc:269
SliceId sliceContaining(const sfileno fileno, const uint64_t nth) const
Definition StoreMap.cc:421
const Anchor * openForReading(const cache_key *const key, sfileno &fileno)
opens entry (identified by key) for reading, increments read level
Definition StoreMap.cc:440
void forgetWritingEntry(const sfileno fileno)
Definition StoreMap.cc:85
int compareVersions(const sfileno oldFileno, time_t newVersion) const
Definition StoreMap.cc:69
bool freeEntry(const sfileno)
Definition StoreMap.cc:313
const SBuf path
cache_dir path or similar cache name; for logging
Definition StoreMap.h:364
void closeForReading(const sfileno fileno)
closes open entry after reading, decrements read level
Definition StoreMap.cc:497
StoreMap(const SBuf &aPath)
Definition StoreMap.cc:55
void importSlice(const SliceId sliceId, const Slice &slice)
copies slice to its designated position
Definition StoreMap.cc:722
const Anchor * peekAtWriter(const sfileno fileno) const
Definition StoreMap.cc:297
void closeForReadingAndFreeIdle(const sfileno fileno)
same as closeForReading() but also frees the entry if it is unlocked
Definition StoreMap.cc:506
void abortWriting(const sfileno fileno)
stop writing the entry, freeing its slot for others to use if possible
Definition StoreMap.cc:252
bool validSlice(const int n) const
whether n is a valid slice coordinate
Definition StoreMap.cc:764
void startAppending(const sfileno fileno)
restrict opened for writing entry to appending operations; allow reads
Definition StoreMap.cc:192
sfileno nameByKey(const cache_key *const key) const
computes entry name (i.e., key hash) for a given entry key
Definition StoreMap.cc:884
void prepFreeSlice(const SliceId sliceId)
prepare a chain-unaffiliated slice for being added to an entry chain
Definition StoreMap.cc:413
Mem::Pointer< StoreMapSlices > slices
chained entry pieces positions
Definition StoreMap.h:367
void closeForUpdating(Update &update)
makes updated info available to others, unlocks, and cleans up
Definition StoreMap.cc:605
std::function< bool(const sfileno name)> NameFilter
Definition StoreMap.h:386
bool openKeyless(Update::Edition &edition)
Definition StoreMap.cc:587
bool purgeOne()
either finds and frees an entry with at least 1 slice or returns false
Definition StoreMap.cc:702
void updateStats(ReadWriteLockStats &stats) const
adds approximate current stats to the supplied ones
Definition StoreMap.cc:751
sfileno fileNoByKey(const cache_key *const key) const
computes map entry anchor position for a given entry key
Definition StoreMap.cc:912
bool hasReadableEntry(const cache_key *const)
whether the index contains a valid readable entry with the given key
Definition StoreMap.cc:363
StoreMapSliceId SliceId
Definition StoreMap.h:227
void freeEntryByKey(const cache_key *const key)
Definition StoreMap.cc:331
sfileno fileNoByName(const sfileno name) const
computes anchor position for a given entry name
Definition StoreMap.cc:894
Anchor & anchorAt(const sfileno fileno)
Definition StoreMap.cc:871
const Anchor & peekAtEntry(const sfileno fileno) const
Definition StoreMap.cc:307
Mem::Pointer< StoreMapFileNos > fileNos
entry inodes (starting blocks)
Definition StoreMap.h:365
Slice & writeableSlice(const AnchorId anchorId, const SliceId sliceId)
writeable slice within an entry chain created by openForWriting()
Definition StoreMap.cc:222
void switchWritingToReading(const sfileno fileno)
stop writing (or updating) the locked entry and start reading it
Definition StoreMap.cc:212
int sliceLimit() const
maximum number of slices possible
Definition StoreMap.cc:745
void freeChainAt(SliceId sliceId, const SliceId splicingPoint)
unconditionally frees an already locked chain of slots; no anchor maintenance
Definition StoreMap.cc:391
int entryLimit() const
maximum entryCount() possible
Definition StoreMap.cc:733
Definition SBuf.h:94
const char * c_str()
Definition SBuf.cc:516
std::chrono::nanoseconds paranoid_hit_validation
uint64_t refusalsDueToZeroSize
uint64_t refusalsDueToTimeLimit
struct StatCounters::@114 hitValidation
uint64_t attempts
uint64_t failures
uint64_t refusalsDueToLocking
uint16_t flags
Definition Store.h:231
time_t expires
Definition Store.h:225
void lastModified(const time_t when)
Definition Store.h:175
void lock(const char *context)
Definition store.cc:445
time_t timestamp
Definition Store.h:223
time_t lastref
Definition Store.h:224
uint64_t swap_file_sz
Definition Store.h:229
uint16_t refcount
Definition Store.h:230
bool hittingRequiresCollapsing() const
whether this entry can feed collapsed requests and only them
Definition Store.h:215
bool markedForDeletion(const cache_key *key) const
A const & max(A const &lhs, A const &rhs)
A const & min(A const &lhs, A const &rhs)
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define EBIT_CLR(flag, bit)
Definition defines.h:66
#define EBIT_SET(flag, bit)
Definition defines.h:65
@ ENTRY_REQUIRES_COLLAPSING
Definition enums.h:113
@ KEY_PRIVATE
Definition enums.h:97
void AssertFlagIsSet(std::atomic_flag &flag)
Controller & Root()
safely access controller singleton
@ SwapFilenMax
Definition forward.h:26
unsigned char cache_key
Store key.
Definition forward.h:29
signed_int32_t sfileno
Definition forward.h:22
const char * storeKeyText(const cache_key *key)
static hash_table * hash