From: Alex Rousskov Date: Mon, 3 May 2021 21:40:14 +0000 (+0000) Subject: Stop processing a response if the Store entry is gone (#806) X-Git-Tag: 4.15-20210522-snapshot~11 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d2b604ecafdc3cc6e2ad2b2b62c4befe0236ddaa;p=thirdparty%2Fsquid.git Stop processing a response if the Store entry is gone (#806) HttpStateData::processReply() is usually called synchronously, after checking the Store entry status, but there are other call chains. StoreEntry::isAccepting() adds STORE_PENDING check to the ENTRY_ABORTED check. An accepting entry is required for writing into Store. In theory, an entry may stop accepting new writes (without being aborted) if FwdState or another entry co-owner writes an error response due to a timeout or some other problem that happens while we are waiting for an I/O callback or some such. N.B. HTTP and FTP code cannot use StoreEntry::isAccepting() directly because their network readers may not be the ones writing into Store -- the content may go through the adaptation layer first and that layer might complete the store entry before the entire peer response is received. For example, imagine an adaptation service that wants to log the whole response containing a virus but also replaces that (large) response with a small error reply. --- diff --git a/src/gopher.cc b/src/gopher.cc index f33ef5d6db..4f7a293778 100644 --- a/src/gopher.cc +++ b/src/gopher.cc @@ -740,7 +740,10 @@ gopherReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm assert(buf == gopherState->replybuf); - if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { + // XXX: Should update delayId, statCounter, etc. before bailing + if (!entry->isAccepting()) { + debugs(10, 3, "terminating due to bad " << *entry); + // TODO: Do not abuse connection for triggering cleanup. gopherState->serverConn->close(); return; } @@ -837,6 +840,13 @@ gopherSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Com statCounter.server.other.kbytes_out += size; } + if (!entry->isAccepting()) { + debugs(10, 3, "terminating due to bad " << *entry); + // TODO: Do not abuse connection for triggering cleanup. + gopherState->serverConn->close(); + return; + } + if (errflag) { const auto err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request, gopherState->fwd->al); err->xerrno = xerrno; diff --git a/src/http.cc b/src/http.cc index 4735d58d56..0dd32501a9 100644 --- a/src/http.cc +++ b/src/http.cc @@ -1314,6 +1314,11 @@ HttpStateData::processReply() Must(!flags.headers_parsed); } + if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { + abortTransaction("store entry aborted while we were waiting for processReply()"); + return; + } + if (!flags.headers_parsed) { // have not parsed headers yet? PROF_start(HttpStateData_processReplyHeader); processReplyHeader(); diff --git a/src/urn.cc b/src/urn.cc index 72ab801a90..83d7ea333d 100644 --- a/src/urn.cc +++ b/src/urn.cc @@ -257,6 +257,12 @@ urnHandleReply(void *data, StoreIOBuffer result) return; } + if (!e->isAccepting()) { + debugs(52, 3, "terminating due to bad " << *e); + delete urnState; + return; + } + /* Update reqofs to point to where in the buffer we'd be */ urnState->reqofs += result.length; diff --git a/src/whois.cc b/src/whois.cc index a1a3beb7b1..f85df20493 100644 --- a/src/whois.cc +++ b/src/whois.cc @@ -118,6 +118,16 @@ WhoisState::readReply(const Comm::ConnectionPointer &conn, char *aBuffer, size_t debugs(75, 3, HERE << conn << " read " << aBufferLength << " bytes"); debugs(75, 5, "{" << aBuffer << "}"); + // TODO: Honor delay pools. + + // XXX: Update statCounter before bailing + if (!entry->isAccepting()) { + debugs(52, 3, "terminating due to bad " << *entry); + // TODO: Do not abuse connection for triggering cleanup. + conn->close(); + return; + } + if (flag != Comm::OK) { debugs(50, 2, conn << ": read failure: " << xstrerr(xerrno));