fwdStart(aFwdStart),
callback_(aCall),
destinations(dests),
+ prime(&HappyConnOpener::notePrimeConnectDone, "HappyConnOpener::notePrimeConnectDone"),
+ spare(&HappyConnOpener::noteSpareConnectDone, "HappyConnOpener::noteSpareConnectDone"),
ale(anAle),
cause(request),
n_tries(tries)
++n_tries;
typedef CommCbMemFunT<HappyConnOpener, CommConnectCbParams> Dialer;
- AsyncCall::Pointer callConnect = JobCallback(48, 5, Dialer, this, HappyConnOpener::connectDone);
+ AsyncCall::Pointer callConnect = asyncCall(48, 5, attempt.callbackMethodName,
+ Dialer(this, attempt.callbackMethod));
const time_t connTimeout = dest->connectTimeout(fwdStart);
Comm::ConnOpener *cs = new Comm::ConnOpener(dest, callConnect, connTimeout);
if (!dest->getPeer())
cs->setHost(host_);
- // XXX: Do not co-own attempt.path with ConnOpener.
- attempt.path = dest;
attempt.connWait.start(cs, callConnect);
AsyncJob::Start(cs);
}
-/// called by Comm::ConnOpener objects after a prime or spare connection attempt
-/// completes (successfully or not)
+/// Comm::ConnOpener callback for the prime connection attempt
void
-HappyConnOpener::connectDone(const CommConnectCbParams ¶ms)
+HappyConnOpener::notePrimeConnectDone(const CommConnectCbParams ¶ms)
{
- Must(params.conn);
- const bool itWasPrime = (params.conn == prime.path);
- 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();
- gotSpareAllowance = false;
- }
+ handleConnOpenerAnswer(prime, params, "new prime connection");
+}
+
+/// Comm::ConnOpener callback for the spare connection attempt
+void
+HappyConnOpener::noteSpareConnectDone(const CommConnectCbParams ¶ms)
+{
+ if (gotSpareAllowance) {
+ TheSpareAllowanceGiver.jobUsedAllowance();
+ gotSpareAllowance = false;
}
+ handleConnOpenerAnswer(spare, params, "new spare connection");
+}
+
+/// prime/spare-agnostic processing of a Comm::ConnOpener result
+void
+HappyConnOpener::handleConnOpenerAnswer(Attempt &attempt, const CommConnectCbParams ¶ms, const char *what)
+{
+ Must(params.conn);
+
+ // finalize the previously selected path before attempt.finish() forgets it
+ auto handledPath = attempt.path;
+ handledPath.finalize(params.conn); // closed on errors
+ attempt.finish();
- const char *what = itWasPrime ? "new prime connection" : "new spare connection";
if (params.flag == Comm::OK) {
sendSuccess(handledPath, false, what);
return;
debugs(17, 8, what << " failed: " << params.conn);
if (const auto peer = params.conn->getPeer())
peerConnectFailed(peer);
- params.conn->close(); // TODO: Comm::ConnOpener should do this instead.
// remember the last failure (we forward it if we cannot connect anywhere)
lastFailedConnection = handledPath;
if (!destinations->empty()) {
if (!currentPeer) {
auto newPrime = destinations->extractFront();
- // XXX: Do not co-own currentPeer Connection with ConnOpener
- // (which is activated via startConnecting() below).
currentPeer = newPrime;
Must(currentPeer);
debugs(17, 7, "new peer " << *currentPeer);
return false;
}
+HappyConnOpener::Attempt::Attempt(const CallbackMethod method, const char *methodName):
+ callbackMethod(method),
+ callbackMethodName(methodName)
+{
+}
+
void
HappyConnOpener::Attempt::finish()
{
/// a connection opening attempt in progress (or falsy)
class Attempt {
public:
+ /// HappyConnOpener method implementing a ConnOpener callback
+ using CallbackMethod = void (HappyConnOpener::*)(const CommConnectCbParams &);
+
+ Attempt(const CallbackMethod method, const char *methodName);
+
explicit operator bool() const { return static_cast<bool>(path); }
/// reacts to a natural attempt completion (successful or otherwise)
PeerConnectionPointer path; ///< the destination we are connecting to
JobWait<Comm::ConnOpener> connWait; ///< establishes a transport connection
+
+ const CallbackMethod callbackMethod; ///< ConnOpener calls this method
+ const char * const callbackMethodName; ///< for callbackMethod debugging
};
friend std::ostream &operator <<(std::ostream &, const Attempt &);
void openFreshConnection(Attempt &, PeerConnectionPointer &);
bool reuseOldConnection(PeerConnectionPointer &);
- void connectDone(const CommConnectCbParams &);
+ void notePrimeConnectDone(const CommConnectCbParams &);
+ void noteSpareConnectDone(const CommConnectCbParams &);
+ void handleConnOpenerAnswer(Attempt &, const CommConnectCbParams &, const char *connDescription);
void checkForNewConnection();
}
if (params.flag != Comm::OK) {
- /* it might have been a timeout with a partially open link */
- if (params.conn != NULL)
- params.conn->close();
peerConnectFailed(peer);
checkpoint("conn opening failure"); // may retry
return;
return;
}
- if (io.flag != Comm::OK)
+ if (io.flag != Comm::OK) {
dieOnConnectionFailure(); // throws
- else
- successfullyConnected();
+ return;
+ }
+
+ // Finalize the details and start owning the supplied connection, possibly
+ // prematurely -- see XXX in successfullyConnected().
+ assert(io.conn);
+ assert(connection);
+ assert(!connection->isOpen());
+ connection = io.conn;
+ successfullyConnected();
}
/// called when successfully connected to an ICAP service
AsyncJob("Comm::ConnOpener"),
host_(NULL),
temporaryFd_(-1),
- conn_(c),
+ conn_(c->cloneDestinationDetails()),
callback_(handler),
totalTries_(0),
failRetries_(0),
if (temporaryFd_ >= 0)
closeFd();
+ // did we abort while owning an open connection?
+ if (conn_ && conn_->isOpen())
+ conn_->close();
+
// did we abort while waiting between retries?
if (calls_.sleep_)
cancelSleep();
" [" << callback_->id << ']' );
// TODO save the pconn to the pconnPool ?
} else {
+ assert(conn_);
+
+ // free resources earlier and simplify recipients
+ if (errFlag != Comm::OK)
+ conn_->close(); // may not be opened
+ else
+ assert(conn_->isOpen());
+
+ // XXX: CommConnectCbParams imply syncWithComm(). Our recipients
+ // need a callback even if conn is closing. TODO: Do not reuse
+ // CommConnectCbParams; implement a different syncing logic.
typedef CommConnectCbParams Params;
Params ¶ms = GetCommParams<Params>(callback_);
params.conn = conn_;
+ conn_ = nullptr; // release ownership; prevent closure by us
params.flag = errFlag;
params.xerrno = xerrno;
ScheduleCallHere(callback_);
void
Comm::ConnOpener::cleanFd()
{
- debugs(5, 4, HERE << conn_ << " closing temp FD " << temporaryFd_);
+ debugs(5, 4, conn_ << "; temp FD " << temporaryFd_);
Must(temporaryFd_ >= 0);
fde &f = fd_table[temporaryFd_];
Comm::ConnOpener::createFd()
{
Must(temporaryFd_ < 0);
+ assert(conn_);
// our initiators signal abort by cancelling their callbacks
if (callback_ == NULL || callback_->canceled())
namespace Comm
{
-/**
- * Async-opener of a Comm connection.
- */
+/// Asynchronously opens a TCP connection. Returns CommConnectCbParams: either
+/// Comm::OK with an open connection or another Comm::Flag with a closed one.
class ConnOpener : public AsyncJob
{
CBDATA_CLASS(ConnOpener);
c->peerType = peerType;
c->flags = flags;
c->peer_ = cbdataReference(getPeer());
+ debugs(5, 5, this << " made " << c);
assert(!c->isOpen());
return c;
}
IdentStateData *state = (IdentStateData *)data;
state->connWait.finish();
+ // Finalize the details and start owning the supplied connection (so that it
+ // is not orphaned if this function bails early). As a (tiny) optimization
+ // or perhaps just diff minimization, the close handler is added later, when
+ // we know we are not bailing. This delay is safe because
+ // comm_remove_close_handler() forgives missing handlers.
+ assert(conn); // but may be closed
+ assert(state->conn);
+ assert(!state->conn->isOpen());
+ state->conn = conn;
+
if (status != Comm::OK) {
if (status == Comm::TIMEOUT)
debugs(30, 3, "IDENT connection timeout to " << state->conn->remote);
return;
}
- assert(conn != NULL && conn == state->conn);
- comm_add_close_handler(conn->fd, Ident::Close, state);
+ assert(state->conn->isOpen());
+ comm_add_close_handler(state->conn->fd, Ident::Close, state);
AsyncCall::Pointer writeCall = commCbCall(5,4, "Ident::WriteFeedback",
CommIoCbPtrFun(Ident::WriteFeedback, state));
state = new IdentStateData;
state->hash.key = xstrdup(key);
- // XXX: Do not co-own state->conn Connection with ConnOpener.
-
// copy the conn details. We do not want the original FD to be re-used by IDENT.
state->conn = conn->cloneIdentDetails();
// NP: use random port for secure outbound to IDENT_PORT
return false;
}
- // XXX: Do not co-own dataConn with ConnOpener.
-
// active transfer: open a data connection from Squid to client
typedef CommCbMemFunT<Server, CommConnectCbParams> Dialer;
AsyncCall::Pointer callback = JobCallback(17, 3, Dialer, this, Ftp::Server::connectedForData);
dataConnWait.finish();
if (params.flag != Comm::OK) {
- /* it might have been a timeout with a partially open link */
- if (params.conn != NULL)
- params.conn->close();
setReply(425, "Cannot open data connection.");
Http::StreamPointer context = pipeline.front();
Must(context->http);
Must(context->http->storeEntry() != NULL);
} else {
- Must(dataConn == params.conn);
+ // Finalize the details and start owning the supplied connection.
+ assert(params.conn);
+ assert(dataConn);
+ assert(!dataConn->isOpen());
+ dataConn = params.conn;
+ // XXX: Missing comm_add_close_handler() to track external closures.
+
Must(Comm::IsConnOpen(params.conn));
fd_note(params.conn->fd, "active client ftp data");
}