]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Stop wasting 96 RAM bytes per slot for high-offset slots in large shared caches
authorAlex Rousskov <rousskov@measurement-factory.com>
Mon, 21 Apr 2014 18:09:06 +0000 (12:09 -0600)
committerAlex Rousskov <rousskov@measurement-factory.com>
Mon, 21 Apr 2014 18:09:06 +0000 (12:09 -0600)
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
src/MemStore.h
src/Transients.cc
src/Transients.h
src/fs/rock/RockSwapDir.cc
src/fs/rock/RockSwapDir.h
src/ipc/StoreMap.cc
src/ipc/StoreMap.h
src/ipc/mem/Segment.cc
src/ipc/mem/Segment.h

index 6117e909f8d9a256dca2fe6310d04f7f3bb9abab..e56c071c1155cc077d02f572197c0e90b3caabc0 100644 (file)
 #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<char*>(PagePointer(extras.page));
+            const MemStoreMapExtras::Item &extra = extras->items[sid];
+
+            char *page = static_cast<char*>(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<Ipc::Mem::PageStack> *spaceOwner; ///< free slices Owner
     MemStoreMap::Owner *mapOwner; ///< primary map Owner
+    Ipc::Mem::Owner<MemStoreMapExtras> *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;
 }
index e57e776537d707f3acb663296c45fad3f7fffab6..5d3382308190eed15b9b199309df7586286da796 100644 (file)
@@ -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<MemStoreMapExtras> MemStoreMap;
+typedef Ipc::StoreMapItems<MemStoreMapExtraItem> 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<Ipc::Mem::PageStack> freeSlots; ///< unused map slot IDs
     MemStoreMap *map; ///< index of mem-cached entries
 
+    typedef MemStoreMapExtras Extras;
+    Ipc::Mem::Pointer<Extras> extras; ///< IDs of pages with slice data
+
     /// the last allocate slice for writing a store entry (during copyToShm)
     sfileno lastWritingSlice;
 
index a4f1dbd89f989eb942cd097e27bbb8c24dfff205..97b2bd80ec6911d69d9dbf48e181d91b88482e70 100644 (file)
 
 #include <limits>
 
-/// 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<TransientsMapExtras> *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;
 }
index a0ebf2063e8838240fcc2d6a3ec8d9f81fb1c232..8ae1f4465b932249700b977f92a8a73fa49b1811 100644 (file)
@@ -9,12 +9,13 @@
 #include <vector>
 
 // 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<TransientsMapExtras> TransientsMap;
+typedef Ipc::StoreMapItems<TransientsMapExtraItem> 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> extras;
+
     typedef std::vector<StoreEntry*> Locals;
     /// local collapsed entries indexed by transient ID, for syncing old StoreEntries
     Locals *locals;
index d3aee6cbaa54a5a106dbf74abea4695547405ff8..6e65ccff05158574d5c047e6e979c2d2d29a53cc 100644 (file)
@@ -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 *
index f9abde75fa0c21d764037380effe0ad71dd27889..22f58493a5933a8b4a52a2ed98dc7f8c259d3ed7 100644 (file)
@@ -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;
 
index cda2a396fe3da100f4508ce1a9c6fd7beb8ef23f..86581fc099aceb733a0ca5b1cfe2a5fdce1e2661 100644 (file)
@@ -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<int>(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<sfileno>(++shared->victim % entryLimit());
-        assert(validEntry(fileno));
-        Anchor &s = shared->slots[fileno].anchor;
+        const sfileno fileno = static_cast<sfileno>(++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<StoreMap&>(*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<StoreMap&>(*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);
 }
 
index e99521146badb8ff6898c1d32b14de8558375e5f..c80452f210f8fb4da3efdae74f2c2ed8e453bd75 100644 (file)
@@ -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<StoreMapSliceId> start;
+};
+
+/// an array of shareable Items
+/// must be the last data member or, if used as a parent class, the last parent
+template <class C>
+class StoreMapItems
+{
+public:
+    typedef C Item;
+    typedef Ipc::Mem::Owner< StoreMapItems<Item> > 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<Item>) + aCapacity*sizeof(Item); }
+
+    const int capacity; ///< total number of items
+    Ipc::Mem::FlexibleArray<Item> 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<StoreMapSlice> 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<uint32_t> victim; ///< starting point for purge search
+    const int capacity; ///< total number of anchors
+    Ipc::Mem::FlexibleArray<StoreMapAnchor> 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<uint32_t> victim; ///< starting point for purge search
-        Ipc::Mem::FlexibleArray<StoreMapSlot> 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<Shared> 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> shared;
+    const SBuf path; ///< cache_dir path or similar cache name; for logging
+    Mem::Pointer<StoreMapAnchors> anchors; ///< entry inodes (starting blocks)
+    Mem::Pointer<StoreMapSlices> 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 ExtrasT>
-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 <class ExtrasT>
-StoreMap::Owner *
-StoreMapWithExtras<ExtrasT>::Init(const char *const path, const int limit)
-{
-    return StoreMap::Init(path, limit, sizeof(Extras));
-}
-
-template <class ExtrasT>
-StoreMapWithExtras<ExtrasT>::StoreMapWithExtras(const char *const aPath):
-        StoreMap(aPath)
-{
-    const size_t sharedSizeWithoutExtras =
-        Shared::SharedMemorySize(sliceLimit(), 0);
-    sharedExtras = reinterpret_cast<Extras *>(reinterpret_cast<char *>(shared.getRaw()) + sharedSizeWithoutExtras);
-}
-
-template <class ExtrasT>
-ExtrasT &
-StoreMapWithExtras<ExtrasT>::extras(const StoreMapSliceId sid)
-{
-    return const_cast<ExtrasT &>(const_cast<const StoreMapWithExtras *>(this)->extras(sid));
-}
-
-template <class ExtrasT>
-const ExtrasT &
-StoreMapWithExtras<ExtrasT>::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,
index 4242119b41321560c7a5644a3516db7081ff43d6..642909784bf08e1a20c641e8f0bcfb07a8da7269 100644 (file)
@@ -9,6 +9,7 @@
 #include "Debug.h"
 #include "fatal.h"
 #include "ipc/mem/Segment.h"
+#include "SBuf.h"
 #include "tools.h"
 
 #include <fcntl.h>
@@ -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):
index 7d0807a81dbde5ee9f055c82866056b859344a2b..b949b42a75ccd8d4126e9dd297b8be0305ad65cd 100644 (file)
@@ -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