Hello,
The attached patch avoids memory corruption and segfaults during ufs
cache store maintenance by removing being-deleted StoreEntry from ufs
cache replacement policies.
SMP Store changes added a SwapDir::disconnect() method that notified
entry's cache_dir that its entry is being destroyed. I added that code
so that shared cache_dirs can update their index when idle store_table
entries are destroyed on cleanup (completely avoiding idle entries
proved to be too difficult with store_table still around).
However, I did not realize that non-shared cache_dirs have the same
problem: Their StoreEntries may be deleted without cache_dirs knowing
that. UFS cache_dir now implement the SwapDir::disconnect() API to stay
in sync. It is likely that the problem was introduced by SMP Store
changes, but I do not know whether there is a better fix for it.
I also added assertions to catch cases where a dirty StoreEntry or
MemObject (i.e., an entry or object that is still in some replacement
policy index) is being destroyed.
TODO: This patch breaks "make check". We need more STUBs somewhere!
Alex.
Avoid memory corruption and segfaults during ufs cache store maintenance
by removing being-deleted StoreEntry from ufs cache replacement policies.
SMP Store changes added a SwapDir::disconnect() method that notified
entry's cache_dir that its entry is being destroyed. I added that code so that
shared cache_dirs can update their index when idle store_table entries are
destroyed on cleanup (completely avoiding idle entries proved to be too
difficult with store_table still around).
However, I did not realize that non-shared cache_dirs have the same problem:
Their StoreEntries may be deleted without cache_dirs knowing that. UFS
cache_dir now implement the SwapDir::disconnect() API to stay in sync. It is
likely that the problem was introduced by SMP Store changes, but I do not know
whether there is a better fix for it.
I also added assertions to catch cases where a dirty StoreEntry or MemObject
(i.e., an entry or object that is still in some replacement policy index) is
being destroyed.
TODO: This patch breaks "make check". We need more STUBs.
=== modified file 'src/MemObject.cc'
--- src/MemObject.cc 2012-07-20 12:44:39 +0000
+++ src/MemObject.cc 2012-07-25 23:37:21 +0000
@@ -101,40 +101,42 @@
/* XXX account log_url */
swapout.decision = SwapOut::swNeedsCheck;
}
MemObject::~MemObject()
{
debugs(20, 3, HERE << "del MemObject " << this);
const Ctx ctx = ctx_enter(url);
#if URL_CHECKSUM_DEBUG
assert(chksum == url_checksum(url));
#endif
if (!shutting_down)
assert(swapout.sio == NULL);
data_hdr.freeContent();
+ assert(!repl.data); // we are not in some replacement policy queue
+
#if 0
/*
* There is no way to abort FD-less clients, so they might
* still have mem->clients set.
*/
assert(clients.head == NULL);
#endif
HTTPMSGUNLOCK(_reply);
HTTPMSGUNLOCK(request);
ctx_exit(ctx); /* must exit before we free mem->url */
safe_free(url);
safe_free(log_url); /* XXX account log_url */
safe_free(vary_headers);
=== modified file 'src/SwapDir.h'
--- src/SwapDir.h 2012-07-13 14:33:19 +0000
+++ src/SwapDir.h 2012-07-26 00:01:59 +0000
@@ -142,41 +142,41 @@
virtual void diskFull();
virtual StoreEntry * get(const cache_key *);
virtual void get(String const, STOREGETCLIENT, void * cbdata);
virtual uint64_t maxSize() const { return max_size;}
virtual uint64_t minSize() const;
virtual int64_t maxObjectSize() const { return max_objsize; }
virtual void getStats(StoreInfoStats &stats) const;
virtual void stat (StoreEntry &anEntry) const;
virtual StoreSearch *search(String const url, HttpRequest *) = 0;
/* migrated from store_dir.cc */
bool objectSizeIsAcceptable(int64_t objsize) const;
/// called when the entry is about to forget its association with cache_dir
- virtual void disconnect(StoreEntry &) {}
+ virtual void disconnect(StoreEntry &e) = 0;
/// called when entry swap out is complete
virtual void swappedOut(const StoreEntry &e) = 0;
protected:
void parseOptions(int reconfiguring);
void dumpOptions(StoreEntry * e) const;
virtual ConfigOption *getOptionTree() const;
virtual bool allowOptionReconfigure(const char *const) const { return true; }
int64_t sizeInBlocks(const int64_t size) const { return (size + fs.blksize - 1) / fs.blksize; }
private:
bool optionReadOnlyParse(char const *option, const char *value, int reconfiguring);
void optionReadOnlyDump(StoreEntry * e) const;
bool optionObjectSizeParse(char const *option, const char *value, int reconfiguring);
void optionObjectSizeDump(StoreEntry * e) const;
char const *theType;
protected:
=== modified file 'src/fs/ufs/store_dir_ufs.cc'
--- src/fs/ufs/store_dir_ufs.cc 2012-07-23 15:34:12 +0000
+++ src/fs/ufs/store_dir_ufs.cc 2012-07-26 00:18:20 +0000
@@ -420,40 +420,60 @@
if (repl->Referenced)
repl->Referenced(repl, &e, &e.repl);
}
/*
* UFSSwapDir::dereference
* This routine is called whenever the last reference to an object is
* removed, to maintain replacement information within the storage fs.
*/
bool
UFSSwapDir::dereference(StoreEntry & e)
{
debugs(47, 3, "UFSSwapDir::dereference: referencing " << &e << " " << e.swap_dirn << "/" << e.swap_filen);
if (repl->Dereferenced)
repl->Dereferenced(repl, &e, &e.repl);
return true; // keep e in the global store_table
}
+void
+UFSSwapDir::disconnect(StoreEntry &e)
+{
+ assert(e.swap_dirn == index);
+ assert(e.swap_filen >= 0);
+
+ // We do not want to unlink(e) in this low-level method because it is
+ // called during Squid shutdown sequence (among other times).
+ if (e.swap_status == SWAPOUT_DONE) {
+ cur_size -= fs.blksize * sizeInBlocks(e.swap_file_sz);
+ --n_disk_objects;
+ }
+ replacementRemove(&e);
+ mapBitReset(e.swap_filen);
+
+ e.swap_dirn = -1;
+ e.swap_filen = -1;
+ e.swap_status = SWAPOUT_NONE;
+}
+
StoreIOState::Pointer
UFSSwapDir::createStoreIO(StoreEntry &e, StoreIOState::STFNCB * file_callback, StoreIOState::STIOCB * aCallback, void *callback_data)
{
return IO->create (this, &e, file_callback, aCallback, callback_data);
}
StoreIOState::Pointer
UFSSwapDir::openStoreIO(StoreEntry &e, StoreIOState::STFNCB * file_callback, StoreIOState::STIOCB * aCallback, void *callback_data)
{
return IO->open (this, &e, file_callback, aCallback, callback_data);
}
int
UFSSwapDir::mapBitTest(sfileno filn)
{
return map->testBit(filn);
}
void
UFSSwapDir::mapBitSet(sfileno filn)
=== modified file 'src/fs/ufs/ufscommon.h'
--- src/fs/ufs/ufscommon.h 2012-07-20 23:11:02 +0000
+++ src/fs/ufs/ufscommon.h 2012-07-26 00:02:33 +0000
@@ -50,40 +50,41 @@
public:
static int IsUFSDir(SwapDir* sd);
static int DirClean(int swap_index);
static int FilenoBelongsHere(int fn, int F0, int F1, int F2);
UFSSwapDir(char const *aType, const char *aModuleType);
virtual void init();
virtual void create();
virtual void dump(StoreEntry &) const;
~UFSSwapDir();
virtual StoreSearch *search(String const url, HttpRequest *);
virtual bool doubleCheck(StoreEntry &);
virtual bool unlinkdUseful() const;
virtual void unlink(StoreEntry &);
virtual void statfs(StoreEntry &)const;
virtual void maintain();
virtual bool canStore(const StoreEntry &e, int64_t diskSpaceNeeded, int &load) const;
virtual void reference(StoreEntry &);
virtual bool dereference(StoreEntry &);
+ virtual void disconnect(StoreEntry &e);
virtual StoreIOState::Pointer createStoreIO(StoreEntry &, StoreIOState::STFNCB *, StoreIOState::STIOCB *, void *);
virtual StoreIOState::Pointer openStoreIO(StoreEntry &, StoreIOState::STFNCB *, StoreIOState::STIOCB *, void *);
virtual void openLog();
virtual void closeLog();
virtual int writeCleanStart();
virtual void writeCleanDone();
virtual void logEntry(const StoreEntry & e, int op) const;
virtual void parse(int index, char *path);
virtual void reconfigure();
virtual int callback();
virtual void sync();
virtual void swappedOut(const StoreEntry &e);
virtual uint64_t currentSize() const { return cur_size; }
virtual uint64_t currentCount() const { return n_disk_objects; }
void unlinkFile(sfileno f);
// move down when unlink is a virtual method
//protected:
UFSStrategy *IO;
char *fullPath(sfileno, char *) const;
=== modified file 'src/store.cc'
--- src/store.cc 2012-07-23 07:02:06 +0000
+++ src/store.cc 2012-07-25 23:37:21 +0000
@@ -386,40 +386,42 @@
hidden_mem_obj(NULL),
swap_file_sz(0)
{
debugs(20, 3, HERE << "new StoreEntry " << this);
mem_obj = new MemObject(aUrl, aLogUrl);
expires = lastmod = lastref = timestamp = -1;
swap_status = SWAPOUT_NONE;
swap_filen = -1;
swap_dirn = -1;
}
StoreEntry::~StoreEntry()
{
if (swap_filen >= 0) {
SwapDir &sd = dynamic_cast<SwapDir&>(*store());
sd.disconnect(*this);
}
delete hidden_mem_obj;
+ assert(!mem_obj);
+ assert(!repl.data);
}
#if USE_ADAPTATION
void
StoreEntry::deferProducer(const AsyncCall::Pointer &producer)
{
if (!deferredProducer)
deferredProducer = producer;
else
debugs(20, 5, HERE << "Deferred producer call is allready set to: " <<
*deferredProducer << ", requested call: " << *producer);
}
void
StoreEntry::kickProducer()
{
if (deferredProducer != NULL) {
ScheduleCallHere(deferredProducer);
deferredProducer = NULL;
}
@@ -434,42 +436,42 @@
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
destroyStoreEntry(void *data)
{
- debugs(20, 3, HERE << "destroyStoreEntry: destroying " << data);
StoreEntry *e = static_cast<StoreEntry *>(static_cast<hash_link *>(data));
+ debugs(20, 3, HERE << "destroyStoreEntry: destroying " << e << " or " << data);
assert(e != NULL);
if (e == NullStoreEntry::getInstance())
return;
e->destroyMemObject();
e->hashDelete();
assert(e->key == NULL);
delete e;
}
/* ----- INTERFACE BETWEEN STORAGE MANAGER AND HASH TABLE FUNCTIONS --------- */
void
StoreEntry::hashInsert(const cache_key * someKey)
{
debugs(20, 3, "StoreEntry::hashInsert: Inserting Entry " << this << " key '" << storeKeyText(someKey) << "'");