]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/cbdata.cc
Cleanup: zap CVS Id tags
[thirdparty/squid.git] / src / cbdata.cc
index ea1f661eeea936c5094ecc65cd06b18dba63cb85..5675a787f94065f6120acf9824d2a097dc3be1bc 100644 (file)
@@ -1,10 +1,11 @@
 
 /*
- * $Id: cbdata.cc,v 1.37 2001/01/31 21:46:04 hno Exp $
+ * $Id$
  *
  * DEBUG: section 45    Callback Data Registry
  * ORIGINAL AUTHOR: Duane Wessels
  * Modified by Moez Mahfoudh (08/12/2000)
+ * History added by Robert Collins (2002-10-25)
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
  * ----------------------------------------------------------
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
- *  
+ *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
- *  
+ *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
-/*
+/**
+ \defgroup CBDATAInternal Callback Data Allocator Internals
+ \ingroup CBDATAAPI
+ *
  * These routines manage a set of registered callback data pointers.
- * One of the easiest ways to make Squid coredump is to issue a 
+ * One of the easiest ways to make Squid coredump is to issue a
  * callback to for some data structure which has previously been
  * freed.  With these routines, we register (add) callback data
  * pointers, lock them just before registering the callback function,
  * validate them before issuing the callback, and then free them
  * when finished.
- * 
- * In terms of time, the sequence goes something like this:
- * 
- * foo = cbdataAlloc(sizeof(foo),NULL);
- * ...
- * some_blocking_operation(..., callback_func, foo);
- *   cbdataLock(foo);
- *   ...
- *   some_blocking_operation_completes()
- *   if (cbdataValid(foo))
- *   callback_func(..., foo)
- *   cbdataUnlock(foo);
- * ...
- * cbdataFree(foo);
- * 
- * The nice thing is that, we do not need to require that Unlock
- * occurs before Free.  If the Free happens first, then the 
- * callback data is marked invalid and the callback will never
- * be made.  When we Unlock and the lock count reaches zero,
- * we free the memory if it is marked invalid.
  */
 
-#include "squid.h"
+#include "cbdata.h"
+#include "CacheManager.h"
+#include "Store.h"
+#if CBDATA_DEBUG
+#include "Stack.h"
+#endif
+#include "Generic.h"
+
+#if WITH_VALGRIND
+#define HASHED_CBDATA 1
+#endif
 
 static int cbdataCount = 0;
+#if CBDATA_DEBUG
+dlink_list cbdataEntries;
+#endif
+
+#if CBDATA_DEBUG
 
-typedef struct _cbdata {
+class CBDataCall
+{
+
+public:
+    CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine) {}
+
+    char const *label;
+    char const *file;
+    int line;
+};
+
+#endif
+
+/// \ingroup CBDATAInternal
+#define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER))
+
+/// \ingroup CBDATAInternal
+class cbdata
+{
+    /** \todo examine making cbdata templated on this - so we get type
+     * safe access to data - RBC 20030902 */
+public:
+#if HASHED_CBDATA
+    hash_link hash;    // Must be first
+#endif
+
+#if CBDATA_DEBUG
+
+    void dump(StoreEntry *)const;
+#endif
+
+#if !HASHED_CBDATA
+    void *operator new(size_t size, void *where);
+    void operator delete(void *where, void *where2);
+#else
+    MEMPROXY_CLASS(cndata);
+#endif
+
+    ~cbdata();
     int valid;
     int locks;
-    CBDUNL *free_func;
-    int type;                  /* move to CBDATA_DEBUG with type argument to cbdataFree */
+    cbdata_type type;
 #if CBDATA_DEBUG
+
+    void addHistory(char const *label, char const *file, int line) {
+        if (calls.size() > 1000)
+            return;
+
+        calls.push_back(new CBDataCall(label, file, line));
+    }
+
+    dlink_node link;
     const char *file;
     int line;
+    Stack<CBDataCall*> calls;
+#endif
+
+    /* cookie used while debugging */
+    long cookie;
+    void check(int line) const {assert(cookie == ((long)this ^ Cookie));}
+    static const long Cookie;
+
+#if !HASHED_CBDATA
+    size_t dataSize() const { return sizeof(data);}
+    static long MakeOffset();
+    static const long Offset;
+    /* MUST be the last per-instance member */
+    void *data;
+#endif
+
+};
+
+const long cbdata::Cookie((long)0xDEADBEEF);
+#if !HASHED_CBDATA
+const long cbdata::Offset(MakeOffset());
+
+void *
+cbdata::operator new(size_t size, void *where)
+{
+    // assert (size == sizeof(cbdata));
+    return where;
+}
+
+/**
+ * Only ever invoked when placement new throws
+ * an exception. Used to prevent an incorrect
+ * free.
+ */
+void
+cbdata::operator delete(void *where, void *where2)
+{
+    ; // empty.
+}
+
+long
+cbdata::MakeOffset()
+{
+    cbdata *zero = (cbdata *)0L;
+    void **dataOffset = &zero->data;
+    return (long)dataOffset;
+}
+#else
+MEMPROXY_CLASS_INLINE(cbdata);
 #endif
-    void *y;                   /* cookie used while debugging */
-    union {
-       void *pointer;
-       double double_float;
-       int integer;
-    } data;
-} cbdata;
 
 static OBJH cbdataDump;
+#ifdef CBDATA_DEBUG
+static OBJH cbdataDumpHistory;
+#endif
 
-static MemPool **cbdata_memory_pool = NULL;
+/// \ingroup CBDATAInternal
+struct CBDataIndex {
+    MemAllocator *pool;
+    FREE *free_func;
+}
+*cbdata_index = NULL;
+
+/// \ingroup CBDATAInternal
 int cbdata_types = 0;
 
-#define OFFSET_OF(type, member) ((int)(char *)&((type *)0L)->member)
+#if HASHED_CBDATA
+static hash_table *cbdata_htable = NULL;
 
-void
-cbdataInitType(cbdata_type type, char *name, int size)
+static int
+cbdata_cmp(const void *p1, const void *p2)
+{
+    return (char *) p1 - (char *) p2;
+}
+
+static unsigned int
+cbdata_hash(const void *p, unsigned int mod)
+{
+    return ((unsigned long) p >> 8) % mod;
+}
+#endif
+
+
+cbdata::~cbdata()
+{
+#if CBDATA_DEBUG
+    CBDataCall *aCall;
+
+    while ((aCall = calls.pop()))
+        delete aCall;
+
+#endif
+
+    FREE *free_func = cbdata_index[type].free_func;
+
+#if HASHED_CBDATA
+    void *p = hash.key;
+#else
+    void *p = &data;
+#endif
+
+    if (free_func)
+        free_func(p);
+}
+
+static void
+cbdataInternalInitType(cbdata_type type, const char *name, int size, FREE * free_func)
 {
     char *label;
-    if (type >= cbdata_types) {
-       cbdata_memory_pool = xrealloc(cbdata_memory_pool, (type + 1) * sizeof(*cbdata_memory_pool));
-       memset(&cbdata_memory_pool[cbdata_types], 0,
-           (type + 1 - cbdata_types) * sizeof(*cbdata_memory_pool));
-       cbdata_types = type + 1;
-    }
-    if (cbdata_memory_pool[type])
-       return;
-    label = xmalloc(strlen(name) + 20);
+    assert (type == cbdata_types + 1);
+
+    cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index));
+    memset(&cbdata_index[type], 0, sizeof(*cbdata_index));
+    cbdata_types = type;
+
+    label = (char *)xmalloc(strlen(name) + 20);
+
     snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
-    assert(OFFSET_OF(cbdata, data) == (sizeof(cbdata) - sizeof(((cbdata *) NULL)->data)));
-    cbdata_memory_pool[type] = memPoolCreate(label, size + OFFSET_OF(cbdata, data));
+
+#if !HASHED_CBDATA
+    assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize()));
+    size += cbdata::Offset;
+#endif
+
+    cbdata_index[type].pool = memPoolCreate(label, size);
+
+    cbdata_index[type].free_func = free_func;
+
+#if HASHED_CBDATA
+    if (!cbdata_htable)
+        cbdata_htable = hash_create(cbdata_cmp, 1 << 12, cbdata_hash);
+#endif
 }
 
 cbdata_type
-cbdataAddType(cbdata_type type, char *name, int size)
+cbdataInternalAddType(cbdata_type type, const char *name, int size, FREE * free_func)
 {
     if (type)
-       return type;
-    type = cbdata_types;
-    cbdataInitType(type, name, size);
+        return type;
+
+    type = (cbdata_type)(cbdata_types + 1);
+
+    cbdataInternalInitType(type, name, size, free_func);
+
     return type;
 }
 
 void
-cbdataInit(void)
+cbdataRegisterWithCacheManager(void)
 {
-    debug(45, 3) ("cbdataInit\n");
-    cachemgrRegister("cbdata",
-       "Callback Data Registry Contents",
-       cbdataDump, 0, 1);
-#define CREATE_CBDATA(type) cbdataInitType(CBDATA_##type, #type, sizeof(type))
-    CREATE_CBDATA(acl_access);
-    CREATE_CBDATA(aclCheck_t);
-    CREATE_CBDATA(clientHttpRequest);
-    CREATE_CBDATA(ConnStateData);
-    CREATE_CBDATA(ErrorState);
-    CREATE_CBDATA(FwdState);
-    CREATE_CBDATA(generic_cbdata);
-    CREATE_CBDATA(helper);
-    CREATE_CBDATA(helper_server);
-    CREATE_CBDATA(statefulhelper);
-    CREATE_CBDATA(helper_stateful_server);
-    CREATE_CBDATA(HttpStateData);
-    CREATE_CBDATA(peer);
-    CREATE_CBDATA(ps_state);
-    CREATE_CBDATA(RemovalPolicy);
-    CREATE_CBDATA(RemovalPolicyWalker);
-    CREATE_CBDATA(RemovalPurgeWalker);
-    CREATE_CBDATA(store_client);
-    CREATE_CBDATA(storeIOState);
+    CacheManager *manager=CacheManager::GetInstance();
+    manager->registerAction("cbdata",
+                            "Callback Data Registry Contents",
+                            cbdataDump, 0, 1);
+#if CBDATA_DEBUG
+
+    manager->registerAction("cbdatahistory",
+                            "Detailed call history for all current cbdata contents",
+                            cbdataDumpHistory, 0, 1);
+#endif
 }
 
 void *
 #if CBDATA_DEBUG
-cbdataInternalAllocDbg(cbdata_type type, CBDUNL * free_func, const char *file, int line)
+cbdataInternalAllocDbg(cbdata_type type, const char *file, int line)
 #else
-cbdataInternalAlloc(cbdata_type type, CBDUNL * free_func)
+cbdataInternalAlloc(cbdata_type type)
 #endif
 {
-    cbdata *p;
-    assert(type > 0 && type < cbdata_types);
-    p = memPoolAlloc(cbdata_memory_pool[type]);
-    p->type = type;
-    p->free_func = free_func;
-    p->valid = 1;
-    p->locks = 0;
-#if CBDATA_DEBUG
-    p->file = file;
-    p->line = line;
+    cbdata *c;
+    void *p;
+    assert(type > 0 && type <= cbdata_types);
+    /* placement new: the pool alloc gives us cbdata + user type memory space
+     * and we init it with cbdata at the start of it
+     */
+#if HASHED_CBDATA
+    c = new cbdata;
+    p = cbdata_index[type].pool->alloc();
+    c->hash.key = p;
+    hash_join(cbdata_htable, &c->hash);
+#else
+    c = new (cbdata_index[type].pool->alloc()) cbdata;
+    p = (void *)&c->data;
 #endif
-    p->y = p;
+
+    c->type = type;
+    c->valid = 1;
+    c->locks = 0;
+    c->cookie = (long) c ^ cbdata::Cookie;
     cbdataCount++;
+#if CBDATA_DEBUG
 
-    return (void *) &p->data;
+    c->file = file;
+    c->line = line;
+    c->calls = Stack<CBDataCall *> ();
+    c->addHistory("Alloc", file, line);
+    dlinkAdd(c, &c->link, &cbdataEntries);
+    debugs(45, 3, "cbdataAlloc: " << p << " " << file << ":" << line);
+#endif
+
+    return p;
 }
 
-void
-cbdataFree(void *p)
+void *
+#if CBDATA_DEBUG
+cbdataInternalFreeDbg(void *p, const char *file, int line)
+#else
+cbdataInternalFree(void *p)
+#endif
 {
     cbdata *c;
-    debug(45, 3) ("cbdataFree: %p\n", p);
-    assert(p);
-    c = (cbdata *) (((char *) p) - OFFSET_OF(cbdata, data));
-    assert(c->y == c);
+#if HASHED_CBDATA
+    c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+    c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+#if CBDATA_DEBUG
+
+    debugs(45, 3, "cbdataFree: " << p << " " << file << ":" << line);
+#else
+
+    debugs(45, 9, "cbdataFree: " << p);
+#endif
+
+    c->check(__LINE__);
+    assert(c->valid);
     c->valid = 0;
+#if CBDATA_DEBUG
+
+    c->addHistory("Free", file, line);
+#endif
+
     if (c->locks) {
-       debug(45, 3) ("cbdataFree: %p has %d locks, not freeing\n",
-           p, c->locks);
-       return;
+        debugs(45, 9, "cbdataFree: " << p << " has " << c->locks << " locks, not freeing");
+        return NULL;
     }
+
     cbdataCount--;
-    debug(45, 3) ("cbdataFree: Freeing %p\n", p);
-    if (c->free_func)
-       c->free_func((void *) p);
-    memPoolFree(cbdata_memory_pool[c->type], c);
+    debugs(45, 9, "cbdataFree: Freeing " << p);
+#if CBDATA_DEBUG
+
+    dlinkDelete(&c->link, &cbdataEntries);
+#endif
+
+    /* This is ugly. But: operator delete doesn't get
+     * the type parameter, so we can't use that
+     * to free the memory.
+     * So, we free it ourselves.
+     * Note that this means a non-placement
+     * new would be a seriously bad idea.
+     * Lastly, if we where a templated class,
+     * we could use the normal delete operator
+     * and it would Just Work. RBC 20030902
+     */
+    cbdata_type theType = c->type;
+#if HASHED_CBDATA
+    hash_remove_link(cbdata_htable, &c->hash);
+    delete c;
+    cbdata_index[theType].pool->free((void *)p);
+#else
+    c->cbdata::~cbdata();
+    cbdata_index[theType].pool->free(c);
+#endif
+    return NULL;
 }
 
 void
 #if CBDATA_DEBUG
-cbdataLockDbg(const void *p, const char *file, int line)
+cbdataInternalLockDbg(const void *p, const char *file, int line)
 #else
-cbdataLock(const void *p)
+cbdataInternalLock(const void *p)
 #endif
 {
     cbdata *c;
+
     if (p == NULL)
-       return;
-    c = (cbdata *) (((char *) p) - OFFSET_OF(cbdata, data));
-    assert(c->y == c);
-    debug(45, 3) ("cbdataLock: %p\n", p);
-    assert(c != NULL);
-    c->locks++;
+        return;
+
+#if HASHED_CBDATA
+    c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+    c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
 #if CBDATA_DEBUG
-    c->file = file;
-    c->line = line;
+
+    debugs(45, 3, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line);
+
+    c->addHistory("Reference", file, line);
+
+#else
+
+    debugs(45, 9, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1));
+
 #endif
+
+    c->check(__LINE__);
+
+    assert(c->locks < 65535);
+
+    c->locks++;
 }
 
 void
 #if CBDATA_DEBUG
-cbdataUnlockDbg(const void *p, const char *file, int line)
+cbdataInternalUnlockDbg(const void *p, const char *file, int line)
 #else
-cbdataUnlock(const void *p)
+cbdataInternalUnlock(const void *p)
 #endif
 {
     cbdata *c;
+
     if (p == NULL)
-       return;
-    c = (cbdata *) (((char *) p) - OFFSET_OF(cbdata, data));
-    assert(c->y == c);
-    debug(45, 3) ("cbdataUnlock: %p\n", p);
+        return;
+
+#if HASHED_CBDATA
+    c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+    c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
+#if CBDATA_DEBUG
+
+    debugs(45, 3, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line);
+
+    c->addHistory("Dereference", file, line);
+
+#else
+
+    debugs(45, 9, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1));
+
+#endif
+
+    c->check(__LINE__);
+
     assert(c != NULL);
+
     assert(c->locks > 0);
+
     c->locks--;
-#if CBDATA_DEBUG
-    c->file = file;
-    c->line = line;
-#endif
+
     if (c->valid || c->locks)
-       return;
+        return;
+
     cbdataCount--;
-    debug(45, 3) ("cbdataUnlock: Freeing %p\n", p);
-    if (c->free_func)
-       c->free_func((void *) p);
-    memPoolFree(cbdata_memory_pool[c->type], c);
+
+    debugs(45, 9, "cbdataUnlock: Freeing " << p);
+
+#if CBDATA_DEBUG
+
+    dlinkDelete(&c->link, &cbdataEntries);
+
+#endif
+
+    /* This is ugly. But: operator delete doesn't get
+     * the type parameter, so we can't use that
+     * to free the memory.
+     * So, we free it ourselves.
+     * Note that this means a non-placement
+     * new would be a seriously bad idea.
+     * Lastly, if we where a templated class,
+     * we could use the normal delete operator
+     * and it would Just Work. RBC 20030902
+     */
+    cbdata_type theType = c->type;
+#if HASHED_CBDATA
+    hash_remove_link(cbdata_htable, &c->hash);
+    delete c;
+    cbdata_index[theType].pool->free((void *)p);
+#else
+    c->cbdata::~cbdata();
+    cbdata_index[theType].pool->free(c);
+#endif
 }
 
 int
-cbdataValid(const void *p)
+cbdataReferenceValid(const void *p)
 {
     cbdata *c;
+
     if (p == NULL)
-       return 1;               /* A NULL pointer cannot become invalid */
-    debug(45, 3) ("cbdataValid: %p\n", p);
-    c = (cbdata *) (((char *) p) - OFFSET_OF(cbdata, data));
-    assert(c->y == c);
+        return 1;              /* A NULL pointer cannot become invalid */
+
+    debugs(45, 9, "cbdataReferenceValid: " << p);
+
+#if HASHED_CBDATA
+    c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+    c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
+    c->check(__LINE__);
+
     assert(c->locks > 0);
+
     return c->valid;
 }
 
+int
+#if CBDATA_DEBUG
+cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line)
+#else
+cbdataInternalReferenceDoneValid(void **pp, void **tp)
+#endif
+{
+    void *p = (void *) *pp;
+    int valid = cbdataReferenceValid(p);
+    *pp = NULL;
+#if CBDATA_DEBUG
+
+    cbdataInternalUnlockDbg(p, file, line);
+#else
+
+    cbdataInternalUnlock(p);
+#endif
+
+    if (valid) {
+        *tp = p;
+        return 1;
+    } else {
+        *tp = NULL;
+        return 0;
+    }
+}
+
+#if CBDATA_DEBUG
+void
+cbdata::dump(StoreEntry *sentry) const
+{
+#if HASHED_CBDATA
+    void *p = (void *)hash.key;
+#else
+    void *p = (void *)&data;
+#endif
+    storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' :
+                      '!', p, type, locks, file, line);
+}
+
+struct CBDataDumper : public unary_function<cbdata, void> {
+    CBDataDumper(StoreEntry *anEntry):where(anEntry) {}
+
+    void operator()(cbdata const &x) {
+        x.dump(where);
+    }
+
+    StoreEntry *where;
+};
+
+#endif
+
 static void
 cbdataDump(StoreEntry * sentry)
 {
     storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
-    storeAppendPrintf(sentry, "see also memory pools section\n");
+#if CBDATA_DEBUG
+
+    storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
+    CBDataDumper dumper(sentry);
+    for_each (cbdataEntries, dumper);
+    storeAppendPrintf(sentry, "\n");
+    storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n");
+
+    for (int i = 1; i < cbdata_types; i++) {
+        MemAllocator *pool = cbdata_index[i].pool;
+
+        if (pool) {
+#if HASHED_CBDATA
+            int obj_size = pool->objectSize();
+#else
+            int obj_size = pool->objectSize() - cbdata::Offset;
+#endif
+            storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.level, (long int)obj_size * pool->getMeter().inuse.level);
+        }
+    }
+
+#else
+    storeAppendPrintf(sentry, "detailed allocation information only available when compiled with CBDATA_DEBUG\n");
+
+#endif
+
+    storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n");
+}
+
+CBDATA_CLASS_INIT(generic_cbdata);
+
+#if CBDATA_DEBUG
+
+struct CBDataCallDumper : public unary_function<CBDataCall, void> {
+    CBDataCallDumper (StoreEntry *anEntry):where(anEntry) {}
+
+    void operator()(CBDataCall const &x) {
+        storeAppendPrintf(where, "%s\t%s\t%d\n", x.label, x.file, x.line);
+    }
+
+    StoreEntry *where;
+};
+
+struct CBDataHistoryDumper : public CBDataDumper {
+    CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry) {}
+
+    void operator()(cbdata const &x) {
+        CBDataDumper::operator()(x);
+        storeAppendPrintf(where, "\n");
+        storeAppendPrintf(where, "Action\tFile\tLine\n");
+        for_each (x.calls,callDumper);
+        storeAppendPrintf(where, "\n");
+    }
+
+    StoreEntry *where;
+    CBDataCallDumper callDumper;
+};
+
+void
+cbdataDumpHistory(StoreEntry *sentry)
+{
+    storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
+    storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
+    CBDataHistoryDumper dumper(sentry);
+    for_each (cbdataEntries, dumper);
 }
+
+#endif