}
// returns a persistent or brand new connection; negative int on failures
-int Adaptation::Icap::ServiceRep::getConnection(bool retriableXact, bool &reused)
+Comm::ConnectionPointer
+Adaptation::Icap::ServiceRep::getConnection(bool retriableXact, bool &reused)
{
- Comm::ConnectionPointer connection = new Comm::Connection;
-
- /* NP: set these here because it applies whether a pconn or a new conn is used */
-
- // TODO: Avoid blocking lookup if s.cfg().host is a hostname
- connection->remote = cfg().host.termedBuf();
- connection->remote.SetPort(cfg().port);
+ Ip::Address client_addr;
+
- int connection = -1;
++ Comm::ConnectionPointer connection;
+
+ /* 2011-06-17: rousskov:
+ * There are two things that happen at the same time in pop(). Both are important.
+ * 1) Ensure that we can use a pconn for this transaction.
+ * 2) Ensure that the number of idle pconns does not grow without bounds.
+ *
+ * Both happen in the beginning of the transaction. Both are dictated by real-world problems.
+ * retriable means you can repeat the request if you suspect the first try failed due to a pconn race.
+ * HTTP and ICAP rules prohibit the use of pconns for non-retriable requests.
+ *
+ * If there are zero idle connections, (2) is irrelevant. (2) is only relevant when there are many
+ * idle connections and we should not open more connections without closing some idle ones,
+ * or instead of just opening a new connection and leaving idle connections as is.
+ * In other words, (2) tells us to close one FD for each new one we open due to retriable.
+ */
+ if (retriableXact)
- connection = theIdleConns.findUseableFD();
++ connection = theIdleConns.pop();
+ else
+ theIdleConns.closeN(1);
- // TODO: check whether NULL domain is appropriate here
- theIdleConns.pop(connection, NULL, retriableXact);
- reused = connection->isOpen(); // reused a persistent connection
- reused = connection >= 0; // reused a persistent connection
++ reused = Comm::IsConnOpen(connection); // reused a persistent connection
- if (reused)
+ if (!reused) { // need a new connection
+ Ip::Address outgoing; // default: IP6_ANY_ADDR
+ if (!Ip::EnableIpv6)
+ outgoing.SetIPv4();
+ else if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && !cfg().ipv6) {
+ /* split-stack for now requires default IPv4-only socket */
+ outgoing.SetIPv4();
+ }
+ connection = comm_open(SOCK_STREAM, 0, outgoing, COMM_NONBLOCKING, cfg().uri.termedBuf());
- }
++ } else
+ debugs(93,3, HERE << "reused pconn " << connection);
- // else, return unopened Comm::Connection for caller to open.
- if (connection->isOpen())
- if (connection >= 0)
++ if (Comm::IsConnOpen(connection))
++theBusyConns;
return connection;
// do not pool an idle connection if we owe connections
if (isReusable && excessConnections() == 0) {
debugs(93, 3, HERE << "pushing pconn" << comment);
- commSetTimeout(fd, -1, NULL, NULL);
- Ip::Address anyAddr;
- theIdleConns.push(fd);
+ commUnsetConnTimeout(conn);
- theIdleConns.push(conn, NULL);
++ theIdleConns.push(conn);
} else {
debugs(93, 3, HERE << "closing pconn" << comment);
// comm_close will clear timeout
}
// a wrapper to avoid exposing theIdleConns
-void Adaptation::Icap::ServiceRep::noteConnectionUse(int fd)
+void Adaptation::Icap::ServiceRep::noteConnectionUse(const Comm::ConnectionPointer &conn)
{
- Must(fd >= 0);
- fd_table[fd].noteUse(NULL); // pconn re-use but not via PconnPool API
+ Must(Comm::IsConnOpen(conn));
- fd_table[conn->fd].noteUse(&theIdleConns);
++ fd_table[conn->fd].noteUse(NULL); // pconn re-use but not via PconnPool API
}
void Adaptation::Icap::ServiceRep::setMaxConnections()
assert (repContext);
switch (hp->request_parse_status) {
case HTTP_HEADER_TOO_LARGE:
- repContext->setReplyToError(ERR_TOO_BIG, HTTP_BAD_REQUEST, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
+ repContext->setReplyToError(ERR_TOO_BIG, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf, NULL);
break;
case HTTP_METHOD_NOT_ALLOWED:
- repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_METHOD_NOT_ALLOWED, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
+ repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_METHOD_NOT_ALLOWED, method, http->uri,
+ conn->clientConnection->remote, NULL, conn->in.buf, NULL);
break;
default:
- repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST, method, http->uri,
- repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri, conn->peer, NULL, conn->in.buf, NULL);
++ repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri,
+ conn->clientConnection->remote, NULL, conn->in.buf, NULL);
}
assert(context->http->out.offset == 0);
context->pullData();
--- /dev/null
- if (fd >= 0)
+/*
+ * DEBUG: section 05 Socket Functions
+ * AUTHOR: Amos Jeffries
+ * AUTHOR: Robert Collins
+ *
+ * SQUID Web Proxy Cache http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ * Squid is the result of efforts by numerous individuals from
+ * the Internet community; see the CONTRIBUTORS file for full
+ * details. Many organizations have provided support for Squid's
+ * development; see the SPONSORS file for full details. Squid is
+ * Copyrighted (C) 2001 by the Regents of the University of
+ * California; see the COPYRIGHT file for full details. Squid
+ * incorporates software developed and/or copyrighted by other
+ * sources; see the CREDITS file for full details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * 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.
+ *
+ *
+ * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
+ * Copyright (c) 2010, Amos Jeffries <amosjeffries@squid-cache.org>
+ */
+
+#ifndef _SQUIDCONNECTIONDETAIL_H_
+#define _SQUIDCONNECTIONDETAIL_H_
+
+#include "config.h"
+#include "comm/forward.h"
+#include "hier_code.h"
+#include "ip/Address.h"
+#include "MemPool.h"
+#include "RefCount.h"
+#include "typedefs.h"
+#if USE_SQUID_EUI
+#include "eui/Eui48.h"
+#include "eui/Eui64.h"
+#endif
+
+#if HAVE_IOSFWD
+#include <iosfwd>
+#endif
+#if HAVE_OSTREAM
+#include <ostream>
+#endif
+
+struct peer;
+
+namespace Comm {
+
+/* TODO: make these a struct of boolean flags members in the connection instead of a bitmap.
+ * we can't do that until all non-comm code uses Commm::Connection objects to create FD
+ * currently there is code still using comm_open() and comm_openex() synchronously!!
+ */
+#define COMM_UNSET 0x00
+#define COMM_NONBLOCKING 0x01 // default flag.
+#define COMM_NOCLOEXEC 0x02
+#define COMM_REUSEADDR 0x04 // shared FD may be both accept()ing and read()ing
+#define COMM_DOBIND 0x08 // requires a bind()
+#define COMM_TRANSPARENT 0x10 // arrived via TPROXY
+#define COMM_INTERCEPTION 0x20 // arrived via NAT
+
+/**
+ * Store data about the physical and logical attributes of a connection.
+ *
+ * Some link state can be infered from the data, however this is not an
+ * object for state data. But a semantic equivalent for FD with easily
+ * accessible cached properties not requiring repeated complex lookups.
+ *
+ * Connection properties may be changed until the connection is opened.
+ * Properties should be considered read-only outside of the Comm layer
+ * code once the connection is open.
+ *
+ * These objects must not be passed around directly,
+ * but a Comm::ConnectionPointer must be passed instead.
+ */
+class Connection : public RefCountable
+{
+public:
+ MEMPROXY_CLASS(Comm::Connection);
+
+ Connection();
+
+ /** Clear the connection properties and close any open socket. */
+ ~Connection();
+
+ /** Copy an existing connections IP and properties.
+ * This excludes the FD. The new copy will be a closed connection.
+ */
+ ConnectionPointer copyDetails() const;
+
+ /** Close any open socket. */
+ void close();
+
+ /** determine whether this object describes an active connection or not. */
+ bool isOpen() const { return (fd >= 0); }
+
+ /** retrieve the peer pointer for use.
+ * The caller is responsible for all CBDATA operations regarding the
+ * used of the pointer returned.
+ */
+ peer * const getPeer() const;
+
+ /** alter the stored peer pointer.
+ * Perform appropriate CBDATA operations for locking the peer pointer
+ */
+ void setPeer(peer * p);
+
+private:
+ /** These objects may not be exactly duplicated. Use copyDetails() instead. */
+ Connection(const Connection &c);
+
+ /** These objects may not be exactly duplicated. Use copyDetails() instead. */
+ Connection & operator =(const Connection &c);
+
+public:
+ /** Address/Port for the Squid end of a TCP link. */
+ Ip::Address local;
+
+ /** Address for the Remote end of a TCP link. */
+ Ip::Address remote;
+
+ /** Hierarchy code for this connection link */
+ hier_code peerType;
+
+ /** Socket used by this connection. Negative if not open. */
+ int fd;
+
+ /** Quality of Service TOS values currently sent on this connection */
+ tos_t tos;
+
+ /** Netfilter MARK values currently sent on this connection */
+ nfmark_t nfmark;
+
+ /** COMM flags set on this connection */
+ int flags;
+
+ char rfc931[USER_IDENT_SZ];
+
+#if USE_SQUID_EUI
+ Eui::Eui48 remoteEui48;
+ Eui::Eui64 remoteEui64;
+#endif
+
+private:
+ // XXX: we need to call this member peer_ but the struct peer_ global type
+ // behind peer* clashes despite our private Comm:: namespace
+ // (it being global gets inherited here too).
+
+ /** cache_peer data object (if any) */
+ peer *_peer;
+};
+
+MEMPROXY_CLASS_INLINE(Connection);
+
+}; // namespace Comm
+
+
+// NP: Order and namespace here is very important.
+// * The second define inlines the first.
+// * Stream inheritance overloading is searched in the global scope first.
+
+inline std::ostream &
+operator << (std::ostream &os, const Comm::Connection &conn)
+{
+ os << "local=" << conn.local << " remote=" << conn.remote;
- if (flags != COMM_UNSET)
++ if (conn.fd >= 0)
+ os << " FD " << conn.fd;
++ if (conn.flags != COMM_UNSET)
+ os << " flags=" << conn.flags;
+#if USE_IDENT
+ if (*conn.rfc931)
+ os << " IDENT::" << conn.rfc931;
+#endif
+ return os;
+}
+
+inline std::ostream &
+operator << (std::ostream &os, const Comm::ConnectionPointer &conn)
+{
+ if (conn != NULL)
+ os << *conn;
+ return os;
+}
+
+#endif
IdleConnList::~IdleConnList()
{
- parent_->unlinkList(this);
- if (parent)
- parent->unlinkList(this);
++ if (parent_)
++ parent_->unlinkList(this);
- /* TODO: re-attach to MemPools.
- if (capacity_ == PCONN_FDS_SZ)
- pconn_fds_pool->freeOne(theList_);
- if (nfds_alloc == PCONN_FDS_SZ)
- pconn_fds_pool->freeOne(fds);
-- else
- */
- xfree(fds);
+ delete[] theList_;
xfree(hash.key);
}
return -1;
}
-void
-IdleConnList::removeFD(int fd)
+/** Remove the entry at specified index.
+ * \retval false The index is not an in-use entry.
+ */
+bool
+IdleConnList::removeAt(int index)
{
- int index = findFDIndex(fd);
- if (index < 0) {
- debugs(48, 2, "IdleConnList::removeFD: FD " << fd << " NOT FOUND!");
- return;
- }
- debugs(48, 3, "IdleConnList::removeFD: found FD " << fd << " at index " << index);
+ if (index < 0 || index >= size_)
+ return false;
- for (; index < nfds - 1; index++)
- fds[index] = fds[index + 1];
+ // shuffle the remaining entries to fill the new gap.
+ for (; index < size_ - 1; index++)
+ theList_[index] = theList_[index + 1];
+ theList_[size_-1] = NULL;
- if (parent_)
- if (parent)
- parent->noteConnectionRemoved();
++ if (parent_) {
+ parent_->noteConnectionRemoved();
- if (--size_ == 0) {
- if (parent && --nfds == 0) {
- debugs(48, 3, "IdleConnList::removeFD: deleting " << hashKeyStr(&hash));
- delete this;
++ if (--size_ == 0) {
++ debugs(48, 3, HERE << "deleting " << hashKeyStr(&hash));
++ delete this;
++ }
+ }
++ return true;
+ }
+
+ // almost a duplicate of removeFD. But drops multiple entries.
+ void
+ IdleConnList::closeN(size_t n)
+ {
+ if (n < 1) {
+ debugs(48, 2, HERE << "Nothing to do.");
+ return;
+ } else if (n < (size_t)count()) {
+ debugs(48, 2, HERE << "Closing all entries.");
- while (nfds >= 0) {
- int fd = fds[--nfds];
- fds[nfds] = -1;
- clearHandlers(fd);
- comm_close(fd);
- if (parent)
- parent->noteConnectionRemoved();
++ while (size_ >= 0) {
++ const Comm::ConnectionPointer &conn = theList_[--size_];
++ theList_[size_] = NULL;
++ clearHandlers(conn);
++ conn->close();
++ if (parent_)
++ parent_->noteConnectionRemoved();
+ }
+ } else {
- debugs(48, 2, HERE << "Closing " << n << " of " << nfds << " entries.");
++ debugs(48, 2, HERE << "Closing " << n << " of " << size_ << " entries.");
+
+ size_t index = 0;
+ // ensure the first N entries are closed
+ while (index < n) {
- int fd = fds[--nfds];
- fds[nfds] = -1;
- clearHandlers(fd);
- comm_close(fd);
- if (parent)
- parent->noteConnectionRemoved();
++ const Comm::ConnectionPointer &conn = theList_[--size_];
++ theList_[size_] = NULL;
++ clearHandlers(conn);
++ conn->close();
++ if (parent_)
++ parent_->noteConnectionRemoved();
+ }
+ // shuffle the list N down.
- for (;index < (size_t)nfds; index++) {
- fds[index - n] = fds[index];
++ for (;index < (size_t)size_; index++) {
++ theList_[index - n] = theList_[index];
+ }
+ // ensure the last N entries are unset
- while (index < ((size_t)nfds) + n) {
- fds[index] = -1;
++ while (index < ((size_t)size_) + n) {
++ theList_[index] = NULL;
+ }
+ }
+
- if (parent && nfds == 0) {
- debugs(48, 3, "IdleConnList::removeFD: deleting " << hashKeyStr(&hash));
++ if (parent_ && size_ == 0) {
+ debugs(48, 3, HERE << "deleting " << hashKeyStr(&hash));
delete this;
}
- return true;
}
void
}
void
-IdleConnList::push(int fd)
+IdleConnList::push(const Comm::ConnectionPointer &conn)
{
- if (nfds == nfds_alloc) {
- debugs(48, 3, "IdleConnList::push: growing FD array");
- nfds_alloc <<= 1;
- int *old = fds;
- fds = (int *)xmalloc(nfds_alloc * sizeof(int));
- memcpy(fds, old, nfds * sizeof(int));
-
- if (nfds == PCONN_FDS_SZ)
- pconn_fds_pool->freeOne(old);
- else
- xfree(old);
+ 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];
+
+ delete[] oldList;
}
- if (parent)
- parent->noteConnectionAdded();
+ if (parent_)
+ parent_->noteConnectionAdded();
- fds[nfds++] = fd;
- comm_read(fd, fakeReadBuf, sizeof(fakeReadBuf), IdleConnList::read, this);
- commSetTimeout(fd, Config.Timeout.pconn, IdleConnList::timeout, this);
+ theList_[size_++] = conn;
+ AsyncCall::Pointer readCall = commCbCall(5,4, "IdleConnList::Read",
+ CommIoCbPtrFun(IdleConnList::Read, this));
+ comm_read(conn, fakeReadBuf_, sizeof(fakeReadBuf_), readCall);
+ AsyncCall::Pointer timeoutCall = commCbCall(5,4, "IdleConnList::Read",
+ CommTimeoutCbPtrFun(IdleConnList::Timeout, this));
+ commSetConnTimeout(conn, Config.Timeout.pconn, timeoutCall);
+}
+
++Comm::ConnectionPointer
++IdleConnList::pop()
++{
++ for (int i=size_-1; i>=0; i--) {
++
++ // Is the FD pending completion of the closure callback?
++ // this flag is set while our early-read/close handler is
++ // waiting for a remote response. It gets unset when the
++ // handler is scheduled.
++ if (!fd_table[theList_[i]->fd].flags.read_pending)
++ continue;
++
++ // connection already closed. useless.
++ if (!Comm::IsConnOpen(theList_[i]))
++ continue;
++
++ // finally, a match. pop and return it.
++ Comm::ConnectionPointer result = theList_[i];
++ /* may delete this */
++ removeAt(i);
++ return result;
++ }
++
++ return Comm::ConnectionPointer();
+ }
+
/*
* 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!)
IdleConnList(const char *key, PconnPool *parent);
~IdleConnList();
- int findFDIndex(int fd); ///< search from the end of array
- void removeFD(int fd);
- void closeN(size_t count);
- void push(int fd);
- int findUseableFD(); ///< find first from the end not pending read fd.
- void clearHandlers(int fd);
+ /// Pass control of the connection to the idle list.
+ void push(const Comm::ConnectionPointer &conn);
+
++ /// get first conn which is not pending read fd.
++ Comm::ConnectionPointer pop();
+
- int count() const { return nfds; }
+ /** Search the list for a connection which matches the 'key' details
+ * and pop it off the list.
+ * The list is created based on remote IP:port hash. This further filters
+ * the choices based on specific local-end details requested.
+ * If nothing usable is found the a nil pointer is returned.
+ */
+ Comm::ConnectionPointer findUseable(const Comm::ConnectionPointer &key);
+
+ void clearHandlers(const Comm::ConnectionPointer &conn);
+
+ int count() const { return size_; }
++ void closeN(size_t count);
private:
- static IOCB read;
- static PF timeout;
+ bool removeAt(int index);
+ int findIndexOf(const Comm::ConnectionPointer &conn) const;
+ static IOCB Read;
+ static CTCB Timeout;
public:
hash_link hash; /** must be first */