if (pconnRace == racePossible) {
debugs(17, 5, HERE << "pconn race happened");
pconnRace = raceHappened;
- destinations->retryPath(serverConn);
+ if (destinationReceipt) {
+ destinations->reinstatePath(destinationReceipt);
+ destinationReceipt = nullptr;
+ }
}
if (ConnStateData *pinned_connection = request->pinnedConnection()) {
comm_remove_close_handler(conn->fd, closeHandler);
closeHandler = NULL;
serverConn = NULL;
+ destinationReceipt = nullptr;
}
// \deprecated use unregister(Comm::ConnectionPointer &conn) instead
void
FwdState::noteConnection(HappyConnOpener::Answer &answer)
{
+ assert(!destinationReceipt);
+
calls.connector = nullptr;
connOpener.clear();
Must(!Comm::IsConnOpen(answer.conn));
answer.error.clear(); // preserve error for errorSendComplete()
} else if (!Comm::IsConnOpen(answer.conn) || fd_table[answer.conn->fd].closing()) {
+ // We do not know exactly why the connection got closed, so we play it
+ // safe, allowing retries only for persistent (reused) connections
+ if (answer.reused) {
+ destinationReceipt = answer.conn;
+ assert(destinationReceipt);
+ }
syncHierNote(answer.conn, request->url.host());
closePendingConnection(answer.conn, "conn was closed while waiting for noteConnection");
error = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request, al);
+ } else {
+ assert(!error);
+ destinationReceipt = answer.conn;
+ assert(destinationReceipt);
+ // serverConn remains nil until syncWithServerConn()
}
if (error) {
fail(error);
- retryOrBail(); // will notice flags.dont_retry and bail
+ retryOrBail();
return;
}
{
Must(IsConnOpen(conn));
serverConn = conn;
+ // no effect on destinationReceipt (which may even be nil here)
closeHandler = comm_add_close_handler(serverConn->fd, fwdServerClosedWrapper, this);
err = nullptr;
request->clearError();
serverConn = nullptr;
+ destinationReceipt = nullptr;
request->hier.startPeerClock();
debugs(17, 7, "connection manager: " << connManager);
try {
+ // TODO: Refactor syncWithServerConn() and callers to always set
+ // serverConn inside that method.
serverConn = ConnStateData::BorrowPinnedConnection(request, al);
debugs(17, 5, "connection: " << serverConn);
} catch (ErrorState * const anErr) {
#include "http/StatusCode.h"
#include "ip/Address.h"
#include "PeerSelectState.h"
+#include "ResolvedPeers.h"
#include "security/forward.h"
#if USE_OPENSSL
#include "ssl/support.h"
HappyConnOpenerPointer connOpener; ///< current connection opening job
ResolvedPeersPointer destinations; ///< paths for forwarding the request
Comm::ConnectionPointer serverConn; ///< a successfully opened connection to a server.
+ PeerConnectionPointer destinationReceipt; ///< peer selection result (or nil)
AsyncCall::Pointer closeHandler; ///< The serverConn close handler
#include "neighbors.h"
#include "pconn.h"
#include "PeerPoolMgr.h"
-#include "ResolvedPeers.h"
#include "SquidConfig.h"
CBDATA_CLASS_INIT(HappyConnOpener);
/// \returns pre-filled Answer if the initiator needs an answer (or nil)
HappyConnOpener::Answer *
-HappyConnOpener::futureAnswer(const Comm::ConnectionPointer &conn)
+HappyConnOpener::futureAnswer(const PeerConnectionPointer &conn)
{
if (callback_ && !callback_->canceled()) {
const auto answer = dynamic_cast<Answer *>(callback_->getDialer());
/// send a successful result to the initiator (if it still needs an answer)
void
-HappyConnOpener::sendSuccess(const Comm::ConnectionPointer &conn, bool reused, const char *connKind)
+HappyConnOpener::sendSuccess(const PeerConnectionPointer &conn, const bool reused, const char *connKind)
{
debugs(17, 4, connKind << ": " << conn);
if (auto *answer = futureAnswer(conn)) {
HappyConnOpener::cancelAttempt(Attempt &attempt, const char *reason)
{
Must(attempt);
- destinations->retryPath(attempt.path); // before attempt.cancel() clears path
+ destinations->reinstatePath(attempt.path); // before attempt.cancel() clears path
attempt.cancel(reason);
}
/// starts opening (or reusing) a connection to the given destination
void
-HappyConnOpener::startConnecting(Attempt &attempt, Comm::ConnectionPointer &dest)
+HappyConnOpener::startConnecting(Attempt &attempt, PeerConnectionPointer &dest)
{
Must(!attempt.path);
Must(!attempt.connector);
/// \returns true if and only if reuse was possible
/// must be called via startConnecting()
bool
-HappyConnOpener::reuseOldConnection(const Comm::ConnectionPointer &dest)
+HappyConnOpener::reuseOldConnection(PeerConnectionPointer &dest)
{
assert(allowPconn_);
if (const auto pconn = fwdPconnPool->pop(dest, host_, retriable_)) {
++n_tries;
- sendSuccess(pconn, true, "reused connection");
+ dest.finalize(pconn);
+ sendSuccess(dest, true, "reused connection");
return true;
}
/// opens a fresh connection to the given destination
/// must be called via startConnecting()
void
-HappyConnOpener::openFreshConnection(Attempt &attempt, Comm::ConnectionPointer &dest)
+HappyConnOpener::openFreshConnection(Attempt &attempt, PeerConnectionPointer &dest)
{
#if URL_CHECKSUM_DEBUG
entry->mem_obj->checkUrlChecksum();
const bool itWasSpare = (params.conn == spare.path);
Must(itWasPrime != itWasSpare);
+ PeerConnectionPointer handledPath;
if (itWasPrime) {
+ handledPath = prime.path;
prime.finish();
} else {
+ handledPath = spare.path;
spare.finish();
if (gotSpareAllowance) {
TheSpareAllowanceGiver.jobUsedAllowance();
const char *what = itWasPrime ? "new prime connection" : "new spare connection";
if (params.flag == Comm::OK) {
- sendSuccess(params.conn, false, what);
+ sendSuccess(handledPath, false, what);
return;
}
params.conn->close(); // TODO: Comm::ConnOpener should do this instead.
// remember the last failure (we forward it if we cannot connect anywhere)
- lastFailedConnection = params.conn;
+ lastFailedConnection = handledPath;
delete lastError;
lastError = nullptr; // in case makeError() throws
lastError = makeError(ERR_CONNECT_FAIL);
// open a new prime and/or a new spare connection if needed
if (!destinations->empty()) {
if (!currentPeer) {
- currentPeer = destinations->extractFront();
+ auto newPrime = destinations->extractFront();
+ currentPeer = newPrime;
Must(currentPeer);
debugs(17, 7, "new peer " << *currentPeer);
primeStart = current_dtime;
- startConnecting(prime, currentPeer);
+ startConnecting(prime, newPrime);
+ // TODO: if reuseOldConnection() in startConnecting() above succeeds,
+ // then we should not get here, and Must(prime) below will fail.
maybeGivePrimeItsChance();
Must(prime); // entering state #1.1
} else {
#include "comm/ConnOpener.h"
#include "http/forward.h"
#include "log/forward.h"
+#include "ResolvedPeers.h"
#include <iosfwd>
class HappyConnOpener;
class HappyOrderEnforcer;
class JobGapEnforcer;
-class ResolvedPeers;
typedef RefCount<ResolvedPeers> ResolvedPeersPointer;
/// A FIFO queue of HappyConnOpener jobs waiting to open a spare connection.
/// on success: an open, ready-to-use Squid-to-peer connection
/// on failure: either a closed failed Squid-to-peer connection or nil
- Comm::ConnectionPointer conn;
+ PeerConnectionPointer conn;
// answer recipients must clear the error member in order to keep its info
// XXX: We should refcount ErrorState instead of cbdata-protecting it.
/// aborts an in-progress attempt
void cancel(const char *reason);
- Comm::ConnectionPointer path; ///< the destination we are connecting to
+ PeerConnectionPointer path; ///< the destination we are connecting to
AsyncCall::Pointer connector; ///< our opener callback
Comm::ConnOpener::Pointer opener; ///< connects to path and calls us
void stopWaitingForSpareAllowance();
void maybeOpenSpareConnection();
- void startConnecting(Attempt &, Comm::ConnectionPointer &);
- void openFreshConnection(Attempt &, Comm::ConnectionPointer &);
- bool reuseOldConnection(const Comm::ConnectionPointer &);
+ void startConnecting(Attempt &, PeerConnectionPointer &);
+ void openFreshConnection(Attempt &, PeerConnectionPointer &);
+ bool reuseOldConnection(PeerConnectionPointer &);
void connectDone(const CommConnectCbParams &);
bool ranOutOfTimeOrAttempts() const;
ErrorState *makeError(const err_type type) const;
- Answer *futureAnswer(const Comm::ConnectionPointer &);
- void sendSuccess(const Comm::ConnectionPointer &conn, bool reused, const char *connKind);
+ Answer *futureAnswer(const PeerConnectionPointer &);
+ void sendSuccess(const PeerConnectionPointer &conn, bool reused, const char *connKind);
void sendFailure();
void cancelAttempt(Attempt &, const char *reason);
AccessLogEntryPointer ale; ///< transaction details
ErrorState *lastError = nullptr; ///< last problem details (or nil)
- Comm::ConnectionPointer lastFailedConnection; ///< nil if none has failed
+ PeerConnectionPointer lastFailedConnection; ///< nil if none has failed
/// whether spare connection attempts disregard happy_eyeballs_* settings
bool ignoreSpareRestrictions = false;
}
void
-ResolvedPeers::retryPath(const Comm::ConnectionPointer &path)
+ResolvedPeers::reinstatePath(const PeerConnectionPointer &path)
{
debugs(17, 4, path);
assert(path);
- // Cannot use pathsToSkip for a faster (reverse) search because there
- // may be unavailable paths past pathsToSkip. We could remember
- // the last extraction index, but, to completely avoid a linear search,
- // extract*() methods should return the path index.
- const auto found = std::find_if(paths_.begin(), paths_.end(),
- [path](const ResolvedPeerPath &candidate) {
- return candidate.connection == path; // (refcounted) pointer comparison
- });
- assert(found != paths_.end());
- assert(!found->available);
- found->available = true;
+
+ const auto pos = path.position_;
+ assert(pos < paths_.size());
+
+ assert(!paths_[pos].available);
+ paths_[pos].available = true;
increaseAvailability();
// if we restored availability of a path that we used to skip, update
- const auto pathsToTheLeft = static_cast<size_type>(found - paths_.begin());
+ const auto pathsToTheLeft = pos;
if (pathsToTheLeft < pathsToSkip) {
pathsToSkip = pathsToTheLeft;
} else {
return makeFinding(path, foundNext);
}
-Comm::ConnectionPointer
+PeerConnectionPointer
ResolvedPeers::extractFront()
{
Must(!empty());
return extractFound("first: ", start());
}
-Comm::ConnectionPointer
+PeerConnectionPointer
ResolvedPeers::extractPrime(const Comm::Connection ¤tPeer)
{
const auto found = findPrime(currentPeer).first;
return nullptr;
}
-Comm::ConnectionPointer
+PeerConnectionPointer
ResolvedPeers::extractSpare(const Comm::Connection ¤tPeer)
{
const auto found = findSpare(currentPeer).first;
}
/// convenience method to finish a successful extract*() call
-Comm::ConnectionPointer
+PeerConnectionPointer
ResolvedPeers::extractFound(const char *description, const Paths::iterator &found)
{
auto &path = *found;
while (++pathsToSkip < paths_.size() && !paths_[pathsToSkip].available) {}
}
- return path.connection;
+ const auto cleanPath = path.connection->cloneDestinationDetails();
+ return PeerConnectionPointer(cleanPath, found - paths_.begin());
}
bool
return os << peers.size() << (peers.destinationsFinalized ? "" : "+") << " paths";
}
+/* PeerConnectionPointer */
+
+void
+PeerConnectionPointer::print(std::ostream &os) const
+{
+ // We should see no combinations of a nil connection and a set position
+ // because assigning nullptr (to our smart pointer) naturally erases both
+ // fields. We report such unexpected combinations for debugging sake, but do
+ // not complicate this code to report them beautifully.
+
+ if (connection_)
+ os << connection_;
+
+ if (position_ != npos)
+ os << " @" << position_;
+}
#include "comm/forward.h"
#include <iosfwd>
+#include <limits>
#include <utility>
class ResolvedPeerPath
bool available; ///< whether this path may be used (i.e., has not been tried already)
};
+class PeerConnectionPointer;
+
/// cache_peer and origin server addresses (a.k.a. paths)
/// selected and resolved by the peering code
class ResolvedPeers: public RefCountable
/// add a candidate path to try after all the existing paths
void addPath(const Comm::ConnectionPointer &);
- /// re-inserts the previously extracted address into the same position
- void retryPath(const Comm::ConnectionPointer &);
+ /// makes the previously extracted path available for extraction at its
+ /// original position
+ void reinstatePath(const PeerConnectionPointer &);
/// extracts and returns the first queued address
- Comm::ConnectionPointer extractFront();
+ PeerConnectionPointer extractFront();
/// extracts and returns the first same-peer same-family address
/// \returns nil if it cannot find the requested address
- Comm::ConnectionPointer extractPrime(const Comm::Connection ¤tPeer);
+ PeerConnectionPointer extractPrime(const Comm::Connection ¤tPeer);
/// extracts and returns the first same-peer different-family address
/// \returns nil if it cannot find the requested address
- Comm::ConnectionPointer extractSpare(const Comm::Connection ¤tPeer);
+ PeerConnectionPointer extractSpare(const Comm::Connection ¤tPeer);
/// whether extractSpare() would return a non-nil path right now
bool haveSpare(const Comm::Connection ¤tPeer);
Finding findSpare(const Comm::Connection ¤tPeer);
Finding findPrime(const Comm::Connection ¤tPeer);
Finding findPeer(const Comm::Connection ¤tPeer);
- Comm::ConnectionPointer extractFound(const char *description, const Paths::iterator &found);
+ PeerConnectionPointer extractFound(const char *description, const Paths::iterator &found);
Finding makeFinding(const Paths::iterator &found, bool foundOther);
bool doneWith(const Finding &findings) const;
size_type availablePaths = 0;
};
+/// An invasive reference-counting Comm::Connection pointer that also keeps an
+/// (optional) ResolvedPeers position for the ResolvedPeers::reinstatePath() usage.
+/// Reference counting mechanism is compatible with Comm::ConnectionPointer.
+class PeerConnectionPointer
+{
+public:
+ using size_type = ResolvedPeers::size_type;
+
+ PeerConnectionPointer() = default;
+ PeerConnectionPointer(std::nullptr_t): PeerConnectionPointer() {} ///< implicit nullptr conversion
+ PeerConnectionPointer(const Comm::ConnectionPointer &conn, const size_type pos): connection_(conn), position_(pos) {}
+
+ /* read-only pointer API; for Connection assignment, see finalize() */
+ explicit operator bool() const { return static_cast<bool>(connection_); }
+ Comm::Connection *operator->() const { assert(connection_); return connection_.getRaw(); }
+ Comm::Connection &operator *() const { assert(connection_); return *connection_; }
+
+ /// convenience conversion to Comm::ConnectionPointer
+ operator const Comm::ConnectionPointer&() const { return connection_; }
+
+ /// upgrade stored peer selection details with a matching actual connection
+ void finalize(const Comm::ConnectionPointer &conn) { connection_ = conn; }
+
+ /// debugging dump
+ void print(std::ostream &) const;
+
+private:
+ /// non-existent position for nil connection
+ static constexpr auto npos = std::numeric_limits<size_type>::max();
+
+ /// half-baked, open, failed, or closed Comm::Connection (or nil)
+ Comm::ConnectionPointer connection_;
+
+ /// ResolvedPeers-maintained membership index (or npos)
+ size_type position_ = npos;
+ friend class ResolvedPeers;
+};
+
/// summarized ResolvedPeers (for debugging)
std::ostream &operator <<(std::ostream &, const ResolvedPeers &);
+inline std::ostream &
+operator <<(std::ostream &os, const PeerConnectionPointer &dest)
+{
+ dest.print(os);
+ return os;
+}
+
#endif /* SQUID_RESOLVEDPEERS_H */
CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener);
-Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &c, const AsyncCall::Pointer &handler, time_t ctimeout) :
+Comm::ConnOpener::ConnOpener(const Comm::ConnectionPointer &c, const AsyncCall::Pointer &handler, time_t ctimeout) :
AsyncJob("Comm::ConnOpener"),
host_(NULL),
temporaryFd_(-1),
virtual bool doneAll() const;
- ConnOpener(Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout);
+ ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout);
~ConnOpener();
void setHost(const char *); ///< set the hostname note for this connection
}
Comm::ConnectionPointer
-Comm::Connection::copyDetails() const
+Comm::Connection::cloneDestinationDetails() const
{
- ConnectionPointer c = new Comm::Connection;
-
+ const ConnectionPointer c = new Comm::Connection;
c->setAddrs(local, remote);
c->peerType = peerType;
+ c->flags = flags;
+ c->peer_ = cbdataReference(getPeer());
+ assert(!c->isOpen());
+ return c;
+}
+
+Comm::ConnectionPointer
+Comm::Connection::cloneIdentDetails() const
+{
+ auto c = cloneDestinationDetails();
c->tos = tos;
c->nfmark = nfmark;
c->nfConnmark = nfConnmark;
- c->flags = flags;
c->startTime_ = startTime_;
-
- // ensure FD is not open in the new copy.
- c->fd = -1;
-
- // ensure we have a cbdata reference to peer_ not a straight ptr copy.
- c->peer_ = cbdataReference(getPeer());
-
return c;
}
/** Clear the connection properties and close any open socket. */
virtual ~Connection();
- /** Copy an existing connections IP and properties.
- * This excludes the FD. The new copy will be a closed connection.
- */
- ConnectionPointer copyDetails() const;
+ /// Create a new (closed) IDENT Connection object based on our from-Squid
+ /// connection properties.
+ ConnectionPointer cloneIdentDetails() const;
+
+ /// Create a new (closed) Connection object pointing to the same destination
+ /// as this from-Squid connection.
+ ConnectionPointer cloneDestinationDetails() const;
/// close the still-open connection when its last reference is gone
void enterOrphanage() { flags |= COMM_ORPHANED; }
virtual std::ostream &detailCodeContext(std::ostream &os) const override;
private:
- /** These objects may not be exactly duplicated. Use copyDetails() instead. */
+ /** These objects may not be exactly duplicated. Use cloneIdentDetails() or
+ * cloneDestinationDetails() instead.
+ */
Connection(const Connection &c);
- /** These objects may not be exactly duplicated. Use copyDetails() instead. */
+ /** These objects may not be exactly duplicated. Use cloneIdentDetails() or
+ * cloneDestinationDetails() instead.
+ */
Connection & operator =(const Connection &c);
public:
state->hash.key = xstrdup(key);
// copy the conn details. We do not want the original FD to be re-used by IDENT.
- state->conn = conn->copyDetails();
+ state->conn = conn->cloneIdentDetails();
// NP: use random port for secure outbound to IDENT_PORT
state->conn->local.port(0);
state->conn->remote.port(IDENT_PORT);
// There can be at most one PINNED destination.
// Non-PINNED destinations are uniquely identified by their CachePeer
// (even though a DIRECT destination might match a cache_peer address).
+ // TODO: We may still add duplicates because the same peer could have
+ // been removed from `servers` already (and given to the requestor).
const bool duplicate = (server->code == PINNED) ?
(code == PINNED) : (server->_peer == peer);
if (duplicate) {
#include "comm/Connection.h"
Comm::Connection::Connection() STUB
Comm::Connection::~Connection() STUB
-Comm::ConnectionPointer Comm::Connection::copyDetails() const STUB_RETVAL(NULL)
+Comm::ConnectionPointer Comm::Connection::cloneIdentDetails() const STUB_RETVAL(nullptr)
+Comm::ConnectionPointer Comm::Connection::cloneDestinationDetails() const STUB_RETVAL(nullptr)
void Comm::Connection::close() STUB
CachePeer * Comm::Connection::getPeer() const STUB_RETVAL(NULL)
void Comm::Connection::setPeer(CachePeer * p) STUB
bool Comm::ConnOpener::doneAll() const STUB_RETVAL(false)
void Comm::ConnOpener::start() STUB
void Comm::ConnOpener::swanSong() STUB
-Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &, const AsyncCall::Pointer &, time_t) : AsyncJob("STUB Comm::ConnOpener") STUB
+Comm::ConnOpener::ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &, time_t) : AsyncJob("STUB Comm::ConnOpener") STUB
Comm::ConnOpener::~ConnOpener() STUB
void Comm::ConnOpener::setHost(const char *) STUB
const char * Comm::ConnOpener::getHost() const STUB_RETVAL(NULL)