]>
Commit | Line | Data |
---|---|---|
9cef6668 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
26ac0430 | 3 | * |
bbc27441 AJ |
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. | |
9cef6668 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 90 Storage Manager Client-Side Interface */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
819be284 | 12 | #include "acl/FilledChecklist.h" |
1fa761af | 13 | #include "base/AsyncCbdataCalls.h" |
f925268a | 14 | #include "base/CodeContext.h" |
a553a5a3 | 15 | #include "event.h" |
22bbd840 | 16 | #include "globals.h" |
528b2c61 | 17 | #include "HttpReply.h" |
582c2af2 FC |
18 | #include "HttpRequest.h" |
19 | #include "MemBuf.h" | |
528b2c61 | 20 | #include "MemObject.h" |
b6149797 | 21 | #include "mime_header.h" |
122a6e3c | 22 | #include "sbuf/Stream.h" |
4d5904f7 | 23 | #include "SquidConfig.h" |
122a6e3c | 24 | #include "SquidMath.h" |
e4f1fdae | 25 | #include "StatCounters.h" |
582c2af2 | 26 | #include "Store.h" |
d448e1eb | 27 | #include "store/SwapMetaIn.h" |
f82b5c64 | 28 | #include "store_swapin.h" |
602d9612 | 29 | #include "StoreClient.h" |
9a0a18de | 30 | #if USE_DELAY_POOLS |
b67e2c8c | 31 | #include "DelayPools.h" |
32 | #endif | |
c8be6d7b | 33 | |
e3ef2b09 | 34 | /* |
35 | * NOTE: 'Header' refers to the swapfile metadata header. | |
2f8abb64 | 36 | * 'OBJHeader' refers to the object header, with canonical |
f53969cc SM |
37 | * processed object headers (which may derive from FTP/HTTP etc |
38 | * upstream protocols | |
e3ef2b09 | 39 | * 'Body' refers to the swapfile body, which is the full |
40 | * HTTP reply (including HTTP headers and body). | |
41 | */ | |
4fcc8876 | 42 | static StoreIOState::STRCB storeClientReadBody; |
43 | static StoreIOState::STRCB storeClientReadHeader; | |
f09f5b26 | 44 | static void storeClientCopy2(StoreEntry * e, store_client * sc); |
ae6568e7 | 45 | static bool CheckQuickAbortIsReasonable(StoreEntry * entry); |
f09f5b26 | 46 | |
b001e822 | 47 | CBDATA_CLASS_INIT(store_client); |
528b2c61 | 48 | |
819be284 EB |
49 | /* StoreClient */ |
50 | ||
51 | bool | |
52 | StoreClient::onCollapsingPath() const | |
53 | { | |
54 | if (!Config.onoff.collapsed_forwarding) | |
55 | return false; | |
56 | ||
57 | if (!Config.accessList.collapsedForwardingAccess) | |
58 | return true; | |
59 | ||
e94ff527 | 60 | ACLFilledChecklist checklist(Config.accessList.collapsedForwardingAccess, nullptr); |
819be284 EB |
61 | fillChecklist(checklist); |
62 | return checklist.fastCheck().allowed(); | |
63 | } | |
64 | ||
65 | bool | |
7976fed3 | 66 | StoreClient::startCollapsingOn(const StoreEntry &e, const bool doingRevalidation) const |
819be284 | 67 | { |
d2a6dcba EB |
68 | if (!e.hittingRequiresCollapsing()) |
69 | return false; // collapsing is impossible due to the entry state | |
70 | ||
71 | if (!onCollapsingPath()) | |
72 | return false; // collapsing is impossible due to Squid configuration | |
73 | ||
74 | /* collapsing is possible; the caller must collapse */ | |
75 | ||
76 | if (const auto tags = loggingTags()) { | |
77 | if (doingRevalidation) | |
78 | tags->collapsingHistory.revalidationCollapses++; | |
79 | else | |
80 | tags->collapsingHistory.otherCollapses++; | |
81 | } | |
82 | ||
83 | debugs(85, 5, e << " doingRevalidation=" << doingRevalidation); | |
84 | return true; | |
819be284 EB |
85 | } |
86 | ||
819be284 EB |
87 | /* store_client */ |
88 | ||
528b2c61 | 89 | int |
90 | store_client::getType() const | |
91 | { | |
92 | return type; | |
93 | } | |
94 | ||
06d2839d | 95 | #if STORE_CLIENT_LIST_DEBUG |
fa80a8ef | 96 | static store_client * |
f09f5b26 | 97 | storeClientListSearch(const MemObject * mem, void *data) |
98 | { | |
06d2839d | 99 | dlink_node *node; |
a1b1756c | 100 | store_client *sc = nullptr; |
62e76326 | 101 | |
06d2839d | 102 | for (node = mem->clients.head; node; node = node->next) { |
62e76326 | 103 | sc = node->data; |
104 | ||
105 | if (sc->owner == data) | |
106 | return sc; | |
f09f5b26 | 107 | } |
62e76326 | 108 | |
a1b1756c | 109 | return nullptr; |
f09f5b26 | 110 | } |
c8be6d7b | 111 | |
112 | int | |
113 | storeClientIsThisAClient(store_client * sc, void *someClient) | |
114 | { | |
115 | return sc->owner == someClient; | |
116 | } | |
62e76326 | 117 | |
06d2839d | 118 | #endif |
924f73bc | 119 | #include "HttpRequest.h" |
f09f5b26 | 120 | |
121 | /* add client with fd to client list */ | |
06d2839d | 122 | store_client * |
f09f5b26 | 123 | storeClientListAdd(StoreEntry * e, void *data) |
124 | { | |
125 | MemObject *mem = e->mem_obj; | |
f09f5b26 | 126 | store_client *sc; |
127 | assert(mem); | |
06d2839d | 128 | #if STORE_CLIENT_LIST_DEBUG |
62e76326 | 129 | |
f09f5b26 | 130 | if (storeClientListSearch(mem, data) != NULL) |
62e76326 | 131 | /* XXX die! */ |
132 | assert(1 == 0); | |
8b082ed9 FC |
133 | #else |
134 | (void)data; | |
6b8e7481 | 135 | #endif |
62e76326 | 136 | |
528b2c61 | 137 | sc = new store_client (e); |
62e76326 | 138 | |
528b2c61 | 139 | mem->addClient(sc); |
62e76326 | 140 | |
06d2839d | 141 | return sc; |
f09f5b26 | 142 | } |
143 | ||
1fa761af EB |
144 | /// finishCallback() wrapper; TODO: Add NullaryMemFunT for non-jobs. |
145 | void | |
146 | store_client::FinishCallback(store_client * const sc) | |
147 | { | |
148 | sc->finishCallback(); | |
149 | } | |
62e76326 | 150 | |
1fa761af EB |
151 | /// finishes a copy()-STCB sequence by synchronously calling STCB |
152 | void | |
153 | store_client::finishCallback() | |
154 | { | |
155 | Assure(_callback.callback_handler); | |
156 | Assure(_callback.notifier); | |
157 | ||
122a6e3c AR |
158 | // XXX: Some legacy code relies on zero-length buffers having nil data |
159 | // pointers. Some other legacy code expects "correct" result.offset even | |
160 | // when there is no body to return. Accommodate all those expectations. | |
161 | auto result = StoreIOBuffer(0, copyInto.offset, nullptr); | |
162 | if (object_ok && parsingBuffer && parsingBuffer->contentSize()) | |
163 | result = parsingBuffer->packBack(); | |
1fa761af | 164 | result.flags.error = object_ok ? 0 : 1; |
1fa761af | 165 | |
3a5c8b44 AR |
166 | // TODO: Move object_ok handling above into this `if` statement. |
167 | if (object_ok) { | |
168 | // works for zero hdr_sz cases as well; see also: nextHttpReadOffset() | |
169 | discardableHttpEnd_ = NaturalSum<int64_t>(entry->mem().baseReply().hdr_sz, result.offset, result.length).value(); | |
170 | } else { | |
171 | // object_ok is sticky, so we will not be able to use any response bytes | |
172 | discardableHttpEnd_ = entry->mem().endOffset(); | |
173 | } | |
174 | debugs(90, 7, "with " << result << "; discardableHttpEnd_=" << discardableHttpEnd_); | |
175 | ||
122a6e3c AR |
176 | // no HTTP headers and no body bytes (but not because there was no space) |
177 | atEof_ = !sendingHttpHeaders() && !result.length && copyInto.length; | |
178 | ||
179 | parsingBuffer.reset(); | |
180 | ++answers; | |
181 | ||
528b2c61 | 182 | STCB *temphandler = _callback.callback_handler; |
6ec49cda AR |
183 | const auto cbdata = _callback.cbData.validDone(); |
184 | _callback = Callback(); | |
aee3523a | 185 | copyInto.data = nullptr; |
62e76326 | 186 | |
6ec49cda | 187 | if (cbdata) |
62e76326 | 188 | temphandler(cbdata, result); |
b04e66e0 | 189 | } |
190 | ||
cc8c4af2 | 191 | store_client::store_client(StoreEntry *e) : |
cc8c4af2 AJ |
192 | #if STORE_CLIENT_LIST_DEBUG |
193 | owner(cbdataReference(data)), | |
b67e2c8c | 194 | #endif |
cc8c4af2 AJ |
195 | entry(e), |
196 | type(e->storeClientType()), | |
1fa761af | 197 | object_ok(true), |
122a6e3c AR |
198 | atEof_(false), |
199 | answers(0) | |
528b2c61 | 200 | { |
836d3c0b AR |
201 | Assure(entry); |
202 | entry->lock("store_client"); | |
203 | ||
3dd52a0b | 204 | flags.disk_io_pending = false; |
cc8c4af2 | 205 | flags.store_copying = false; |
5db6bf73 | 206 | ++ entry->refcount; |
62e76326 | 207 | |
cc8c4af2 | 208 | if (getType() == STORE_DISK_CLIENT) { |
62e76326 | 209 | /* assert we'll be able to get the data we want */ |
02a2d80b | 210 | /* maybe we should open swapin_sio here */ |
02ba667b | 211 | assert(entry->hasDisk() && !entry->swapoutFailed()); |
cc8c4af2 | 212 | } |
528b2c61 | 213 | } |
214 | ||
2f44bd34 | 215 | store_client::~store_client() |
836d3c0b AR |
216 | { |
217 | assert(entry); | |
218 | entry->unlock("store_client"); | |
219 | } | |
2f44bd34 | 220 | |
f09f5b26 | 221 | /* copy bytes requested by the client */ |
222 | void | |
a4b8110e | 223 | storeClientCopy(store_client * sc, |
62e76326 | 224 | StoreEntry * e, |
225 | StoreIOBuffer copyInto, | |
226 | STCB * callback, | |
227 | void *data) | |
f09f5b26 | 228 | { |
aee3523a | 229 | assert (sc != nullptr); |
528b2c61 | 230 | sc->copy(e, copyInto,callback,data); |
231 | } | |
232 | ||
233 | void | |
234 | store_client::copy(StoreEntry * anEntry, | |
62e76326 | 235 | StoreIOBuffer copyRequest, |
236 | STCB * callback_fn, | |
237 | void *data) | |
528b2c61 | 238 | { |
239 | assert (anEntry == entry); | |
240 | assert (callback_fn); | |
241 | assert (data); | |
242 | assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED)); | |
bf8fe701 | 243 | debugs(90, 3, "store_client::copy: " << entry->getMD5Text() << ", from " << |
47f6e231 | 244 | copyRequest.offset << ", for length " << |
bf8fe701 | 245 | (int) copyRequest.length << ", cb " << callback_fn << ", cbdata " << |
246 | data); | |
247 | ||
06d2839d | 248 | #if STORE_CLIENT_LIST_DEBUG |
62e76326 | 249 | |
528b2c61 | 250 | assert(this == storeClientListSearch(entry->mem_obj, data)); |
06d2839d | 251 | #endif |
62e76326 | 252 | |
90703668 | 253 | assert(!_callback.pending()); |
6ec49cda | 254 | _callback = Callback(callback_fn, data); |
528b2c61 | 255 | copyInto.data = copyRequest.data; |
256 | copyInto.length = copyRequest.length; | |
257 | copyInto.offset = copyRequest.offset; | |
122a6e3c AR |
258 | Assure(copyInto.offset >= 0); |
259 | ||
260 | if (!copyInto.length) { | |
261 | // During the first storeClientCopy() call, a zero-size buffer means | |
262 | // that we will have to drop any HTTP response body bytes we read (with | |
263 | // the HTTP headers from disk). After that, it means we cannot return | |
264 | // anything to the caller at all. | |
265 | debugs(90, 2, "WARNING: zero-size storeClientCopy() buffer: " << copyInto); | |
266 | // keep going; moreToRead() should prevent any from-Store reading | |
267 | } | |
268 | ||
269 | // Our nextHttpReadOffset() expects the first copy() call to have zero | |
270 | // offset. More complex code could handle a positive first offset, but it | |
271 | // would only be useful when reading responses from memory: We would not | |
272 | // _delay_ the response (to read the requested HTTP body bytes from disk) | |
273 | // when we already can respond with HTTP headers. | |
274 | Assure(!copyInto.offset || answeredOnce()); | |
275 | ||
276 | parsingBuffer.emplace(copyInto); | |
528b2c61 | 277 | |
3a5c8b44 AR |
278 | discardableHttpEnd_ = nextHttpReadOffset(); |
279 | debugs(90, 7, "discardableHttpEnd_=" << discardableHttpEnd_); | |
280 | ||
1d5161bd | 281 | static bool copying (false); |
282 | assert (!copying); | |
283 | copying = true; | |
a46d2c0e | 284 | /* we might be blocking comm reads due to readahead limits |
285 | * now we have a new offset, trigger those reads... | |
286 | */ | |
287 | entry->mem_obj->kickReads(); | |
1d5161bd | 288 | copying = false; |
a46d2c0e | 289 | |
4475555f AR |
290 | anEntry->lock("store_client::copy"); // see deletion note below |
291 | ||
528b2c61 | 292 | storeClientCopy2(entry, this); |
0ad2b63b | 293 | |
4475555f AR |
294 | // Bug 3480: This store_client object may be deleted now if, for example, |
295 | // the client rejects the hit response copied above. Use on-stack pointers! | |
296 | ||
0ad2b63b | 297 | #if USE_ADAPTATION |
4475555f | 298 | anEntry->kickProducer(); |
0ad2b63b | 299 | #endif |
4475555f | 300 | anEntry->unlock("store_client::copy"); |
1809cebd | 301 | |
1809cebd | 302 | // Add no code here. This object may no longer exist. |
f09f5b26 | 303 | } |
304 | ||
122a6e3c | 305 | /// Whether Store has (or possibly will have) more entry data for us. |
f25d697f | 306 | bool |
122a6e3c | 307 | store_client::moreToRead() const |
07304bf9 | 308 | { |
122a6e3c AR |
309 | if (!copyInto.length) |
310 | return false; // the client supplied a zero-size buffer | |
311 | ||
f25d697f AR |
312 | if (entry->store_status == STORE_PENDING) |
313 | return true; // there may be more coming | |
314 | ||
315 | /* STORE_OK, including aborted entries: no more data is coming */ | |
316 | ||
122a6e3c AR |
317 | if (canReadFromMemory()) |
318 | return true; // memory has the first byte wanted by the client | |
62e76326 | 319 | |
122a6e3c AR |
320 | if (!entry->hasDisk()) |
321 | return false; // cannot read anything from disk either | |
62e76326 | 322 | |
122a6e3c AR |
323 | if (entry->objectLen() >= 0 && copyInto.offset >= entry->contentLen()) |
324 | return false; // the disk cannot have byte(s) wanted by the client | |
62e76326 | 325 | |
122a6e3c AR |
326 | // we cannot be sure until we swap in metadata and learn contentLen(), |
327 | // but the disk may have the byte(s) wanted by the client | |
328 | return true; | |
07304bf9 | 329 | } |
330 | ||
f09f5b26 | 331 | static void |
332 | storeClientCopy2(StoreEntry * e, store_client * sc) | |
333 | { | |
528b2c61 | 334 | /* reentrancy not allowed - note this could lead to |
1fa761af | 335 | * dropped notifications about response data availability |
528b2c61 | 336 | */ |
62e76326 | 337 | |
67fd69de | 338 | if (sc->flags.store_copying) { |
1fa761af | 339 | debugs(90, 3, "prevented recursive copying for " << *e); |
62e76326 | 340 | return; |
67fd69de | 341 | } |
62e76326 | 342 | |
bf8fe701 | 343 | debugs(90, 3, "storeClientCopy2: " << e->getMD5Text()); |
90703668 | 344 | assert(sc->_callback.pending()); |
0bb129ee | 345 | /* |
b7fe0ab0 | 346 | * We used to check for ENTRY_ABORTED here. But there were some |
0bb129ee | 347 | * problems. For example, we might have a slow client (or two) and |
d5430dc8 AJ |
348 | * the peer server is reading far ahead and swapping to disk. Even |
349 | * if the peer aborts, we want to give the client(s) | |
0bb129ee | 350 | * everything we got before the abort condition occurred. |
351 | */ | |
528b2c61 | 352 | sc->doCopy(e); |
cfac48c2 | 353 | } |
354 | ||
122a6e3c AR |
355 | /// Whether our answer, if sent right now, will announce the availability of |
356 | /// HTTP response headers (to the STCB callback) for the first time. | |
357 | bool | |
358 | store_client::sendingHttpHeaders() const | |
359 | { | |
360 | return !answeredOnce() && entry->mem().baseReply().hdr_sz > 0; | |
361 | } | |
362 | ||
528b2c61 | 363 | void |
364 | store_client::doCopy(StoreEntry *anEntry) | |
cfac48c2 | 365 | { |
1fa761af EB |
366 | Assure(_callback.pending()); |
367 | Assure(!flags.disk_io_pending); | |
368 | Assure(!flags.store_copying); | |
369 | ||
528b2c61 | 370 | assert (anEntry == entry); |
3dd52a0b | 371 | flags.store_copying = true; |
528b2c61 | 372 | MemObject *mem = entry->mem_obj; |
cd748f27 | 373 | |
122a6e3c AR |
374 | debugs(33, 5, this << " into " << copyInto << |
375 | " hi: " << mem->endOffset() << | |
376 | " objectLen: " << entry->objectLen() << | |
377 | " past_answers: " << answers); | |
add2192d | 378 | |
122a6e3c AR |
379 | const auto sendHttpHeaders = sendingHttpHeaders(); |
380 | ||
381 | if (!sendHttpHeaders && !moreToRead()) { | |
62e76326 | 382 | /* There is no more to send! */ |
bf95c10a | 383 | debugs(33, 3, "There is no more to send!"); |
122a6e3c | 384 | noteNews(); |
3dd52a0b | 385 | flags.store_copying = false; |
62e76326 | 386 | return; |
cfac48c2 | 387 | } |
62e76326 | 388 | |
122a6e3c | 389 | if (!sendHttpHeaders && anEntry->store_status == STORE_PENDING && nextHttpReadOffset() >= mem->endOffset()) { |
bf8fe701 | 390 | debugs(90, 3, "store_client::doCopy: Waiting for more"); |
3dd52a0b | 391 | flags.store_copying = false; |
62e76326 | 392 | return; |
cfac48c2 | 393 | } |
62e76326 | 394 | |
cfac48c2 | 395 | /* |
396 | * Slight weirdness here. We open a swapin file for any | |
397 | * STORE_DISK_CLIENT, even if we can copy the requested chunk | |
398 | * from memory in the next block. We must try to open the | |
399 | * swapin file before sending any data to the client side. If | |
400 | * we postpone the open, and then can not open the file later | |
401 | * on, the client loses big time. Its transfer just gets cut | |
402 | * off. Better to open it early (while the client side handler | |
403 | * is clientCacheHit) so that we can fall back to a cache miss | |
404 | * if needed. | |
405 | */ | |
fa80a8ef | 406 | |
aee3523a | 407 | if (STORE_DISK_CLIENT == getType() && swapin_sio == nullptr) { |
0cdcf3d7 AR |
408 | if (!startSwapin()) |
409 | return; // failure | |
410 | } | |
122a6e3c | 411 | |
6e73b4aa AR |
412 | // Send any immediately available body bytes unless we sendHttpHeaders. |
413 | // TODO: Send those body bytes when we sendHttpHeaders as well. | |
414 | if (!sendHttpHeaders && canReadFromMemory()) { | |
122a6e3c AR |
415 | readFromMemory(); |
416 | noteNews(); // will sendHttpHeaders (if needed) as well | |
417 | flags.store_copying = false; | |
418 | return; | |
419 | } | |
420 | ||
421 | if (sendHttpHeaders) { | |
422 | debugs(33, 5, "just send HTTP headers: " << mem->baseReply().hdr_sz); | |
423 | noteNews(); | |
424 | flags.store_copying = false; | |
425 | return; | |
426 | } | |
427 | ||
428 | // no information that the client needs is available immediately | |
429 | scheduleDiskRead(); | |
4e70dae3 | 430 | } |
431 | ||
0cdcf3d7 AR |
432 | /// opens the swapin "file" if possible; otherwise, fail()s and returns false |
433 | bool | |
4e70dae3 | 434 | store_client::startSwapin() |
435 | { | |
bf8fe701 | 436 | debugs(90, 3, "store_client::doCopy: Need to open swap in file"); |
4e70dae3 | 437 | /* gotta open the swapin file */ |
438 | ||
439 | if (storeTooManyDiskFilesOpen()) { | |
440 | /* yuck -- this causes a TCP_SWAPFAIL_MISS on the client side */ | |
441 | fail(); | |
3dd52a0b | 442 | flags.store_copying = false; |
0cdcf3d7 | 443 | return false; |
4e70dae3 | 444 | } else if (!flags.disk_io_pending) { |
445 | /* Don't set store_io_pending here */ | |
446 | storeSwapInStart(this); | |
62e76326 | 447 | |
aee3523a | 448 | if (swapin_sio == nullptr) { |
62e76326 | 449 | fail(); |
3dd52a0b | 450 | flags.store_copying = false; |
0cdcf3d7 | 451 | return false; |
62e76326 | 452 | } |
62e76326 | 453 | |
0cdcf3d7 | 454 | return true; |
4e70dae3 | 455 | } else { |
e0236918 | 456 | debugs(90, DBG_IMPORTANT, "WARNING: Averted multiple fd operation (1)"); |
3dd52a0b | 457 | flags.store_copying = false; |
0cdcf3d7 | 458 | return false; |
cfac48c2 | 459 | } |
4e70dae3 | 460 | } |
62e76326 | 461 | |
1fa761af EB |
462 | void |
463 | store_client::noteSwapInDone(const bool error) | |
464 | { | |
465 | Assure(_callback.pending()); | |
466 | if (error) | |
467 | fail(); | |
468 | else | |
122a6e3c | 469 | noteNews(); |
4e70dae3 | 470 | } |
471 | ||
472 | void | |
473 | store_client::scheduleDiskRead() | |
474 | { | |
cd748f27 | 475 | /* What the client wants is not in memory. Schedule a disk read */ |
0cdcf3d7 AR |
476 | if (getType() == STORE_DISK_CLIENT) { |
477 | // we should have called startSwapin() already | |
aee3523a | 478 | assert(swapin_sio != nullptr); |
2da4bfe6 | 479 | } else if (!swapin_sio && !startSwapin()) { |
0cdcf3d7 AR |
480 | debugs(90, 3, "bailing after swapin start failure for " << *entry); |
481 | assert(!flags.store_copying); | |
482 | return; | |
483 | } | |
62e76326 | 484 | |
528b2c61 | 485 | assert(!flags.disk_io_pending); |
62e76326 | 486 | |
0cdcf3d7 | 487 | debugs(90, 3, "reading " << *entry << " from disk"); |
62e76326 | 488 | |
528b2c61 | 489 | fileRead(); |
62e76326 | 490 | |
3dd52a0b | 491 | flags.store_copying = false; |
f09f5b26 | 492 | } |
493 | ||
122a6e3c AR |
494 | /// whether at least one byte wanted by the client is in memory |
495 | bool | |
496 | store_client::canReadFromMemory() const | |
497 | { | |
498 | const auto &mem = entry->mem(); | |
499 | const auto memReadOffset = nextHttpReadOffset(); | |
6e73b4aa | 500 | // XXX: This (lo <= offset < end) logic does not support Content-Range gaps. |
122a6e3c AR |
501 | return mem.inmem_lo <= memReadOffset && memReadOffset < mem.endOffset() && |
502 | parsingBuffer->spaceSize(); | |
503 | } | |
504 | ||
505 | /// The offset of the next stored HTTP response byte wanted by the client. | |
506 | int64_t | |
507 | store_client::nextHttpReadOffset() const | |
508 | { | |
509 | Assure(parsingBuffer); | |
510 | const auto &mem = entry->mem(); | |
511 | const auto hdr_sz = mem.baseReply().hdr_sz; | |
512 | // Certain SMP cache manager transactions do not store HTTP headers in | |
513 | // mem_hdr; they store just a kid-specific piece of the future report body. | |
514 | // In such cases, hdr_sz ought to be zero. In all other (known) cases, | |
515 | // mem_hdr contains HTTP response headers (positive hdr_sz if parsed) | |
516 | // followed by HTTP response body. This code math accommodates all cases. | |
517 | return NaturalSum<int64_t>(hdr_sz, copyInto.offset, parsingBuffer->contentSize()).value(); | |
518 | } | |
519 | ||
520 | /// Copies at least some of the requested body bytes from MemObject memory, | |
521 | /// satisfying the copy() request. | |
522 | /// \pre canReadFromMemory() is true | |
4e70dae3 | 523 | void |
122a6e3c | 524 | store_client::readFromMemory() |
4e70dae3 | 525 | { |
122a6e3c AR |
526 | Assure(parsingBuffer); |
527 | const auto readInto = parsingBuffer->space().positionAt(nextHttpReadOffset()); | |
528 | ||
529 | debugs(90, 3, "copying HTTP body bytes from memory into " << readInto); | |
530 | const auto sz = entry->mem_obj->data_hdr.copy(readInto); | |
531 | Assure(sz > 0); // our canReadFromMemory() precondition guarantees that | |
532 | parsingBuffer->appended(readInto.data, sz); | |
4e70dae3 | 533 | } |
534 | ||
528b2c61 | 535 | void |
536 | store_client::fileRead() | |
f09f5b26 | 537 | { |
528b2c61 | 538 | MemObject *mem = entry->mem_obj; |
539 | ||
90703668 | 540 | assert(_callback.pending()); |
528b2c61 | 541 | assert(!flags.disk_io_pending); |
3dd52a0b | 542 | flags.disk_io_pending = true; |
62e76326 | 543 | |
122a6e3c AR |
544 | // mem->swap_hdr_sz is zero here during initial read(s) |
545 | const auto nextStoreReadOffset = NaturalSum<int64_t>(mem->swap_hdr_sz, nextHttpReadOffset()).value(); | |
546 | ||
547 | // XXX: If fileRead() is called when we do not yet know mem->swap_hdr_sz, | |
548 | // then we must start reading from disk offset zero to learn it: we cannot | |
549 | // compute correct HTTP response start offset on disk without it. However, | |
550 | // late startSwapin() calls imply that the assertion below might fail. | |
551 | Assure(mem->swap_hdr_sz > 0 || !nextStoreReadOffset); | |
552 | ||
553 | // TODO: Remove this assertion. Introduced in 1998 commit 3157c72, it | |
554 | // assumes that swapped out memory is freed unconditionally, but we no | |
555 | // longer do that because trimMemory() path checks lowestMemReaderOffset(). | |
556 | // It is also misplaced: We are not swapping out anything here and should | |
557 | // not care about any swapout invariants. | |
528b2c61 | 558 | if (mem->swap_hdr_sz != 0) |
4310f8b0 | 559 | if (entry->swappingOut()) |
122a6e3c AR |
560 | assert(mem->swapout.sio->offset() > nextStoreReadOffset); |
561 | ||
562 | // XXX: We should let individual cache_dirs limit the read size instead, but | |
563 | // we cannot do that without more fixes and research because: | |
564 | // * larger reads corrupt responses when cache_dir uses SharedMemory::get(); | |
565 | // * we do not know how to find all I/O code that assumes this limit; | |
566 | // * performance effects of larger disk reads may be negative somewhere. | |
567 | const decltype(StoreIOBuffer::length) maxReadSize = SM_PAGE_SIZE; | |
568 | ||
569 | Assure(parsingBuffer); | |
570 | // also, do not read more than we can return (via a copyInto.length buffer) | |
571 | const auto readSize = std::min(copyInto.length, maxReadSize); | |
572 | lastDiskRead = parsingBuffer->makeSpace(readSize).positionAt(nextStoreReadOffset); | |
573 | debugs(90, 5, "into " << lastDiskRead); | |
62e76326 | 574 | |
528b2c61 | 575 | storeRead(swapin_sio, |
122a6e3c AR |
576 | lastDiskRead.data, |
577 | lastDiskRead.length, | |
578 | lastDiskRead.offset, | |
62e76326 | 579 | mem->swap_hdr_sz == 0 ? storeClientReadHeader |
580 | : storeClientReadBody, | |
581 | this); | |
f09f5b26 | 582 | } |
583 | ||
beae59b0 | 584 | void |
122a6e3c | 585 | store_client::readBody(const char * const buf, const ssize_t lastIoResult) |
beae59b0 | 586 | { |
122a6e3c | 587 | Assure(flags.disk_io_pending); |
3dd52a0b | 588 | flags.disk_io_pending = false; |
beae59b0 | 589 | assert(_callback.pending()); |
122a6e3c AR |
590 | Assure(parsingBuffer); |
591 | debugs(90, 3, "got " << lastIoResult << " using " << *parsingBuffer); | |
62e76326 | 592 | |
122a6e3c | 593 | if (lastIoResult < 0) |
18102f7d EB |
594 | return fail(); |
595 | ||
122a6e3c AR |
596 | if (!lastIoResult) { |
597 | if (answeredOnce()) | |
598 | return noteNews(); | |
599 | ||
600 | debugs(90, DBG_CRITICAL, "ERROR: Truncated HTTP headers in on-disk object"); | |
601 | return fail(); | |
06a5ae20 | 602 | } |
62e76326 | 603 | |
122a6e3c AR |
604 | assert(lastDiskRead.data == buf); |
605 | lastDiskRead.length = lastIoResult; | |
606 | ||
607 | parsingBuffer->appended(buf, lastIoResult); | |
608 | ||
609 | // we know swap_hdr_sz by now and were reading beyond swap metadata because | |
610 | // readHead() would have been called otherwise (to read swap metadata) | |
611 | const auto swap_hdr_sz = entry->mem().swap_hdr_sz; | |
612 | Assure(swap_hdr_sz > 0); | |
613 | Assure(!Less(lastDiskRead.offset, swap_hdr_sz)); | |
614 | ||
615 | // Map lastDiskRead (i.e. the disk area we just read) to an HTTP reply part. | |
616 | // The bytes are the same, but disk and HTTP offsets differ by swap_hdr_sz. | |
617 | const auto httpOffset = lastDiskRead.offset - swap_hdr_sz; | |
618 | const auto httpPart = StoreIOBuffer(lastDiskRead).positionAt(httpOffset); | |
619 | ||
620 | maybeWriteFromDiskToMemory(httpPart); | |
621 | handleBodyFromDisk(); | |
622 | } | |
623 | ||
624 | /// de-serializes HTTP response (partially) read from disk storage | |
625 | void | |
626 | store_client::handleBodyFromDisk() | |
627 | { | |
628 | // We cannot de-serialize on-disk HTTP response without MemObject because | |
629 | // without MemObject::swap_hdr_sz we cannot know where that response starts. | |
630 | Assure(entry->mem_obj); | |
631 | Assure(entry->mem_obj->swap_hdr_sz > 0); | |
632 | ||
633 | if (!answeredOnce()) { | |
634 | // All on-disk responses have HTTP headers. First disk body read(s) | |
635 | // include HTTP headers that we must parse (if needed) and skip. | |
39b5a589 | 636 | const auto haveHttpHeaders = entry->hasParsedReplyHeader(); |
122a6e3c AR |
637 | if (!haveHttpHeaders && !parseHttpHeadersFromDisk()) |
638 | return; | |
639 | skipHttpHeadersFromDisk(); | |
640 | } | |
641 | ||
642 | noteNews(); | |
643 | } | |
644 | ||
645 | /// Adds HTTP response data loaded from disk to the memory cache (if | |
646 | /// needed/possible). The given part may contain portions of HTTP response | |
647 | /// headers and/or HTTP response body. | |
648 | void | |
649 | store_client::maybeWriteFromDiskToMemory(const StoreIOBuffer &httpResponsePart) | |
650 | { | |
651 | // XXX: Reject [memory-]uncachable/unshareable responses instead of assuming | |
652 | // that an HTTP response should be written to MemObject's data_hdr (and that | |
653 | // it may purge already cached entries) just because it "fits" and was | |
654 | // loaded from disk. For example, this response may already be marked for | |
655 | // release. The (complex) cachability decision(s) should be made outside | |
656 | // (and obeyed by) this low-level code. | |
657 | if (httpResponsePart.length && entry->mem_obj->inmem_lo == 0 && entry->objectLen() <= (int64_t)Config.Store.maxInMemObjSize && Config.onoff.memory_cache_disk) { | |
658 | storeGetMemSpace(httpResponsePart.length); | |
659 | // XXX: This "recheck" is not needed because storeGetMemSpace() cannot | |
660 | // purge mem_hdr bytes of a locked entry, and we do lock ours. And | |
661 | // inmem_lo offset itself should not be relevant to appending new bytes. | |
662 | // | |
836d3c0b | 663 | // recheck for the above call may purge entry's data from the memory cache |
6d3c2758 | 664 | if (entry->mem_obj->inmem_lo == 0) { |
122a6e3c AR |
665 | // XXX: This code assumes a non-shared memory cache. |
666 | if (httpResponsePart.offset == entry->mem_obj->endOffset()) | |
667 | entry->mem_obj->write(httpResponsePart); | |
3196f526 | 668 | } |
beae59b0 | 669 | } |
528b2c61 | 670 | } |
671 | ||
672 | void | |
62e76326 | 673 | store_client::fail() |
528b2c61 | 674 | { |
1fa761af EB |
675 | debugs(90, 3, (object_ok ? "once" : "again")); |
676 | if (!object_ok) | |
677 | return; // we failed earlier; nothing to do now | |
678 | ||
528b2c61 | 679 | object_ok = false; |
1fa761af EB |
680 | |
681 | noteNews(); | |
682 | } | |
683 | ||
684 | /// if necessary and possible, informs the Store reader about copy() result | |
685 | void | |
686 | store_client::noteNews() | |
687 | { | |
db2ff94c | 688 | /* synchronous open failures callback from the store, |
689 | * before startSwapin detects the failure. | |
690 | * TODO: fix this inconsistent behaviour - probably by | |
26ac0430 | 691 | * having storeSwapInStart become a callback functions, |
db2ff94c | 692 | * not synchronous |
693 | */ | |
694 | ||
1fa761af EB |
695 | if (!_callback.callback_handler) { |
696 | debugs(90, 5, "client lost interest"); | |
697 | return; | |
698 | } | |
699 | ||
700 | if (_callback.notifier) { | |
701 | debugs(90, 5, "earlier news is being delivered by " << _callback.notifier); | |
702 | return; | |
703 | } | |
704 | ||
705 | _callback.notifier = asyncCall(90, 4, "store_client::FinishCallback", cbdataDialer(store_client::FinishCallback, this)); | |
706 | ScheduleCallHere(_callback.notifier); | |
707 | ||
708 | Assure(!_callback.pending()); | |
f09f5b26 | 709 | } |
710 | ||
e3ef2b09 | 711 | static void |
ced8def3 | 712 | storeClientReadHeader(void *data, const char *buf, ssize_t len, StoreIOState::Pointer) |
e3ef2b09 | 713 | { |
e6ccf245 | 714 | store_client *sc = (store_client *)data; |
528b2c61 | 715 | sc->readHeader(buf, len); |
716 | } | |
717 | ||
beae59b0 | 718 | static void |
ced8def3 | 719 | storeClientReadBody(void *data, const char *buf, ssize_t len, StoreIOState::Pointer) |
beae59b0 HN |
720 | { |
721 | store_client *sc = (store_client *)data; | |
722 | sc->readBody(buf, len); | |
723 | } | |
724 | ||
528b2c61 | 725 | void |
726 | store_client::readHeader(char const *buf, ssize_t len) | |
727 | { | |
728 | MemObject *const mem = entry->mem_obj; | |
62e76326 | 729 | |
528b2c61 | 730 | assert(flags.disk_io_pending); |
3dd52a0b | 731 | flags.disk_io_pending = false; |
90703668 | 732 | assert(_callback.pending()); |
528b2c61 | 733 | |
4ea266b7 | 734 | // abort if we fail()'d earlier |
528b2c61 | 735 | if (!object_ok) |
62e76326 | 736 | return; |
737 | ||
122a6e3c AR |
738 | Assure(parsingBuffer); |
739 | debugs(90, 3, "got " << len << " using " << *parsingBuffer); | |
740 | ||
18102f7d EB |
741 | if (len < 0) |
742 | return fail(); | |
743 | ||
d448e1eb | 744 | try { |
122a6e3c AR |
745 | Assure(!parsingBuffer->contentSize()); |
746 | parsingBuffer->appended(buf, len); | |
d448e1eb | 747 | Store::UnpackHitSwapMeta(buf, len, *entry); |
122a6e3c | 748 | parsingBuffer->consume(mem->swap_hdr_sz); |
d448e1eb EB |
749 | } catch (...) { |
750 | debugs(90, DBG_IMPORTANT, "ERROR: Failed to unpack Store entry metadata: " << CurrentException); | |
4ea266b7 CF |
751 | fail(); |
752 | return; | |
753 | } | |
754 | ||
122a6e3c AR |
755 | maybeWriteFromDiskToMemory(parsingBuffer->content()); |
756 | handleBodyFromDisk(); | |
e3ef2b09 | 757 | } |
758 | ||
06d2839d | 759 | /* |
760 | * This routine hasn't been optimised to take advantage of the | |
761 | * passed sc. Yet. | |
762 | */ | |
f09f5b26 | 763 | int |
a4b8110e | 764 | storeUnregister(store_client * sc, StoreEntry * e, void *data) |
f09f5b26 | 765 | { |
766 | MemObject *mem = e->mem_obj; | |
06d2839d | 767 | #if STORE_CLIENT_LIST_DEBUG |
768 | assert(sc == storeClientListSearch(e->mem_obj, data)); | |
8b082ed9 FC |
769 | #else |
770 | (void)data; | |
06d2839d | 771 | #endif |
62e76326 | 772 | |
aee3523a | 773 | if (mem == nullptr) |
62e76326 | 774 | return 0; |
775 | ||
26ac0430 | 776 | debugs(90, 3, "storeUnregister: called for '" << e->getMD5Text() << "'"); |
62e76326 | 777 | |
aee3523a | 778 | if (sc == nullptr) { |
bf8fe701 | 779 | debugs(90, 3, "storeUnregister: No matching client for '" << e->getMD5Text() << "'"); |
62e76326 | 780 | return 0; |
2f44bd34 | 781 | } |
62e76326 | 782 | |
0e3f3e0d | 783 | if (mem->clientCount() == 0) { |
bf8fe701 | 784 | debugs(90, 3, "storeUnregister: Consistency failure - store client being unregistered is not in the mem object's list for '" << e->getMD5Text() << "'"); |
62e76326 | 785 | return 0; |
2f44bd34 | 786 | } |
62e76326 | 787 | |
06d2839d | 788 | dlinkDelete(&sc->node, &mem->clients); |
5e263176 | 789 | -- mem->nclients; |
62e76326 | 790 | |
02ba667b EB |
791 | const auto swapoutFinished = e->swappedOut() || e->swapoutFailed(); |
792 | if (e->store_status == STORE_OK && !swapoutFinished) | |
c07cbbf4 | 793 | e->swapOut(); |
62e76326 | 794 | |
aee3523a | 795 | if (sc->swapin_sio != nullptr) { |
aa1a691e | 796 | storeClose(sc->swapin_sio, StoreIOState::readerDone); |
aee3523a | 797 | sc->swapin_sio = nullptr; |
5db6bf73 | 798 | ++statCounter.swap.ins; |
eb824054 | 799 | } |
62e76326 | 800 | |
1fa761af EB |
801 | if (sc->_callback.callback_handler || sc->_callback.notifier) { |
802 | debugs(90, 3, "forgetting store_client callback for " << *e); | |
803 | // Do not notify: Callers want to stop copying and forget about this | |
804 | // pending copy request. Some would mishandle a notification from here. | |
805 | if (sc->_callback.notifier) | |
806 | sc->_callback.notifier->cancel("storeUnregister"); | |
f09f5b26 | 807 | } |
62e76326 | 808 | |
fa80a8ef | 809 | #if STORE_CLIENT_LIST_DEBUG |
810 | cbdataReferenceDone(sc->owner); | |
62e76326 | 811 | |
fa80a8ef | 812 | #endif |
62e76326 | 813 | |
836d3c0b AR |
814 | // We must lock to safely dereference e below, after deleting sc and after |
815 | // calling CheckQuickAbortIsReasonable(). | |
816 | e->lock("storeUnregister"); | |
817 | ||
1fa761af EB |
818 | // XXX: We might be inside sc store_client method somewhere up the call |
819 | // stack. TODO: Convert store_client to AsyncJob to make destruction async. | |
528b2c61 | 820 | delete sc; |
62e76326 | 821 | |
f1ba1fba EB |
822 | if (CheckQuickAbortIsReasonable(e)) |
823 | e->abort(); | |
a46d2c0e | 824 | else |
825 | mem->kickReads(); | |
62e76326 | 826 | |
0ad2b63b CT |
827 | #if USE_ADAPTATION |
828 | e->kickProducer(); | |
829 | #endif | |
830 | ||
178c7e33 | 831 | e->unlock("storeUnregister"); |
f09f5b26 | 832 | return 1; |
833 | } | |
834 | ||
f09f5b26 | 835 | /* Call handlers waiting for data to be appended to E. */ |
836 | void | |
d88e3c49 | 837 | StoreEntry::invokeHandlers() |
f09f5b26 | 838 | { |
70eb3fde EB |
839 | if (EBIT_TEST(flags, DELAY_SENDING)) { |
840 | debugs(90, 3, "DELAY_SENDING is on, exiting " << *this); | |
841 | return; | |
842 | } | |
843 | if (EBIT_TEST(flags, ENTRY_FWD_HDR_WAIT)) { | |
844 | debugs(90, 3, "ENTRY_FWD_HDR_WAIT is on, exiting " << *this); | |
845 | return; | |
846 | } | |
847 | ||
528b2c61 | 848 | /* Commit what we can to disk, if appropriate */ |
d88e3c49 | 849 | swapOut(); |
f09f5b26 | 850 | int i = 0; |
f09f5b26 | 851 | store_client *sc; |
aee3523a | 852 | dlink_node *nx = nullptr; |
06d2839d | 853 | dlink_node *node; |
854 | ||
f925268a | 855 | debugs(90, 3, mem_obj->nclients << " clients; " << *this << ' ' << getMD5Text()); |
f09f5b26 | 856 | /* walk the entire list looking for valid callbacks */ |
62e76326 | 857 | |
f925268a | 858 | const auto savedContext = CodeContext::Current(); |
d88e3c49 | 859 | for (node = mem_obj->clients.head; node; node = nx) { |
62e76326 | 860 | sc = (store_client *)node->data; |
861 | nx = node->next; | |
5db6bf73 | 862 | ++i; |
62e76326 | 863 | |
90703668 | 864 | if (!sc->_callback.pending()) |
62e76326 | 865 | continue; |
866 | ||
867 | if (sc->flags.disk_io_pending) | |
868 | continue; | |
869 | ||
1fa761af EB |
870 | if (sc->flags.store_copying) |
871 | continue; | |
872 | ||
873 | // XXX: If invokeHandlers() is (indirectly) called from a store_client | |
874 | // method, then the above three conditions may not be sufficient to | |
875 | // prevent us from reentering the same store_client object! This | |
876 | // probably does not happen in the current code, but no observed | |
877 | // invariant prevents this from (accidentally) happening in the future. | |
878 | ||
879 | // TODO: Convert store_client into AsyncJob; make this call asynchronous | |
f925268a AR |
880 | CodeContext::Reset(sc->_callback.codeContext); |
881 | debugs(90, 3, "checking client #" << i); | |
d88e3c49 | 882 | storeClientCopy2(this, sc); |
f09f5b26 | 883 | } |
f925268a | 884 | CodeContext::Reset(savedContext); |
f09f5b26 | 885 | } |
886 | ||
22bbd840 | 887 | // Does not account for remote readers/clients. |
f09f5b26 | 888 | int |
889 | storePendingNClients(const StoreEntry * e) | |
890 | { | |
f09f5b26 | 891 | MemObject *mem = e->mem_obj; |
aee3523a | 892 | int npend = nullptr == mem ? 0 : mem->nclients; |
bf8fe701 | 893 | debugs(90, 3, "storePendingNClients: returning " << npend); |
f09f5b26 | 894 | return npend; |
895 | } | |
77b32a34 | 896 | |
ae6568e7 AJ |
897 | /* return true if the request should be aborted */ |
898 | static bool | |
899 | CheckQuickAbortIsReasonable(StoreEntry * entry) | |
77b32a34 | 900 | { |
f1ba1fba EB |
901 | assert(entry); |
902 | debugs(90, 3, "entry=" << *entry); | |
903 | ||
904 | if (storePendingNClients(entry) > 0) { | |
905 | debugs(90, 3, "quick-abort? NO storePendingNClients() > 0"); | |
906 | return false; | |
907 | } | |
908 | ||
23b79630 | 909 | if (Store::Root().transientReaders(*entry)) { |
f1ba1fba EB |
910 | debugs(90, 3, "quick-abort? NO still have one or more transient readers"); |
911 | return false; | |
912 | } | |
913 | ||
914 | if (entry->store_status != STORE_PENDING) { | |
915 | debugs(90, 3, "quick-abort? NO store_status != STORE_PENDING"); | |
916 | return false; | |
917 | } | |
918 | ||
919 | if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) { | |
920 | debugs(90, 3, "quick-abort? NO ENTRY_SPECIAL"); | |
921 | return false; | |
922 | } | |
923 | ||
23b79630 AR |
924 | if (shutting_down) { |
925 | debugs(90, 3, "quick-abort? YES avoid heavy optional work during shutdown"); | |
926 | return true; | |
927 | } | |
928 | ||
528b2c61 | 929 | MemObject * const mem = entry->mem_obj; |
77b32a34 | 930 | assert(mem); |
f1ba1fba | 931 | debugs(90, 3, "mem=" << mem); |
62e76326 | 932 | |
45e5102d | 933 | if (mem->request && !mem->request->flags.cachable) { |
ae6568e7 AJ |
934 | debugs(90, 3, "quick-abort? YES !mem->request->flags.cachable"); |
935 | return true; | |
77b32a34 | 936 | } |
62e76326 | 937 | |
77b32a34 | 938 | if (EBIT_TEST(entry->flags, KEY_PRIVATE)) { |
ae6568e7 AJ |
939 | debugs(90, 3, "quick-abort? YES KEY_PRIVATE"); |
940 | return true; | |
77b32a34 | 941 | } |
62e76326 | 942 | |
66d51f4f | 943 | const auto &reply = mem->baseReply(); |
0e3f3e0d | 944 | |
66d51f4f AR |
945 | if (reply.hdr_sz <= 0) { |
946 | // TODO: Check whether this condition works for HTTP/0 responses. | |
ae6568e7 AJ |
947 | debugs(90, 3, "quick-abort? YES no object data received yet"); |
948 | return true; | |
949 | } | |
0e3f3e0d | 950 | |
47f6e231 | 951 | if (Config.quickAbort.min < 0) { |
ae6568e7 AJ |
952 | debugs(90, 3, "quick-abort? NO disabled"); |
953 | return false; | |
77b32a34 | 954 | } |
62e76326 | 955 | |
ae6568e7 | 956 | if (mem->request && mem->request->range && mem->request->getRangeOffsetLimit() < 0) { |
66d51f4f | 957 | // the admin has configured "range_offset_limit none" |
ae6568e7 AJ |
958 | debugs(90, 3, "quick-abort? NO admin configured range replies to full-download"); |
959 | return false; | |
ab275c7b AJ |
960 | } |
961 | ||
66d51f4f AR |
962 | if (reply.content_length < 0) { |
963 | // XXX: cf.data.pre does not document what should happen in this case | |
964 | // We know that quick_abort is enabled, but no limit can be applied. | |
965 | debugs(90, 3, "quick-abort? YES unknown content length"); | |
966 | return true; | |
967 | } | |
968 | const auto expectlen = reply.hdr_sz + reply.content_length; | |
969 | ||
970 | int64_t curlen = mem->endOffset(); | |
971 | ||
77b32a34 | 972 | if (curlen > expectlen) { |
f22b0af0 | 973 | debugs(90, 3, "quick-abort? YES bad content length (" << curlen << " of " << expectlen << " bytes received)"); |
ae6568e7 | 974 | return true; |
77b32a34 | 975 | } |
62e76326 | 976 | |
47f6e231 | 977 | if ((expectlen - curlen) < (Config.quickAbort.min << 10)) { |
ae6568e7 AJ |
978 | debugs(90, 3, "quick-abort? NO only a little more object left to receive"); |
979 | return false; | |
77b32a34 | 980 | } |
62e76326 | 981 | |
77b32a34 | 982 | if ((expectlen - curlen) > (Config.quickAbort.max << 10)) { |
ae6568e7 AJ |
983 | debugs(90, 3, "quick-abort? YES too much left to go"); |
984 | return true; | |
77b32a34 | 985 | } |
62e76326 | 986 | |
66d51f4f | 987 | // XXX: This is absurd! TODO: For positives, "a/(b/c) > d" is "a*c > b*d". |
77b32a34 | 988 | if (expectlen < 100) { |
ae6568e7 AJ |
989 | debugs(90, 3, "quick-abort? NO avoid FPE"); |
990 | return false; | |
77b32a34 | 991 | } |
62e76326 | 992 | |
47f6e231 | 993 | if ((curlen / (expectlen / 100)) > (Config.quickAbort.pct)) { |
ae6568e7 AJ |
994 | debugs(90, 3, "quick-abort? NO past point of no return"); |
995 | return false; | |
77b32a34 | 996 | } |
62e76326 | 997 | |
ae6568e7 AJ |
998 | debugs(90, 3, "quick-abort? YES default"); |
999 | return true; | |
77b32a34 | 1000 | } |
1001 | ||
122a6e3c AR |
1002 | /// parses HTTP header bytes loaded from disk |
1003 | /// \returns false if fail() or scheduleDiskRead() has been called and, hence, | |
1004 | /// the caller should just quit without any further action | |
1005 | bool | |
1006 | store_client::parseHttpHeadersFromDisk() | |
1007 | { | |
1008 | try { | |
1009 | return tryParsingHttpHeaders(); | |
1010 | } catch (...) { | |
1011 | // XXX: Our parser enforces Config.maxReplyHeaderSize limit, but our | |
1012 | // packer does not. Since packing might increase header size, we may | |
1013 | // cache a header that we cannot parse and get here. Same for MemStore. | |
1014 | debugs(90, DBG_CRITICAL, "ERROR: Cannot parse on-disk HTTP headers" << | |
1015 | Debug::Extra << "exception: " << CurrentException << | |
1016 | Debug::Extra << "raw input size: " << parsingBuffer->contentSize() << " bytes" << | |
1017 | Debug::Extra << "current buffer capacity: " << parsingBuffer->capacity() << " bytes"); | |
1018 | fail(); | |
1019 | return false; | |
1020 | } | |
1021 | } | |
1022 | ||
1023 | /// parseHttpHeadersFromDisk() helper | |
1024 | /// \copydoc parseHttpHeaders() | |
1025 | bool | |
1026 | store_client::tryParsingHttpHeaders() | |
1027 | { | |
1028 | Assure(parsingBuffer); | |
1029 | Assure(!copyInto.offset); // otherwise, parsingBuffer cannot have HTTP response headers | |
1030 | auto &adjustableReply = entry->mem().adjustableBaseReply(); | |
1031 | if (adjustableReply.parseTerminatedPrefix(parsingBuffer->c_str(), parsingBuffer->contentSize())) | |
1032 | return true; | |
1033 | ||
1034 | // TODO: Optimize by checking memory as well. For simplicity sake, we | |
1035 | // continue on the disk-reading path, but readFromMemory() can give us the | |
1036 | // missing header bytes immediately if a concurrent request put those bytes | |
1037 | // into memory while we were waiting for our disk response. | |
1038 | scheduleDiskRead(); | |
1039 | return false; | |
1040 | } | |
1041 | ||
1042 | /// skips HTTP header bytes previously loaded from disk | |
1043 | void | |
1044 | store_client::skipHttpHeadersFromDisk() | |
1045 | { | |
1046 | const auto hdr_sz = entry->mem_obj->baseReply().hdr_sz; | |
1047 | Assure(hdr_sz > 0); // all on-disk responses have HTTP headers | |
1048 | if (Less(parsingBuffer->contentSize(), hdr_sz)) { | |
1049 | debugs(90, 5, "discovered " << hdr_sz << "-byte HTTP headers in memory after reading some of them from disk: " << *parsingBuffer); | |
1050 | parsingBuffer->consume(parsingBuffer->contentSize()); // skip loaded HTTP header prefix | |
1051 | } else { | |
1052 | parsingBuffer->consume(hdr_sz); // skip loaded HTTP headers | |
1053 | const auto httpBodyBytesAfterHeader = parsingBuffer->contentSize(); // may be zero | |
1054 | Assure(httpBodyBytesAfterHeader <= copyInto.length); | |
1055 | debugs(90, 5, "read HTTP body prefix: " << httpBodyBytesAfterHeader); | |
1056 | } | |
1057 | } | |
1058 | ||
c8be6d7b | 1059 | void |
fcc35180 | 1060 | store_client::dumpStats(MemBuf * output, int clientNumber) const |
c8be6d7b | 1061 | { |
90703668 | 1062 | if (_callback.pending()) |
62e76326 | 1063 | return; |
1064 | ||
6ec49cda | 1065 | output->appendf("\tClient #%d, %p\n", clientNumber, this); |
4391cd15 | 1066 | output->appendf("\t\tcopy_offset: %" PRId64 "\n", copyInto.offset); |
a20724e4 | 1067 | output->appendf("\t\tcopy_size: %zu\n", copyInto.length); |
4391cd15 | 1068 | output->append("\t\tflags:", 8); |
62e76326 | 1069 | |
528b2c61 | 1070 | if (flags.disk_io_pending) |
4391cd15 | 1071 | output->append(" disk_io_pending", 16); |
62e76326 | 1072 | |
528b2c61 | 1073 | if (flags.store_copying) |
4391cd15 | 1074 | output->append(" store_copying", 14); |
62e76326 | 1075 | |
1fa761af EB |
1076 | if (_callback.notifier) |
1077 | output->append(" notifying", 10); | |
62e76326 | 1078 | |
4391cd15 | 1079 | output->append("\n",1); |
528b2c61 | 1080 | } |
c8be6d7b | 1081 | |
528b2c61 | 1082 | bool |
90703668 | 1083 | store_client::Callback::pending() const |
528b2c61 | 1084 | { |
1fa761af | 1085 | return callback_handler && !notifier; |
c8be6d7b | 1086 | } |
528b2c61 | 1087 | |
f925268a AR |
1088 | store_client::Callback::Callback(STCB *function, void *data): |
1089 | callback_handler(function), | |
6ec49cda | 1090 | cbData(data), |
f925268a AR |
1091 | codeContext(CodeContext::Current()) |
1092 | { | |
1093 | } | |
b67e2c8c | 1094 | |
9a0a18de | 1095 | #if USE_DELAY_POOLS |
1fa761af EB |
1096 | int |
1097 | store_client::bytesWanted() const | |
1098 | { | |
1099 | // TODO: To avoid using stale copyInto, return zero if !_callback.pending()? | |
1100 | return delayId.bytesWanted(0, copyInto.length); | |
1101 | } | |
1102 | ||
b67e2c8c | 1103 | void |
1104 | store_client::setDelayId(DelayId delay_id) | |
1105 | { | |
1106 | delayId = delay_id; | |
1107 | } | |
515ec4dc | 1108 | #endif |
f53969cc | 1109 |