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