]>
Commit | Line | Data |
---|---|---|
a22e6cd3 | 1 | /* |
ef57eb7b | 2 | * Copyright (C) 1996-2016 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
a22e6cd3 AR |
7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 93 Adaptation */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
1adcebc3 | 12 | #include "adaptation/Answer.h" |
a22e6cd3 AR |
13 | #include "adaptation/Config.h" |
14 | #include "adaptation/Iterator.h" | |
15 | #include "adaptation/Service.h" | |
16 | #include "adaptation/ServiceFilter.h" | |
17 | #include "adaptation/ServiceGroups.h" | |
3d93a84d | 18 | #include "base/TextException.h" |
3d93a84d | 19 | #include "HttpMsg.h" |
602d9612 A |
20 | #include "HttpReply.h" |
21 | #include "HttpRequest.h" | |
0d1c9fe8 | 22 | #include "sbuf/SBufStringConvert.h" |
a22e6cd3 | 23 | |
4299f876 | 24 | Adaptation::Iterator::Iterator( |
4cb2536f | 25 | HttpMsg *aMsg, HttpRequest *aCause, |
af0ded40 | 26 | AccessLogEntry::Pointer &alp, |
4cb2536f | 27 | const ServiceGroupPointer &aGroup): |
f53969cc SM |
28 | AsyncJob("Iterator"), |
29 | Adaptation::Initiate("Iterator"), | |
30 | theGroup(aGroup), | |
31 | theMsg(aMsg), | |
32 | theCause(aCause), | |
33 | al(alp), | |
34 | theLauncher(0), | |
35 | iterations(0), | |
36 | adapted(false) | |
a22e6cd3 | 37 | { |
b248c2a3 AJ |
38 | if (theCause != NULL) |
39 | HTTPMSGLOCK(theCause); | |
40 | ||
41 | if (theMsg != NULL) | |
42 | HTTPMSGLOCK(theMsg); | |
a22e6cd3 AR |
43 | } |
44 | ||
45 | Adaptation::Iterator::~Iterator() | |
46 | { | |
47 | assert(!theLauncher); | |
48 | HTTPMSGUNLOCK(theMsg); | |
49 | HTTPMSGUNLOCK(theCause); | |
50 | } | |
51 | ||
52 | void Adaptation::Iterator::start() | |
53 | { | |
54 | Adaptation::Initiate::start(); | |
55 | ||
56 | thePlan = ServicePlan(theGroup, filter()); | |
c302ddb5 CT |
57 | |
58 | // Add adaptation group name once and now, before | |
59 | // dynamic groups change it at step() time. | |
60 | if (Adaptation::Config::needHistory && !thePlan.exhausted() && (dynamic_cast<ServiceSet *>(theGroup.getRaw()) || dynamic_cast<ServiceChain *>(theGroup.getRaw()))) { | |
61 | HttpRequest *request = dynamic_cast<HttpRequest*>(theMsg); | |
62 | if (!request) | |
63 | request = theCause; | |
64 | Must(request); | |
65 | Adaptation::History::Pointer ah = request->adaptHistory(true); | |
0d1c9fe8 | 66 | SBuf gid(StringToSBuf(theGroup->id)); |
c302ddb5 CT |
67 | ah->recordAdaptationService(gid); |
68 | } | |
69 | ||
a22e6cd3 AR |
70 | step(); |
71 | } | |
72 | ||
73 | void Adaptation::Iterator::step() | |
74 | { | |
75 | ++iterations; | |
76 | debugs(93,5, HERE << '#' << iterations << " plan: " << thePlan); | |
77 | ||
78 | Must(!theLauncher); | |
79 | ||
80 | if (thePlan.exhausted()) { // nothing more to do | |
3af10ac0 | 81 | sendAnswer(Answer::Forward(theMsg)); |
a22e6cd3 AR |
82 | Must(done()); |
83 | return; | |
84 | } | |
85 | ||
129fe2a1 CT |
86 | HttpRequest *request = dynamic_cast<HttpRequest*>(theMsg); |
87 | if (!request) | |
88 | request = theCause; | |
89 | assert(request); | |
90 | request->clearError(); | |
91 | ||
a22e6cd3 AR |
92 | if (iterations > Adaptation::Config::service_iteration_limit) { |
93 | debugs(93,DBG_CRITICAL, "Adaptation iterations limit (" << | |
e1381638 AJ |
94 | Adaptation::Config::service_iteration_limit << ") exceeded:\n" << |
95 | "\tPossible service loop with " << | |
96 | theGroup->kind << " " << theGroup->id << ", plan=" << thePlan); | |
a22e6cd3 AR |
97 | throw TexcHere("too many adaptations"); |
98 | } | |
99 | ||
100 | ServicePointer service = thePlan.current(); | |
101 | Must(service != NULL); | |
102 | debugs(93,5, HERE << "using adaptation service: " << service->cfg().key); | |
103 | ||
c302ddb5 CT |
104 | if (Adaptation::Config::needHistory) { |
105 | Adaptation::History::Pointer ah = request->adaptHistory(true); | |
0d1c9fe8 | 106 | SBuf uid(StringToSBuf(thePlan.current()->cfg().key)); |
c302ddb5 CT |
107 | ah->recordAdaptationService(uid); |
108 | } | |
109 | ||
a22e6cd3 | 110 | theLauncher = initiateAdaptation( |
af0ded40 | 111 | service->makeXactLauncher(theMsg, theCause, al)); |
4299f876 | 112 | Must(initiated(theLauncher)); |
a22e6cd3 AR |
113 | Must(!done()); |
114 | } | |
115 | ||
3af10ac0 AR |
116 | void |
117 | Adaptation::Iterator::noteAdaptationAnswer(const Answer &answer) | |
118 | { | |
119 | switch (answer.kind) { | |
120 | case Answer::akForward: | |
b248c2a3 | 121 | handleAdaptedHeader(const_cast<HttpMsg*>(answer.message.getRaw())); |
3af10ac0 AR |
122 | break; |
123 | ||
124 | case Answer::akBlock: | |
125 | handleAdaptationBlock(answer); | |
126 | break; | |
127 | ||
128 | case Answer::akError: | |
129 | handleAdaptationError(answer.final); | |
130 | break; | |
131 | } | |
132 | } | |
133 | ||
134 | void | |
135 | Adaptation::Iterator::handleAdaptedHeader(HttpMsg *aMsg) | |
a22e6cd3 AR |
136 | { |
137 | // set theCause if we switched to request satisfaction mode | |
138 | if (!theCause) { // probably sent a request message | |
139 | if (dynamic_cast<HttpReply*>(aMsg)) { // we got a response message | |
140 | if (HttpRequest *cause = dynamic_cast<HttpRequest*>(theMsg)) { | |
141 | // definately sent request, now use it as the cause | |
142 | theCause = cause; // moving the lock | |
143 | theMsg = 0; | |
144 | debugs(93,3, HERE << "in request satisfaction mode"); | |
145 | } | |
146 | } | |
147 | } | |
148 | ||
149 | Must(aMsg); | |
150 | HTTPMSGUNLOCK(theMsg); | |
b248c2a3 AJ |
151 | theMsg = aMsg; |
152 | HTTPMSGLOCK(theMsg); | |
a22e6cd3 AR |
153 | adapted = true; |
154 | ||
155 | clearAdaptation(theLauncher); | |
156 | if (!updatePlan(true)) // do not immediatelly advance the new plan | |
157 | thePlan.next(filter()); | |
158 | step(); | |
159 | } | |
160 | ||
161 | void Adaptation::Iterator::noteInitiatorAborted() | |
162 | { | |
163 | announceInitiatorAbort(theLauncher); // propogate to the transaction | |
164 | clearInitiator(); | |
165 | mustStop("initiator gone"); | |
166 | } | |
167 | ||
3af10ac0 AR |
168 | void Adaptation::Iterator::handleAdaptationBlock(const Answer &answer) |
169 | { | |
170 | debugs(93,5, HERE << "blocked by " << answer); | |
171 | clearAdaptation(theLauncher); | |
172 | updatePlan(false); | |
173 | sendAnswer(answer); | |
174 | mustStop("blocked"); | |
175 | } | |
176 | ||
177 | void Adaptation::Iterator::handleAdaptationError(bool final) | |
a22e6cd3 AR |
178 | { |
179 | debugs(93,5, HERE << "final: " << final << " plan: " << thePlan); | |
180 | clearAdaptation(theLauncher); | |
181 | updatePlan(false); | |
182 | ||
183 | // can we replace the failed service (group-level bypass)? | |
e1381638 AJ |
184 | const bool srcIntact = !theMsg->body_pipe || |
185 | !theMsg->body_pipe->consumedSize(); | |
a22e6cd3 AR |
186 | // can we ignore the failure (compute while thePlan is not exhausted)? |
187 | Must(!thePlan.exhausted()); | |
188 | const bool canIgnore = thePlan.current()->cfg().bypass; | |
189 | debugs(85,5, HERE << "flags: " << srcIntact << canIgnore << adapted); | |
190 | ||
191 | if (srcIntact) { | |
192 | if (thePlan.replacement(filter()) != NULL) { | |
193 | debugs(93,3, HERE << "trying a replacement service"); | |
194 | step(); | |
195 | return; | |
196 | } | |
197 | } | |
198 | ||
199 | if (canIgnore && srcIntact && adapted) { | |
200 | debugs(85,3, HERE << "responding with older adapted msg"); | |
3af10ac0 | 201 | sendAnswer(Answer::Forward(theMsg)); |
a22e6cd3 AR |
202 | mustStop("sent older adapted msg"); |
203 | return; | |
204 | } | |
205 | ||
206 | // caller may recover if we can ignore the error and virgin msg is intact | |
207 | const bool useVirgin = canIgnore && !adapted && srcIntact; | |
208 | tellQueryAborted(!useVirgin); | |
209 | mustStop("group failure"); | |
210 | } | |
211 | ||
212 | bool Adaptation::Iterator::doneAll() const | |
213 | { | |
214 | return Adaptation::Initiate::doneAll() && thePlan.exhausted(); | |
215 | } | |
216 | ||
217 | void Adaptation::Iterator::swanSong() | |
218 | { | |
4299f876 | 219 | if (theInitiator.set()) |
a22e6cd3 AR |
220 | tellQueryAborted(true); // abnormal condition that should not happen |
221 | ||
4299f876 | 222 | if (initiated(theLauncher)) |
a22e6cd3 AR |
223 | clearAdaptation(theLauncher); |
224 | ||
225 | Adaptation::Initiate::swanSong(); | |
226 | } | |
227 | ||
228 | bool Adaptation::Iterator::updatePlan(bool adopt) | |
229 | { | |
230 | HttpRequest *r = theCause ? theCause : dynamic_cast<HttpRequest*>(theMsg); | |
231 | Must(r); | |
232 | ||
233 | Adaptation::History::Pointer ah = r->adaptHistory(); | |
aaf0559d AR |
234 | if (!ah) { |
235 | debugs(85,9, HERE << "no history to store a service-proposed plan"); | |
a22e6cd3 | 236 | return false; // the feature is not enabled or is not triggered |
aaf0559d | 237 | } |
a22e6cd3 AR |
238 | |
239 | String services; | |
240 | if (!ah->extractNextServices(services)) { // clears history | |
241 | debugs(85,9, HERE << "no service-proposed plan received"); | |
242 | return false; // the service did not provide a new plan | |
243 | } | |
244 | ||
245 | if (!adopt) { | |
246 | debugs(85,3, HERE << "rejecting service-proposed plan"); | |
247 | return false; | |
248 | } | |
e1381638 | 249 | |
a22e6cd3 | 250 | debugs(85,3, HERE << "retiring old plan: " << thePlan); |
53340485 | 251 | |
eb898410 | 252 | Adaptation::ServiceFilter f = this->filter(); |
53340485 | 253 | DynamicGroupCfg current, future; |
eb898410 | 254 | DynamicServiceChain::Split(f, services, current, future); |
53340485 AR |
255 | |
256 | if (!future.empty()) { | |
257 | ah->setFutureServices(future); | |
258 | debugs(85,3, HERE << "noted future service-proposed plan: " << future); | |
259 | } | |
260 | ||
261 | // use the current config even if it is empty; we must replace the old plan | |
eb898410 AJ |
262 | theGroup = new DynamicServiceChain(current, f); // refcounted |
263 | thePlan = ServicePlan(theGroup, f); | |
a22e6cd3 AR |
264 | debugs(85,3, HERE << "adopted service-proposed plan: " << thePlan); |
265 | return true; | |
266 | } | |
267 | ||
268 | Adaptation::ServiceFilter Adaptation::Iterator::filter() const | |
269 | { | |
270 | // the method may differ from theGroup->method due to request satisfaction | |
271 | Method method = methodNone; | |
272 | // temporary variables, no locking needed | |
273 | HttpRequest *req = NULL; | |
274 | HttpReply *rep = NULL; | |
275 | ||
276 | if (HttpRequest *r = dynamic_cast<HttpRequest*>(theMsg)) { | |
277 | method = methodReqmod; | |
278 | req = r; | |
279 | rep = NULL; | |
b0365bd9 | 280 | } else if (HttpReply *theReply = dynamic_cast<HttpReply*>(theMsg)) { |
a22e6cd3 AR |
281 | method = methodRespmod; |
282 | req = theCause; | |
b0365bd9 | 283 | rep = theReply; |
a22e6cd3 AR |
284 | } else { |
285 | Must(false); // should not happen | |
286 | } | |
287 | ||
af0ded40 | 288 | return ServiceFilter(method, theGroup->point, req, rep, al); |
a22e6cd3 AR |
289 | } |
290 | ||
291 | CBDATA_NAMESPACED_CLASS_INIT(Adaptation, Iterator); | |
f53969cc | 292 |