2 * Copyright (C) 1996-2023 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"
13 #include "base/AsyncCbdataCalls.h"
14 #include "base/CodeContext.h"
17 #include "HttpReply.h"
18 #include "HttpRequest.h"
20 #include "MemObject.h"
21 #include "mime_header.h"
22 #include "SquidConfig.h"
23 #include "StatCounters.h"
25 #include "store/SwapMetaIn.h"
26 #include "store_swapin.h"
27 #include "StoreClient.h"
29 #include "DelayPools.h"
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
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 bool CheckQuickAbortIsReasonable(StoreEntry
* entry
);
45 CBDATA_CLASS_INIT(store_client
);
50 StoreClient::onCollapsingPath() const
52 if (!Config
.onoff
.collapsed_forwarding
)
55 if (!Config
.accessList
.collapsedForwardingAccess
)
58 ACLFilledChecklist
checklist(Config
.accessList
.collapsedForwardingAccess
, nullptr, nullptr);
59 fillChecklist(checklist
);
60 return checklist
.fastCheck().allowed();
64 StoreClient::startCollapsingOn(const StoreEntry
&e
, const bool doingRevalidation
) const
66 if (!e
.hittingRequiresCollapsing())
67 return false; // collapsing is impossible due to the entry state
69 if (!onCollapsingPath())
70 return false; // collapsing is impossible due to Squid configuration
72 /* collapsing is possible; the caller must collapse */
74 if (const auto tags
= loggingTags()) {
75 if (doingRevalidation
)
76 tags
->collapsingHistory
.revalidationCollapses
++;
78 tags
->collapsingHistory
.otherCollapses
++;
81 debugs(85, 5, e
<< " doingRevalidation=" << doingRevalidation
);
88 store_client::getType() const
93 #if STORE_CLIENT_LIST_DEBUG
95 storeClientListSearch(const MemObject
* mem
, void *data
)
98 store_client
*sc
= NULL
;
100 for (node
= mem
->clients
.head
; node
; node
= node
->next
) {
103 if (sc
->owner
== data
)
111 storeClientIsThisAClient(store_client
* sc
, void *someClient
)
113 return sc
->owner
== someClient
;
117 #include "HttpRequest.h"
119 /* add client with fd to client list */
121 storeClientListAdd(StoreEntry
* e
, void *data
)
123 MemObject
*mem
= e
->mem_obj
;
126 #if STORE_CLIENT_LIST_DEBUG
128 if (storeClientListSearch(mem
, data
) != NULL
)
135 sc
= new store_client (e
);
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
145 store_client::callback(const ssize_t outcome
)
148 return noteCopiedBytes(outcome
);
156 /// finishCallback() wrapper; TODO: Add NullaryMemFunT for non-jobs.
158 store_client::FinishCallback(store_client
* const sc
)
160 sc
->finishCallback();
163 /// finishes a copy()-STCB sequence by synchronously calling STCB
165 store_client::finishCallback()
167 Assure(_callback
.callback_handler
);
168 Assure(_callback
.notifier
);
170 // callers are not ready to handle a content+error combination
171 Assure(object_ok
|| !copiedSize
);
173 StoreIOBuffer
result(copiedSize
, copyInto
.offset
, copyInto
.data
);
174 result
.flags
.error
= object_ok
? 0 : 1;
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;
183 if (cbdataReferenceValid(cbdata
))
184 temphandler(cbdata
, result
);
186 cbdataReferenceDone(cbdata
);
189 /// schedules asynchronous STCB call to relay a successful disk or memory read
190 /// \param bytesCopied the number of response bytes copied into copyInto
192 store_client::noteCopiedBytes(const size_t bytesCopied
)
194 debugs(90, 5, bytesCopied
);
195 Assure(bytesCopied
> 0);
197 copiedSize
= bytesCopied
;
202 store_client::noteEof()
204 debugs(90, 5, copiedSize
);
209 store_client::store_client(StoreEntry
*e
) :
211 #if STORE_CLIENT_LIST_DEBUG
212 owner(cbdataReference(data
)),
215 type(e
->storeClientType()),
220 entry
->lock("store_client");
222 flags
.disk_io_pending
= false;
223 flags
.store_copying
= false;
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());
233 store_client::~store_client()
236 entry
->unlock("store_client");
239 /* copy bytes requested by the client */
241 storeClientCopy(store_client
* sc
,
243 StoreIOBuffer copyInto
,
247 assert (sc
!= nullptr);
248 sc
->copy(e
, copyInto
,callback
,data
);
252 store_client::copy(StoreEntry
* anEntry
,
253 StoreIOBuffer copyRequest
,
257 assert (anEntry
== entry
);
258 assert (callback_fn
);
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 " <<
266 #if STORE_CLIENT_LIST_DEBUG
268 assert(this == storeClientListSearch(entry
->mem_obj
, data
));
271 assert(!_callback
.pending());
272 #if ONLYCONTIGUOUSREQUESTS
274 assert(cmp_offset
== copyRequest
.offset
);
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
;
283 static bool copying (false);
286 /* we might be blocking comm reads due to readahead limits
287 * now we have a new offset, trigger those reads...
289 entry
->mem_obj
->kickReads();
292 anEntry
->lock("store_client::copy"); // see deletion note below
294 storeClientCopy2(entry
, this);
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!
300 anEntry
->kickProducer();
302 anEntry
->unlock("store_client::copy");
304 // Add no code here. This object may no longer exist.
307 /// Whether there is (or will be) more entry data for us.
309 store_client::moreToSend() const
311 if (entry
->store_status
== STORE_PENDING
)
312 return true; // there may be more coming
314 /* STORE_OK, including aborted entries: no more data is coming */
316 const int64_t len
= entry
->objectLen();
318 // If we do not know the entry length, then we have to open the swap file.
319 const bool canSwapIn
= entry
->hasDisk();
323 if (copyInto
.offset
>= len
)
324 return false; // sent everything there is
327 return true; // if we lack prefix, we can swap it in
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
;
333 mem
->inmem_lo
<= copyInto
.offset
&& copyInto
.offset
< mem
->endOffset();
337 storeClientCopy2(StoreEntry
* e
, store_client
* sc
)
339 /* reentrancy not allowed - note this could lead to
340 * dropped notifications about response data availability
343 if (sc
->flags
.store_copying
) {
344 debugs(90, 3, "prevented recursive copying for " << *e
);
348 debugs(90, 3, "storeClientCopy2: " << e
->getMD5Text());
349 assert(sc
->_callback
.pending());
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.
361 store_client::doCopy(StoreEntry
*anEntry
)
363 Assure(_callback
.pending());
364 Assure(!flags
.disk_io_pending
);
365 Assure(!flags
.store_copying
);
367 assert (anEntry
== entry
);
368 flags
.store_copying
= true;
369 MemObject
*mem
= entry
->mem_obj
;
371 debugs(33, 5, "store_client::doCopy: co: " <<
372 copyInto
.offset
<< ", hi: " <<
376 /* There is no more to send! */
377 debugs(33, 3, "There is no more to send!");
379 flags
.store_copying
= false;
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;
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
402 if (STORE_DISK_CLIENT
== getType() && swapin_sio
== nullptr) {
409 /// opens the swapin "file" if possible; otherwise, fail()s and returns false
411 store_client::startSwapin()
413 debugs(90, 3, "store_client::doCopy: Need to open swap in file");
414 /* gotta open the swapin file */
416 if (storeTooManyDiskFilesOpen()) {
417 /* yuck -- this causes a TCP_SWAPFAIL_MISS on the client side */
419 flags
.store_copying
= false;
421 } else if (!flags
.disk_io_pending
) {
422 /* Don't set store_io_pending here */
423 storeSwapInStart(this);
425 if (swapin_sio
== nullptr) {
427 flags
.store_copying
= false;
433 debugs(90, DBG_IMPORTANT
, "WARNING: Averted multiple fd operation (1)");
434 flags
.store_copying
= false;
440 store_client::noteSwapInDone(const bool error
)
442 Assure(_callback
.pending());
450 store_client::scheduleRead()
452 MemObject
*mem
= entry
->mem_obj
;
454 if (copyInto
.offset
>= mem
->inmem_lo
&& copyInto
.offset
< mem
->endOffset())
461 store_client::scheduleDiskRead()
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
);
473 assert(!flags
.disk_io_pending
);
475 debugs(90, 3, "reading " << *entry
<< " from disk");
479 flags
.store_copying
= false;
483 store_client::scheduleMemRead()
485 /* What the client wants is in memory */
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
490 flags
.store_copying
= false;
494 store_client::fileRead()
496 MemObject
*mem
= entry
->mem_obj
;
498 assert(_callback
.pending());
499 assert(!flags
.disk_io_pending
);
500 flags
.disk_io_pending
= true;
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
);
506 storeRead(swapin_sio
,
509 copyInto
.offset
+ mem
->swap_hdr_sz
,
510 mem
->swap_hdr_sz
== 0 ? storeClientReadHeader
511 : storeClientReadBody
,
516 store_client::readBody(const char *, ssize_t len
)
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
<< "");
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");
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
541 if (copyInto
.offset
== entry
->mem_obj
->endOffset()) {
542 entry
->mem_obj
->write(StoreIOBuffer(len
, copyInto
.offset
, copyInto
.data
));
553 debugs(90, 3, (object_ok
? "once" : "again"));
555 return; // we failed earlier; nothing to do now
562 /// if necessary and possible, informs the Store reader about copy() result
564 store_client::noteNews()
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,
573 if (!_callback
.callback_handler
) {
574 debugs(90, 5, "client lost interest");
578 if (_callback
.notifier
) {
579 debugs(90, 5, "earlier news is being delivered by " << _callback
.notifier
);
583 _callback
.notifier
= asyncCall(90, 4, "store_client::FinishCallback", cbdataDialer(store_client::FinishCallback
, this));
584 ScheduleCallHere(_callback
.notifier
);
586 Assure(!_callback
.pending());
590 storeClientReadHeader(void *data
, const char *buf
, ssize_t len
, StoreIOState::Pointer
)
592 store_client
*sc
= (store_client
*)data
;
593 sc
->readHeader(buf
, len
);
597 storeClientReadBody(void *data
, const char *buf
, ssize_t len
, StoreIOState::Pointer
)
599 store_client
*sc
= (store_client
*)data
;
600 sc
->readBody(buf
, len
);
604 store_client::readHeader(char const *buf
, ssize_t len
)
606 MemObject
*const mem
= entry
->mem_obj
;
608 assert(flags
.disk_io_pending
);
609 flags
.disk_io_pending
= false;
610 assert(_callback
.pending());
612 // abort if we fail()'d earlier
620 Store::UnpackHitSwapMeta(buf
, len
, *entry
);
622 debugs(90, DBG_IMPORTANT
, "ERROR: Failed to unpack Store entry metadata: " << CurrentException
);
628 * If our last read got some data the client wants, then give
629 * it to them, otherwise schedule another read.
631 size_t body_sz
= len
- mem
->swap_hdr_sz
;
633 if (copyInto
.offset
< static_cast<int64_t>(body_sz
)) {
635 * we have (part of) what they want
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
);
641 readBody(copyInto
.data
, copy_sz
);
647 * we don't have what the client wants, but at least we now
648 * know the swap header size.
654 * This routine hasn't been optimised to take advantage of the
658 storeUnregister(store_client
* sc
, StoreEntry
* e
, void *data
)
660 MemObject
*mem
= e
->mem_obj
;
661 #if STORE_CLIENT_LIST_DEBUG
662 assert(sc
== storeClientListSearch(e
->mem_obj
, data
));
670 debugs(90, 3, "storeUnregister: called for '" << e
->getMD5Text() << "'");
673 debugs(90, 3, "storeUnregister: No matching client for '" << e
->getMD5Text() << "'");
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() << "'");
682 dlinkDelete(&sc
->node
, &mem
->clients
);
685 const auto swapoutFinished
= e
->swappedOut() || e
->swapoutFailed();
686 if (e
->store_status
== STORE_OK
&& !swapoutFinished
)
689 if (sc
->swapin_sio
!= nullptr) {
690 storeClose(sc
->swapin_sio
, StoreIOState::readerDone
);
691 sc
->swapin_sio
= nullptr;
692 ++statCounter
.swap
.ins
;
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");
703 #if STORE_CLIENT_LIST_DEBUG
704 cbdataReferenceDone(sc
->owner
);
708 // We must lock to safely dereference e below, after deleting sc and after
709 // calling CheckQuickAbortIsReasonable().
710 e
->lock("storeUnregister");
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.
716 if (CheckQuickAbortIsReasonable(e
))
725 e
->unlock("storeUnregister");
729 /* Call handlers waiting for data to be appended to E. */
731 StoreEntry::invokeHandlers()
733 if (EBIT_TEST(flags
, DELAY_SENDING
)) {
734 debugs(90, 3, "DELAY_SENDING is on, exiting " << *this);
737 if (EBIT_TEST(flags
, ENTRY_FWD_HDR_WAIT
)) {
738 debugs(90, 3, "ENTRY_FWD_HDR_WAIT is on, exiting " << *this);
742 /* Commit what we can to disk, if appropriate */
746 dlink_node
*nx
= nullptr;
749 debugs(90, 3, mem_obj
->nclients
<< " clients; " << *this << ' ' << getMD5Text());
750 /* walk the entire list looking for valid callbacks */
752 const auto savedContext
= CodeContext::Current();
753 for (node
= mem_obj
->clients
.head
; node
; node
= nx
) {
754 sc
= (store_client
*)node
->data
;
758 if (!sc
->_callback
.pending())
761 if (sc
->flags
.disk_io_pending
)
764 if (sc
->flags
.store_copying
)
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.
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
);
778 CodeContext::Reset(savedContext
);
781 // Does not account for remote readers/clients.
783 storePendingNClients(const StoreEntry
* e
)
785 MemObject
*mem
= e
->mem_obj
;
786 int npend
= nullptr == mem
? 0 : mem
->nclients
;
787 debugs(90, 3, "storePendingNClients: returning " << npend
);
791 /* return true if the request should be aborted */
793 CheckQuickAbortIsReasonable(StoreEntry
* entry
)
796 debugs(90, 3, "entry=" << *entry
);
798 if (storePendingNClients(entry
) > 0) {
799 debugs(90, 3, "quick-abort? NO storePendingNClients() > 0");
803 if (Store::Root().transientReaders(*entry
)) {
804 debugs(90, 3, "quick-abort? NO still have one or more transient readers");
808 if (entry
->store_status
!= STORE_PENDING
) {
809 debugs(90, 3, "quick-abort? NO store_status != STORE_PENDING");
813 if (EBIT_TEST(entry
->flags
, ENTRY_SPECIAL
)) {
814 debugs(90, 3, "quick-abort? NO ENTRY_SPECIAL");
819 debugs(90, 3, "quick-abort? YES avoid heavy optional work during shutdown");
823 MemObject
* const mem
= entry
->mem_obj
;
825 debugs(90, 3, "mem=" << mem
);
827 if (mem
->request
&& !mem
->request
->flags
.cachable
) {
828 debugs(90, 3, "quick-abort? YES !mem->request->flags.cachable");
832 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
)) {
833 debugs(90, 3, "quick-abort? YES KEY_PRIVATE");
837 const auto &reply
= mem
->baseReply();
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");
845 if (Config
.quickAbort
.min
< 0) {
846 debugs(90, 3, "quick-abort? NO disabled");
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");
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");
862 const auto expectlen
= reply
.hdr_sz
+ reply
.content_length
;
864 int64_t curlen
= mem
->endOffset();
866 if (curlen
> expectlen
) {
867 debugs(90, 3, "quick-abort? YES bad content length (" << curlen
<< " of " << expectlen
<< " bytes received)");
871 if ((expectlen
- curlen
) < (Config
.quickAbort
.min
<< 10)) {
872 debugs(90, 3, "quick-abort? NO only a little more object left to receive");
876 if ((expectlen
- curlen
) > (Config
.quickAbort
.max
<< 10)) {
877 debugs(90, 3, "quick-abort? YES too much left to go");
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");
887 if ((curlen
/ (expectlen
/ 100)) > (Config
.quickAbort
.pct
)) {
888 debugs(90, 3, "quick-abort? NO past point of no return");
892 debugs(90, 3, "quick-abort? YES default");
897 store_client::dumpStats(MemBuf
* output
, int clientNumber
) const
899 if (_callback
.pending())
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);
907 if (flags
.disk_io_pending
)
908 output
->append(" disk_io_pending", 16);
910 if (flags
.store_copying
)
911 output
->append(" store_copying", 14);
913 if (_callback
.notifier
)
914 output
->append(" notifying", 10);
916 output
->append("\n",1);
920 store_client::Callback::pending() const
922 return callback_handler
&& !notifier
;
925 store_client::Callback::Callback(STCB
*function
, void *data
):
926 callback_handler(function
),
928 codeContext(CodeContext::Current())
934 store_client::bytesWanted() const
936 // TODO: To avoid using stale copyInto, return zero if !_callback.pending()?
937 return delayId
.bytesWanted(0, copyInto
.length
);
941 store_client::setDelayId(DelayId delay_id
)