Skip to content

Commit d3e066e

Browse files
eduard-bagdasaryansquid-anubis
authored andcommitted
Bug 5279: FwdState.cc:279: "!storedWholeReply_" assertion (#2052)
This assertion may be triggered when RESPMOD adaptation is aborted (e.g., when an essential ICAP service is down, or when it sends an ICAP response status code unsupported by Squid). Ftp::Relay::forwardReply() calls markParsedVirginReplyAsWhole() before adaptOrFinalizeReply(). Thus, at that markParsedVirginReplyAsWhole() call time, startedAdaptation remained false, markStoredReplyAsWhole() was called, and storedWholeReply_ became true. If Squid then decided to start adaptation, but that adaptation had failed, FwdState::completed() detected a mismatch between true storedWholeReply_ and a still-empty STORE_PENDING StoreEntry. This bug was added in 2021 commit ba3fe8d. Squid does not write virgin response to Store while adaptation_access check is pending or after RESPMOD adaptation has started. Code that does not write to Store must not make storedWholeReply_ true. On the other hand, after adaptation_access is denied, and Squid finishes storing all virgin response bytes, Squid needs to know whether storedWholeReply_ should be set (i.e. whether Squid has received the whole virgin reply and called markParsedVirginReplyAsWhole()). This fix adds Client::markedParsedVirginReplyAsWhole to remember the latter event.
1 parent fefaea1 commit d3e066e

2 files changed

Lines changed: 61 additions & 36 deletions

File tree

src/clients/Client.cc

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,7 @@ Client::markParsedVirginReplyAsWhole(const char *reasonWeAreSure)
159159
{
160160
assert(reasonWeAreSure);
161161
debugs(11, 3, reasonWeAreSure);
162-
163-
// The code storing adapted reply takes care of markStoredReplyAsWhole().
164-
// We need to take care of the remaining regular network-to-store case.
165-
#if USE_ADAPTATION
166-
if (startedAdaptation) {
167-
debugs(11, 5, "adaptation handles markStoredReplyAsWhole()");
168-
return;
169-
}
170-
#endif
171-
172-
// Convert the "parsed whole virgin reply" event into the "stored..." event
173-
// because, without adaptation, we store everything we parse: There is no
174-
// buffer for parsed content; addVirginReplyBody() stores every parsed byte.
175-
fwd->markStoredReplyAsWhole(reasonWeAreSure);
162+
markedParsedVirginReplyAsWhole = reasonWeAreSure;
176163
}
177164

178165
// called when no more server communication is expected; may quit
@@ -230,6 +217,24 @@ Client::completeForwarding()
230217
{
231218
debugs(11,5, "completing forwarding for " << fwd);
232219
assert(fwd != nullptr);
220+
221+
auto storedWholeReply = markedParsedVirginReplyAsWhole;
222+
#if USE_ADAPTATION
223+
// This precondition is necessary for its two implications:
224+
// * We cannot be waiting to decide whether to adapt this response. Thus,
225+
// the startedAdaptation check below correctly detects all adaptation
226+
// cases (i.e. it does not miss adaptationAccessCheckPending ones).
227+
// * We cannot be waiting to consume/store received adapted response bytes.
228+
// Thus, receivedWholeAdaptedReply implies that we stored everything.
229+
Assure(doneWithAdaptation());
230+
231+
if (startedAdaptation)
232+
storedWholeReply = receivedWholeAdaptedReply ? "receivedWholeAdaptedReply" : nullptr;
233+
#endif
234+
235+
if (storedWholeReply)
236+
fwd->markStoredReplyAsWhole(storedWholeReply);
237+
233238
doneWithFwd = "completeForwarding()";
234239
fwd->complete();
235240
}
@@ -738,9 +743,11 @@ Client::handleAdaptedHeader(Http::Message *msg)
738743
// assume that ICAP does not auto-consume on failures
739744
const bool result = adaptedBodySource->setConsumerIfNotLate(this);
740745
assert(result);
746+
checkAdaptationWithBodyCompletion();
741747
} else {
742748
// no body
743-
fwd->markStoredReplyAsWhole("setFinalReply() stored header-only adapted reply");
749+
Assure(!adaptedReplyAborted);
750+
receivedWholeAdaptedReply = true;
744751
if (doneWithAdaptation()) // we may still be sending virgin response
745752
handleAdaptationCompleted();
746753
}
@@ -757,8 +764,7 @@ Client::resumeBodyStorage()
757764

758765
handleMoreAdaptedBodyAvailable();
759766

760-
if (adaptedBodySource != nullptr && adaptedBodySource->exhausted())
761-
endAdaptedBodyConsumption();
767+
checkAdaptationWithBodyCompletion();
762768
}
763769

764770
// more adapted response body is available
@@ -815,28 +821,36 @@ Client::handleAdaptedBodyProductionEnded()
815821
if (abortOnBadEntry("entry went bad while waiting for adapted body eof"))
816822
return;
817823

818-
// distinguish this code path from handleAdaptedBodyProducerAborted()
824+
Assure(!adaptedReplyAborted);
819825
receivedWholeAdaptedReply = true;
820826

821-
// end consumption if we consumed everything
822-
if (adaptedBodySource != nullptr && adaptedBodySource->exhausted())
823-
endAdaptedBodyConsumption();
824-
// else resumeBodyStorage() will eventually consume the rest
827+
checkAdaptationWithBodyCompletion();
825828
}
826829

827830
void
828-
Client::endAdaptedBodyConsumption()
831+
Client::checkAdaptationWithBodyCompletion()
829832
{
830-
stopConsumingFrom(adaptedBodySource);
833+
if (!adaptedBodySource) {
834+
debugs(11, 7, "not consuming; " << startedAdaptation);
835+
return;
836+
}
837+
838+
if (!receivedWholeAdaptedReply && !adaptedReplyAborted) {
839+
// wait for noteBodyProductionEnded() or noteBodyProducerAborted()
840+
// because completeForwarding() needs to know whether we receivedWholeAdaptedReply
841+
debugs(11, 7, "waiting for adapted body production ending");
842+
return;
843+
}
831844

832-
if (receivedWholeAdaptedReply) {
833-
// We received the entire adapted reply per receivedWholeAdaptedReply.
834-
// We are called when we consumed everything received (per our callers).
835-
// We consume only what we store per handleMoreAdaptedBodyAvailable().
836-
fwd->markStoredReplyAsWhole("received,consumed=>stored the entire RESPMOD reply");
845+
if (!adaptedBodySource->exhausted()) {
846+
debugs(11, 5, "waiting to consume the remainder of the adapted body from " << adaptedBodySource->status());
847+
return; // resumeBodyStorage() should eventually consume the rest
837848
}
838849

839-
handleAdaptationCompleted();
850+
stopConsumingFrom(adaptedBodySource);
851+
852+
if (doneWithAdaptation()) // we may still be sending virgin response
853+
handleAdaptationCompleted();
840854
}
841855

842856
// premature end of the adapted response body
@@ -845,18 +859,18 @@ void Client::handleAdaptedBodyProducerAborted()
845859
if (abortOnBadEntry("entry went bad while waiting for the now-aborted adapted body"))
846860
return;
847861

862+
Assure(!receivedWholeAdaptedReply);
863+
adaptedReplyAborted = true;
848864
Must(adaptedBodySource != nullptr);
849865
if (!adaptedBodySource->exhausted()) {
850866
debugs(11,5, "waiting to consume the remainder of the aborted adapted body");
851867
return; // resumeBodyStorage() should eventually consume the rest
852868
}
853869

854-
stopConsumingFrom(adaptedBodySource);
855-
856870
if (handledEarlyAdaptationAbort())
857871
return;
858872

859-
handleAdaptationCompleted(); // the user should get a truncated response
873+
checkAdaptationWithBodyCompletion(); // the user should get a truncated response
860874
}
861875

862876
// common part of noteAdaptationAnswer and handleAdaptedBodyProductionEnded

src/clients/Client.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,11 @@ class Client:
141141

142142
/// called by StoreEntry when it has more buffer space available
143143
void resumeBodyStorage();
144-
/// called when the entire adapted response body is consumed
145-
void endAdaptedBodyConsumption();
144+
145+
/// Reacts to adaptedBodySource-related changes. May end adapted body
146+
/// consumption, adaptation as a whole, or forwarding. Safe to call multiple
147+
/// times, including calls made after the end of adaptation.
148+
void checkAdaptationWithBodyCompletion();
146149
#endif
147150

148151
protected:
@@ -189,14 +192,22 @@ class Client:
189192
bool adaptationAccessCheckPending = false;
190193
bool startedAdaptation = false;
191194

192-
/// handleAdaptedBodyProductionEnded() was called
195+
/// Whether the entire adapted response (including bodyless responses) has
196+
/// been successfully produced. A successful end of body production does not
197+
/// imply that we have consumed all of the produced body bytes.
193198
bool receivedWholeAdaptedReply = false;
199+
200+
bool adaptedReplyAborted = false; ///< handleAdaptedBodyProducerAborted() has been called
194201
#endif
195202
bool receivedWholeRequestBody = false; ///< handleRequestBodyProductionEnded called
196203

197204
/// whether we are waiting for MemObject::delayRead() to call us back
198205
bool waitingForDelayAwareReadChance = false;
199206

207+
/// markParsedVirginReplyAsWhole() parameter (if that method was called) or nil;
208+
/// points to a string literal which is used only for debugging
209+
const char *markedParsedVirginReplyAsWhole = nullptr;
210+
200211
/// whether we should not be talking to FwdState; XXX: clear fwd instead
201212
/// points to a string literal which is used only for debugging
202213
const char *doneWithFwd = nullptr;

0 commit comments

Comments
 (0)