]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/pconn.cc
Merge form trunk
[thirdparty/squid.git] / src / pconn.cc
index 4e0482ac097eeec8dcd6017f23d3b24099247b4e..e14865e6fe10820fc72f557ebfd2c6d0e337a3f8 100644 (file)
@@ -1,6 +1,5 @@
-
 /*
- * $Id: pconn.cc,v 1.33 2002/10/13 20:35:02 robertc Exp $
+ * $Id$
  *
  * DEBUG: section 48    Persistent Connections
  * AUTHOR: Duane Wessels
  *  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.
  */
 
 #include "squid.h"
+#include "comm.h"
+#include "comm/Connection.h"
+#include "fde.h"
+#include "mgr/Registration.h"
+#include "pconn.h"
 #include "Store.h"
 
-struct _pconn {
-    hash_link hash;            /* must be first */
-    int *fds;
-    int nfds_alloc;
-    int nfds;
-};
-typedef struct _pconn pconn;
-
 #define PCONN_FDS_SZ   8       /* pconn set size, increase for better memcache hit rate */
-#define PCONN_HIST_SZ (1<<16)
-int client_pconn_hist[PCONN_HIST_SZ];
-int server_pconn_hist[PCONN_HIST_SZ];
 
-static PF pconnRead;
-static PF pconnTimeout;
-static const char *pconnKey(const char *host, u_short port);
-static hash_table *table = NULL;
-static struct _pconn *pconnNew(const char *key);
-static void pconnDelete(struct _pconn *p);
-static void pconnRemoveFD(struct _pconn *p, int fd);
-static OBJH pconnHistDump;
-static MemPool *pconn_fds_pool = NULL;
-CBDATA_TYPE(pconn);
+//TODO: re-attach to MemPools. WAS: static MemAllocator *pconn_fds_pool = NULL;
+PconnModule * PconnModule::instance = NULL;
+CBDATA_CLASS_INIT(IdleConnList);
 
+/* ========== IdleConnList ============================================ */
 
+IdleConnList::IdleConnList(const char *key, PconnPool *thePool) :
+        capacity_(PCONN_FDS_SZ),
+        size_(0),
+        parent_(thePool)
+{
+    hash.key = xstrdup(key);
+    theList_ = new Comm::ConnectionPointer[capacity_];
+// TODO: re-attach to MemPools. WAS: theList = (?? *)pconn_fds_pool->alloc();
+}
 
-static const char *
-pconnKey(const char *host, u_short port)
+IdleConnList::~IdleConnList()
 {
-    LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN + 10);
-    snprintf(buf, SQUIDHOSTNAMELEN + 10, "%s.%d", host, (int) port);
-    return buf;
+    parent_->unlinkList(this);
+
+/* TODO: re-attach to MemPools.
+    if (capacity_ == PCONN_FDS_SZ)
+        pconn_fds_pool->freeOne(theList_);
+    else
+*/
+    delete[] theList_;
+
+    xfree(hash.key);
+}
+
+/** Search the list. Matches by FD socket number.
+ * Performed from the end of list where newest entries are.
+ *
+ * \retval <0   The connection is not listed
+ * \retval >=0  The connection array index
+ */
+int
+IdleConnList::findIndexOf(const Comm::ConnectionPointer &conn) const
+{
+    for (int index = size_ - 1; index >= 0; --index) {
+        if (conn->fd == theList_[index]->fd) {
+            debugs(48, 3, HERE << "found " << conn << " at index " << index);
+            return index;
+        }
+    }
+
+    debugs(48, 2, HERE << conn << " NOT FOUND!");
+    return -1;
+}
+
+/** Remove the entry at specified index.
+ * \retval false The index is not an in-use entry.
+ */
+bool
+IdleConnList::removeAt(int index)
+{
+    if (index < 0 || index >= size_)
+        return false;
+
+    // shuffle the remaining entries to fill the new gap.
+    for (; index < size_ - 1; index++)
+        theList_[index] = theList_[index + 1];
+    theList_[size_] = NULL;
+
+    if (--size_ == 0) {
+        debugs(48, 3, HERE << "deleting " << hashKeyStr(&hash));
+        delete this;
+    }
+    return true;
 }
 
-static struct _pconn *
-pconnNew(const char *key)
+void
+IdleConnList::clearHandlers(const Comm::ConnectionPointer &conn)
 {
-    pconn *p;
-    CBDATA_INIT_TYPE(pconn);
-    p = cbdataAlloc(pconn);
-    p->hash.key = xstrdup(key);
-    p->nfds_alloc = PCONN_FDS_SZ;
-    p->fds = (int *)memPoolAlloc(pconn_fds_pool);
-    debug(48, 3) ("pconnNew: adding %s\n", hashKeyStr(&p->hash));
-    hash_join(table, &p->hash);
-    return p;
+    comm_read_cancel(conn->fd, IdleConnList::Read, this);
+    commSetTimeout(conn->fd, -1, NULL, NULL);
 }
 
-static void
-pconnDelete(struct _pconn *p)
+void
+IdleConnList::push(const Comm::ConnectionPointer &conn)
 {
-    debug(48, 3) ("pconnDelete: deleting %s\n", hashKeyStr(&p->hash));
-    hash_remove_link(table, (hash_link *) p);
-    if (p->nfds_alloc == PCONN_FDS_SZ)
-       memPoolFree(pconn_fds_pool, p->fds);
-    else
-       xfree(p->fds);
-    xfree(p->hash.key);
-    cbdataFree(p);
+    if (size_ == capacity_) {
+        debugs(48, 3, HERE << "growing idle Connection array");
+        capacity_ <<= 1;
+        const Comm::ConnectionPointer *oldList = theList_;
+        theList_ = new Comm::ConnectionPointer[capacity_];
+        for (int index = 0; index < size_; index++)
+            theList_[index] = oldList[index];
+
+/* TODO: re-attach to MemPools.
+        if (size_ == PCONN_FDS_SZ)
+            pconn_fds_pool->freeOne(oldList);
+        else
+*/
+        delete[] oldList;
+    }
+
+    theList_[size_++] = conn;
+    comm_read(conn, fakeReadBuf_, sizeof(fakeReadBuf_), IdleConnList::Read, this);
+    commSetTimeout(conn->fd, Config.Timeout.pconn, IdleConnList::Timeout, this);
 }
 
-static void
-pconnRemoveFD(struct _pconn *p, int fd)
+/*
+ * XXX this routine isn't terribly efficient - if there's a pending
+ * read event (which signifies the fd will close in the next IO loop!)
+ * we ignore the FD and move onto the next one. This means, as an example,
+ * if we have a lot of FDs open to a very popular server and we get a bunch
+ * of requests JUST as they timeout (say, it shuts down) we'll be wasting
+ * quite a bit of CPU. Just keep it in mind.
+ */
+Comm::ConnectionPointer
+IdleConnList::findUseable(const Comm::ConnectionPointer &key)
 {
-    int i;
-    for (i = 0; i < p->nfds; i++) {
-       if (p->fds[i] == fd)
-           break;
+    assert(size_);
+
+    for (int i=size_-1; i>=0; i--) {
+
+        // callback pending indicates that remote end of the conn has just closed.
+        if (comm_has_pending_read_callback(theList_[i]->fd))
+            continue;
+
+        // local end port is required, but dont match.
+        if (key->local.GetPort() > 0 && key->local.GetPort() != theList_[i]->local.GetPort())
+            continue;
+
+        // local address is required, but does not match.
+        if (!key->local.IsAnyAddr() && key->local.matchIPAddr(theList_[i]->local) != 0)
+            continue;
+
+        // finally, a match. pop and return it.
+        Comm::ConnectionPointer result = theList_[i];
+        /* may delete this */
+        removeAt(i);
+        return result;
     }
-    assert(i < p->nfds);
-    debug(48, 3) ("pconnRemoveFD: found FD %d at index %d\n", fd, i);
-    for (; i < p->nfds - 1; i++)
-       p->fds[i] = p->fds[i + 1];
-    if (--p->nfds == 0)
-       pconnDelete(p);
+
+    return Comm::ConnectionPointer();
 }
 
-static void
-pconnTimeout(int fd, void *data)
+void
+IdleConnList::Read(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
 {
-    struct _pconn *p = (_pconn *)data;
-    assert(table != NULL);
-    debug(48, 3) ("pconnTimeout: FD %d %s\n", fd, hashKeyStr(&p->hash));
-    pconnRemoveFD(p, fd);
-    comm_close(fd);
+    debugs(48, 3, HERE << len << " bytes from " << conn);
+
+    if (flag == COMM_ERR_CLOSING) {
+        /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
+        return;
+    }
+
+    IdleConnList *list = (IdleConnList *) data;
+    int index = list->findIndexOf(conn);
+    if (index >= 0) {
+        /* might delete list */
+        list->removeAt(index);
+        conn->close();
+    }
 }
 
-static void
-pconnRead(int fd, void *data)
+void
+IdleConnList::Timeout(int fd, void *data)
 {
-    LOCAL_ARRAY(char, buf, 256);
-    struct _pconn *p = (_pconn *)data;
-    int n;
-    assert(table != NULL);
-    statCounter.syscalls.sock.reads++;
-    n = FD_READ_METHOD(fd, buf, 256);
-    debug(48, 3) ("pconnRead: %d bytes from FD %d, %s\n", n, fd,
-       hashKeyStr(&p->hash));
-    pconnRemoveFD(p, fd);
-    comm_close(fd);
+    debugs(48, 3, HERE << "FD " << fd);
+    IdleConnList *list = (IdleConnList *) data;
+    Comm::ConnectionPointer temp = new Comm::Connection; // XXX: transition. make timeouts pass conn in
+    temp->fd = fd;
+    int index = list->findIndexOf(temp);
+    if (index >= 0) {
+        /* might delete list */
+        list->removeAt(index);
+        temp->close();
+    } else
+        temp->fd = -1; // XXX: transition. prevent temp erasure double-closing FD until timeout CB passess conn in.
 }
 
-static void
-pconnHistDump(StoreEntry * e)
+/* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
+
+const char *
+PconnPool::key(const Comm::ConnectionPointer &destLink, const char *domain)
 {
-    int i;
-    storeAppendPrintf(e,
-       "Client-side persistent connection counts:\n"
-       "\n"
-       "\treq/\n"
-       "\tconn      count\n"
-       "\t----  ---------\n");
-    for (i = 0; i < PCONN_HIST_SZ; i++) {
-       if (client_pconn_hist[i] == 0)
-           continue;
-       storeAppendPrintf(e, "\t%4d  %9d\n", i, client_pconn_hist[i]);
+    LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 3 + 10);
+
+    destLink->remote.ToURL(buf, SQUIDHOSTNAMELEN * 3 + 10);
+    if (domain) {
+        const int used = strlen(buf);
+        snprintf(buf+used, SQUIDHOSTNAMELEN * 3 + 10-used, "/%s", domain);
     }
+
+    debugs(48,6,"PconnPool::key(" << destLink << ", " << (domain?domain:"[no domain]") << ") is {" << buf << "}" );
+    return buf;
+}
+
+void
+PconnPool::dumpHist(StoreEntry * e) const
+{
     storeAppendPrintf(e,
-       "\n"
-       "Server-side persistent connection counts:\n"
-       "\n"
-       "\treq/\n"
-       "\tconn      count\n"
-       "\t----  ---------\n");
-    for (i = 0; i < PCONN_HIST_SZ; i++) {
-       if (server_pconn_hist[i] == 0)
-           continue;
-       storeAppendPrintf(e, "\t%4d  %9d\n", i, server_pconn_hist[i]);
+                      "%s persistent connection counts:\n"
+                      "\n"
+                      "\treq/\n"
+                      "\tconn      count\n"
+                      "\t----  ---------\n",
+                      descr);
+
+    for (int i = 0; i < PCONN_HIST_SZ; i++) {
+        if (hist[i] == 0)
+            continue;
+
+        storeAppendPrintf(e, "\t%4d  %9d\n", i, hist[i]);
     }
 }
 
-/* ========== PUBLIC FUNCTIONS ============================================ */
+void
+PconnPool::dumpHash(StoreEntry *e) const
+{
+    hash_table *hid = table;
+    hash_first(hid);
 
+    int i = 0;
+    for (hash_link *walker = hid->next; walker; walker = hash_next(hid)) {
+        storeAppendPrintf(e, "\t item %5d: %s\n", i++, (char *)(walker->key));
+    }
+}
 
-void
-pconnInit(void)
+/* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
+
+PconnPool::PconnPool(const char *aDescr) : table(NULL), descr(aDescr)
 {
-    int i;
-    assert(table == NULL);
     table = hash_create((HASHCMP *) strcmp, 229, hash_string);
-    for (i = 0; i < PCONN_HIST_SZ; i++) {
-       client_pconn_hist[i] = 0;
-       server_pconn_hist[i] = 0;
-    }
-    pconn_fds_pool = memPoolCreate("pconn_fds", PCONN_FDS_SZ * sizeof(int));
 
-    cachemgrRegister("pconn",
-       "Persistent Connection Utilization Histograms",
-       pconnHistDump, 0, 1);
-    debug(48, 3) ("persistent connection module initialized\n");
+    for (int i = 0; i < PCONN_HIST_SZ; i++)
+        hist[i] = 0;
+
+    PconnModule::GetInstance()->add(this);
+}
+
+PconnPool::~PconnPool()
+{
+    descr = NULL;
+    hashFreeMemory(table);
 }
 
 void
-pconnPush(int fd, const char *host, u_short port)
+PconnPool::push(const Comm::ConnectionPointer &conn, const char *domain)
 {
-    struct _pconn *p;
-    int *old;
-    LOCAL_ARRAY(char, key, SQUIDHOSTNAMELEN + 10);
-    LOCAL_ARRAY(char, desc, FD_DESC_SZ);
-    if (fdNFree() < (RESERVED_FD << 1)) {
-       debug(48, 3) ("pconnPush: Not many unused FDs\n");
-       comm_close(fd);
-       return;
+    if (fdUsageHigh()) {
+        debugs(48, 3, HERE << "Not many unused FDs");
+        conn->close();
+        return;
     } else if (shutting_down) {
-       comm_close(fd);
-       return;
+        conn->close();
+        debugs(48, 3, HERE << "Squid is shutting down. Refusing to do anything");
+        return;
     }
-    assert(table != NULL);
-    strcpy(key, pconnKey(host, port));
-    p = (struct _pconn *) hash_lookup(table, key);
-    if (p == NULL)
-       p = pconnNew(key);
-    if (p->nfds == p->nfds_alloc) {
-       debug(48, 3) ("pconnPush: growing FD array\n");
-       p->nfds_alloc <<= 1;
-       old = p->fds;
-       p->fds = (int *)xmalloc(p->nfds_alloc * sizeof(int));
-       xmemcpy(p->fds, old, p->nfds * sizeof(int));
-       if (p->nfds == PCONN_FDS_SZ)
-           memPoolFree(pconn_fds_pool, old);
-       else
-           xfree(old);
+
+    const char *aKey = key(conn, domain);
+    IdleConnList *list = (IdleConnList *) hash_lookup(table, aKey);
+
+    if (list == NULL) {
+        list = new IdleConnList(aKey, this);
+        debugs(48, 3, HERE << "new IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
+        hash_join(table, &list->hash);
+    } else {
+        debugs(48, 3, HERE << "found IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
     }
-    p->fds[p->nfds++] = fd;
-    commSetSelect(fd, COMM_SELECT_READ, pconnRead, p, 0);
-    commSetTimeout(fd, Config.Timeout.pconn, pconnTimeout, p);
-    snprintf(desc, FD_DESC_SZ, "%s idle connection", host);
-    fd_note(fd, desc);
-    debug(48, 3) ("pconnPush: pushed FD %d for %s\n", fd, key);
+
+    list->push(conn);
+    assert(!comm_has_incomplete_write(conn->fd));
+
+    LOCAL_ARRAY(char, desc, FD_DESC_SZ);
+    snprintf(desc, FD_DESC_SZ, "Idle: %s", aKey);
+    fd_note(conn->fd, desc);
+    debugs(48, 3, HERE << "pushed " << conn << " for " << aKey);
 }
 
-int
-pconnPop(const char *host, u_short port)
+Comm::ConnectionPointer
+PconnPool::pop(const Comm::ConnectionPointer &destLink, const char *domain, bool isRetriable)
 {
-    struct _pconn *p;
-    hash_link *hptr;
-    int fd = -1;
-    LOCAL_ARRAY(char, key, SQUIDHOSTNAMELEN + 10);
-    assert(table != NULL);
-    strcpy(key, pconnKey(host, port));
-    hptr = (hash_link *)hash_lookup(table, key);
-    if (hptr != NULL) {
-       p = (struct _pconn *) hptr;
-       assert(p->nfds > 0);
-       fd = p->fds[0];
-       pconnRemoveFD(p, fd);
-       commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
-       commSetTimeout(fd, -1, NULL, NULL);
+    const char * aKey = key(destLink, domain);
+
+    IdleConnList *list = (IdleConnList *)hash_lookup(table, aKey);
+    if (list == NULL) {
+        debugs(48, 3, HERE << "lookup for key {" << aKey << "} failed.");
+        return Comm::ConnectionPointer();
+    } else {
+        debugs(48, 3, HERE << "found " << hashKeyStr(&list->hash) << (isRetriable?"(to use)":"(to kill)") );
     }
-    return fd;
+
+    /* may delete list */
+    Comm::ConnectionPointer temp = list->findUseable(destLink);
+    if (Comm::IsConnOpen(temp) && !isRetriable)
+        temp->close();
+
+    return temp;
 }
 
 void
-pconnHistCount(int what, int i)
+PconnPool::unlinkList(IdleConnList *list) const
 {
-    if (i >= PCONN_HIST_SZ)
-       i = PCONN_HIST_SZ - 1;
-    /* what == 0 for client, 1 for server */
-    if (what == 0)
-       client_pconn_hist[i]++;
-    else if (what == 1)
-       server_pconn_hist[i]++;
-    else
-       assert(0);
+    hash_remove_link(table, &list->hash);
+}
+
+void
+PconnPool::count(int uses)
+{
+    if (uses >= PCONN_HIST_SZ)
+        uses = PCONN_HIST_SZ - 1;
+
+    hist[uses]++;
+}
+
+/* ========== PconnModule ============================================ */
+
+/*
+ * This simple class exists only for the cache manager
+ */
+
+PconnModule::PconnModule() : pools(NULL), poolCount(0)
+{
+    pools = (PconnPool **) xcalloc(MAX_NUM_PCONN_POOLS, sizeof(*pools));
+//TODO: re-link to MemPools. WAS:    pconn_fds_pool = memPoolCreate("pconn_fds", PCONN_FDS_SZ * sizeof(int));
+    debugs(48, 0, "persistent connection module initialized");
+    registerWithCacheManager();
+}
+
+PconnModule *
+PconnModule::GetInstance()
+{
+    if (instance == NULL)
+        instance = new PconnModule;
+
+    return instance;
+}
+
+void
+PconnModule::registerWithCacheManager(void)
+{
+    Mgr::RegisterAction("pconn",
+                        "Persistent Connection Utilization Histograms",
+                        DumpWrapper, 0, 1);
+}
+
+void
+
+PconnModule::add(PconnPool *aPool)
+{
+    assert(poolCount < MAX_NUM_PCONN_POOLS);
+    *(pools+poolCount) = aPool;
+    poolCount++;
+}
+
+void
+PconnModule::dump(StoreEntry *e)
+{
+    for (int i = 0; i < poolCount; i++) {
+        storeAppendPrintf(e, "\n Pool %d Stats\n", i);
+        (*(pools+i))->dumpHist(e);
+        storeAppendPrintf(e, "\n Pool %d Hash Table\n",i);
+        (*(pools+i))->dumpHash(e);
+    }
+}
+
+void
+PconnModule::DumpWrapper(StoreEntry *e)
+{
+    PconnModule::GetInstance()->dump(e);
 }