-
/*
- * $Id: pconn.cc,v 1.44 2004/08/30 05:12:31 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 "Store.h"
#include "comm.h"
-
-struct _pconn
-{
- hash_link hash; /* must be first */
- int *fds;
- int nfds_alloc;
- int nfds;
- char buf[4096];
-};
-
-typedef struct _pconn pconn;
+#include "comm/Connection.h"
+#include "fde.h"
+#include "mgr/Registration.h"
+#include "pconn.h"
+#include "Store.h"
#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 IOCB pconnRead;
-static PF pconnTimeout;
-static const char *pconnKey(const char *host, u_short port, const char *domain);
-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 MemAllocator *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, const char *domain)
+IdleConnList::~IdleConnList()
{
- LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 2 + 10);
+ parent_->unlinkList(this);
- if (domain)
- snprintf(buf, SQUIDHOSTNAMELEN * 2 + 10, "%s:%d/%s", host, (int) port, domain);
+/* TODO: re-attach to MemPools.
+ if (capacity_ == PCONN_FDS_SZ)
+ pconn_fds_pool->freeOne(theList_);
else
- snprintf(buf, SQUIDHOSTNAMELEN * 2 + 10, "%s:%d", host, (int) port);
+*/
+ delete[] theList_;
- return buf;
+ xfree(hash.key);
}
-static struct _pconn *
- pconnNew(const char *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
{
- pconn *p;
- CBDATA_INIT_TYPE(pconn);
- p = cbdataAlloc(pconn);
- p->hash.key = xstrdup(key);
- p->nfds_alloc = PCONN_FDS_SZ;
- p->nfds = 0;
- p->fds = (int *)pconn_fds_pool->alloc();
- debug(48, 3) ("pconnNew: adding %s\n", hashKeyStr(&p->hash));
- hash_join(table, &p->hash);
- return p;
-}
+ 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;
+ }
+ }
-static void
+ debugs(48, 2, HERE << conn << " NOT FOUND!");
+ return -1;
+}
-pconnDelete(struct _pconn *p)
+/** Remove the entry at specified index.
+ * \retval false The index is not an in-use entry.
+ */
+bool
+IdleConnList::removeAt(int index)
{
- debug(48, 3) ("pconnDelete: deleting %s\n", hashKeyStr(&p->hash));
- hash_remove_link(table, (hash_link *) p);
-
- if (p->nfds_alloc == PCONN_FDS_SZ)
- pconn_fds_pool->free(p->fds);
- else
- xfree(p->fds);
+ if (index < 0 || index >= size_)
+ return false;
- xfree(p->hash.key);
+ // shuffle the remaining entries to fill the new gap.
+ for (; index < size_ - 1; index++)
+ theList_[index] = theList_[index + 1];
+ theList_[size_] = NULL;
- cbdataFree(p);
+ if (--size_ == 0) {
+ debugs(48, 3, HERE << "deleting " << hashKeyStr(&hash));
+ delete this;
+ }
+ return true;
}
-static int
-
-pconnFindFDIndex (struct _pconn *p, int fd)
+void
+IdleConnList::clearHandlers(const Comm::ConnectionPointer &conn)
{
- int result;
+ comm_read_cancel(conn->fd, IdleConnList::Read, this);
+ commSetTimeout(conn->fd, -1, NULL, NULL);
+}
- for (result = p->nfds - 1; result >= 0; --result)
- {
- if (p->fds[result] == fd)
- return result;
+void
+IdleConnList::push(const Comm::ConnectionPointer &conn)
+{
+ 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;
}
- return p->nfds;
+ theList_[size_++] = conn;
+ comm_read(conn, fakeReadBuf_, sizeof(fakeReadBuf_), IdleConnList::Read, this);
+ commSetTimeout(conn->fd, Config.Timeout.pconn, IdleConnList::Timeout, this);
}
-static void
-
-pconnRemoveFDByIndex (struct _pconn *p, int index)
+/*
+ * 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)
{
- for (; index < p->nfds - 1; index++)
- p->fds[index] = p->fds[index + 1];
-}
+ assert(size_);
-static void
-
-pconnPreventHandingOutFD(struct _pconn *p, int fd)
-{
- int i = pconnFindFDIndex (p, fd);
- assert(i >= 0);
- debug(48, 3) ("pconnRemoveFD: found FD %d at index %d\n", fd, i);
- pconnRemoveFDByIndex(p, i);
-}
+ for (int i=size_-1; i>=0; i--) {
-static void
+ // callback pending indicates that remote end of the conn has just closed.
+ if (comm_has_pending_read_callback(theList_[i]->fd))
+ continue;
-pconnRemoveFD(struct _pconn *p, int fd)
-{
- pconnPreventHandingOutFD(p, fd);
+ // local end port is required, but dont match.
+ if (key->local.GetPort() > 0 && key->local.GetPort() != theList_[i]->local.GetPort())
+ continue;
- if (--p->nfds == 0)
- pconnDelete(p);
-}
+ // local address is required, but does not match.
+ if (!key->local.IsAnyAddr() && key->local.matchIPAddr(theList_[i]->local) != 0)
+ continue;
-static void
-pconnTimeout(int fd, void *data)
-{
+ // finally, a match. pop and return it.
+ Comm::ConnectionPointer result = theList_[i];
+ /* may delete this */
+ removeAt(i);
+ return result;
+ }
- 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);
+ return Comm::ConnectionPointer();
}
-static void
-pconnRead(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, 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);
- /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
+ 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;
}
- debug(48, 3) ("pconnRead: %d bytes from FD %d, %s\n", (int) len, fd,
- hashKeyStr(&p->hash));
- pconnRemoveFD(p, fd);
- comm_close(fd);
+ IdleConnList *list = (IdleConnList *) data;
+ int index = list->findIndexOf(conn);
+ if (index >= 0) {
+ /* might delete list */
+ list->removeAt(index);
+ conn->close();
+ }
}
-static void
-pconnHistDump(StoreEntry * e)
+void
+IdleConnList::Timeout(int fd, void *data)
{
- int i;
- storeAppendPrintf(e,
- "Client-side persistent connection counts:\n"
- "\n"
- "\treq/\n"
- "\tconn count\n"
- "\t---- ---------\n");
+ 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.
+}
- for (i = 0; i < PCONN_HIST_SZ; i++) {
- if (client_pconn_hist[i] == 0)
- continue;
+/* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
+
+const char *
+PconnPool::key(const Comm::ConnectionPointer &destLink, const char *domain)
+{
+ LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 3 + 10);
- storeAppendPrintf(e, "\t%4d %9d\n", i, client_pconn_hist[i]);
+ 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"
+ "%s persistent connection counts:\n"
"\n"
"\treq/\n"
"\tconn count\n"
- "\t---- ---------\n");
+ "\t---- ---------\n",
+ descr);
- for (i = 0; i < PCONN_HIST_SZ; i++) {
- if (server_pconn_hist[i] == 0)
+ for (int i = 0; i < PCONN_HIST_SZ; i++) {
+ if (hist[i] == 0)
continue;
- storeAppendPrintf(e, "\t%4d %9d\n", i, server_pconn_hist[i]);
+ 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));
+ }
+}
+/* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
-void
-pconnInit(void)
+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;
- }
+ for (int i = 0; i < PCONN_HIST_SZ; i++)
+ hist[i] = 0;
- pconn_fds_pool = MemPools::GetInstance().create("pconn_fds", PCONN_FDS_SZ * sizeof(int));
+ PconnModule::GetInstance()->add(this);
+}
- cachemgrRegister("pconn",
- "Persistent Connection Utilization Histograms",
- pconnHistDump, 0, 1);
- debug(48, 3) ("persistent connection module initialized\n");
+PconnPool::~PconnPool()
+{
+ descr = NULL;
+ hashFreeMemory(table);
}
void
-pconnPush(int fd, const char *host, u_short port, const char *domain)
+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 (fdUsageHigh()) {
- debug(48, 3) ("pconnPush: Not many unused FDs\n");
- comm_close(fd);
+ debugs(48, 3, HERE << "Not many unused FDs");
+ conn->close();
return;
} else if (shutting_down) {
- comm_close(fd);
+ conn->close();
+ debugs(48, 3, HERE << "Squid is shutting down. Refusing to do anything");
return;
}
- assert(table != NULL);
- strncpy(key, pconnKey(host, port, domain), SQUIDHOSTNAMELEN + 10);
+ const char *aKey = key(conn, domain);
+ IdleConnList *list = (IdleConnList *) hash_lookup(table, aKey);
- p = (struct _pconn *) hash_lookup(table, key);
+ 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) << "}" );
+ }
- if (p == NULL)
- p = pconnNew(key);
+ list->push(conn);
+ assert(!comm_has_incomplete_write(conn->fd));
- 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));
+ 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);
+}
- if (p->nfds == PCONN_FDS_SZ)
- pconn_fds_pool->free(old);
- else
- xfree(old);
+Comm::ConnectionPointer
+PconnPool::pop(const Comm::ConnectionPointer &destLink, const char *domain, bool isRetriable)
+{
+ 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)") );
}
- assert(!comm_has_incomplete_write(fd));
- p->fds[p->nfds++] = fd;
- comm_read(fd, p->buf, BUFSIZ, pconnRead, p);
- 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);
+ /* may delete list */
+ Comm::ConnectionPointer temp = list->findUseable(destLink);
+ if (Comm::IsConnOpen(temp) && !isRetriable)
+ temp->close();
+
+ return temp;
+}
+
+void
+PconnPool::unlinkList(IdleConnList *list) const
+{
+ hash_remove_link(table, &list->hash);
}
+void
+PconnPool::count(int uses)
+{
+ if (uses >= PCONN_HIST_SZ)
+ uses = PCONN_HIST_SZ - 1;
+
+ hist[uses]++;
+}
+
+/* ========== PconnModule ============================================ */
+
/*
- * return a pconn fd for host:port, or -1 if none are available
- *
- * 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.
+ * This simple class exists only for the cache manager
*/
-int
-pconnPop(const char *host, u_short port, const char *domain)
+
+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();
+}
- struct _pconn *p;
- hash_link *hptr;
- int fd = -1;
- LOCAL_ARRAY(char, key, SQUIDHOSTNAMELEN + 10);
- assert(table != NULL);
- strncpy(key, pconnKey(host, port, domain), SQUIDHOSTNAMELEN + 10);
- hptr = (hash_link *)hash_lookup(table, key);
-
- if (hptr != NULL) {
-
- p = (struct _pconn *) hptr;
- assert(p->nfds > 0);
-
- for (int i = 0; i < p->nfds; i++) {
- fd = p->fds[p->nfds - 1];
- /* If there are pending read callbacks - we're about to close it, so don't issue it! */
-
- if (!comm_has_pending_read_callback(fd)) {
- pconnRemoveFD(p, fd);
- comm_read_cancel(fd, pconnRead, p);
- commSetTimeout(fd, -1, NULL, NULL);
- return fd;
- }
- }
- }
+PconnModule *
+PconnModule::GetInstance()
+{
+ if (instance == NULL)
+ instance = new PconnModule;
- /* Nothing (valid!) found */
- return -1;
+ return instance;
}
void
-pconnHistCount(int what, int i)
+PconnModule::registerWithCacheManager(void)
{
- 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);
+ 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);
}