]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Added initial shared memory cache implementation (MemStore) and integrated it.
authorAlex Rousskov <rousskov@measurement-factory.com>
Tue, 12 Apr 2011 00:33:41 +0000 (18:33 -0600)
committerAlex Rousskov <rousskov@measurement-factory.com>
Tue, 12 Apr 2011 00:33:41 +0000 (18:33 -0600)
Like Rock Store, shared memory cache keeps its own compact index of cached
entries using extended Ipc::StoreMap class (MemStoreMap). Like Rock Store, the
cache also struggles to keep its Root.get() results out of the store_table
except during transit.

There are several XXXs and TODOs that still need to be addressed for a more
polished implementation.

Eventually, the non-shared/local memory cache should also be implemented
using a MemStore-like class, I think. This will allow to clearly isolate
local from shared memory cache code.

12 files changed:
src/Makefile.am
src/MemObject.cc
src/MemObject.h
src/MemStore.cc [new file with mode: 0644]
src/MemStore.h [new file with mode: 0644]
src/MemStoreMap.cc [new file with mode: 0644]
src/MemStoreMap.h [new file with mode: 0644]
src/Store.h
src/SwapDir.h
src/main.cc
src/store.cc
src/store_dir.cc

index 89d76e77a44c49c5947cc9683c7954ce21c2fdfa..7d8090fcfa89806ee2e405b9240a825fabd785f3 100644 (file)
@@ -463,8 +463,8 @@ squid_SOURCES = \
        Server.h \
        structs.h \
        swap_log_op.h \
-       SwapDir.cc \
-       SwapDir.h \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
+       SwapDir.h MemStore.h MemStoreMap.h \
        time.cc \
        tools.cc \
        tunnel.cc \
@@ -1268,7 +1268,7 @@ tests_testCacheManager_SOURCES = \
        $(TEST_CALL_SOURCES) \
        tools.cc \
        tunnel.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        url.cc \
        URLScheme.cc \
        urn.cc \
@@ -1469,7 +1469,7 @@ tests_testEvent_SOURCES = \
        $(TEST_CALL_SOURCES) \
        tools.cc \
        tunnel.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        url.cc \
        URLScheme.cc \
        urn.cc \
@@ -1629,7 +1629,7 @@ tests_testEventLoop_SOURCES = \
        $(TEST_CALL_SOURCES) \
        tools.cc \
        tunnel.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        url.cc \
        URLScheme.cc \
        urn.cc \
@@ -1778,7 +1778,7 @@ tests_test_http_range_SOURCES = \
        StoreMetaVary.cc \
        StoreSwapLogData.cc \
        String.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        $(TEST_CALL_SOURCES) \
        time.cc \
        tools.cc \
@@ -1944,7 +1944,7 @@ tests_testHttpRequest_SOURCES = \
        $(TEST_CALL_SOURCES) \
        tools.cc \
        tunnel.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        url.cc \
        URLScheme.cc \
        urn.cc \
@@ -1997,7 +1997,7 @@ STORE_TEST_SOURCES=\
        store_key_md5.cc \
        Parsing.cc \
        ConfigOption.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        tests/stub_acl.cc tests/stub_cache_cf.cc \
        tests/stub_helper.cc cbdata.cc String.cc \
        tests/stub_comm.cc \
@@ -2391,7 +2391,7 @@ tests_testURL_SOURCES = \
        $(TEST_CALL_SOURCES) \
        tools.cc \
        tunnel.cc \
-       SwapDir.cc \
+       SwapDir.cc MemStore.cc MemStoreMap.cc \
        urn.cc \
        wccp2.cc \
        whois.cc \
index 884b77162e6b2848a30909464492ca5544a024a7..d53834c71b5f4592f16c5af556a626dd20063eaf 100644 (file)
@@ -71,6 +71,15 @@ MemObject::inUseCount()
     return Pool().inUseCount();
 }
 
+void
+MemObject::resetUrls(char const *aUrl, char const *aLog_url)
+{
+    safe_free(url);
+    safe_free(log_url);    /* XXX account log_url */
+    log_url = xstrdup(aLog_url);
+    url = xstrdup(aUrl);
+}
+
 MemObject::MemObject(char const *aUrl, char const *aLog_url)
 {
     debugs(20, 3, HERE << "new MemObject " << this);
index a5051067b5bc6897ceb0be5cb8125457b5cf7795..513e73668bb8420474e5246177aea0f5bbd111aa 100644 (file)
@@ -58,6 +58,9 @@ public:
     MemObject(char const *, char const *);
     ~MemObject();
 
+    /// replaces construction-time URLs with correct ones; see hidden_mem_obj
+    void resetUrls(char const *aUrl, char const *aLog_url);
+
     void write(StoreIOBuffer, STMCB *, void *);
     void unlinkRequest();
     HttpReply const *getReply() const;
diff --git a/src/MemStore.cc b/src/MemStore.cc
new file mode 100644 (file)
index 0000000..a82e191
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 20    Memory Cache
+ *
+ */
+
+#include "config.h"
+#include "ipc/mem/Page.h"
+#include "ipc/mem/Pages.h"
+#include "MemObject.h"
+#include "MemStore.h"
+#include "HttpReply.h"
+
+
+// XXX: support storage using more than one page per entry
+
+
+MemStore::MemStore(): map(NULL)
+{
+}
+
+MemStore::~MemStore()
+{
+    delete map;
+}
+
+void
+MemStore::init()
+{
+    if (!map && Config.memMaxSize && (!UsingSmp() || IamWorkerProcess())) {
+        // TODO: warn if we cannot support the configured maximum entry size
+        const int64_t entrySize = Ipc::Mem::PageSize(); // for now
+        const int64_t entryCount = Config.memMaxSize / entrySize;
+        // TODO: warn if we cannot cache at least one item (misconfiguration)
+        if (entryCount > 0)
+            map = new MemStoreMap("cache_mem", entryCount);
+    }
+}
+
+void
+MemStore::stat(StoreEntry &output) const
+{
+    storeAppendPrintf(&output, "Memory Cache");
+    // TODO: implement
+}
+
+void
+MemStore::maintain()
+{
+}
+
+uint64_t
+MemStore::minSize() const
+{
+    return 0; // XXX: irrelevant, but Store parent forces us to implement this
+}
+
+uint64_t
+MemStore::maxSize() const
+{
+    return 0; // XXX: make configurable
+}
+
+void
+MemStore::updateSize(int64_t eSize, int sign)
+{
+    // XXX: irrelevant, but Store parent forces us to implement this
+    fatal("MemStore::updateSize should not be called");
+}
+
+void
+MemStore::reference(StoreEntry &)
+{
+}
+
+void
+MemStore::dereference(StoreEntry &)
+{
+}
+
+int
+MemStore::callback()
+{
+    return 0;
+}
+
+StoreSearch *
+MemStore::search(String const, HttpRequest *)
+{
+    fatal("not implemented");
+    return NULL;
+}
+
+StoreEntry *
+MemStore::get(const cache_key *key)
+{
+    if (!map)
+        return NULL;
+
+    // XXX: replace sfileno with a bigger word (sfileno is only for cache_dirs)
+    sfileno index;
+    const Ipc::StoreMapSlot *const slot = map->openForReading(key, index);
+    if (!slot)
+        return NULL;
+
+    const Ipc::StoreMapSlot::Basics &basics = slot->basics;
+    const MemStoreMap::Extras &extras = map->extras(index);
+
+    // create a brand new store entry and initialize it with stored info
+    StoreEntry *e = new StoreEntry();
+    e->lock_count = 0;
+
+    e->swap_file_sz = basics.swap_file_sz;
+    e->lastref = basics.lastref;
+    e->timestamp = basics.timestamp;
+    e->expires = basics.expires;
+    e->lastmod = basics.lastmod;
+    e->refcount = basics.refcount;
+    e->flags = basics.flags;
+
+    e->store_status = STORE_OK;
+    e->mem_status = IN_MEMORY; // setMemStatus(IN_MEMORY) requires mem_obj
+    //e->swap_status = set in StoreEntry constructor to SWAPOUT_NONE;
+    e->ping_status = PING_NONE;
+
+    EBIT_SET(e->flags, ENTRY_CACHABLE);
+    EBIT_CLR(e->flags, RELEASE_REQUEST);
+    EBIT_CLR(e->flags, KEY_PRIVATE);
+    EBIT_SET(e->flags, ENTRY_VALIDATED);
+
+    const bool copied = copyFromShm(*e, extras);
+
+    // we copied everything we could to local memory; no more need to lock
+    map->closeForReading(index);
+
+    if (copied) {
+        e->hashInsert(key);
+        return e;
+    }
+
+    debugs(20, 3, HERE << "mem-loading failed; freeing " << index);
+    map->free(index); // do not let others into the same trap
+    return NULL;
+}
+
+void
+MemStore::get(String const key, STOREGETCLIENT aCallback, void *aCallbackData)
+{
+    // XXX: not needed but Store parent forces us to implement this
+    fatal("MemStore::get(key,callback,data) should not be called");
+}
+
+bool
+MemStore::copyFromShm(StoreEntry &e, const MemStoreMap::Extras &extras)
+{
+    const Ipc::Mem::PageId &page = extras.page;
+
+    StoreIOBuffer sourceBuf(extras.storedSize, 0,
+                              static_cast<char*>(PagePointer(page)));
+
+    // XXX: We do not know the URLs yet, only the key, but we need to parse and
+    // store the response for the Root().get() callers to be happy because they
+    // expect IN_MEMORY entries to already have the response headers and body.
+    // At least one caller calls createMemObject() if there is not one, so
+    // we hide the true object until that happens (to avoid leaking TBD URLs).
+    e.createMemObject("TBD", "TBD");
+
+    // emulate the usual Store code but w/o inapplicable checks and callbacks:
+
+    // from store_client::readBody():
+    HttpReply *rep = (HttpReply *)e.getReply();
+    const ssize_t end = headersEnd(sourceBuf.data, sourceBuf.length);
+    if (!rep->parseCharBuf(sourceBuf.data, end)) {
+        debugs(20, DBG_IMPORTANT, "Could not parse mem-cached headers: " << e);
+        return false;
+    }
+    // local memory stores both headers and body
+    e.mem_obj->object_sz = sourceBuf.length; // from StoreEntry::complete()
+
+    storeGetMemSpace(sourceBuf.length); // from StoreEntry::write()
+
+    assert(e.mem_obj->data_hdr.write(sourceBuf)); // from MemObject::write()
+    const int64_t written = e.mem_obj->endOffset();
+    assert(written == sourceBuf.length); // StoreEntry::write never fails?
+    // would be nice to call validLength() here, but it needs e.key
+
+    debugs(20, 7, HERE << "mem-loaded all " << written << " bytes of " << e <<
+           " from " << page);
+
+    e.hideMemObject();
+
+    return true;
+}
+
+void
+MemStore::considerKeeping(StoreEntry &e)
+{
+    if (!e.memoryCachable()) {
+        debugs(20, 7, HERE << "Not memory cachable: " << e);
+        return; // cannot keep due to entry state or properties
+    }
+
+    assert(e.mem_obj);
+    if (!willFit(e.mem_obj->endOffset())) {
+        debugs(20, 5, HERE << "No mem-cache space for " << e);
+        return; // failed to free enough space
+    }
+
+    keep(e); // may still fail
+}
+
+bool
+MemStore::willFit(int64_t need)
+{
+    // TODO: obey configured maximum entry size (with page-based rounding)
+    return need <= Ipc::Mem::PageSize();
+}
+
+/// allocates map slot and calls copyToShm to store the entry in shared memory
+void
+MemStore::keep(StoreEntry &e)
+{
+    if (!map) {
+        debugs(20, 5, HERE << "No map to mem-cache " << e);
+        return;
+    }
+
+    sfileno index = 0;
+    Ipc::StoreMapSlot *slot = map->openForWriting(reinterpret_cast<const cache_key *>(e.key), index);
+    if (!slot) {
+        debugs(20, 5, HERE << "No room in mem-cache map to index " << e);
+        return;
+    }
+
+    MemStoreMap::Extras &extras = map->extras(index);
+    if (copyToShm(e, extras)) {
+        slot->set(e);
+        map->closeForWriting(index, false);
+    } else {
+        map->abortIo(index);
+    }
+}
+
+/// uses mem_hdr::copy() to copy local data to shared memory
+bool
+MemStore::copyToShm(StoreEntry &e, MemStoreMap::Extras &extras)
+{
+    Ipc::Mem::PageId page;
+    if (!Ipc::Mem::GetPage(page)) {
+        debugs(20, 5, HERE << "No mem-cache page for " << e);
+        return false; // GetPage is responsible for any cleanup on failures
+    }
+
+    const int64_t bufSize = Ipc::Mem::PageSize();
+    const int64_t eSize = e.mem_obj->endOffset();
+
+    StoreIOBuffer sharedSpace(bufSize, 0,
+                              static_cast<char*>(PagePointer(page)));
+    
+    // check that we kept everything or purge incomplete/sparse cached entry
+    const ssize_t copied = e.mem_obj->data_hdr.copy(sharedSpace);
+    if (eSize != copied) {
+        debugs(20, 2, HERE << "Failed to mem-cache " << e << ": " <<
+               eSize << "!=" << copied);
+        // cleanup
+        PutPage(page);
+        return false;
+    }
+
+    debugs(20, 7, HERE << "mem-cached all " << eSize << " bytes of " << e <<
+           " in " << page);
+
+    // remember storage location and size
+    extras.page = page;
+    extras.storedSize = copied;
+    return true;
+}
diff --git a/src/MemStore.h b/src/MemStore.h
new file mode 100644 (file)
index 0000000..bb4320b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * $Id$
+ */
+
+#ifndef SQUID_MEMSTORE_H
+#define SQUID_MEMSTORE_H
+
+#include "Store.h"
+#include "MemStoreMap.h"
+
+/// Stores HTTP entities in RAM. Current implementation uses shared memory.
+/// Unlike a disk store (SwapDir), operations are synchronous (and fast).
+class MemStore: public Store {
+public:
+    MemStore();
+    virtual ~MemStore();
+
+    /// cache the entry or forget about it until the next considerKeeping call
+    void considerKeeping(StoreEntry &e);
+
+    /* Store API */
+    virtual int callback();
+    virtual StoreEntry * get(const cache_key *);
+    virtual void get(String const key , STOREGETCLIENT callback, void *cbdata);
+    virtual void init();
+    virtual uint64_t maxSize() const;
+    virtual uint64_t minSize() const;
+    virtual void stat(StoreEntry &) const;
+    virtual StoreSearch *search(String const url, HttpRequest *);
+    virtual void reference(StoreEntry &);
+    virtual void dereference(StoreEntry &);
+    virtual void maintain();
+    virtual void updateSize(int64_t size, int sign);
+
+protected:
+    bool willFit(int64_t needed);
+    void keep(StoreEntry &e);
+
+    bool copyToShm(StoreEntry &e, MemStoreMap::Extras &extras);
+    bool copyFromShm(StoreEntry &e, const MemStoreMap::Extras &extras);
+
+private:
+    MemStoreMap *map; ///< index of mem-cached entries
+};
+
+// Why use Store as a base? MemStore and SwapDir are both "caches".
+
+// Why not just use a SwapDir API? That would not help much because Store has
+// to check/update memory cache separately from the disk cache. And same API
+// would hurt because we can support synchronous get/put, unlike the disks.
+
+#endif /* SQUID_MEMSTORE_H */
diff --git a/src/MemStoreMap.cc b/src/MemStoreMap.cc
new file mode 100644 (file)
index 0000000..30e65bb
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 20    Memory Cache
+ */
+
+#include "config.h"
+
+#include "Store.h"
+#include "MemStoreMap.h"
+
+MemStoreMap::MemStoreMap(const char *const aPath, const int limit):
+    Ipc::StoreMap(aPath, limit, Shared::MemSize(limit))
+{
+    assert(shm.mem());
+    shared = new (shm.reserve(Shared::MemSize(limit))) Shared;
+}
+
+MemStoreMap::MemStoreMap(const char *const aPath):
+    Ipc::StoreMap(aPath)
+{
+    const int limit = entryLimit();
+    assert(shm.mem());
+    shared = reinterpret_cast<Shared *>(shm.reserve(Shared::MemSize(limit)));
+}
+
+MemStoreMap::Extras &
+MemStoreMap::extras(const sfileno fileno)
+{
+    assert(0 <= fileno && fileno < entryLimit());
+    assert(shared);
+    return shared->extras[fileno];
+}
+
+const MemStoreMap::Extras &
+MemStoreMap::extras(const sfileno fileno) const
+{
+    assert(0 <= fileno && fileno < entryLimit());
+    assert(shared);
+    return shared->extras[fileno];
+}
+
+size_t
+MemStoreMap::Shared::MemSize(int limit)
+{
+    return sizeof(Shared) + limit*sizeof(Extras);
+}
diff --git a/src/MemStoreMap.h b/src/MemStoreMap.h
new file mode 100644 (file)
index 0000000..e9379cd
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef SQUID_MEMSTOREMAP_H
+#define SQUID_MEMSTOREMAP_H
+
+#include "ipc/StoreMap.h"
+#include "ipc/mem/Page.h"
+
+/// map of MemStore-cached entries
+class MemStoreMap: public Ipc::StoreMap
+{
+public:
+    // StoreEntry restoration info not already stored by Ipc::StoreMap
+    class Extras {
+    public:
+        Ipc::Mem::PageId page; ///< shared memory page with the entry content
+        int64_t storedSize; ///< total size of the stored entry content
+       };
+
+public:
+    MemStoreMap(const char *const aPath, const int limit); ///< create a new shared StoreMap
+    MemStoreMap(const char *const aPath); ///< open an existing shared StoreMap
+
+    /// write access to the extras; call openForWriting() first!
+    Extras &extras(const sfileno fileno);
+    /// read-only access to the extras; call openForReading() first!
+    const Extras &extras(const sfileno fileno) const;
+
+private:
+    /// data shared by all MemStoreMaps with the same path
+    class Shared {
+    public:
+        static size_t MemSize(int limit);
+        Extras extras[]; ///< extras storage
+    };
+
+    Shared *shared; ///< pointer to shared memory
+};
+
+#endif /* SQUID_MEMSTOREMAP_H */
index be5874e67901104084d36e6e2abd2b9f217f2469..2b2cc54d1619210931546a740a45c8132db468af 100644 (file)
@@ -110,6 +110,7 @@ public:
     void cacheNegatively();            /** \todo argh, why both? */
     void invokeHandlers();
     void purgeMem();
+    void cacheInMemory(); ///< start or continue storing in memory cache
     void swapOut();
     bool swapOutAble() const;
     void swapOutFileClose(int how);
@@ -118,8 +119,9 @@ public:
     int checkNegativeHit() const;
     int locked() const;
     int validToSend() const;
-    int keepInMemory() const;
+    bool memoryCachable() const; ///< may be cached in memory
     void createMemObject(const char *, const char *);
+    void hideMemObject(); ///< no mem_obj for callers until createMemObject
     void dump(int debug_lvl) const;
     void hashDelete();
     void hashInsert(const cache_key *);
@@ -144,6 +146,7 @@ public:
     virtual RefCount<Store> store() const;
 
     MemObject *mem_obj;
+    MemObject *hidden_mem_obj; ///< mem_obj created before URLs were known
     RemovalPolicyNode repl;
     /* START OF ON-DISK STORE_META_STD TLV field */
     time_t timestamp;
@@ -319,6 +322,11 @@ public:
 
     virtual void maintain() = 0; /* perform regular maintenance should be private and self registered ... */
 
+    // XXX: This method belongs to Store::Root/StoreController, but it is here
+    // because test cases use non-StoreController derivatives as Root
+    /// called when the entry is no longer needed by any transaction
+    virtual void handleIdleEntry(StoreEntry &e) {}
+
     /* These should really be private */
     virtual void updateSize(int64_t size, int sign) = 0;
 
index aedefe477256cae21eb0be49b7485526c5f6f03a..2c11895724693ec249b6d30413474dab2521c58c 100644 (file)
 
 /* forward decls */
 class RemovalPolicy;
+class MemStore;
 
 /* Store dir configuration routines */
 /* SwapDir *sd, char *path ( + char *opt later when the strtok mess is gone) */
 
 class ConfigOption;
 
-/* New class that replaces the static SwapDir methods as part of the Store overhaul */
-
+/// hides memory/disk cache distinction from callers
 class StoreController : public Store
 {
 
@@ -58,6 +58,9 @@ public:
 
     virtual void get(String const, STOREGETCLIENT, void * cbdata);
 
+    /* Store parent API */
+    virtual void handleIdleEntry(StoreEntry &e);
+
     virtual void init();
 
     virtual void maintain(); /* perform regular maintenance should be private and self registered ... */
@@ -84,7 +87,8 @@ public:
 private:
     void createOneStore(Store &aStore);
 
-    StorePointer swapDir;
+    StorePointer swapDir; ///< summary view of all disk caches
+    MemStore *memStore; ///< memory cache
 };
 
 /* migrating from the Config based list of swapdirs */
@@ -108,7 +112,7 @@ SQUIDCEXTERN void storeDirLRUAdd(StoreEntry *);
 SQUIDCEXTERN int storeDirGetBlkSize(const char *path, int *blksize);
 SQUIDCEXTERN int storeDirGetUFSStats(const char *, int *, int *, int *, int *);
 
-
+/// manages a single cache_dir
 class SwapDir : public Store
 {
 
index 1aaebde76c58acaee8adb1dc73aa8fa71833ecc0..de345a17905e51a89931614d6541241eb7dee46b 100644 (file)
@@ -64,6 +64,7 @@
 #include "StoreFileSystem.h"
 #include "DiskIO/DiskIOModule.h"
 #include "ipc/Kids.h"
+#include "ipc/mem/Pages.h"
 #include "ipc/Coordinator.h"
 #include "ipc/Strand.h"
 #include "ip/tools.h"
@@ -1411,6 +1412,13 @@ SquidMain(int argc, char **argv)
         /* NOTREACHED */
     }
 
+    // XXX: this logic should probably be moved into Ipc::Mem::Init()
+    if (IamMasterProcess())
+        Ipc::Mem::Init();
+    else
+    if (UsingSmp() && IamWorkerProcess())
+        Ipc::Mem::Attach();
+
     if (!opt_no_daemon && Config.workers > 0)
         watch_child(argv);
 
index 65c795335520cbdeec5cde4d033536e80615ec8d..0ac43b8a34017be4813138762bd51ef61714243a 100644 (file)
@@ -314,6 +314,13 @@ StoreEntry::storeClientType() const
      * offset 0 in the memory object is the HTTP headers.
      */
 
+    if (/* XXX: restore: UsingSmp() && */ mem_status == IN_MEMORY) {
+        // clients of an object cached in shared memory are memory clients
+        return STORE_MEM_CLIENT;
+    }
+
+    assert(mem_obj);
+
     if (mem_obj->inmem_lo)
         return STORE_DISK_CLIENT;
 
@@ -365,6 +372,7 @@ StoreEntry::storeClientType() const
 }
 
 StoreEntry::StoreEntry():
+        hidden_mem_obj(NULL),
         swap_file_sz(0)
 {
     debugs(20, 3, HERE << "new StoreEntry " << this);
@@ -378,6 +386,7 @@ StoreEntry::StoreEntry():
 }
 
 StoreEntry::StoreEntry(const char *aUrl, const char *aLogUrl):
+        hidden_mem_obj(NULL),
         swap_file_sz(0)
 {
     debugs(20, 3, HERE << "new StoreEntry " << this);
@@ -396,6 +405,7 @@ StoreEntry::~StoreEntry()
         SwapDir &sd = dynamic_cast<SwapDir&>(*store());
         sd.disconnect(*this);
     }
+    delete hidden_mem_obj;
 }
 
 void
@@ -406,6 +416,18 @@ StoreEntry::destroyMemObject()
     MemObject *mem = mem_obj;
     mem_obj = NULL;
     delete mem;
+    delete hidden_mem_obj;
+    hidden_mem_obj = NULL;
+}
+
+void
+StoreEntry::hideMemObject()
+{
+    debugs(20, 3, HERE << "hiding " << mem_obj);
+    assert(mem_obj);
+    assert(!hidden_mem_obj);
+    hidden_mem_obj = mem_obj;
+    mem_obj = NULL;
 }
 
 void
@@ -532,29 +554,10 @@ StoreEntry::unlock()
         return 0;
     }
 
-    // XXX: Rock store specific: since each SwapDir controls the index,
-    // unlocked entries should not stay in the global store_table
-    if (fileno >= 0) {
-        Store::Root().dereference(*this);
-        debugs(20, 5, HERE << "destroying unlocked entry: " << this << ' ' << *this);
-        setMemStatus(NOT_IN_MEMORY);
-        destroyStoreEntry(static_cast<hash_link *>(this));
-        return 0;
-    }
-    else if (keepInMemory()) {
-        Store::Root().dereference(*this);
-        setMemStatus(IN_MEMORY);
-        mem_obj->unlinkRequest();
-    } else {
-        Store::Root().dereference(*this);
-
-        if (EBIT_TEST(flags, KEY_PRIVATE))
-            debugs(20, 1, "WARNING: " << __FILE__ << ":" << __LINE__ << ": found KEY_PRIVATE");
-
-        /* StoreEntry::purgeMem may free e */
-        purgeMem();
-    }
+    if (EBIT_TEST(flags, KEY_PRIVATE))
+        debugs(20, 1, "WARNING: " << __FILE__ << ":" << __LINE__ << ": found KEY_PRIVATE");
 
+    Store::Root().handleIdleEntry(*this); // may delete us
     return 0;
 }
 
@@ -722,6 +725,8 @@ StoreEntry::setPublicKey()
             }
         }
 
+        // TODO: storeGetPublic() calls below may create unlocked entries.
+        // We should add/use storeHas() API or lock/unlock those entries.
         if (mem_obj->vary_headers && !storeGetPublic(mem_obj->url, mem_obj->method)) {
             /* Create "vary" base object */
             String vary;
@@ -1428,8 +1433,8 @@ storeConfigure(void)
     store_pages_max = Config.memMaxSize / sizeof(mem_node);
 }
 
-int
-StoreEntry::keepInMemory() const
+bool
+StoreEntry::memoryCachable() const
 {
     if (mem_obj == NULL)
         return 0;
@@ -1443,8 +1448,9 @@ StoreEntry::keepInMemory() const
     if (!Config.onoff.memory_cache_first && swap_status == SWAPOUT_DONE && refcount == 1)
         return 0;
 
-    // already kept more than allowed
-    if (mem_node::InUseCount() > store_pages_max)
+    const int64_t expectedSize = mem_obj->expectedReplySize();
+    // objects of unknown size are not allowed into the memory cache, for now
+    if (expectedSize < 0 || expectedSize > Config.Store.maxInMemObjSize)
         return 0;
 
     return 1;
@@ -1612,6 +1618,16 @@ StoreEntry::setMemStatus(mem_status_t new_status)
     if (new_status == mem_status)
         return;
 
+    // XXX: restore: if (UsingSmp())
+    {
+        assert(new_status != IN_MEMORY); // we do not call this otherwise
+        // This method was designed to update replacement policy, not to
+        // actually purge something from the memory cache (TODO: rename?).
+        // Shared memory cache does not have a policy that needs updates.
+        mem_status = new_status;
+        return;
+    }
+
     assert(mem_obj != NULL);
 
     if (new_status == IN_MEMORY) {
@@ -1624,7 +1640,7 @@ StoreEntry::setMemStatus(mem_status_t new_status)
             debugs(20, 4, "StoreEntry::setMemStatus: inserted mem node " << mem_obj->url << " key: " << getMD5Text());
         }
 
-        hot_obj_count++;
+        hot_obj_count++; // TODO: maintain for the shared hot cache as well
     } else {
         if (EBIT_TEST(flags, ENTRY_SPECIAL)) {
             debugs(20, 4, "StoreEntry::setMemStatus: special entry " << mem_obj->url);
@@ -1656,6 +1672,14 @@ StoreEntry::createMemObject(const char *aUrl, const char *aLogUrl)
     if (mem_obj)
         return;
 
+    if (hidden_mem_obj) {
+        debugs(20, 3, HERE << "restoring " << hidden_mem_obj);
+        mem_obj = hidden_mem_obj;
+        hidden_mem_obj = NULL;
+        mem_obj->resetUrls(aUrl, aLogUrl);
+        return;
+    }
+
     mem_obj = new MemObject(aUrl, aLogUrl);
 }
 
index 67873f02393a917fbf8e2875fff125bd52ef948a..3f83a938341d9cfd02db25fcbd48459e0bd9eb2d 100644 (file)
@@ -36,6 +36,8 @@
 #include "squid.h"
 #include "Store.h"
 #include "MemObject.h"
+#include "MemStore.h"
+#include "mem_node.h"
 #include "SquidMath.h"
 #include "SquidTime.h"
 #include "SwapDir.h"
@@ -73,10 +75,13 @@ static STDIRSELECT storeDirSelectSwapDirLeastLoad;
 int StoreController::store_dirs_rebuilding = 1;
 
 StoreController::StoreController() : swapDir (new StoreHashIndex())
+    , memStore(NULL)
 {}
 
 StoreController::~StoreController()
-{}
+{
+    delete memStore;
+}
 
 /*
  * This function pointer is set according to 'store_dir_select_algorithm'
@@ -87,6 +92,10 @@ STDIRSELECT *storeDirSelectSwapDir = storeDirSelectSwapDirLeastLoad;
 void
 StoreController::init()
 {
+    // XXX: add: if (UsingSmp())
+    memStore = new MemStore;
+    memStore->init();
+
     swapDir->init();
 
     if (0 == strcasecmp(Config.store_dir_select_algorithm, "round-robin")) {
@@ -336,6 +345,9 @@ SwapDir::updateSize(int64_t size, int sign)
 void
 StoreController::stat(StoreEntry &output) const
 {
+    if (memStore)
+        memStore->stat(output);
+
     storeAppendPrintf(&output, "Store Directory Statistics:\n");
     storeAppendPrintf(&output, "Store Entries          : %lu\n",
                       (unsigned long int)StoreEntry::inUseCount());
@@ -509,7 +521,8 @@ StoreHashIndex::dir(const int i) const
 void
 StoreController::sync(void)
 {
-    /* sync mem cache? */
+    if (memStore)
+        memStore->sync();
     swapDir->sync();
 }
 
@@ -657,7 +670,11 @@ StoreController::reference(StoreEntry &e)
     if (e.swap_dirn > -1)
         e.store()->reference(e);
 
-    /* Notify the memory cache that we're referencing this object again */
+    // Notify the memory cache that we're referencing this object again
+    if (memStore && e.mem_status == IN_MEMORY)
+        memStore->reference(e);
+
+    // TODO: move this code to a non-shared memory cache class when we have it
     if (e.mem_obj) {
         if (mem_policy->Referenced)
             mem_policy->Referenced(mem_policy, &e, &e.mem_obj->repl);
@@ -672,7 +689,11 @@ StoreController::dereference(StoreEntry & e)
     if (e.swap_filen > -1)
         e.store()->dereference(e);
 
-    /* Notify the memory cache that we're not referencing this object any more */
+    // Notify the memory cache that we're not referencing this object any more
+    if (memStore && e.mem_status == IN_MEMORY)
+        memStore->dereference(e);
+
+    // TODO: move this code to a non-shared memory cache class when we have it
     if (e.mem_obj) {
         if (mem_policy->Dereferenced)
             mem_policy->Dereferenced(mem_policy, &e, &e.mem_obj->repl);
@@ -683,10 +704,20 @@ StoreEntry *
 StoreController::get(const cache_key *key)
 {
     if (StoreEntry *e = swapDir->get(key)) {
+        // TODO: ignore and maybe handleIdleEntry() unlocked intransit entries
+        // because their backing store slot may be gone already.
         debugs(20, 3, HERE << "got in-transit entry: " << *e);
         return e;
     }
 
+    if (memStore) {
+        if (StoreEntry *e = memStore->get(key)) {
+            debugs(20, 3, HERE << "got mem-cached entry: " << *e);
+            return e;
+        }
+    }
+
+    // TODO: this disk iteration is misplaced; move to StoreHashIndex
     if (const int cacheDirs = Config.cacheSwap.n_configured) {
         // ask each cache_dir until the entry is found; use static starting
         // point to avoid asking the same subset of disks more often
@@ -717,6 +748,38 @@ StoreController::get(String const key, STOREGETCLIENT aCallback, void *aCallback
     fatal("not implemented");
 }
 
+void
+StoreController::handleIdleEntry(StoreEntry &e)
+{
+    bool keepInLocalMemory = false;
+    if (memStore) {
+        memStore->considerKeeping(e);
+        // leave keepInLocalMemory false; memStore maintains its own cache
+    } else {
+        keepInLocalMemory = e.memoryCachable() && // entry is in good shape and
+            // the local memory cache is not overflowing
+            (mem_node::InUseCount() <= store_pages_max);
+    }
+
+    dereference(e);
+
+    // XXX: Rock store specific: Since each SwapDir controls its index,
+    // unlocked entries should not stay in the global store_table.
+    if (fileno >= 0) {
+        debugs(20, 5, HERE << "destroying unlocked entry: " << &e << ' ' << e);
+        destroyStoreEntry(static_cast<hash_link*>(&e));
+        return;
+    }
+
+    // TODO: move this into [non-shared] memory cache class when we have one
+    if (keepInLocalMemory) {
+        e.setMemStatus(IN_MEMORY);
+        e.mem_obj->unlinkRequest();
+    } else {
+        e.purgeMem(); // may free e
+    }
+}
+
 StoreHashIndex::StoreHashIndex()
 {
     if (store_table)