Since r11969, Squid calls trimMemory() for all entries, including
non-swappable, to release unused MemObjects memory. But it should not
release objects that are or should be stored in local memory cache.
StoreEntry::trimMemory() has a check for IN_MEMORY status for that. But
IN_MEMORY status is set too late in StoreController::handleIdleEntry(),
after trimMemory() marks entry for release:
clientReplyContext::removeStoreReference()
storeUnregister()
StoreEntry::swapOut()
StoreEntry::trimMemory()
StoreEntry::releaseRequest()
StoreEntry::unlock()
StoreController::handleIdleEntry() // never get here because entry is
set IN_MEMORY status // already marked for release
The patch adds StoreController::keepInLocalMemory() method to determine
if an entry should be kept in memory for later use. It uses different
checks depending on the configuration: shared memory cache checks are
implemented in MemStore::keepInLocalMemory() and local ones are in
StoreController::keepInLocalMemoryCache(). Both methods use
StoreEntry::memoryCachable() for general checks. Shared memory
cache-specific checks are moved from StoreEntry::memoryCachable() to
MemStore::keepInLocalMemory().
Regards,
Dmitry
Do not release entries that may be stored in memory cache later.
Since r11969, Squid calls trimMemory() for all entries, including
non-swappable, to release unused MemObjects memory. But it should not
release objects that are or should be stored in local memory cache.
StoreEntry::trimMemory() has a check for IN_MEMORY status for that. But
IN_MEMORY status is set too late in StoreController::handleIdleEntry(),
after trimMemory() marks entry for release:
clientReplyContext::removeStoreReference()
storeUnregister()
StoreEntry::swapOut()
StoreEntry::trimMemory()
StoreEntry::releaseRequest()
StoreEntry::unlock()
StoreController::handleIdleEntry() // never get here because entry is
set IN_MEMORY status // already marked for release
The patch adds StoreController::keepInLocalMemory() method to determine
if an entry should be kept in memory for later use. It uses different
checks depending on the configuration: shared memory cache checks are
implemented in MemStore::keepInLocalMemory() and local ones are in
StoreController::keepInLocalMemoryCache(). Both methods use
StoreEntry::memoryCachable() for general checks. Shared memory
cache-specific checks are moved from StoreEntry::memoryCachable() to
MemStore::keepInLocalMemory().
=== modified file 'src/MemStore.cc'
--- src/MemStore.cc 2011-10-28 01:01:41 +0000
+++ src/MemStore.cc 2012-07-05 21:17:28 +0000
@@ -76,40 +76,51 @@ MemStore::stat(StoreEntry &e) const
const int limit = map->entryLimit();
storeAppendPrintf(&e, "Maximum entries: %9d\n", limit);
if (limit > 0) {
storeAppendPrintf(&e, "Current entries: %"PRId64" %.2f%%\n",
currentCount(), (100.0 * currentCount() / limit));
if (limit < 100) { // XXX: otherwise too expensive to count
Ipc::ReadWriteLockStats stats;
map->updateStats(stats);
stats.dump(e);
}
}
}
}
void
MemStore::maintain()
{
}
+bool
+MemStore::keepInLocalMemory(const StoreEntry &e) const {
+ if (!e.memoryCachable())
+ return false;
+
+ Must(e.mem_obj);
+ const int64_t size =
+ max(e.mem_obj->expectedReplySize(), e.mem_obj->endOffset());
+ return willFit(size);
+}
+
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
}
uint64_t
MemStore::currentSize() const
{
return theCurrentSize;
}
uint64_t
MemStore::currentCount() const
@@ -252,41 +263,41 @@ MemStore::copyFromShm(StoreEntry &e, con
}
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)
+MemStore::willFit(const int64_t need) const
{
// TODO: obey configured maximum entry size (with page-based rounding)
return need <= static_cast<int64_t>(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;
}
=== modified file 'src/MemStore.h'
--- src/MemStore.h 2011-10-14 16:21:48 +0000
+++ src/MemStore.h 2012-07-05 21:12:45 +0000
@@ -26,45 +26,46 @@ public:
/// 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 uint64_t currentSize() const;
virtual uint64_t currentCount() const;
virtual int64_t maxObjectSize() const;
virtual void getStats(StoreInfoStats &stats) const;
virtual void stat(StoreEntry &) const;
virtual StoreSearch *search(String const url, HttpRequest *);
virtual void reference(StoreEntry &);
virtual bool dereference(StoreEntry &);
virtual void maintain();
+ virtual bool keepInLocalMemory(const StoreEntry &e) const;
static int64_t EntryLimit();
protected:
- bool willFit(int64_t needed);
+ bool willFit(const int64_t need) const;
void keep(StoreEntry &e);
bool copyToShm(StoreEntry &e, MemStoreMap::Extras &extras);
bool copyFromShm(StoreEntry &e, const MemStoreMap::Extras &extras);
// Ipc::StoreMapCleaner API
virtual void cleanReadable(const sfileno fileno);
private:
MemStoreMap *map; ///< index of mem-cached entries
uint64_t theCurrentSize; ///< currently used space in the storage area
};
// 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 */
=== modified file 'src/Store.h'
--- src/Store.h 2012-01-13 13:49:26 +0000
+++ src/Store.h 2012-07-05 21:24:31 +0000
@@ -334,40 +334,43 @@ public:
/** remove a Store entry from the store */
virtual void unlink (StoreEntry &);
/* search in the store */
virtual StoreSearch *search(String const url, HttpRequest *) = 0;
/* pulled up from SwapDir for migration.... probably do not belong here */
virtual void reference(StoreEntry &) = 0; /* Reference this object */
/// Undo reference(), returning false iff idle e should be destroyed
virtual bool dereference(StoreEntry &e) = 0;
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) {}
+ /// whether entry should be kept in local memory for later use
+ virtual bool keepInLocalMemory(const StoreEntry &) const { return false; }
+
private:
static RefCount<Store> CurrentRoot;
};
/// \ingroup StoreAPI
typedef RefCount<Store> StorePointer;
/// \ingroup StoreAPI
SQUIDCEXTERN size_t storeEntryInUse();
/// \ingroup StoreAPI
SQUIDCEXTERN const char *storeEntryFlags(const StoreEntry *);
/// \ingroup StoreAPI
extern void storeEntryReplaceObject(StoreEntry *, HttpReply *);
/// \ingroup StoreAPI
SQUIDCEXTERN StoreEntry *storeGetPublic(const char *uri, const HttpRequestMethod& method);
/// \ingroup StoreAPI
=== modified file 'src/SwapDir.h'
--- src/SwapDir.h 2011-10-27 23:14:28 +0000
+++ src/SwapDir.h 2012-07-05 20:54:42 +0000
@@ -69,45 +69,48 @@ public:
virtual uint64_t minSize() const;
virtual uint64_t currentSize() const;
virtual uint64_t currentCount() const;
virtual int64_t maxObjectSize() const;
virtual void getStats(StoreInfoStats &stats) const;
virtual void stat(StoreEntry &) const;
virtual void sync(); /* Sync the store prior to shutdown */
virtual StoreSearch *search(String const url, HttpRequest *);
virtual void reference(StoreEntry &); /* Reference this object */
virtual bool dereference(StoreEntry &); /* Unreference this object */
+ virtual bool keepInLocalMemory(const StoreEntry &e) const;
+
/* the number of store dirs being rebuilt. */
static int store_dirs_rebuilding;
private:
void createOneStore(Store &aStore);
+ bool keepInLocalMemoryCache(const StoreEntry &e) const;
StorePointer swapDir; ///< summary view of all disk caches
MemStore *memStore; ///< memory cache
};
/* migrating from the Config based list of swapdirs */
extern void allocate_new_swapdir(SquidConfig::_cacheSwap *);
extern void free_cachedir(SquidConfig::_cacheSwap * swap);
SQUIDCEXTERN OBJH storeDirStats;
SQUIDCEXTERN char *storeDirSwapLogFile(int, const char *);
SQUIDCEXTERN char *storeSwapFullPath(int, char *);
SQUIDCEXTERN char *storeSwapSubSubDir(int, char *);
SQUIDCEXTERN const char *storeSwapPath(int);
SQUIDCEXTERN int storeDirWriteCleanLogs(int reopen);
SQUIDCEXTERN STDIRSELECT *storeDirSelectSwapDir;
SQUIDCEXTERN int storeVerifySwapDirs(void);
SQUIDCEXTERN void storeDirCloseSwapLogs(void);
SQUIDCEXTERN void storeDirCloseTmpSwapLog(int dirn);
SQUIDCEXTERN void storeDirDiskFull(sdirno);
SQUIDCEXTERN void storeDirOpenSwapLogs(void);
=== modified file 'src/store.cc'
--- src/store.cc 2012-01-13 13:49:26 +0000
+++ src/store.cc 2012-07-05 21:25:34 +0000
@@ -1435,47 +1435,44 @@ storeConfigure(void)
store_swap_low = (long) (((float) Store::Root().maxSize() *
(float) Config.Swap.lowWaterMark) / (float) 100);
store_pages_max = Config.memMaxSize / sizeof(mem_node);
}
bool
StoreEntry::memoryCachable() const
{
if (mem_obj == NULL)
return 0;
if (mem_obj->data_hdr.size() == 0)
return 0;
if (mem_obj->inmem_lo != 0)
return 0;
if (!Config.onoff.memory_cache_first && swap_status == SWAPOUT_DONE && refcount == 1)
return 0;
- if (Config.memShared && IamWorkerProcess()) {
- const int64_t expectedSize = mem_obj->expectedReplySize();
- // objects of unknown size are not allowed into memory cache, for now
- if (expectedSize < 0 ||
- expectedSize > static_cast<int64_t>(Config.Store.maxInMemObjSize))
- return 0;
- }
+ const int64_t size =
+ max(mem_obj->expectedReplySize(), mem_obj->endOffset());
+ if (size > static_cast<int64_t>(Config.Store.maxInMemObjSize))
+ return 0;
return 1;
}
int
StoreEntry::checkNegativeHit() const
{
if (!EBIT_TEST(flags, ENTRY_NEGCACHED))
return 0;
if (expires <= squid_curtime)
return 0;
if (store_status != STORE_OK)
return 0;
return 1;
}
/**
@@ -1885,40 +1882,43 @@ StoreEntry::getSerialisedMetaData()
StoreMeta *tlv_list = storeSwapMetaBuild(this);
int swap_hdr_sz;
char *result = storeSwapMetaPack(tlv_list, &swap_hdr_sz);
storeSwapTLVFree(tlv_list);
assert (swap_hdr_sz >= 0);
mem_obj->swap_hdr_sz = (size_t) swap_hdr_sz;
return result;
}
void
StoreEntry::trimMemory(const bool preserveSwappable)
{
/*
* DPW 2007-05-09
* Bug #1943. We must not let go any data for IN_MEMORY
* objects. We have to wait until the mem_status changes.
*/
if (mem_status == IN_MEMORY)
return;
+ if (Store::Root().keepInLocalMemory(*this))
+ return; // keep entry in memory for later use
+
if (!preserveSwappable) {
if (mem_obj->policyLowestOffsetToKeep(0) == 0) {
/* Nothing to do */
return;
}
/*
* Its not swap-able, and we're about to delete a chunk,
* so we must make it PRIVATE. This is tricky/ugly because
* for the most part, we treat swapable == cachable here.
*/
releaseRequest();
mem_obj->trimUnSwappable ();
} else {
mem_obj->trimSwappable ();
}
}
bool
StoreEntry::modifiedSince(HttpRequest * request) const
{
=== modified file 'src/store_dir.cc'
--- src/store_dir.cc 2011-10-14 16:21:48 +0000
+++ src/store_dir.cc 2012-07-05 21:02:36 +0000
@@ -707,40 +707,57 @@ StoreController::dereference(StoreEntry
bool keepInStoreTable = true; // keep if there are no objections
/* Notify the fs that we're not referencing this object any more */
if (e.swap_filen > -1)
keepInStoreTable = swapDir->dereference(e) && keepInStoreTable;
// Notify the memory cache that we're not referencing this object any more
if (memStore && e.mem_status == IN_MEMORY)
keepInStoreTable = memStore->dereference(e) && keepInStoreTable;
// 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);
}
return keepInStoreTable;
}
+bool
+StoreController::keepInLocalMemory(const StoreEntry &e) const
+{
+ return memStore ? memStore->keepInLocalMemory(e) :
+ keepInLocalMemoryCache(e);
+}
+
+// whether entry should be kept in local memory cache
+bool
+StoreController::keepInLocalMemoryCache(const StoreEntry &e) const
+{
+ Must(!memStore); // Should(), really
+ return e.memoryCachable() && // entry is in good shape and
+ // the local memory cache is not overflowing
+ (mem_node::InUseCount() <= store_pages_max);
+}
+
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 when
// the global store_table is no longer used for in-transit objects.
if (const int cacheDirs = Config.cacheSwap.n_configured) {
@@ -763,45 +780,42 @@ StoreController::get(const cache_key *ke
}
debugs(20, 4, HERE << "none of " << Config.cacheSwap.n_configured <<
" cache_dirs have " << storeKeyText(key));
return NULL;
}
void
StoreController::get(String const key, STOREGETCLIENT aCallback, void *aCallbackData)
{
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);
- }
+ } else
+ keepInLocalMemory = keepInLocalMemoryCache(e);
// An idle, unlocked entry that belongs to a SwapDir which controls
// its own index, should not stay in the global store_table.
if (!dereference(e)) {
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()
{