/// resumes operations after the (possibly failed) HTTP CONNECT exchange
void tunnelEstablishmentDone(Http::TunnelerAnswer &answer);
+ void finishWritingAndDelete(Connection &);
void deleteThis();
void cancelStep(const char *reason);
{
server.noteClosure();
- retryOrBail(__FUNCTION__);
+ request->hier.stopPeerClock(false);
+
+ finishWritingAndDelete(client);
}
/// TunnelStateData::clientClosed() wrapper
TunnelStateData::clientClosed()
{
client.noteClosure();
+ finishWritingAndDelete(server);
+}
+/// Gracefully handles non-retriable connection closure. If necessary, either
+/// starts closing the given connection or waits for its pending write to
+/// finish. Otherwise, immediately destroys the tunnel object.
+/// \prec The other Connection is not open.
+void
+TunnelStateData::finishWritingAndDelete(Connection &remainingConnection)
+{
if (noConnections())
return deleteThis();
- if (!server.writer)
- server.conn->close();
+ // XXX: The code below should precede the noConnections() check above. When
+ // there is no writer, we should trigger an immediate noConnections()
+ // outcome instead of waiting for an asynchronous call to our own closure
+ // callback (that will call this method again). We should not move this code
+ // until a noConnections() outcome guarantees a nil writer because such a
+ // move will unnecessary delay deleteThis().
+
+ if (remainingConnection.writer) {
+ debugs(26, 5, "waiting to finish writing to " << remainingConnection.conn);
+ // the write completion callback must close its remainingConnection
+ // after noticing that the other connection is gone
+ return;
+ }
+
+ // XXX: Stop abusing connection closure callback for terminating tunneling
+ // in cases like this, where our code knows that tunneling must end. The
+ // closure callback should be dedicated to handling rare connection closures
+ // originated _outside_ of TunnelStateData (e.g., during shutdown). In all
+ // other cases, our own close()-calling code must detail the
+ // closure-triggering error (if any) _and_ clear all callbacks: Our code
+ // does not need to be (asynchronously) notified of the closure that it
+ // itself has initiated! Until that (significant) refactoring,
+ // serverClosed() and clientClosed() callbacks will continue to mishandle
+ // those rare closures as regular ones, and access.log records will continue
+ // to lack some tunneling error indicators/details.
+ //
+ // This asynchronous close() leads to another finishWritingAndDelete() call
+ // but with true noConnections() that finally triggers deleteThis().
+ remainingConnection.conn->close();
}
/// destroys the tunnel (after performing potentially-throwing cleanup)
return sendError(savedError, bailDescription ? bailDescription : context);
*status_ptr = savedError->httpStatus;
- if (noConnections())
- return deleteThis();
-
- // This is a "Comm::IsConnOpen(client.conn) but !canSendError" case.
- // Closing the connection (after finishing writing) is the best we can do.
- if (!client.writer)
- client.conn->close();
- // else writeClientDone() must notice a closed server and close the client
+ finishWritingAndDelete(client);
}
int