2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 90 Storage Manager Client-Side Interface */
12 #include "acl/FilledChecklist.h"
15 #include "HttpReply.h"
16 #include "HttpRequest.h"
18 #include "MemObject.h"
19 #include "mime_header.h"
20 #include "profiler/Profiler.h"
21 #include "SquidConfig.h"
22 #include "StatCounters.h"
24 #include "store_swapin.h"
25 #include "StoreClient.h"
26 #include "StoreMeta.h"
27 #include "StoreMetaUnpacker.h"
29 #include "DelayPools.h"
33 * NOTE: 'Header' refers to the swapfile metadata header.
34 * 'OBJHeader' refers to the object header, with cannonical
35 * processed object headers (which may derive from FTP/HTTP etc
37 * 'Body' refers to the swapfile body, which is the full
38 * HTTP reply (including HTTP headers and body).
40 static StoreIOState::STRCB storeClientReadBody
;
41 static StoreIOState::STRCB storeClientReadHeader
;
42 static void storeClientCopy2(StoreEntry
* e
, store_client
* sc
);
43 static EVH storeClientCopyEvent
;
44 static bool CheckQuickAbortIsReasonable(StoreEntry
* entry
);
46 CBDATA_CLASS_INIT(store_client
);
51 StoreClient::onCollapsingPath() const
53 if (!Config
.onoff
.collapsed_forwarding
)
56 if (!Config
.accessList
.collapsedForwardingAccess
)
59 ACLFilledChecklist
checklist(Config
.accessList
.collapsedForwardingAccess
, nullptr, nullptr);
60 fillChecklist(checklist
);
61 return checklist
.fastCheck().allowed();
65 StoreClient::startCollapsingOn(const StoreEntry
&e
, const bool doingRevalidation
)
67 if (!e
.hittingRequiresCollapsing())
68 return false; // collapsing is impossible due to the entry state
70 if (!onCollapsingPath())
71 return false; // collapsing is impossible due to Squid configuration
73 /* collapsing is possible; the caller must collapse */
75 if (const auto tags
= loggingTags()) {
76 if (doingRevalidation
)
77 tags
->collapsingHistory
.revalidationCollapses
++;
79 tags
->collapsingHistory
.otherCollapses
++;
82 debugs(85, 5, e
<< " doingRevalidation=" << doingRevalidation
);
87 StoreClient::fillChecklist(ACLFilledChecklist
&checklist
) const
89 // TODO: Consider moving all CF-related methods into a new dedicated class.
90 Must(!"startCollapsingOn() caller must override fillChecklist()");
96 store_client::memReaderHasLowerOffset(int64_t anOffset
) const
98 return getType() == STORE_MEM_CLIENT
&& copyInto
.offset
< anOffset
;
102 store_client::getType() const
107 #if STORE_CLIENT_LIST_DEBUG
108 static store_client
*
109 storeClientListSearch(const MemObject
* mem
, void *data
)
112 store_client
*sc
= NULL
;
114 for (node
= mem
->clients
.head
; node
; node
= node
->next
) {
117 if (sc
->owner
== data
)
125 storeClientIsThisAClient(store_client
* sc
, void *someClient
)
127 return sc
->owner
== someClient
;
131 #include "HttpRequest.h"
133 /* add client with fd to client list */
135 storeClientListAdd(StoreEntry
* e
, void *data
)
137 MemObject
*mem
= e
->mem_obj
;
140 #if STORE_CLIENT_LIST_DEBUG
142 if (storeClientListSearch(mem
, data
) != NULL
)
148 sc
= new store_client (e
);
156 store_client::callback(ssize_t sz
, bool error
)
160 if (sz
>= 0 && !error
)
163 StoreIOBuffer
result(bSz
, 0,copyInto
.data
);
166 result
.flags
.error
= 1;
168 result
.offset
= cmp_offset
;
169 assert(_callback
.pending());
170 cmp_offset
= copyInto
.offset
+ bSz
;
171 STCB
*temphandler
= _callback
.callback_handler
;
172 void *cbdata
= _callback
.callback_data
;
173 _callback
= Callback(NULL
, NULL
);
174 copyInto
.data
= NULL
;
176 if (cbdataReferenceValid(cbdata
))
177 temphandler(cbdata
, result
);
179 cbdataReferenceDone(cbdata
);
183 storeClientCopyEvent(void *data
)
185 store_client
*sc
= (store_client
*)data
;
186 debugs(90, 3, "storeClientCopyEvent: Running");
187 assert (sc
->flags
.copy_event_pending
);
188 sc
->flags
.copy_event_pending
= false;
190 if (!sc
->_callback
.pending())
193 storeClientCopy2(sc
->entry
, sc
);
196 store_client::store_client(StoreEntry
*e
) :
198 #if STORE_CLIENT_LIST_DEBUG
199 owner(cbdataReference(data
)),
202 type(e
->storeClientType()),
205 flags
.disk_io_pending
= false;
206 flags
.store_copying
= false;
207 flags
.copy_event_pending
= false;
210 if (getType() == STORE_DISK_CLIENT
) {
211 /* assert we'll be able to get the data we want */
212 /* maybe we should open swapin_sio here */
213 assert(entry
->hasDisk() && !entry
->swapoutFailed());
217 store_client::~store_client()
220 /* copy bytes requested by the client */
222 storeClientCopy(store_client
* sc
,
224 StoreIOBuffer copyInto
,
229 sc
->copy(e
, copyInto
,callback
,data
);
233 store_client::copy(StoreEntry
* anEntry
,
234 StoreIOBuffer copyRequest
,
238 assert (anEntry
== entry
);
239 assert (callback_fn
);
241 assert(!EBIT_TEST(entry
->flags
, ENTRY_ABORTED
));
242 debugs(90, 3, "store_client::copy: " << entry
->getMD5Text() << ", from " <<
243 copyRequest
.offset
<< ", for length " <<
244 (int) copyRequest
.length
<< ", cb " << callback_fn
<< ", cbdata " <<
247 #if STORE_CLIENT_LIST_DEBUG
249 assert(this == storeClientListSearch(entry
->mem_obj
, data
));
252 assert(!_callback
.pending());
253 #if ONLYCONTIGUOUSREQUESTS
255 assert(cmp_offset
== copyRequest
.offset
);
257 /* range requests will skip into the body */
258 cmp_offset
= copyRequest
.offset
;
259 _callback
= Callback (callback_fn
, cbdataReference(data
));
260 copyInto
.data
= copyRequest
.data
;
261 copyInto
.length
= copyRequest
.length
;
262 copyInto
.offset
= copyRequest
.offset
;
264 static bool copying (false);
267 PROF_start(storeClient_kickReads
);
268 /* we might be blocking comm reads due to readahead limits
269 * now we have a new offset, trigger those reads...
271 entry
->mem_obj
->kickReads();
272 PROF_stop(storeClient_kickReads
);
275 anEntry
->lock("store_client::copy"); // see deletion note below
277 storeClientCopy2(entry
, this);
279 // Bug 3480: This store_client object may be deleted now if, for example,
280 // the client rejects the hit response copied above. Use on-stack pointers!
283 anEntry
->kickProducer();
285 anEntry
->unlock("store_client::copy");
287 // Add no code here. This object may no longer exist.
290 /// Whether there is (or will be) more entry data for us.
292 store_client::moreToSend() const
294 if (entry
->store_status
== STORE_PENDING
)
295 return true; // there may be more coming
297 /* STORE_OK, including aborted entries: no more data is coming */
299 const int64_t len
= entry
->objectLen();
301 // If we do not know the entry length, then we have to open the swap file.
302 const bool canSwapIn
= entry
->hasDisk();
306 if (copyInto
.offset
>= len
)
307 return false; // sent everything there is
310 return true; // if we lack prefix, we can swap it in
312 // If we cannot swap in, make sure we have what we want in RAM. Otherwise,
313 // scheduleRead calls scheduleDiskRead which asserts without a swap file.
314 const MemObject
*mem
= entry
->mem_obj
;
316 mem
->inmem_lo
<= copyInto
.offset
&& copyInto
.offset
< mem
->endOffset();
320 storeClientCopy2(StoreEntry
* e
, store_client
* sc
)
322 /* reentrancy not allowed - note this could lead to
326 if (sc
->flags
.copy_event_pending
) {
330 if (sc
->flags
.store_copying
) {
331 sc
->flags
.copy_event_pending
= true;
332 debugs(90, 3, "storeClientCopy2: Queueing storeClientCopyEvent()");
333 eventAdd("storeClientCopyEvent", storeClientCopyEvent
, sc
, 0.0, 0);
337 debugs(90, 3, "storeClientCopy2: " << e
->getMD5Text());
338 assert(sc
->_callback
.pending());
340 * We used to check for ENTRY_ABORTED here. But there were some
341 * problems. For example, we might have a slow client (or two) and
342 * the peer server is reading far ahead and swapping to disk. Even
343 * if the peer aborts, we want to give the client(s)
344 * everything we got before the abort condition occurred.
346 /* Warning: doCopy may indirectly free itself in callbacks,
347 * hence the lock to keep it active for the duration of
349 * XXX: Locking does not prevent calling sc destructor (it only prevents
350 * freeing sc memory) so sc may become invalid from C++ p.o.v.
352 CbcPointer
<store_client
> tmpLock
= sc
;
353 assert (!sc
->flags
.store_copying
);
355 assert(!sc
->flags
.store_copying
);
359 store_client::doCopy(StoreEntry
*anEntry
)
361 assert (anEntry
== entry
);
362 flags
.store_copying
= true;
363 MemObject
*mem
= entry
->mem_obj
;
365 debugs(33, 5, "store_client::doCopy: co: " <<
366 copyInto
.offset
<< ", hi: " <<
370 /* There is no more to send! */
371 debugs(33, 3, HERE
<< "There is no more to send!");
373 flags
.store_copying
= false;
377 /* Check that we actually have data */
378 if (anEntry
->store_status
== STORE_PENDING
&& copyInto
.offset
>= mem
->endOffset()) {
379 debugs(90, 3, "store_client::doCopy: Waiting for more");
380 flags
.store_copying
= false;
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
396 if (STORE_DISK_CLIENT
== getType() && swapin_sio
== NULL
) {
403 /// opens the swapin "file" if possible; otherwise, fail()s and returns false
405 store_client::startSwapin()
407 debugs(90, 3, "store_client::doCopy: Need to open swap in file");
408 /* gotta open the swapin file */
410 if (storeTooManyDiskFilesOpen()) {
411 /* yuck -- this causes a TCP_SWAPFAIL_MISS on the client side */
413 flags
.store_copying
= false;
415 } else if (!flags
.disk_io_pending
) {
416 /* Don't set store_io_pending here */
417 storeSwapInStart(this);
419 if (swapin_sio
== NULL
) {
421 flags
.store_copying
= false;
427 debugs(90, DBG_IMPORTANT
, "WARNING: Averted multiple fd operation (1)");
428 flags
.store_copying
= false;
434 store_client::scheduleRead()
436 MemObject
*mem
= entry
->mem_obj
;
438 if (copyInto
.offset
>= mem
->inmem_lo
&& copyInto
.offset
< mem
->endOffset())
445 store_client::scheduleDiskRead()
447 /* What the client wants is not in memory. Schedule a disk read */
448 if (getType() == STORE_DISK_CLIENT
) {
449 // we should have called startSwapin() already
450 assert(swapin_sio
!= NULL
);
451 } else if (!swapin_sio
&& !startSwapin()) {
452 debugs(90, 3, "bailing after swapin start failure for " << *entry
);
453 assert(!flags
.store_copying
);
457 assert(!flags
.disk_io_pending
);
459 debugs(90, 3, "reading " << *entry
<< " from disk");
463 flags
.store_copying
= false;
467 store_client::scheduleMemRead()
469 /* What the client wants is in memory */
471 debugs(90, 3, "store_client::doCopy: Copying normal from memory");
472 size_t sz
= entry
->mem_obj
->data_hdr
.copy(copyInto
);
474 flags
.store_copying
= false;
478 store_client::fileRead()
480 MemObject
*mem
= entry
->mem_obj
;
482 assert(_callback
.pending());
483 assert(!flags
.disk_io_pending
);
484 flags
.disk_io_pending
= true;
486 if (mem
->swap_hdr_sz
!= 0)
487 if (entry
->swappingOut())
488 assert(mem
->swapout
.sio
->offset() > copyInto
.offset
+ (int64_t)mem
->swap_hdr_sz
);
490 storeRead(swapin_sio
,
493 copyInto
.offset
+ mem
->swap_hdr_sz
,
494 mem
->swap_hdr_sz
== 0 ? storeClientReadHeader
495 : storeClientReadBody
,
500 store_client::readBody(const char *, ssize_t len
)
502 int parsed_header
= 0;
504 // Don't assert disk_io_pending here.. may be called by read_header
505 flags
.disk_io_pending
= false;
506 assert(_callback
.pending());
507 debugs(90, 3, "storeClientReadBody: len " << len
<< "");
512 const auto rep
= entry
->mem_obj
? &entry
->mem().baseReply() : nullptr;
513 if (copyInto
.offset
== 0 && len
> 0 && rep
&& rep
->sline
.status() == Http::scNone
) {
514 /* Our structure ! */
515 if (!entry
->mem_obj
->adjustableBaseReply().parseCharBuf(copyInto
.data
, headersEnd(copyInto
.data
, len
))) {
516 debugs(90, DBG_CRITICAL
, "Could not parse headers from on disk object");
522 if (len
> 0 && rep
&& entry
->mem_obj
->inmem_lo
== 0 && entry
->objectLen() <= (int64_t)Config
.Store
.maxInMemObjSize
&& Config
.onoff
.memory_cache_disk
) {
523 storeGetMemSpace(len
);
524 // The above may start to free our object so we need to check again
525 if (entry
->mem_obj
->inmem_lo
== 0) {
526 /* Copy read data back into memory.
527 * copyInto.offset includes headers, which is what mem cache needs
529 int64_t mem_offset
= entry
->mem_obj
->endOffset();
530 if ((copyInto
.offset
== mem_offset
) || (parsed_header
&& mem_offset
== rep
->hdr_sz
)) {
531 entry
->mem_obj
->write(StoreIOBuffer(len
, copyInto
.offset
, copyInto
.data
));
543 /* synchronous open failures callback from the store,
544 * before startSwapin detects the failure.
545 * TODO: fix this inconsistent behaviour - probably by
546 * having storeSwapInStart become a callback functions,
550 if (_callback
.pending())
555 storeClientReadHeader(void *data
, const char *buf
, ssize_t len
, StoreIOState::Pointer
)
557 store_client
*sc
= (store_client
*)data
;
558 sc
->readHeader(buf
, len
);
562 storeClientReadBody(void *data
, const char *buf
, ssize_t len
, StoreIOState::Pointer
)
564 store_client
*sc
= (store_client
*)data
;
565 sc
->readBody(buf
, len
);
569 store_client::unpackHeader(char const *buf
, ssize_t len
)
571 debugs(90, 3, "store_client::unpackHeader: len " << len
<< "");
575 tlv
*tlv_list
= nullptr;
577 StoreMetaUnpacker
aBuilder(buf
, len
, &swap_hdr_sz
);
578 tlv_list
= aBuilder
.createStoreMeta();
579 } catch (const std::exception
&e
) {
580 debugs(90, DBG_IMPORTANT
, "WARNING: failed to unpack metadata because " << e
.what());
586 * Check the meta data and make sure we got the right object.
588 for (tlv
*t
= tlv_list
; t
; t
= t
->next
) {
589 if (!t
->checkConsistency(entry
)) {
590 storeSwapTLVFree(tlv_list
);
595 storeSwapTLVFree(tlv_list
);
597 assert(swap_hdr_sz
>= 0);
598 entry
->mem_obj
->swap_hdr_sz
= swap_hdr_sz
;
599 if (entry
->swap_file_sz
> 0) { // collapsed hits may not know swap_file_sz
600 assert(entry
->swap_file_sz
>= static_cast<uint64_t>(swap_hdr_sz
));
601 entry
->mem_obj
->object_sz
= entry
->swap_file_sz
- swap_hdr_sz
;
603 debugs(90, 5, "store_client::unpackHeader: swap_file_sz=" <<
604 entry
->swap_file_sz
<< "( " << swap_hdr_sz
<< " + " <<
605 entry
->mem_obj
->object_sz
<< ")");
610 store_client::readHeader(char const *buf
, ssize_t len
)
612 MemObject
*const mem
= entry
->mem_obj
;
614 assert(flags
.disk_io_pending
);
615 flags
.disk_io_pending
= false;
616 assert(_callback
.pending());
618 // abort if we fail()'d earlier
625 if (!unpackHeader(buf
, len
)) {
631 * If our last read got some data the client wants, then give
632 * it to them, otherwise schedule another read.
634 size_t body_sz
= len
- mem
->swap_hdr_sz
;
636 if (copyInto
.offset
< static_cast<int64_t>(body_sz
)) {
638 * we have (part of) what they want
640 size_t copy_sz
= min(copyInto
.length
, body_sz
);
641 debugs(90, 3, "storeClientReadHeader: copying " << copy_sz
<< " bytes of body");
642 memmove(copyInto
.data
, copyInto
.data
+ mem
->swap_hdr_sz
, copy_sz
);
644 readBody(copyInto
.data
, copy_sz
);
650 * we don't have what the client wants, but at least we now
651 * know the swap header size.
657 storeClientCopyPending(store_client
* sc
, StoreEntry
* e
, void *data
)
659 #if STORE_CLIENT_LIST_DEBUG
660 assert(sc
== storeClientListSearch(e
->mem_obj
, data
));
667 assert(sc
->entry
== e
);
675 if (!sc
->_callback
.pending())
682 * This routine hasn't been optimised to take advantage of the
686 storeUnregister(store_client
* sc
, StoreEntry
* e
, void *data
)
688 MemObject
*mem
= e
->mem_obj
;
689 #if STORE_CLIENT_LIST_DEBUG
691 assert(sc
== storeClientListSearch(e
->mem_obj
, data
));
697 debugs(90, 3, "storeUnregister: called for '" << e
->getMD5Text() << "'");
700 debugs(90, 3, "storeUnregister: No matching client for '" << e
->getMD5Text() << "'");
704 if (mem
->clientCount() == 0) {
705 debugs(90, 3, "storeUnregister: Consistency failure - store client being unregistered is not in the mem object's list for '" << e
->getMD5Text() << "'");
709 dlinkDelete(&sc
->node
, &mem
->clients
);
712 const auto swapoutFinished
= e
->swappedOut() || e
->swapoutFailed();
713 if (e
->store_status
== STORE_OK
&& !swapoutFinished
)
716 if (sc
->swapin_sio
!= NULL
) {
717 storeClose(sc
->swapin_sio
, StoreIOState::readerDone
);
718 sc
->swapin_sio
= NULL
;
719 ++statCounter
.swap
.ins
;
722 if (sc
->_callback
.pending()) {
723 /* callback with ssize = -1 to indicate unexpected termination */
724 debugs(90, 3, "store_client for " << *e
<< " has a callback");
728 #if STORE_CLIENT_LIST_DEBUG
729 cbdataReferenceDone(sc
->owner
);
736 // An entry locked by others may be unlocked (and destructed) by others, so
737 // we must lock again to safely dereference e after CheckQuickAbortIsReasonable().
738 e
->lock("storeUnregister");
740 if (CheckQuickAbortIsReasonable(e
))
749 e
->unlock("storeUnregister");
753 /* Call handlers waiting for data to be appended to E. */
755 StoreEntry::invokeHandlers()
757 if (EBIT_TEST(flags
, DELAY_SENDING
)) {
758 debugs(90, 3, "DELAY_SENDING is on, exiting " << *this);
761 if (EBIT_TEST(flags
, ENTRY_FWD_HDR_WAIT
)) {
762 debugs(90, 3, "ENTRY_FWD_HDR_WAIT is on, exiting " << *this);
766 /* Commit what we can to disk, if appropriate */
770 dlink_node
*nx
= NULL
;
773 PROF_start(InvokeHandlers
);
775 debugs(90, 3, "InvokeHandlers: " << getMD5Text() );
776 /* walk the entire list looking for valid callbacks */
778 for (node
= mem_obj
->clients
.head
; node
; node
= nx
) {
779 sc
= (store_client
*)node
->data
;
781 debugs(90, 3, "StoreEntry::InvokeHandlers: checking client #" << i
);
784 if (!sc
->_callback
.pending())
787 if (sc
->flags
.disk_io_pending
)
790 storeClientCopy2(this, sc
);
792 PROF_stop(InvokeHandlers
);
795 // Does not account for remote readers/clients.
797 storePendingNClients(const StoreEntry
* e
)
799 MemObject
*mem
= e
->mem_obj
;
800 int npend
= NULL
== mem
? 0 : mem
->nclients
;
801 debugs(90, 3, "storePendingNClients: returning " << npend
);
805 /* return true if the request should be aborted */
807 CheckQuickAbortIsReasonable(StoreEntry
* entry
)
810 debugs(90, 3, "entry=" << *entry
);
812 if (storePendingNClients(entry
) > 0) {
813 debugs(90, 3, "quick-abort? NO storePendingNClients() > 0");
817 if (!shutting_down
&& Store::Root().transientReaders(*entry
)) {
818 debugs(90, 3, "quick-abort? NO still have one or more transient readers");
822 if (entry
->store_status
!= STORE_PENDING
) {
823 debugs(90, 3, "quick-abort? NO store_status != STORE_PENDING");
827 if (EBIT_TEST(entry
->flags
, ENTRY_SPECIAL
)) {
828 debugs(90, 3, "quick-abort? NO ENTRY_SPECIAL");
832 MemObject
* const mem
= entry
->mem_obj
;
834 debugs(90, 3, "mem=" << mem
);
836 if (mem
->request
&& !mem
->request
->flags
.cachable
) {
837 debugs(90, 3, "quick-abort? YES !mem->request->flags.cachable");
841 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
)) {
842 debugs(90, 3, "quick-abort? YES KEY_PRIVATE");
846 const auto &reply
= mem
->baseReply();
848 if (reply
.hdr_sz
<= 0) {
849 // TODO: Check whether this condition works for HTTP/0 responses.
850 debugs(90, 3, "quick-abort? YES no object data received yet");
854 if (Config
.quickAbort
.min
< 0) {
855 debugs(90, 3, "quick-abort? NO disabled");
859 if (mem
->request
&& mem
->request
->range
&& mem
->request
->getRangeOffsetLimit() < 0) {
860 // the admin has configured "range_offset_limit none"
861 debugs(90, 3, "quick-abort? NO admin configured range replies to full-download");
865 if (reply
.content_length
< 0) {
866 // XXX: cf.data.pre does not document what should happen in this case
867 // We know that quick_abort is enabled, but no limit can be applied.
868 debugs(90, 3, "quick-abort? YES unknown content length");
871 const auto expectlen
= reply
.hdr_sz
+ reply
.content_length
;
873 int64_t curlen
= mem
->endOffset();
875 if (curlen
> expectlen
) {
876 debugs(90, 3, "quick-abort? YES bad content length (" << curlen
<< " of " << expectlen
<< " bytes received)");
880 if ((expectlen
- curlen
) < (Config
.quickAbort
.min
<< 10)) {
881 debugs(90, 3, "quick-abort? NO only a little more object left to receive");
885 if ((expectlen
- curlen
) > (Config
.quickAbort
.max
<< 10)) {
886 debugs(90, 3, "quick-abort? YES too much left to go");
890 // XXX: This is absurd! TODO: For positives, "a/(b/c) > d" is "a*c > b*d".
891 if (expectlen
< 100) {
892 debugs(90, 3, "quick-abort? NO avoid FPE");
896 if ((curlen
/ (expectlen
/ 100)) > (Config
.quickAbort
.pct
)) {
897 debugs(90, 3, "quick-abort? NO past point of no return");
901 debugs(90, 3, "quick-abort? YES default");
906 store_client::dumpStats(MemBuf
* output
, int clientNumber
) const
908 if (_callback
.pending())
911 output
->appendf("\tClient #%d, %p\n", clientNumber
, _callback
.callback_data
);
912 output
->appendf("\t\tcopy_offset: %" PRId64
"\n", copyInto
.offset
);
913 output
->appendf("\t\tcopy_size: %" PRIuSIZE
"\n", copyInto
.length
);
914 output
->append("\t\tflags:", 8);
916 if (flags
.disk_io_pending
)
917 output
->append(" disk_io_pending", 16);
919 if (flags
.store_copying
)
920 output
->append(" store_copying", 14);
922 if (flags
.copy_event_pending
)
923 output
->append(" copy_event_pending", 19);
925 output
->append("\n",1);
929 store_client::Callback::pending() const
931 return callback_handler
&& callback_data
;
934 store_client::Callback::Callback(STCB
*function
, void *data
) : callback_handler(function
), callback_data (data
) {}
938 store_client::setDelayId(DelayId delay_id
)