]>
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" |
4d5904f7 | 22 | #include "SquidConfig.h" |
e4f1fdae | 23 | #include "StatCounters.h" |
582c2af2 | 24 | #include "Store.h" |
d448e1eb | 25 | #include "store/SwapMetaIn.h" |
f82b5c64 | 26 | #include "store_swapin.h" |
602d9612 | 27 | #include "StoreClient.h" |
9a0a18de | 28 | #if USE_DELAY_POOLS |
b67e2c8c | 29 | #include "DelayPools.h" |
30 | #endif | |
c8be6d7b | 31 | |
e3ef2b09 | 32 | /* |
33 | * NOTE: 'Header' refers to the swapfile metadata header. | |
2f8abb64 | 34 | * 'OBJHeader' refers to the object header, with canonical |
f53969cc SM |
35 | * processed object headers (which may derive from FTP/HTTP etc |
36 | * upstream protocols | |
e3ef2b09 | 37 | * 'Body' refers to the swapfile body, which is the full |
38 | * HTTP reply (including HTTP headers and body). | |
39 | */ | |
4fcc8876 | 40 | static StoreIOState::STRCB storeClientReadBody; |
41 | static StoreIOState::STRCB storeClientReadHeader; | |
f09f5b26 | 42 | static void storeClientCopy2(StoreEntry * e, store_client * sc); |
ae6568e7 | 43 | static bool CheckQuickAbortIsReasonable(StoreEntry * entry); |
f09f5b26 | 44 | |
b001e822 | 45 | CBDATA_CLASS_INIT(store_client); |
528b2c61 | 46 | |
819be284 EB |
47 | /* StoreClient */ |
48 | ||
49 | bool | |
50 | StoreClient::onCollapsingPath() const | |
51 | { | |
52 | if (!Config.onoff.collapsed_forwarding) | |
53 | return false; | |
54 | ||
55 | if (!Config.accessList.collapsedForwardingAccess) | |
56 | return true; | |
57 | ||
58 | ACLFilledChecklist checklist(Config.accessList.collapsedForwardingAccess, nullptr, nullptr); | |
59 | fillChecklist(checklist); | |
60 | return checklist.fastCheck().allowed(); | |
61 | } | |
62 | ||
63 | bool | |
7976fed3 | 64 | StoreClient::startCollapsingOn(const StoreEntry &e, const bool doingRevalidation) const |
819be284 | 65 | { |
d2a6dcba EB |
66 | if (!e.hittingRequiresCollapsing()) |
67 | return false; // collapsing is impossible due to the entry state | |
68 | ||
69 | if (!onCollapsingPath()) | |
70 | return false; // collapsing is impossible due to Squid configuration | |
71 | ||
72 | /* collapsing is possible; the caller must collapse */ | |
73 | ||
74 | if (const auto tags = loggingTags()) { | |
75 | if (doingRevalidation) | |
76 | tags->collapsingHistory.revalidationCollapses++; | |
77 | else | |
78 | tags->collapsingHistory.otherCollapses++; | |
79 | } | |
80 | ||
81 | debugs(85, 5, e << " doingRevalidation=" << doingRevalidation); | |
82 | return true; | |
819be284 EB |
83 | } |
84 | ||
819be284 EB |
85 | /* store_client */ |
86 | ||
528b2c61 | 87 | int |
88 | store_client::getType() const | |
89 | { | |
90 | return type; | |
91 | } | |
92 | ||
06d2839d | 93 | #if STORE_CLIENT_LIST_DEBUG |
fa80a8ef | 94 | static store_client * |
f09f5b26 | 95 | storeClientListSearch(const MemObject * mem, void *data) |
96 | { | |
06d2839d | 97 | dlink_node *node; |
98 | store_client *sc = NULL; | |
62e76326 | 99 | |
06d2839d | 100 | for (node = mem->clients.head; node; node = node->next) { |
62e76326 | 101 | sc = node->data; |
102 | ||
103 | if (sc->owner == data) | |
104 | return sc; | |
f09f5b26 | 105 | } |
62e76326 | 106 | |
06d2839d | 107 | return NULL; |
f09f5b26 | 108 | } |
c8be6d7b | 109 | |
110 | int | |
111 | storeClientIsThisAClient(store_client * sc, void *someClient) | |
112 | { | |
113 | return sc->owner == someClient; | |
114 | } | |
62e76326 | 115 | |
06d2839d | 116 | #endif |
924f73bc | 117 | #include "HttpRequest.h" |
f09f5b26 | 118 | |
119 | /* add client with fd to client list */ | |
06d2839d | 120 | store_client * |
f09f5b26 | 121 | storeClientListAdd(StoreEntry * e, void *data) |
122 | { | |
123 | MemObject *mem = e->mem_obj; | |
f09f5b26 | 124 | store_client *sc; |
125 | assert(mem); | |
06d2839d | 126 | #if STORE_CLIENT_LIST_DEBUG |
62e76326 | 127 | |
f09f5b26 | 128 | if (storeClientListSearch(mem, data) != NULL) |
62e76326 | 129 | /* XXX die! */ |
130 | assert(1 == 0); | |
8b082ed9 FC |
131 | #else |
132 | (void)data; | |
6b8e7481 | 133 | #endif |
62e76326 | 134 | |
528b2c61 | 135 | sc = new store_client (e); |
62e76326 | 136 | |
528b2c61 | 137 | mem->addClient(sc); |
62e76326 | 138 | |
06d2839d | 139 | return sc; |
f09f5b26 | 140 | } |
141 | ||
1fa761af EB |
142 | /// schedules asynchronous STCB call to relay disk or memory read results |
143 | /// \param outcome an error signal (if negative), an EOF signal (if zero), or the number of bytes read | |
528b2c61 | 144 | void |
1fa761af | 145 | store_client::callback(const ssize_t outcome) |
b04e66e0 | 146 | { |
1fa761af EB |
147 | if (outcome > 0) |
148 | return noteCopiedBytes(outcome); | |
62e76326 | 149 | |
1fa761af EB |
150 | if (outcome < 0) |
151 | return fail(); | |
63326655 | 152 | |
1fa761af EB |
153 | noteEof(); |
154 | } | |
63326655 | 155 | |
1fa761af EB |
156 | /// finishCallback() wrapper; TODO: Add NullaryMemFunT for non-jobs. |
157 | void | |
158 | store_client::FinishCallback(store_client * const sc) | |
159 | { | |
160 | sc->finishCallback(); | |
161 | } | |
62e76326 | 162 | |
1fa761af EB |
163 | /// finishes a copy()-STCB sequence by synchronously calling STCB |
164 | void | |
165 | store_client::finishCallback() | |
166 | { | |
167 | Assure(_callback.callback_handler); | |
168 | Assure(_callback.notifier); | |
169 | ||
170 | // callers are not ready to handle a content+error combination | |
171 | Assure(object_ok || !copiedSize); | |
172 | ||
173 | StoreIOBuffer result(copiedSize, copyInto.offset, copyInto.data); | |
174 | result.flags.error = object_ok ? 0 : 1; | |
175 | copiedSize = 0; | |
176 | ||
177 | cmp_offset = result.offset + result.length; | |
528b2c61 | 178 | STCB *temphandler = _callback.callback_handler; |
179 | void *cbdata = _callback.callback_data; | |
aee3523a AR |
180 | _callback = Callback(nullptr, nullptr); |
181 | copyInto.data = nullptr; | |
62e76326 | 182 | |
528b2c61 | 183 | if (cbdataReferenceValid(cbdata)) |
62e76326 | 184 | temphandler(cbdata, result); |
185 | ||
528b2c61 | 186 | cbdataReferenceDone(cbdata); |
b04e66e0 | 187 | } |
188 | ||
1fa761af EB |
189 | /// schedules asynchronous STCB call to relay a successful disk or memory read |
190 | /// \param bytesCopied the number of response bytes copied into copyInto | |
191 | void | |
192 | store_client::noteCopiedBytes(const size_t bytesCopied) | |
f115fadd | 193 | { |
1fa761af EB |
194 | debugs(90, 5, bytesCopied); |
195 | Assure(bytesCopied > 0); | |
196 | Assure(!copiedSize); | |
197 | copiedSize = bytesCopied; | |
198 | noteNews(); | |
199 | } | |
62e76326 | 200 | |
1fa761af EB |
201 | void |
202 | store_client::noteEof() | |
203 | { | |
204 | debugs(90, 5, copiedSize); | |
205 | Assure(!copiedSize); | |
206 | noteNews(); | |
f115fadd | 207 | } |
208 | ||
cc8c4af2 AJ |
209 | store_client::store_client(StoreEntry *e) : |
210 | cmp_offset(0), | |
211 | #if STORE_CLIENT_LIST_DEBUG | |
212 | owner(cbdataReference(data)), | |
b67e2c8c | 213 | #endif |
cc8c4af2 AJ |
214 | entry(e), |
215 | type(e->storeClientType()), | |
1fa761af EB |
216 | object_ok(true), |
217 | copiedSize(0) | |
528b2c61 | 218 | { |
3dd52a0b | 219 | flags.disk_io_pending = false; |
cc8c4af2 | 220 | flags.store_copying = false; |
5db6bf73 | 221 | ++ entry->refcount; |
62e76326 | 222 | |
cc8c4af2 | 223 | if (getType() == STORE_DISK_CLIENT) { |
62e76326 | 224 | /* assert we'll be able to get the data we want */ |
02a2d80b | 225 | /* maybe we should open swapin_sio here */ |
02ba667b | 226 | assert(entry->hasDisk() && !entry->swapoutFailed()); |
cc8c4af2 | 227 | } |
528b2c61 | 228 | } |
229 | ||
2f44bd34 | 230 | store_client::~store_client() |
62e76326 | 231 | {} |
2f44bd34 | 232 | |
f09f5b26 | 233 | /* copy bytes requested by the client */ |
234 | void | |
a4b8110e | 235 | storeClientCopy(store_client * sc, |
62e76326 | 236 | StoreEntry * e, |
237 | StoreIOBuffer copyInto, | |
238 | STCB * callback, | |
239 | void *data) | |
f09f5b26 | 240 | { |
aee3523a | 241 | assert (sc != nullptr); |
528b2c61 | 242 | sc->copy(e, copyInto,callback,data); |
243 | } | |
244 | ||
245 | void | |
246 | store_client::copy(StoreEntry * anEntry, | |
62e76326 | 247 | StoreIOBuffer copyRequest, |
248 | STCB * callback_fn, | |
249 | void *data) | |
528b2c61 | 250 | { |
251 | assert (anEntry == entry); | |
252 | assert (callback_fn); | |
253 | assert (data); | |
254 | assert(!EBIT_TEST(entry->flags, ENTRY_ABORTED)); | |
bf8fe701 | 255 | debugs(90, 3, "store_client::copy: " << entry->getMD5Text() << ", from " << |
47f6e231 | 256 | copyRequest.offset << ", for length " << |
bf8fe701 | 257 | (int) copyRequest.length << ", cb " << callback_fn << ", cbdata " << |
258 | data); | |
259 | ||
06d2839d | 260 | #if STORE_CLIENT_LIST_DEBUG |
62e76326 | 261 | |
528b2c61 | 262 | assert(this == storeClientListSearch(entry->mem_obj, data)); |
06d2839d | 263 | #endif |
62e76326 | 264 | |
90703668 | 265 | assert(!_callback.pending()); |
528b2c61 | 266 | #if ONLYCONTIGUOUSREQUESTS |
62e76326 | 267 | |
528b2c61 | 268 | assert(cmp_offset == copyRequest.offset); |
269 | #endif | |
270 | /* range requests will skip into the body */ | |
271 | cmp_offset = copyRequest.offset; | |
272 | _callback = Callback (callback_fn, cbdataReference(data)); | |
273 | copyInto.data = copyRequest.data; | |
274 | copyInto.length = copyRequest.length; | |
275 | copyInto.offset = copyRequest.offset; | |
276 | ||
1d5161bd | 277 | static bool copying (false); |
278 | assert (!copying); | |
279 | copying = true; | |
a46d2c0e | 280 | /* we might be blocking comm reads due to readahead limits |
281 | * now we have a new offset, trigger those reads... | |
282 | */ | |
283 | entry->mem_obj->kickReads(); | |
1d5161bd | 284 | copying = false; |
a46d2c0e | 285 | |
4475555f AR |
286 | anEntry->lock("store_client::copy"); // see deletion note below |
287 | ||
528b2c61 | 288 | storeClientCopy2(entry, this); |
0ad2b63b | 289 | |
4475555f AR |
290 | // Bug 3480: This store_client object may be deleted now if, for example, |
291 | // the client rejects the hit response copied above. Use on-stack pointers! | |
292 | ||
0ad2b63b | 293 | #if USE_ADAPTATION |
4475555f | 294 | anEntry->kickProducer(); |
0ad2b63b | 295 | #endif |
4475555f | 296 | anEntry->unlock("store_client::copy"); |
1809cebd | 297 | |
1809cebd | 298 | // Add no code here. This object may no longer exist. |
f09f5b26 | 299 | } |
300 | ||
f25d697f AR |
301 | /// Whether there is (or will be) more entry data for us. |
302 | bool | |
303 | store_client::moreToSend() const | |
07304bf9 | 304 | { |
f25d697f AR |
305 | if (entry->store_status == STORE_PENDING) |
306 | return true; // there may be more coming | |
307 | ||
308 | /* STORE_OK, including aborted entries: no more data is coming */ | |
309 | ||
310 | const int64_t len = entry->objectLen(); | |
62e76326 | 311 | |
0cdcf3d7 | 312 | // If we do not know the entry length, then we have to open the swap file. |
4310f8b0 | 313 | const bool canSwapIn = entry->hasDisk(); |
f25d697f AR |
314 | if (len < 0) |
315 | return canSwapIn; | |
62e76326 | 316 | |
f25d697f AR |
317 | if (copyInto.offset >= len) |
318 | return false; // sent everything there is | |
62e76326 | 319 | |
f25d697f AR |
320 | if (canSwapIn) |
321 | return true; // if we lack prefix, we can swap it in | |
62e76326 | 322 | |
f25d697f | 323 | // If we cannot swap in, make sure we have what we want in RAM. Otherwise, |
0cdcf3d7 | 324 | // scheduleRead calls scheduleDiskRead which asserts without a swap file. |
f25d697f AR |
325 | const MemObject *mem = entry->mem_obj; |
326 | return mem && | |
9d4e9cfb | 327 | mem->inmem_lo <= copyInto.offset && copyInto.offset < mem->endOffset(); |
07304bf9 | 328 | } |
329 | ||
f09f5b26 | 330 | static void |
331 | storeClientCopy2(StoreEntry * e, store_client * sc) | |
332 | { | |
528b2c61 | 333 | /* reentrancy not allowed - note this could lead to |
1fa761af | 334 | * dropped notifications about response data availability |
528b2c61 | 335 | */ |
62e76326 | 336 | |
67fd69de | 337 | if (sc->flags.store_copying) { |
1fa761af | 338 | debugs(90, 3, "prevented recursive copying for " << *e); |
62e76326 | 339 | return; |
67fd69de | 340 | } |
62e76326 | 341 | |
bf8fe701 | 342 | debugs(90, 3, "storeClientCopy2: " << e->getMD5Text()); |
90703668 | 343 | assert(sc->_callback.pending()); |
0bb129ee | 344 | /* |
b7fe0ab0 | 345 | * We used to check for ENTRY_ABORTED here. But there were some |
0bb129ee | 346 | * problems. For example, we might have a slow client (or two) and |
d5430dc8 AJ |
347 | * the peer server is reading far ahead and swapping to disk. Even |
348 | * if the peer aborts, we want to give the client(s) | |
0bb129ee | 349 | * everything we got before the abort condition occurred. |
350 | */ | |
528b2c61 | 351 | sc->doCopy(e); |
cfac48c2 | 352 | } |
353 | ||
528b2c61 | 354 | void |
355 | store_client::doCopy(StoreEntry *anEntry) | |
cfac48c2 | 356 | { |
1fa761af EB |
357 | Assure(_callback.pending()); |
358 | Assure(!flags.disk_io_pending); | |
359 | Assure(!flags.store_copying); | |
360 | ||
528b2c61 | 361 | assert (anEntry == entry); |
3dd52a0b | 362 | flags.store_copying = true; |
528b2c61 | 363 | MemObject *mem = entry->mem_obj; |
cd748f27 | 364 | |
bf8fe701 | 365 | debugs(33, 5, "store_client::doCopy: co: " << |
47f6e231 | 366 | copyInto.offset << ", hi: " << |
367 | mem->endOffset()); | |
add2192d | 368 | |
f25d697f | 369 | if (!moreToSend()) { |
62e76326 | 370 | /* There is no more to send! */ |
bf95c10a | 371 | debugs(33, 3, "There is no more to send!"); |
1fa761af | 372 | noteEof(); |
3dd52a0b | 373 | flags.store_copying = false; |
62e76326 | 374 | return; |
cfac48c2 | 375 | } |
62e76326 | 376 | |
add2192d | 377 | /* Check that we actually have data */ |
528b2c61 | 378 | if (anEntry->store_status == STORE_PENDING && copyInto.offset >= mem->endOffset()) { |
bf8fe701 | 379 | debugs(90, 3, "store_client::doCopy: Waiting for more"); |
3dd52a0b | 380 | flags.store_copying = false; |
62e76326 | 381 | return; |
cfac48c2 | 382 | } |
62e76326 | 383 | |
cfac48c2 | 384 | /* |
385 | * Slight weirdness here. We open a swapin file for any | |
386 | * STORE_DISK_CLIENT, even if we can copy the requested chunk | |
387 | * from memory in the next block. We must try to open the | |
388 | * swapin file before sending any data to the client side. If | |
389 | * we postpone the open, and then can not open the file later | |
390 | * on, the client loses big time. Its transfer just gets cut | |
391 | * off. Better to open it early (while the client side handler | |
392 | * is clientCacheHit) so that we can fall back to a cache miss | |
393 | * if needed. | |
394 | */ | |
fa80a8ef | 395 | |
aee3523a | 396 | if (STORE_DISK_CLIENT == getType() && swapin_sio == nullptr) { |
0cdcf3d7 AR |
397 | if (!startSwapin()) |
398 | return; // failure | |
399 | } | |
400 | scheduleRead(); | |
4e70dae3 | 401 | } |
402 | ||
0cdcf3d7 AR |
403 | /// opens the swapin "file" if possible; otherwise, fail()s and returns false |
404 | bool | |
4e70dae3 | 405 | store_client::startSwapin() |
406 | { | |
bf8fe701 | 407 | debugs(90, 3, "store_client::doCopy: Need to open swap in file"); |
4e70dae3 | 408 | /* gotta open the swapin file */ |
409 | ||
410 | if (storeTooManyDiskFilesOpen()) { | |
411 | /* yuck -- this causes a TCP_SWAPFAIL_MISS on the client side */ | |
412 | fail(); | |
3dd52a0b | 413 | flags.store_copying = false; |
0cdcf3d7 | 414 | return false; |
4e70dae3 | 415 | } else if (!flags.disk_io_pending) { |
416 | /* Don't set store_io_pending here */ | |
417 | storeSwapInStart(this); | |
62e76326 | 418 | |
aee3523a | 419 | if (swapin_sio == nullptr) { |
62e76326 | 420 | fail(); |
3dd52a0b | 421 | flags.store_copying = false; |
0cdcf3d7 | 422 | return false; |
62e76326 | 423 | } |
62e76326 | 424 | |
0cdcf3d7 | 425 | return true; |
4e70dae3 | 426 | } else { |
e0236918 | 427 | debugs(90, DBG_IMPORTANT, "WARNING: Averted multiple fd operation (1)"); |
3dd52a0b | 428 | flags.store_copying = false; |
0cdcf3d7 | 429 | return false; |
cfac48c2 | 430 | } |
4e70dae3 | 431 | } |
62e76326 | 432 | |
1fa761af EB |
433 | void |
434 | store_client::noteSwapInDone(const bool error) | |
435 | { | |
436 | Assure(_callback.pending()); | |
437 | if (error) | |
438 | fail(); | |
439 | else | |
440 | noteEof(); | |
441 | } | |
442 | ||
4e70dae3 | 443 | void |
444 | store_client::scheduleRead() | |
445 | { | |
446 | MemObject *mem = entry->mem_obj; | |
447 | ||
448 | if (copyInto.offset >= mem->inmem_lo && copyInto.offset < mem->endOffset()) | |
449 | scheduleMemRead(); | |
450 | else | |
451 | scheduleDiskRead(); | |
452 | } | |
453 | ||
454 | void | |
455 | store_client::scheduleDiskRead() | |
456 | { | |
cd748f27 | 457 | /* What the client wants is not in memory. Schedule a disk read */ |
0cdcf3d7 AR |
458 | if (getType() == STORE_DISK_CLIENT) { |
459 | // we should have called startSwapin() already | |
aee3523a | 460 | assert(swapin_sio != nullptr); |
2da4bfe6 | 461 | } else if (!swapin_sio && !startSwapin()) { |
0cdcf3d7 AR |
462 | debugs(90, 3, "bailing after swapin start failure for " << *entry); |
463 | assert(!flags.store_copying); | |
464 | return; | |
465 | } | |
62e76326 | 466 | |
528b2c61 | 467 | assert(!flags.disk_io_pending); |
62e76326 | 468 | |
0cdcf3d7 | 469 | debugs(90, 3, "reading " << *entry << " from disk"); |
62e76326 | 470 | |
528b2c61 | 471 | fileRead(); |
62e76326 | 472 | |
3dd52a0b | 473 | flags.store_copying = false; |
f09f5b26 | 474 | } |
475 | ||
4e70dae3 | 476 | void |
477 | store_client::scheduleMemRead() | |
478 | { | |
479 | /* What the client wants is in memory */ | |
480 | /* Old style */ | |
bf8fe701 | 481 | debugs(90, 3, "store_client::doCopy: Copying normal from memory"); |
1fa761af | 482 | const auto sz = entry->mem_obj->data_hdr.copy(copyInto); // may be <= 0 per copy() API |
4e70dae3 | 483 | callback(sz); |
3dd52a0b | 484 | flags.store_copying = false; |
4e70dae3 | 485 | } |
486 | ||
528b2c61 | 487 | void |
488 | store_client::fileRead() | |
f09f5b26 | 489 | { |
528b2c61 | 490 | MemObject *mem = entry->mem_obj; |
491 | ||
90703668 | 492 | assert(_callback.pending()); |
528b2c61 | 493 | assert(!flags.disk_io_pending); |
3dd52a0b | 494 | flags.disk_io_pending = true; |
62e76326 | 495 | |
528b2c61 | 496 | if (mem->swap_hdr_sz != 0) |
4310f8b0 | 497 | if (entry->swappingOut()) |
47f6e231 | 498 | assert(mem->swapout.sio->offset() > copyInto.offset + (int64_t)mem->swap_hdr_sz); |
62e76326 | 499 | |
528b2c61 | 500 | storeRead(swapin_sio, |
62e76326 | 501 | copyInto.data, |
502 | copyInto.length, | |
503 | copyInto.offset + mem->swap_hdr_sz, | |
504 | mem->swap_hdr_sz == 0 ? storeClientReadHeader | |
505 | : storeClientReadBody, | |
506 | this); | |
f09f5b26 | 507 | } |
508 | ||
beae59b0 | 509 | void |
ced8def3 | 510 | store_client::readBody(const char *, ssize_t len) |
beae59b0 | 511 | { |
beae59b0 | 512 | // Don't assert disk_io_pending here.. may be called by read_header |
3dd52a0b | 513 | flags.disk_io_pending = false; |
beae59b0 | 514 | assert(_callback.pending()); |
4a7a3d56 | 515 | debugs(90, 3, "storeClientReadBody: len " << len << ""); |
62e76326 | 516 | |
18102f7d EB |
517 | if (len < 0) |
518 | return fail(); | |
519 | ||
66d51f4f AR |
520 | const auto rep = entry->mem_obj ? &entry->mem().baseReply() : nullptr; |
521 | if (copyInto.offset == 0 && len > 0 && rep && rep->sline.status() == Http::scNone) { | |
62e76326 | 522 | /* Our structure ! */ |
66d51f4f | 523 | if (!entry->mem_obj->adjustableBaseReply().parseCharBuf(copyInto.data, headersEnd(copyInto.data, len))) { |
d816f28d | 524 | debugs(90, DBG_CRITICAL, "ERROR: Could not parse headers from on disk object"); |
3196f526 | 525 | } |
06a5ae20 | 526 | } |
62e76326 | 527 | |
ff4b33f4 | 528 | if (len > 0 && rep && entry->mem_obj->inmem_lo == 0 && entry->objectLen() <= (int64_t)Config.Store.maxInMemObjSize && Config.onoff.memory_cache_disk) { |
6d3c2758 HN |
529 | storeGetMemSpace(len); |
530 | // The above may start to free our object so we need to check again | |
531 | if (entry->mem_obj->inmem_lo == 0) { | |
532 | /* Copy read data back into memory. | |
55759ffb | 533 | * copyInto.offset includes headers, which is what mem cache needs |
6d3c2758 | 534 | */ |
c0bc2b1b | 535 | if (copyInto.offset == entry->mem_obj->endOffset()) { |
55759ffb | 536 | entry->mem_obj->write(StoreIOBuffer(len, copyInto.offset, copyInto.data)); |
6d3c2758 | 537 | } |
3196f526 | 538 | } |
beae59b0 HN |
539 | } |
540 | ||
541 | callback(len); | |
528b2c61 | 542 | } |
543 | ||
544 | void | |
62e76326 | 545 | store_client::fail() |
528b2c61 | 546 | { |
1fa761af EB |
547 | debugs(90, 3, (object_ok ? "once" : "again")); |
548 | if (!object_ok) | |
549 | return; // we failed earlier; nothing to do now | |
550 | ||
528b2c61 | 551 | object_ok = false; |
1fa761af EB |
552 | |
553 | noteNews(); | |
554 | } | |
555 | ||
556 | /// if necessary and possible, informs the Store reader about copy() result | |
557 | void | |
558 | store_client::noteNews() | |
559 | { | |
db2ff94c | 560 | /* synchronous open failures callback from the store, |
561 | * before startSwapin detects the failure. | |
562 | * TODO: fix this inconsistent behaviour - probably by | |
26ac0430 | 563 | * having storeSwapInStart become a callback functions, |
db2ff94c | 564 | * not synchronous |
565 | */ | |
566 | ||
1fa761af EB |
567 | if (!_callback.callback_handler) { |
568 | debugs(90, 5, "client lost interest"); | |
569 | return; | |
570 | } | |
571 | ||
572 | if (_callback.notifier) { | |
573 | debugs(90, 5, "earlier news is being delivered by " << _callback.notifier); | |
574 | return; | |
575 | } | |
576 | ||
577 | _callback.notifier = asyncCall(90, 4, "store_client::FinishCallback", cbdataDialer(store_client::FinishCallback, this)); | |
578 | ScheduleCallHere(_callback.notifier); | |
579 | ||
580 | Assure(!_callback.pending()); | |
f09f5b26 | 581 | } |
582 | ||
e3ef2b09 | 583 | static void |
ced8def3 | 584 | storeClientReadHeader(void *data, const char *buf, ssize_t len, StoreIOState::Pointer) |
e3ef2b09 | 585 | { |
e6ccf245 | 586 | store_client *sc = (store_client *)data; |
528b2c61 | 587 | sc->readHeader(buf, len); |
588 | } | |
589 | ||
beae59b0 | 590 | static void |
ced8def3 | 591 | storeClientReadBody(void *data, const char *buf, ssize_t len, StoreIOState::Pointer) |
beae59b0 HN |
592 | { |
593 | store_client *sc = (store_client *)data; | |
594 | sc->readBody(buf, len); | |
595 | } | |
596 | ||
528b2c61 | 597 | void |
598 | store_client::readHeader(char const *buf, ssize_t len) | |
599 | { | |
600 | MemObject *const mem = entry->mem_obj; | |
62e76326 | 601 | |
528b2c61 | 602 | assert(flags.disk_io_pending); |
3dd52a0b | 603 | flags.disk_io_pending = false; |
90703668 | 604 | assert(_callback.pending()); |
528b2c61 | 605 | |
4ea266b7 | 606 | // abort if we fail()'d earlier |
528b2c61 | 607 | if (!object_ok) |
62e76326 | 608 | return; |
609 | ||
18102f7d EB |
610 | if (len < 0) |
611 | return fail(); | |
612 | ||
d448e1eb EB |
613 | try { |
614 | Store::UnpackHitSwapMeta(buf, len, *entry); | |
615 | } catch (...) { | |
616 | debugs(90, DBG_IMPORTANT, "ERROR: Failed to unpack Store entry metadata: " << CurrentException); | |
4ea266b7 CF |
617 | fail(); |
618 | return; | |
619 | } | |
620 | ||
e3ef2b09 | 621 | /* |
622 | * If our last read got some data the client wants, then give | |
623 | * it to them, otherwise schedule another read. | |
624 | */ | |
528b2c61 | 625 | size_t body_sz = len - mem->swap_hdr_sz; |
62e76326 | 626 | |
47f6e231 | 627 | if (copyInto.offset < static_cast<int64_t>(body_sz)) { |
62e76326 | 628 | /* |
629 | * we have (part of) what they want | |
630 | */ | |
d85c3078 | 631 | size_t copy_sz = min(copyInto.length, body_sz); |
4a7a3d56 | 632 | debugs(90, 3, "storeClientReadHeader: copying " << copy_sz << " bytes of body"); |
41d00cd3 | 633 | memmove(copyInto.data, copyInto.data + mem->swap_hdr_sz, copy_sz); |
62e76326 | 634 | |
3196f526 | 635 | readBody(copyInto.data, copy_sz); |
528b2c61 | 636 | |
62e76326 | 637 | return; |
e3ef2b09 | 638 | } |
62e76326 | 639 | |
e3ef2b09 | 640 | /* |
641 | * we don't have what the client wants, but at least we now | |
642 | * know the swap header size. | |
643 | */ | |
528b2c61 | 644 | fileRead(); |
e3ef2b09 | 645 | } |
646 | ||
f09f5b26 | 647 | int |
a4b8110e | 648 | storeClientCopyPending(store_client * sc, StoreEntry * e, void *data) |
f09f5b26 | 649 | { |
06d2839d | 650 | #if STORE_CLIENT_LIST_DEBUG |
651 | assert(sc == storeClientListSearch(e->mem_obj, data)); | |
8b082ed9 FC |
652 | #else |
653 | (void)data; | |
edce4d98 | 654 | #endif |
62e76326 | 655 | |
edce4d98 | 656 | assert(sc); |
06d2839d | 657 | assert(sc->entry == e); |
62e76326 | 658 | |
90703668 | 659 | if (!sc->_callback.pending()) |
62e76326 | 660 | return 0; |
661 | ||
f09f5b26 | 662 | return 1; |
663 | } | |
664 | ||
06d2839d | 665 | /* |
666 | * This routine hasn't been optimised to take advantage of the | |
667 | * passed sc. Yet. | |
668 | */ | |
f09f5b26 | 669 | int |
a4b8110e | 670 | storeUnregister(store_client * sc, StoreEntry * e, void *data) |
f09f5b26 | 671 | { |
672 | MemObject *mem = e->mem_obj; | |
06d2839d | 673 | #if STORE_CLIENT_LIST_DEBUG |
674 | assert(sc == storeClientListSearch(e->mem_obj, data)); | |
8b082ed9 FC |
675 | #else |
676 | (void)data; | |
06d2839d | 677 | #endif |
62e76326 | 678 | |
aee3523a | 679 | if (mem == nullptr) |
62e76326 | 680 | return 0; |
681 | ||
26ac0430 | 682 | debugs(90, 3, "storeUnregister: called for '" << e->getMD5Text() << "'"); |
62e76326 | 683 | |
aee3523a | 684 | if (sc == nullptr) { |
bf8fe701 | 685 | debugs(90, 3, "storeUnregister: No matching client for '" << e->getMD5Text() << "'"); |
62e76326 | 686 | return 0; |
2f44bd34 | 687 | } |
62e76326 | 688 | |
0e3f3e0d | 689 | if (mem->clientCount() == 0) { |
bf8fe701 | 690 | debugs(90, 3, "storeUnregister: Consistency failure - store client being unregistered is not in the mem object's list for '" << e->getMD5Text() << "'"); |
62e76326 | 691 | return 0; |
2f44bd34 | 692 | } |
62e76326 | 693 | |
06d2839d | 694 | dlinkDelete(&sc->node, &mem->clients); |
5e263176 | 695 | -- mem->nclients; |
62e76326 | 696 | |
02ba667b EB |
697 | const auto swapoutFinished = e->swappedOut() || e->swapoutFailed(); |
698 | if (e->store_status == STORE_OK && !swapoutFinished) | |
c07cbbf4 | 699 | e->swapOut(); |
62e76326 | 700 | |
aee3523a | 701 | if (sc->swapin_sio != nullptr) { |
aa1a691e | 702 | storeClose(sc->swapin_sio, StoreIOState::readerDone); |
aee3523a | 703 | sc->swapin_sio = nullptr; |
5db6bf73 | 704 | ++statCounter.swap.ins; |
eb824054 | 705 | } |
62e76326 | 706 | |
1fa761af EB |
707 | if (sc->_callback.callback_handler || sc->_callback.notifier) { |
708 | debugs(90, 3, "forgetting store_client callback for " << *e); | |
709 | // Do not notify: Callers want to stop copying and forget about this | |
710 | // pending copy request. Some would mishandle a notification from here. | |
711 | if (sc->_callback.notifier) | |
712 | sc->_callback.notifier->cancel("storeUnregister"); | |
f09f5b26 | 713 | } |
62e76326 | 714 | |
fa80a8ef | 715 | #if STORE_CLIENT_LIST_DEBUG |
716 | cbdataReferenceDone(sc->owner); | |
62e76326 | 717 | |
fa80a8ef | 718 | #endif |
62e76326 | 719 | |
1fa761af EB |
720 | // XXX: We might be inside sc store_client method somewhere up the call |
721 | // stack. TODO: Convert store_client to AsyncJob to make destruction async. | |
528b2c61 | 722 | delete sc; |
62e76326 | 723 | |
1bfe9ade | 724 | assert(e->locked()); |
178c7e33 | 725 | // An entry locked by others may be unlocked (and destructed) by others, so |
f1ba1fba | 726 | // we must lock again to safely dereference e after CheckQuickAbortIsReasonable(). |
178c7e33 | 727 | e->lock("storeUnregister"); |
62e76326 | 728 | |
f1ba1fba EB |
729 | if (CheckQuickAbortIsReasonable(e)) |
730 | e->abort(); | |
a46d2c0e | 731 | else |
732 | mem->kickReads(); | |
62e76326 | 733 | |
0ad2b63b CT |
734 | #if USE_ADAPTATION |
735 | e->kickProducer(); | |
736 | #endif | |
737 | ||
178c7e33 | 738 | e->unlock("storeUnregister"); |
f09f5b26 | 739 | return 1; |
740 | } | |
741 | ||
f09f5b26 | 742 | /* Call handlers waiting for data to be appended to E. */ |
743 | void | |
d88e3c49 | 744 | StoreEntry::invokeHandlers() |
f09f5b26 | 745 | { |
70eb3fde EB |
746 | if (EBIT_TEST(flags, DELAY_SENDING)) { |
747 | debugs(90, 3, "DELAY_SENDING is on, exiting " << *this); | |
748 | return; | |
749 | } | |
750 | if (EBIT_TEST(flags, ENTRY_FWD_HDR_WAIT)) { | |
751 | debugs(90, 3, "ENTRY_FWD_HDR_WAIT is on, exiting " << *this); | |
752 | return; | |
753 | } | |
754 | ||
528b2c61 | 755 | /* Commit what we can to disk, if appropriate */ |
d88e3c49 | 756 | swapOut(); |
f09f5b26 | 757 | int i = 0; |
f09f5b26 | 758 | store_client *sc; |
aee3523a | 759 | dlink_node *nx = nullptr; |
06d2839d | 760 | dlink_node *node; |
761 | ||
f925268a | 762 | debugs(90, 3, mem_obj->nclients << " clients; " << *this << ' ' << getMD5Text()); |
f09f5b26 | 763 | /* walk the entire list looking for valid callbacks */ |
62e76326 | 764 | |
f925268a | 765 | const auto savedContext = CodeContext::Current(); |
d88e3c49 | 766 | for (node = mem_obj->clients.head; node; node = nx) { |
62e76326 | 767 | sc = (store_client *)node->data; |
768 | nx = node->next; | |
5db6bf73 | 769 | ++i; |
62e76326 | 770 | |
90703668 | 771 | if (!sc->_callback.pending()) |
62e76326 | 772 | continue; |
773 | ||
774 | if (sc->flags.disk_io_pending) | |
775 | continue; | |
776 | ||
1fa761af EB |
777 | if (sc->flags.store_copying) |
778 | continue; | |
779 | ||
780 | // XXX: If invokeHandlers() is (indirectly) called from a store_client | |
781 | // method, then the above three conditions may not be sufficient to | |
782 | // prevent us from reentering the same store_client object! This | |
783 | // probably does not happen in the current code, but no observed | |
784 | // invariant prevents this from (accidentally) happening in the future. | |
785 | ||
786 | // TODO: Convert store_client into AsyncJob; make this call asynchronous | |
f925268a AR |
787 | CodeContext::Reset(sc->_callback.codeContext); |
788 | debugs(90, 3, "checking client #" << i); | |
d88e3c49 | 789 | storeClientCopy2(this, sc); |
f09f5b26 | 790 | } |
f925268a | 791 | CodeContext::Reset(savedContext); |
f09f5b26 | 792 | } |
793 | ||
22bbd840 | 794 | // Does not account for remote readers/clients. |
f09f5b26 | 795 | int |
796 | storePendingNClients(const StoreEntry * e) | |
797 | { | |
f09f5b26 | 798 | MemObject *mem = e->mem_obj; |
aee3523a | 799 | int npend = nullptr == mem ? 0 : mem->nclients; |
bf8fe701 | 800 | debugs(90, 3, "storePendingNClients: returning " << npend); |
f09f5b26 | 801 | return npend; |
802 | } | |
77b32a34 | 803 | |
ae6568e7 AJ |
804 | /* return true if the request should be aborted */ |
805 | static bool | |
806 | CheckQuickAbortIsReasonable(StoreEntry * entry) | |
77b32a34 | 807 | { |
f1ba1fba EB |
808 | assert(entry); |
809 | debugs(90, 3, "entry=" << *entry); | |
810 | ||
811 | if (storePendingNClients(entry) > 0) { | |
812 | debugs(90, 3, "quick-abort? NO storePendingNClients() > 0"); | |
813 | return false; | |
814 | } | |
815 | ||
23b79630 | 816 | if (Store::Root().transientReaders(*entry)) { |
f1ba1fba EB |
817 | debugs(90, 3, "quick-abort? NO still have one or more transient readers"); |
818 | return false; | |
819 | } | |
820 | ||
821 | if (entry->store_status != STORE_PENDING) { | |
822 | debugs(90, 3, "quick-abort? NO store_status != STORE_PENDING"); | |
823 | return false; | |
824 | } | |
825 | ||
826 | if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) { | |
827 | debugs(90, 3, "quick-abort? NO ENTRY_SPECIAL"); | |
828 | return false; | |
829 | } | |
830 | ||
23b79630 AR |
831 | if (shutting_down) { |
832 | debugs(90, 3, "quick-abort? YES avoid heavy optional work during shutdown"); | |
833 | return true; | |
834 | } | |
835 | ||
528b2c61 | 836 | MemObject * const mem = entry->mem_obj; |
77b32a34 | 837 | assert(mem); |
f1ba1fba | 838 | debugs(90, 3, "mem=" << mem); |
62e76326 | 839 | |
45e5102d | 840 | if (mem->request && !mem->request->flags.cachable) { |
ae6568e7 AJ |
841 | debugs(90, 3, "quick-abort? YES !mem->request->flags.cachable"); |
842 | return true; | |
77b32a34 | 843 | } |
62e76326 | 844 | |
77b32a34 | 845 | if (EBIT_TEST(entry->flags, KEY_PRIVATE)) { |
ae6568e7 AJ |
846 | debugs(90, 3, "quick-abort? YES KEY_PRIVATE"); |
847 | return true; | |
77b32a34 | 848 | } |
62e76326 | 849 | |
66d51f4f | 850 | const auto &reply = mem->baseReply(); |
0e3f3e0d | 851 | |
66d51f4f AR |
852 | if (reply.hdr_sz <= 0) { |
853 | // TODO: Check whether this condition works for HTTP/0 responses. | |
ae6568e7 AJ |
854 | debugs(90, 3, "quick-abort? YES no object data received yet"); |
855 | return true; | |
856 | } | |
0e3f3e0d | 857 | |
47f6e231 | 858 | if (Config.quickAbort.min < 0) { |
ae6568e7 AJ |
859 | debugs(90, 3, "quick-abort? NO disabled"); |
860 | return false; | |
77b32a34 | 861 | } |
62e76326 | 862 | |
ae6568e7 | 863 | if (mem->request && mem->request->range && mem->request->getRangeOffsetLimit() < 0) { |
66d51f4f | 864 | // the admin has configured "range_offset_limit none" |
ae6568e7 AJ |
865 | debugs(90, 3, "quick-abort? NO admin configured range replies to full-download"); |
866 | return false; | |
ab275c7b AJ |
867 | } |
868 | ||
66d51f4f AR |
869 | if (reply.content_length < 0) { |
870 | // XXX: cf.data.pre does not document what should happen in this case | |
871 | // We know that quick_abort is enabled, but no limit can be applied. | |
872 | debugs(90, 3, "quick-abort? YES unknown content length"); | |
873 | return true; | |
874 | } | |
875 | const auto expectlen = reply.hdr_sz + reply.content_length; | |
876 | ||
877 | int64_t curlen = mem->endOffset(); | |
878 | ||
77b32a34 | 879 | if (curlen > expectlen) { |
f22b0af0 | 880 | debugs(90, 3, "quick-abort? YES bad content length (" << curlen << " of " << expectlen << " bytes received)"); |
ae6568e7 | 881 | return true; |
77b32a34 | 882 | } |
62e76326 | 883 | |
47f6e231 | 884 | if ((expectlen - curlen) < (Config.quickAbort.min << 10)) { |
ae6568e7 AJ |
885 | debugs(90, 3, "quick-abort? NO only a little more object left to receive"); |
886 | return false; | |
77b32a34 | 887 | } |
62e76326 | 888 | |
77b32a34 | 889 | if ((expectlen - curlen) > (Config.quickAbort.max << 10)) { |
ae6568e7 AJ |
890 | debugs(90, 3, "quick-abort? YES too much left to go"); |
891 | return true; | |
77b32a34 | 892 | } |
62e76326 | 893 | |
66d51f4f | 894 | // XXX: This is absurd! TODO: For positives, "a/(b/c) > d" is "a*c > b*d". |
77b32a34 | 895 | if (expectlen < 100) { |
ae6568e7 AJ |
896 | debugs(90, 3, "quick-abort? NO avoid FPE"); |
897 | return false; | |
77b32a34 | 898 | } |
62e76326 | 899 | |
47f6e231 | 900 | if ((curlen / (expectlen / 100)) > (Config.quickAbort.pct)) { |
ae6568e7 AJ |
901 | debugs(90, 3, "quick-abort? NO past point of no return"); |
902 | return false; | |
77b32a34 | 903 | } |
62e76326 | 904 | |
ae6568e7 AJ |
905 | debugs(90, 3, "quick-abort? YES default"); |
906 | return true; | |
77b32a34 | 907 | } |
908 | ||
c8be6d7b | 909 | void |
fcc35180 | 910 | store_client::dumpStats(MemBuf * output, int clientNumber) const |
c8be6d7b | 911 | { |
90703668 | 912 | if (_callback.pending()) |
62e76326 | 913 | return; |
914 | ||
4391cd15 AJ |
915 | output->appendf("\tClient #%d, %p\n", clientNumber, _callback.callback_data); |
916 | output->appendf("\t\tcopy_offset: %" PRId64 "\n", copyInto.offset); | |
fa938e45 | 917 | output->appendf("\t\tcopy_size: %" PRIuSIZE "\n", copyInto.length); |
4391cd15 | 918 | output->append("\t\tflags:", 8); |
62e76326 | 919 | |
528b2c61 | 920 | if (flags.disk_io_pending) |
4391cd15 | 921 | output->append(" disk_io_pending", 16); |
62e76326 | 922 | |
528b2c61 | 923 | if (flags.store_copying) |
4391cd15 | 924 | output->append(" store_copying", 14); |
62e76326 | 925 | |
1fa761af EB |
926 | if (_callback.notifier) |
927 | output->append(" notifying", 10); | |
62e76326 | 928 | |
4391cd15 | 929 | output->append("\n",1); |
528b2c61 | 930 | } |
c8be6d7b | 931 | |
528b2c61 | 932 | bool |
90703668 | 933 | store_client::Callback::pending() const |
528b2c61 | 934 | { |
1fa761af | 935 | return callback_handler && !notifier; |
c8be6d7b | 936 | } |
528b2c61 | 937 | |
f925268a AR |
938 | store_client::Callback::Callback(STCB *function, void *data): |
939 | callback_handler(function), | |
940 | callback_data(data), | |
941 | codeContext(CodeContext::Current()) | |
942 | { | |
943 | } | |
b67e2c8c | 944 | |
9a0a18de | 945 | #if USE_DELAY_POOLS |
1fa761af EB |
946 | int |
947 | store_client::bytesWanted() const | |
948 | { | |
949 | // TODO: To avoid using stale copyInto, return zero if !_callback.pending()? | |
950 | return delayId.bytesWanted(0, copyInto.length); | |
951 | } | |
952 | ||
b67e2c8c | 953 | void |
954 | store_client::setDelayId(DelayId delay_id) | |
955 | { | |
956 | delayId = delay_id; | |
957 | } | |
515ec4dc | 958 | #endif |
f53969cc | 959 |