From 1860fbac93c9da43094bf7c49ebcb7d22db60ed6 Mon Sep 17 00:00:00 2001 From: Alex Rousskov Date: Mon, 21 Apr 2014 12:09:06 -0600 Subject: [PATCH] Stop wasting 96 RAM bytes per slot for high-offset slots in large shared caches with more than 16777216 slots. Ipc::StoreMap was using the same structure for all db slots. However, slots at offsets exceeding SwapFilenMax (16777215) could not contain store entry anchors and the anchor part of the structure was wasting RAM for those slots. This change splits a single array of StoreMapSlots into two arrays, one storing StoreMapAnchors and one storing StoreMapSlices. The anchors array is shorter for caches with more than 16777216 slots. For example, a StoreMap for a 1TB shared cache with default 16KB slot sizes (67108864 slots) occupied about 6.5GB of RAM. After this change, the same information is stored in about 2.0GB because unused anchors are not stored. 32-bit environments were wasting 72 (instead of 96) bytes per high-offset slot. Also simplified Ipc::StoreMap API by removing its StoreMapWithExtras part. The added complexity caused bugs and was not worth saving a few extra lines of callers code. With the StoreMap storage array split in two, the extras may belong to each part (although the current code only adds extras to slices), further complicating the WithExtras part of the StoreMap API. These extras are now stored in dedicated shared memory segments (*_ex.shm). Added Ipc::Mem::Segment::Name() function to standardize segment name formation. TODO: Attempt to convert shm_new/old API to use SBuf instead of char* to simplify callers, most of which have to form Segment IDs by concatenating strings. --- src/MemStore.cc | 26 ++++-- src/MemStore.h | 8 +- src/Transients.cc | 34 +++++--- src/Transients.h | 9 +- src/fs/rock/RockSwapDir.cc | 7 +- src/fs/rock/RockSwapDir.h | 2 +- src/ipc/StoreMap.cc | 168 ++++++++++++++++++++++--------------- src/ipc/StoreMap.h | 156 +++++++++++++--------------------- src/ipc/mem/Segment.cc | 10 +++ src/ipc/mem/Segment.h | 5 ++ 10 files changed, 228 insertions(+), 197 deletions(-) diff --git a/src/MemStore.cc b/src/MemStore.cc index 6117e909f8..e56c071c11 100644 --- a/src/MemStore.cc +++ b/src/MemStore.cc @@ -18,9 +18,11 @@ #include "tools.h" /// shared memory segment path to use for MemStore maps -static const char *MapLabel = "cache_mem_map"; +static const SBuf MapLabel("cache_mem_map"); /// shared memory segment path to use for the free slices index static const char *SpaceLabel = "cache_mem_space"; +/// shared memory segment path to use for IDs of shared pages with slice data +static const char *ExtrasLabel = "cache_mem_ex"; // TODO: sync with Rock::SwapDir::*Path() // We store free slot IDs (i.e., "space") as Page objects so that we can use @@ -61,6 +63,7 @@ MemStore::init() } freeSlots = shm_old(Ipc::Mem::PageStack)(SpaceLabel); + extras = shm_old(Extras)(ExtrasLabel); Must(!map); map = new MemStoreMap(MapLabel); @@ -327,15 +330,16 @@ MemStore::copyFromShm(StoreEntry &e, const sfileno index, const Ipc::StoreMapAnc const size_t prefixSize = e.mem_obj->endOffset() - sliceOffset; assert(prefixSize <= wasSize); - const MemStoreMap::Extras &extras = map->extras(sid); - char *page = static_cast(PagePointer(extras.page)); + const MemStoreMapExtras::Item &extra = extras->items[sid]; + + char *page = static_cast(PagePointer(extra.page)); const StoreIOBuffer sliceBuf(wasSize - prefixSize, e.mem_obj->endOffset(), page + prefixSize); if (!copyFromShmSlice(e, sliceBuf, wasEof)) return false; debugs(20, 9, "entry " << index << " copied slice " << sid << - " from " << extras.page << " +" << prefixSize); + " from " << extra.page << '+' << prefixSize); } // else skip a [possibly incomplete] slice that we copied earlier @@ -522,7 +526,7 @@ MemStore::copyToShm(StoreEntry &e) if (anchor.start < 0) { // must allocate the very first slot for e Ipc::Mem::PageId page; anchor.start = reserveSapForWriting(page); // throws - map->extras(anchor.start).page = page; + extras->items[anchor.start].page = page; } lastWritingSlice = anchor.start; @@ -542,7 +546,7 @@ MemStore::copyToShm(StoreEntry &e) Ipc::Mem::PageId page; slice.next = lastWritingSlice = reserveSapForWriting(page); - map->extras(lastWritingSlice).page = page; + extras->items[lastWritingSlice].page = page; debugs(20, 7, "entry " << index << " new slice: " << lastWritingSlice); } @@ -559,7 +563,7 @@ MemStore::copyToShmSlice(StoreEntry &e, Ipc::StoreMapAnchor &anchor) Ipc::StoreMap::Slice &slice = map->writeableSlice(e.mem_obj->memCache.index, lastWritingSlice); - Ipc::Mem::PageId page = map->extras(lastWritingSlice).page; + Ipc::Mem::PageId page = extras->items[lastWritingSlice].page; assert(lastWritingSlice >= 0 && page); debugs(20, 7, "entry " << e << " slice " << lastWritingSlice << " has " << page); @@ -625,7 +629,7 @@ MemStore::reserveSapForWriting(Ipc::Mem::PageId &page) void MemStore::noteFreeMapSlice(const Ipc::StoreMapSliceId sliceId) { - Ipc::Mem::PageId &pageId = map->extras(sliceId).page; + Ipc::Mem::PageId &pageId = extras->items[sliceId].page; debugs(20, 9, "slice " << sliceId << " freed " << pageId); assert(pageId); Ipc::Mem::PageId slotId; @@ -762,7 +766,7 @@ class MemStoreRr: public Ipc::Mem::RegisteredRunner { public: /* RegisteredRunner API */ - MemStoreRr(): spaceOwner(NULL), mapOwner(NULL) {} + MemStoreRr(): spaceOwner(NULL), mapOwner(NULL), extrasOwner(NULL) {} virtual void finalizeConfig(); virtual void claimMemoryNeeds(); virtual void useConfig(); @@ -775,6 +779,7 @@ protected: private: Ipc::Mem::Owner *spaceOwner; ///< free slices Owner MemStoreMap::Owner *mapOwner; ///< primary map Owner + Ipc::Mem::Owner *extrasOwner; ///< PageIds Owner }; RunnerRegistrationEntry(MemStoreRr); @@ -832,10 +837,13 @@ MemStoreRr::create() entryLimit, 0); Must(!mapOwner); mapOwner = MemStoreMap::Init(MapLabel, entryLimit); + Must(!extrasOwner); + extrasOwner = shm_new(MemStoreMapExtras)(ExtrasLabel, entryLimit); } MemStoreRr::~MemStoreRr() { + delete extrasOwner; delete mapOwner; delete spaceOwner; } diff --git a/src/MemStore.h b/src/MemStore.h index e57e776537..5d33823081 100644 --- a/src/MemStore.h +++ b/src/MemStore.h @@ -7,10 +7,11 @@ #include "Store.h" // StoreEntry restoration info not already stored by Ipc::StoreMap -struct MemStoreMapExtras { +struct MemStoreMapExtraItem { Ipc::Mem::PageId page; ///< shared memory page with entry slice content }; -typedef Ipc::StoreMapWithExtras MemStoreMap; +typedef Ipc::StoreMapItems MemStoreMapExtras; +typedef Ipc::StoreMap MemStoreMap; /// Stores HTTP entities in RAM. Current implementation uses shared memory. /// Unlike a disk store (SwapDir), operations are synchronous (and fast). @@ -79,6 +80,9 @@ private: Ipc::Mem::Pointer freeSlots; ///< unused map slot IDs MemStoreMap *map; ///< index of mem-cached entries + typedef MemStoreMapExtras Extras; + Ipc::Mem::Pointer extras; ///< IDs of pages with slice data + /// the last allocate slice for writing a store entry (during copyToShm) sfileno lastWritingSlice; diff --git a/src/Transients.cc b/src/Transients.cc index a4f1dbd89f..97b2bd80ec 100644 --- a/src/Transients.cc +++ b/src/Transients.cc @@ -19,8 +19,10 @@ #include -/// shared memory segment path to use for Transients maps -static const char *MapLabel = "transients_map"; +/// shared memory segment path to use for Transients map +static const SBuf MapLabel("transients_map"); +/// shared memory segment path to use for Transients map extras +static const char *ExtrasLabel = "transients_ex"; Transients::Transients(): map(NULL), locals(NULL) { @@ -43,6 +45,8 @@ Transients::init() map = new TransientsMap(MapLabel); map->cleaner = this; + extras = shm_old(TransientsMapExtras)(ExtrasLabel); + locals = new Locals(entryLimit, 0); } @@ -177,14 +181,14 @@ Transients::get(const cache_key *key) StoreEntry * Transients::copyFromShm(const sfileno index) { - const TransientsMap::Extras &extras = map->extras(index); + const TransientsMapExtras::Item &extra = extras->items[index]; // create a brand new store entry and initialize it with stored info - StoreEntry *e = storeCreatePureEntry(extras.url, extras.url, - extras.reqFlags, extras.reqMethod); + StoreEntry *e = storeCreatePureEntry(extra.url, extra.url, + extra.reqFlags, extra.reqMethod); assert(e->mem_obj); - e->mem_obj->method = extras.reqMethod; + e->mem_obj->method = extra.reqMethod; e->mem_obj->xitTable.io = MemObject::ioReading; e->mem_obj->xitTable.index = index; @@ -271,18 +275,18 @@ Transients::copyToShm(const StoreEntry &e, const sfileno index, const RequestFlags &reqFlags, const HttpRequestMethod &reqMethod) { - TransientsMap::Extras &extras = map->extras(index); + TransientsMapExtras::Item &extra = extras->items[index]; const char *url = e.url(); const size_t urlLen = strlen(url); - Must(urlLen < sizeof(extras.url)); // we have space to store it all, plus 0 - strncpy(extras.url, url, sizeof(extras.url)); - extras.url[urlLen] = '\0'; + Must(urlLen < sizeof(extra.url)); // we have space to store it all, plus 0 + strncpy(extra.url, url, sizeof(extra.url)); + extra.url[urlLen] = '\0'; - extras.reqFlags = reqFlags; + extra.reqFlags = reqFlags; Must(reqMethod != Http::METHOD_OTHER); - extras.reqMethod = reqMethod.id(); + extra.reqMethod = reqMethod.id(); return true; } @@ -383,7 +387,7 @@ class TransientsRr: public Ipc::Mem::RegisteredRunner { public: /* RegisteredRunner API */ - TransientsRr(): mapOwner(NULL) {} + TransientsRr(): mapOwner(NULL), extrasOwner(NULL) {} virtual void useConfig(); virtual ~TransientsRr(); @@ -392,6 +396,7 @@ protected: private: TransientsMap::Owner *mapOwner; + Ipc::Mem::Owner *extrasOwner; }; RunnerRegistrationEntry(TransientsRr); @@ -415,9 +420,12 @@ TransientsRr::create() Must(!mapOwner); mapOwner = TransientsMap::Init(MapLabel, entryLimit); + Must(!extrasOwner); + extrasOwner = shm_new(TransientsMapExtras)(ExtrasLabel, entryLimit); } TransientsRr::~TransientsRr() { + delete extrasOwner; delete mapOwner; } diff --git a/src/Transients.h b/src/Transients.h index a0ebf2063e..8ae1f4465b 100644 --- a/src/Transients.h +++ b/src/Transients.h @@ -9,12 +9,13 @@ #include // StoreEntry restoration info not already stored by Ipc::StoreMap -struct TransientsMapExtras { +struct TransientsMapExtraItem { char url[MAX_URL+1]; ///< Request-URI; TODO: decrease MAX_URL by one RequestFlags reqFlags; ///< request flags Http::MethodType reqMethod; ///< request method; extensions are not supported }; -typedef Ipc::StoreMapWithExtras TransientsMap; +typedef Ipc::StoreMapItems TransientsMapExtras; +typedef Ipc::StoreMap TransientsMap; /// Keeps track of store entries being delivered to clients that arrived before /// those entries were [fully] cached. This shared table is necessary to sync @@ -79,6 +80,10 @@ private: /// shared packed info indexed by Store keys, for creating new StoreEntries TransientsMap *map; + /// shared packed info that standard StoreMap does not store for us + typedef TransientsMapExtras Extras; + Ipc::Mem::Pointer extras; + typedef std::vector Locals; /// local collapsed entries indexed by transient ID, for syncing old StoreEntries Locals *locals; diff --git a/src/fs/rock/RockSwapDir.cc b/src/fs/rock/RockSwapDir.cc index d3aee6cbaa..6e65ccff05 100644 --- a/src/fs/rock/RockSwapDir.cc +++ b/src/fs/rock/RockSwapDir.cc @@ -1007,13 +1007,10 @@ Rock::SwapDir::statfs(StoreEntry &e) const } -const char * +SBuf Rock::SwapDir::inodeMapPath() const { - static String inodesPath; - inodesPath = path; - inodesPath.append("_inodes"); - return inodesPath.termedBuf(); + return Ipc::Mem::Segment::Name(SBuf(path), "map"); } const char * diff --git a/src/fs/rock/RockSwapDir.h b/src/fs/rock/RockSwapDir.h index f9abde75fa..22f58493a5 100644 --- a/src/fs/rock/RockSwapDir.h +++ b/src/fs/rock/RockSwapDir.h @@ -42,7 +42,7 @@ public: virtual void parse(int index, char *path); // temporary path to the shared memory map of first slots of cached entries - const char *inodeMapPath() const; + SBuf inodeMapPath() const; // temporary path to the shared memory stack of free slots const char *freeSlotsPath() const; diff --git a/src/ipc/StoreMap.cc b/src/ipc/StoreMap.cc index cda2a396fe..86581fc099 100644 --- a/src/ipc/StoreMap.cc +++ b/src/ipc/StoreMap.cc @@ -4,38 +4,49 @@ #include "squid.h" #include "ipc/StoreMap.h" +#include "SBuf.h" #include "Store.h" #include "store_key_md5.h" #include "tools.h" -Ipc::StoreMap::Owner * -Ipc::StoreMap::Init(const char *const path, const int slotLimit, const size_t extrasSize) +static SBuf +StoreMapSlicesId(const SBuf &path) { - assert(slotLimit > 0); // we should not be created otherwise - Owner *const owner = shm_new(Shared)(path, slotLimit, extrasSize); - debugs(54, 5, HERE << "new map [" << path << "] created: " << slotLimit); - return owner; + return Ipc::Mem::Segment::Name(path, "slices"); } -Ipc::StoreMap::Owner * -Ipc::StoreMap::Init(const char *const path, const int slotLimit) +static SBuf +StoreMapAnchorsId(const SBuf &path) { - return Init(path, slotLimit, 0); + return Ipc::Mem::Segment::Name(path, "anchors"); } -Ipc::StoreMap::StoreMap(const char *const aPath): cleaner(NULL), path(aPath), - shared(shm_old(Shared)(aPath)) +Ipc::StoreMap::Owner * +Ipc::StoreMap::Init(const SBuf &path, const int sliceLimit) +{ + assert(sliceLimit > 0); // we should not be created otherwise + const int anchorLimit = min(sliceLimit, static_cast(SwapFilenMax)); + Owner *owner = new Owner; + owner->anchors = shm_new(Anchors)(StoreMapAnchorsId(path).c_str(), anchorLimit); + owner->slices = shm_new(Slices)(StoreMapSlicesId(path).c_str(), sliceLimit); + debugs(54, 5, "created " << path << " with " << anchorLimit << '+' << sliceLimit); + return owner; +} + +Ipc::StoreMap::StoreMap(const SBuf &aPath): cleaner(NULL), path(aPath), + anchors(shm_old(Anchors)(StoreMapAnchorsId(path).c_str())), + slices(shm_old(Slices)(StoreMapSlicesId(path).c_str())) { - assert(shared->slotLimit > 0); // we should not be created otherwise - debugs(54, 5, HERE << "attached map [" << path << "] created: " << - shared->slotLimit); + debugs(54, 5, "attached " << path << " with " << + anchors->capacity << '+' << slices->capacity); + assert(entryLimit() > 0); // key-to-position mapping requires this + assert(entryLimit() <= sliceLimit()); // at least one slice per entry } int Ipc::StoreMap::compareVersions(const sfileno fileno, time_t newVersion) const { - assert(validEntry(fileno)); - Anchor &inode = shared->slots[fileno].anchor; + const Anchor &inode = anchorAt(fileno); // note: we do not lock, so comparison may be inacurate @@ -51,8 +62,7 @@ Ipc::StoreMap::compareVersions(const sfileno fileno, time_t newVersion) const void Ipc::StoreMap::forgetWritingEntry(sfileno fileno) { - assert(validEntry(fileno)); - Anchor &inode = shared->slots[fileno].anchor; + Anchor &inode = anchorAt(fileno); assert(inode.writing()); @@ -64,7 +74,7 @@ Ipc::StoreMap::forgetWritingEntry(sfileno fileno) inode.rewind(); inode.lock.unlockExclusive(); - --shared->entryCount; + anchors->count; debugs(54, 8, "closed entry " << fileno << " for writing " << path); } @@ -87,7 +97,7 @@ Ipc::StoreMap::openForWriting(const cache_key *const key, sfileno &fileno) Ipc::StoreMap::Anchor * Ipc::StoreMap::openForWritingAt(const sfileno fileno, bool overwriteExisting) { - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); ReadWriteLock &lock = s.lock; if (lock.lockExclusive()) { @@ -107,7 +117,7 @@ Ipc::StoreMap::openForWritingAt(const sfileno fileno, bool overwriteExisting) assert(s.empty()); s.start = -1; // we have not allocated any slices yet - ++shared->entryCount; + ++anchors->count; //s.setKey(key); // XXX: the caller should do that debugs(54, 5, "opened entry " << fileno << " for writing " << path); @@ -122,8 +132,7 @@ Ipc::StoreMap::openForWritingAt(const sfileno fileno, bool overwriteExisting) void Ipc::StoreMap::startAppending(const sfileno fileno) { - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); assert(s.writing()); s.lock.startAppending(); debugs(54, 5, "restricted entry " << fileno << " to appending " << path); @@ -132,8 +141,7 @@ Ipc::StoreMap::startAppending(const sfileno fileno) void Ipc::StoreMap::closeForWriting(const sfileno fileno, bool lockForReading) { - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); assert(s.writing()); if (lockForReading) { s.lock.switchExclusiveToShared(); @@ -150,43 +158,38 @@ Ipc::StoreMap::closeForWriting(const sfileno fileno, bool lockForReading) Ipc::StoreMap::Slice & Ipc::StoreMap::writeableSlice(const AnchorId anchorId, const SliceId sliceId) { - assert(validEntry(anchorId)); - assert(shared->slots[anchorId].anchor.writing()); + assert(anchorAt(anchorId).writing()); assert(validSlice(sliceId)); - return shared->slots[sliceId].slice; + return sliceAt(sliceId); } const Ipc::StoreMap::Slice & Ipc::StoreMap::readableSlice(const AnchorId anchorId, const SliceId sliceId) const { - assert(validEntry(anchorId)); - assert(shared->slots[anchorId].anchor.reading()); + assert(anchorAt(anchorId).reading()); assert(validSlice(sliceId)); - return shared->slots[sliceId].slice; + return sliceAt(sliceId); } Ipc::StoreMap::Anchor & Ipc::StoreMap::writeableEntry(const AnchorId anchorId) { - assert(validEntry(anchorId)); - assert(shared->slots[anchorId].anchor.writing()); - return shared->slots[anchorId].anchor; + assert(anchorAt(anchorId).writing()); + return anchorAt(anchorId); } const Ipc::StoreMap::Anchor & Ipc::StoreMap::readableEntry(const AnchorId anchorId) const { - assert(validEntry(anchorId)); - assert(shared->slots[anchorId].anchor.reading()); - return shared->slots[anchorId].anchor; + assert(anchorAt(anchorId).reading()); + return anchorAt(anchorId); } void Ipc::StoreMap::abortWriting(const sfileno fileno) { debugs(54, 5, "aborting entry " << fileno << " for writing " << path); - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); assert(s.writing()); s.lock.appending = false; // locks out any new readers if (!s.lock.readers) { @@ -202,8 +205,7 @@ Ipc::StoreMap::abortWriting(const sfileno fileno) const Ipc::StoreMap::Anchor * Ipc::StoreMap::peekAtReader(const sfileno fileno) const { - assert(validEntry(fileno)); - const Anchor &s = shared->slots[fileno].anchor; + const Anchor &s = anchorAt(fileno); if (s.reading()) return &s; // immediate access by lock holder so no locking if (s.writing()) @@ -215,8 +217,7 @@ Ipc::StoreMap::peekAtReader(const sfileno fileno) const const Ipc::StoreMap::Anchor & Ipc::StoreMap::peekAtEntry(const sfileno fileno) const { - assert(validEntry(fileno)); - return shared->slots[fileno].anchor; + return anchorAt(fileno); } void @@ -224,8 +225,7 @@ Ipc::StoreMap::freeEntry(const sfileno fileno) { debugs(54, 5, "marking entry " << fileno << " to be freed in " << path); - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); if (s.lock.lockExclusive()) freeChain(fileno, s, false); @@ -240,7 +240,7 @@ Ipc::StoreMap::freeEntryByKey(const cache_key *const key) << " to be freed in " << path); const int idx = anchorIndexByKey(key); - Anchor &s = shared->slots[idx].anchor; + Anchor &s = anchorAt(idx); if (s.lock.lockExclusive()) { if (s.sameKey(key)) freeChain(idx, s, true); @@ -267,7 +267,7 @@ Ipc::StoreMap::freeChain(const sfileno fileno, Anchor &inode, const bool keepLoc sfileno sliceId = inode.start; debugs(54, 8, "first slice " << sliceId); while (sliceId >= 0) { - Slice &slice = shared->slots[sliceId].slice; + Slice &slice = sliceAt(sliceId); const sfileno nextId = slice.next; slice.size = 0; slice.next = -1; @@ -282,7 +282,7 @@ Ipc::StoreMap::freeChain(const sfileno fileno, Anchor &inode, const bool keepLoc if (!keepLocked) inode.lock.unlockExclusive(); - --shared->entryCount; + --anchors->count; debugs(54, 5, "freed entry " << fileno << " in " << path); } @@ -307,8 +307,7 @@ const Ipc::StoreMap::Anchor * Ipc::StoreMap::openForReadingAt(const sfileno fileno) { debugs(54, 5, "opening entry " << fileno << " for reading " << path); - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); if (!s.lock.lockShared()) { debugs(54, 5, "cannot open busy entry " << fileno << @@ -337,8 +336,7 @@ Ipc::StoreMap::openForReadingAt(const sfileno fileno) void Ipc::StoreMap::closeForReading(const sfileno fileno) { - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + Anchor &s = anchorAt(fileno); assert(s.reading()); s.lock.unlockShared(); debugs(54, 5, "closed entry " << fileno << " for reading " << path); @@ -352,9 +350,8 @@ Ipc::StoreMap::purgeOne() const int searchLimit = min(10000, entryLimit()); int tries = 0; for (; tries < searchLimit; ++tries) { - const sfileno fileno = static_cast(++shared->victim % entryLimit()); - assert(validEntry(fileno)); - Anchor &s = shared->slots[fileno].anchor; + const sfileno fileno = static_cast(++anchors->victim % entryLimit()); + Anchor &s = anchorAt(fileno); if (s.lock.lockExclusive()) { // the caller wants a free slice; empty anchor is not enough if (!s.empty() && s.start >= 0) { @@ -378,7 +375,7 @@ Ipc::StoreMap::importSlice(const SliceId sliceId, const Slice &slice) // reliably because the anchor for the imported slice may not have been // imported yet. assert(validSlice(sliceId)); - shared->slots[sliceId].slice = slice; + sliceAt(sliceId) = slice; } int @@ -390,20 +387,20 @@ Ipc::StoreMap::entryLimit() const int Ipc::StoreMap::entryCount() const { - return shared->entryCount; + return anchors->count; } int Ipc::StoreMap::sliceLimit() const { - return shared->slotLimit; + return slices->capacity; } void Ipc::StoreMap::updateStats(ReadWriteLockStats &stats) const { - for (int i = 0; i < shared->slotLimit; ++i) - shared->slots[i].anchor.lock.updateStats(stats); + for (int i = 0; i < anchors->capacity; ++i) + anchorAt(i).lock.updateStats(stats); } bool @@ -418,6 +415,17 @@ Ipc::StoreMap::validSlice(const int pos) const return 0 <= pos && pos < sliceLimit(); } +Ipc::StoreMap::Anchor& +Ipc::StoreMap::anchorAt(const sfileno fileno) { + assert(validEntry(fileno)); + return anchors->items[fileno]; +} + +const Ipc::StoreMap::Anchor& +Ipc::StoreMap::anchorAt(const sfileno fileno) const { + return const_cast(*this).anchorAt(fileno); +} + sfileno Ipc::StoreMap::anchorIndexByKey(const cache_key *const key) const { @@ -429,7 +437,18 @@ Ipc::StoreMap::anchorIndexByKey(const cache_key *const key) const Ipc::StoreMap::Anchor & Ipc::StoreMap::anchorByKey(const cache_key *const key) { - return shared->slots[anchorIndexByKey(key)].anchor; + return anchorAt(anchorIndexByKey(key)); +} + +Ipc::StoreMap::Slice& +Ipc::StoreMap::sliceAt(const SliceId sliceId) { + assert(validSlice(sliceId)); + return slices->items[sliceId]; +} + +const Ipc::StoreMap::Slice& +Ipc::StoreMap::sliceAt(const SliceId sliceId) const { + return const_cast(*this).sliceAt(sliceId); } /* Ipc::StoreMapAnchor */ @@ -478,24 +497,35 @@ Ipc::StoreMapAnchor::rewind() // but keep the lock } -/* Ipc::StoreMap::Shared */ +Ipc::StoreMap::Owner::Owner(): anchors(NULL), slices(NULL) +{ +} + +Ipc::StoreMap::Owner::~Owner() +{ + delete anchors; + delete slices; +} + +/* Ipc::StoreMapAnchors */ -Ipc::StoreMap::Shared::Shared(const int aSlotLimit, const size_t anExtrasSize): - slotLimit(aSlotLimit), extrasSize(anExtrasSize), entryCount(0), +Ipc::StoreMapAnchors::StoreMapAnchors(const int aCapacity): + count(0), victim(0), - slots(aSlotLimit) + capacity(aCapacity), + items(aCapacity) { } size_t -Ipc::StoreMap::Shared::sharedMemorySize() const +Ipc::StoreMapAnchors::sharedMemorySize() const { - return SharedMemorySize(slotLimit, extrasSize); + return SharedMemorySize(capacity); } size_t -Ipc::StoreMap::Shared::SharedMemorySize(const int slotLimit, const size_t extrasSize) +Ipc::StoreMapAnchors::SharedMemorySize(const int capacity) { - return sizeof(Shared) + slotLimit * (sizeof(StoreMapSlot) + extrasSize); + return sizeof(StoreMapAnchors) + capacity * sizeof(StoreMapAnchor); } diff --git a/src/ipc/StoreMap.h b/src/ipc/StoreMap.h index e99521146b..c80452f210 100644 --- a/src/ipc/StoreMap.h +++ b/src/ipc/StoreMap.h @@ -4,6 +4,7 @@ #include "ipc/mem/FlexibleArray.h" #include "ipc/mem/Pointer.h" #include "ipc/ReadWriteLock.h" +#include "SBuf.h" #include "typedefs.h" namespace Ipc @@ -70,62 +71,80 @@ public: /// where the chain of StoreEntry slices begins [app] Atomic::WordT start; +}; + +/// an array of shareable Items +/// must be the last data member or, if used as a parent class, the last parent +template +class StoreMapItems +{ +public: + typedef C Item; + typedef Ipc::Mem::Owner< StoreMapItems > Owner; + + explicit StoreMapItems(const int aCapacity): capacity(aCapacity), items(aCapacity) {} -#if 0 - /// possible persistent states - typedef enum { - Empty, ///< ready for writing, with nothing of value - Writeable, ///< transitions from Empty to Readable - Readable, ///< ready for reading - } State; - State state; ///< current state -#endif + size_t sharedMemorySize() const { return SharedMemorySize(capacity); } + static size_t SharedMemorySize(const int aCapacity) { return sizeof(StoreMapItems) + aCapacity*sizeof(Item); } + + const int capacity; ///< total number of items + Ipc::Mem::FlexibleArray items; ///< storage }; -/// A hack to allocate one shared array for both anchors and slices. -/// Anchors are indexed by store entry ID and are independent from each other. -/// Slices are indexed by slice IDs and form entry chains using slice.next. -class StoreMapSlot +/// StoreMapSlices indexed by their slice ID. +typedef StoreMapItems StoreMapSlices; + +/// StoreMapAnchors indexed by entry fileno plus +/// sharing-safe basic housekeeping info about Store entries +class StoreMapAnchors { public: - StoreMapAnchor anchor; ///< information about store entry as a whole - StoreMapSlice slice; ///< information about one stored entry piece + typedef Ipc::Mem::Owner< StoreMapAnchors > Owner; + + explicit StoreMapAnchors(const int aCapacity); + + size_t sharedMemorySize() const; + static size_t SharedMemorySize(const int anAnchorLimit); + + Atomic::Word count; ///< current number of entries + Atomic::WordT victim; ///< starting point for purge search + const int capacity; ///< total number of anchors + Ipc::Mem::FlexibleArray items; ///< anchors storage }; +// TODO: Find an elegant way to use StoreMapItems in StoreMapAnchors class StoreMapCleaner; -/// map of StoreMapSlots indexed by their keys, with read/write slice locking -/// kids extend to store custom data +/// Manages shared Store index (e.g., locking/unlocking/freeing entries) using +/// StoreMapAnchors indexed by their keys and +/// StoreMapSlices indexed by their slide ID. class StoreMap { public: typedef StoreMapAnchor Anchor; + typedef StoreMapAnchors Anchors; typedef sfileno AnchorId; typedef StoreMapSlice Slice; + typedef StoreMapSlices Slices; typedef StoreMapSliceId SliceId; - /// data shared across maps in different processes - class Shared - { +public: + /// aggregates anchor and slice owners for Init() caller convenience + class Owner { public: - Shared(const int aSlotLimit, const size_t anExtrasSize); - size_t sharedMemorySize() const; - static size_t SharedMemorySize(const int aSlotLimit, const size_t anExtrasSize); - - const int slotLimit; ///< total number of slots - const size_t extrasSize; ///< size of extra data in each slot - Atomic::Word entryCount; ///< current number of entries - Atomic::WordT victim; ///< starting point for purge search - Ipc::Mem::FlexibleArray slots; ///< storage + Owner(); + ~Owner(); + Anchors::Owner *anchors; + Slices::Owner *slices; + private: + Owner(const Owner &); // not implemented + Owner &operator =(const Owner &); // not implemented }; -public: - typedef Mem::Owner Owner; - /// initialize shared memory - static Owner *Init(const char *const path, const int slotLimit); + static Owner *Init(const SBuf &path, const int slotLimit); - StoreMap(const char *const aPath); + StoreMap(const SBuf &aPath); /// computes map entry position for a given entry key sfileno anchorIndexByKey(const cache_key *const key) const; @@ -199,43 +218,22 @@ public: StoreMapCleaner *cleaner; ///< notified before a readable entry is freed protected: - static Owner *Init(const char *const path, const int limit, const size_t extrasSize); - - const String path; ///< cache_dir path or similar cache name; for logging - Mem::Pointer shared; + const SBuf path; ///< cache_dir path or similar cache name; for logging + Mem::Pointer anchors; ///< entry inodes (starting blocks) + Mem::Pointer slices; ///< chained entry pieces positions private: + Anchor &anchorAt(const sfileno fileno); + const Anchor &anchorAt(const sfileno fileno) const; Anchor &anchorByKey(const cache_key *const key); + Slice &sliceAt(const SliceId sliceId); + const Slice &sliceAt(const SliceId sliceId) const; Anchor *openForReading(Slice &s); void freeChain(const sfileno fileno, Anchor &inode, const bool keepLock); }; -/// StoreMap with extra slice data -/// Note: ExtrasT must be POD, it is initialized with zeroes, no -/// constructors or destructors are called -template -class StoreMapWithExtras: public StoreMap -{ -public: - typedef ExtrasT Extras; - - /// initialize shared memory - static Owner *Init(const char *const path, const int limit); - - StoreMapWithExtras(const char *const path); - - /// write access to the extras; call openForWriting() first! - ExtrasT &extras(const StoreMapSliceId sid); - /// read-only access to the extras; call openForReading() first! - const ExtrasT &extras(const StoreMapSliceId sid) const; - -protected: - - ExtrasT *sharedExtras; ///< pointer to extras in shared memory -}; - /// API for adjusting external state when dirty map slice is being freed class StoreMapCleaner { @@ -246,40 +244,6 @@ public: virtual void noteFreeMapSlice(const StoreMapSliceId sliceId) = 0; }; -// StoreMapWithExtras implementation - -template -StoreMap::Owner * -StoreMapWithExtras::Init(const char *const path, const int limit) -{ - return StoreMap::Init(path, limit, sizeof(Extras)); -} - -template -StoreMapWithExtras::StoreMapWithExtras(const char *const aPath): - StoreMap(aPath) -{ - const size_t sharedSizeWithoutExtras = - Shared::SharedMemorySize(sliceLimit(), 0); - sharedExtras = reinterpret_cast(reinterpret_cast(shared.getRaw()) + sharedSizeWithoutExtras); -} - -template -ExtrasT & -StoreMapWithExtras::extras(const StoreMapSliceId sid) -{ - return const_cast(const_cast(this)->extras(sid)); -} - -template -const ExtrasT & -StoreMapWithExtras::extras(const StoreMapSliceId sid) const -{ - assert(sharedExtras); - assert(validSlice(sid)); - return sharedExtras[sid]; -} - } // namespace Ipc // We do not reuse FileMap because we cannot control its size, diff --git a/src/ipc/mem/Segment.cc b/src/ipc/mem/Segment.cc index 4242119b41..642909784b 100644 --- a/src/ipc/mem/Segment.cc +++ b/src/ipc/mem/Segment.cc @@ -9,6 +9,7 @@ #include "Debug.h" #include "fatal.h" #include "ipc/mem/Segment.h" +#include "SBuf.h" #include "tools.h" #include @@ -33,6 +34,15 @@ Ipc::Mem::Segment::reserve(size_t chunkSize) return result; } +SBuf +Ipc::Mem::Segment::Name(const SBuf &prefix, const char *suffix) +{ + SBuf result = prefix; + result.append("_"); + result.append(suffix); + return result; +} + #if HAVE_SHM Ipc::Mem::Segment::Segment(const char *const id): diff --git a/src/ipc/mem/Segment.h b/src/ipc/mem/Segment.h index 7d0807a81d..b949b42a75 100644 --- a/src/ipc/mem/Segment.h +++ b/src/ipc/mem/Segment.h @@ -7,6 +7,8 @@ #include "base/RunnersRegistry.h" #include "SquidString.h" +class SBuf; + namespace Ipc { @@ -36,6 +38,9 @@ public: /// common path of all segment names in path-based environments static const char *BasePath; + /// concatenates parts of a name to form a complete name (or its prefix) + static SBuf Name(const SBuf &prefix, const char *suffix); + private: // not implemented -- 2.47.2