/*
- * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
static DeferredRead::DeferrableRead DeferReader;
bool checkDeferRead(int fd) const;
- virtual const char *getMD5Text() const;
+ const char *getMD5Text() const;
StoreEntry();
virtual ~StoreEntry();
- virtual HttpReply const *getReply() const;
- virtual void write (StoreIOBuffer);
+ MemObject &mem() { assert(mem_obj); return *mem_obj; }
+ const MemObject &mem() const { assert(mem_obj); return *mem_obj; }
+
+ /// \retval * the address of freshest reply (if mem_obj exists)
+ /// \retval nullptr when mem_obj does not exist
+ /// \see MemObject::freshestReply()
+ const HttpReply *hasFreshestReply() const { return mem_obj ? &mem_obj->freshestReply() : nullptr; }
- /** Check if the Store entry is emtpty
+ void write(StoreIOBuffer);
+
+ /** Check if the Store entry is empty
* \retval true Store contains 0 bytes of data.
* \retval false Store contains 1 or more bytes of data.
* \retval false Store contains negative content !!!!!!
*/
- virtual bool isEmpty() const {
- assert (mem_obj);
- return mem_obj->endOffset() == 0;
- }
- virtual bool isAccepting() const;
- virtual size_t bytesWanted(Range<size_t> const aRange, bool ignoreDelayPool = false) const;
+ bool isEmpty() const { return mem().endOffset() == 0; }
+ bool isAccepting() const;
+ size_t bytesWanted(Range<size_t> const aRange, bool ignoreDelayPool = false) const;
/// flags [truncated or too big] entry with ENTRY_BAD_LENGTH and releases it
void lengthWentBad(const char *reason);
- virtual void complete();
- virtual store_client_t storeClientType() const;
- virtual char const *getSerialisedMetaData();
+ void complete();
+ store_client_t storeClientType() const;
+ /// \returns a malloc()ed buffer containing a length-long packed swap header
+ const char *getSerialisedMetaData(size_t &length) const;
/// Store a prepared error response. MemObject locks the reply object.
void storeErrorResponse(HttpReply *reply);
- void replaceHttpReply(HttpReply *, bool andStartWriting = true);
+ void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting = true);
void startWriting(); ///< pack and write reply headers and, maybe, body
/// whether we may start writing to disk (now or in the future)
- virtual bool mayStartSwapOut();
- virtual void trimMemory(const bool preserveSwappable);
+ bool mayStartSwapOut();
+ void trimMemory(const bool preserveSwappable);
// called when a decision to cache in memory has been made
void memOutDecision(const bool willCacheInRam);
void swapOutDecision(const MemObject::SwapOut::Decision &decision);
void abort();
- void makePublic(const KeyScope keyScope = ksDefault);
+ bool makePublic(const KeyScope keyScope = ksDefault);
void makePrivate(const bool shareable);
/// A low-level method just resetting "private key" flags.
/// To avoid key inconsistency please use forcePublicKey()
/// or similar instead.
void clearPrivate();
- void setPublicKey(const KeyScope keyScope = ksDefault);
+ bool setPublicKey(const KeyScope keyScope = ksDefault);
/// Resets existing public key to a public key with default scope,
/// releasing the old default-scope entry (if any).
/// Does nothing if the existing public key already has default scope.
void clearPublicKeyScope();
- void setPrivateKey(const bool shareable);
+
+ /// \returns public key (if the entry has it) or nil (otherwise)
+ const cache_key *publicKey() const {
+ return (!EBIT_TEST(flags, KEY_PRIVATE)) ?
+ reinterpret_cast<const cache_key*>(key): // may be nil
+ nullptr;
+ }
+
+ /// Either fills this entry with private key or changes the existing key
+ /// from public to private.
+ /// \param permanent whether this entry should be private forever.
+ void setPrivateKey(const bool shareable, const bool permanent);
+
void expireNow();
+ /// Makes the StoreEntry private and marks the corresponding entry
+ /// for eventual removal from the Store.
void releaseRequest(const bool shareable = false);
void negativeCache();
- void cacheNegatively(); /** \todo argh, why both? */
+ bool cacheNegatively(); /** \todo argh, why both? */
void invokeHandlers();
- void purgeMem();
void cacheInMemory(); ///< start or continue storing in memory cache
void swapOut();
/// whether we are in the process of writing this entry to disk
bool swappingOut() const { return swap_status == SWAPOUT_WRITING; }
+ /// whether the entire entry is now on disk (possibly marked for deletion)
+ bool swappedOut() const { return swap_status == SWAPOUT_DONE; }
+ /// whether we failed to write this entry to disk
+ bool swapoutFailed() const { return swap_status == SWAPOUT_FAILED; }
void swapOutFileClose(int how);
const char *url() const;
/// Satisfies cachability requirements shared among disk and RAM caches.
/// TODO: Rename and make private so only those two methods can call this.
bool checkCachable();
int checkNegativeHit() const;
- int locked() const;
+ int locked() const { return lock_count; }
int validToSend() const;
bool memoryCachable(); ///< checkCachable() and can be cached in memory
/// whether this entry has an ETag; if yes, puts ETag value into parameter
bool hasEtag(ETag &etag) const;
+ /// Updates easily-accessible non-Store-specific parts of the entry.
+ /// Use Controller::updateOnNotModified() instead of this helper.
+ /// \returns whether anything was actually updated
+ bool updateOnNotModified(const StoreEntry &e304);
+
/// the disk this entry is [being] cached on; asserts for entries w/o a disk
Store::Disk &disk() const;
+ /// whether one of this StoreEntry owners has locked the corresponding
+ /// disk entry (at the specified disk entry coordinates, if any)
+ bool hasDisk(const sdirno dirn = -1, const sfileno filen = -1) const;
+ /// Makes hasDisk(dirn, filn) true. The caller should have locked
+ /// the corresponding disk store entry for reading or writing.
+ void attachToDisk(const sdirno, const sfileno, const swap_status_t);
+ /// Makes hasDisk() false. The caller should have unlocked
+ /// the corresponding disk store entry.
+ void detachFromDisk();
+
+ /// whether there is a corresponding locked transients table entry
+ bool hasTransients() const { return mem_obj && mem_obj->xitTable.index >= 0; }
+ /// whether there is a corresponding locked shared memory table entry
+ bool hasMemStore() const { return mem_obj && mem_obj->memCache.index >= 0; }
+
+ /// whether this entry can feed collapsed requests and only them
+ bool hittingRequiresCollapsing() const { return EBIT_TEST(flags, ENTRY_REQUIRES_COLLAPSING); }
+
+ /// allow or forbid collapsed requests feeding
+ void setCollapsingRequirement(const bool required);
MemObject *mem_obj;
RemovalPolicyNode repl;
static void getPublicByRequest(StoreClient * aClient, HttpRequest * request);
static void getPublic(StoreClient * aClient, const char *uri, const HttpRequestMethod& method);
- virtual bool isNull() {
- return false;
- };
-
void *operator new(size_t byteCount);
void operator delete(void *address);
- void setReleaseFlag();
#if USE_SQUID_ESI
ESIElement::Pointer cachedESITree;
#endif
- virtual int64_t objectLen() const;
- virtual int64_t contentLen() const;
+ int64_t objectLen() const { return mem().object_sz; }
+ int64_t contentLen() const { return objectLen() - mem().baseReply().hdr_sz; }
/// claim shared ownership of this entry (for use in a given context)
/// matching lock() and unlock() contexts eases leak triage but is optional
/// update last reference timestamp and related Store metadata
void touch();
- virtual void release(const bool shareable = false);
+ /// One of the three methods to get rid of an unlocked StoreEntry object.
+ /// Removes all unlocked (and marks for eventual removal all locked) Store
+ /// entries, including attached and unattached entries that have our key.
+ /// Also destroys us if we are unlocked or makes us private otherwise.
+ void release(const bool shareable = false);
+
+ /// One of the three methods to get rid of an unlocked StoreEntry object.
+ /// May destroy this object if it is unlocked; does nothing otherwise.
+ /// Unlike release(), may not trigger eviction of underlying store entries,
+ /// but, unlike destroyStoreEntry(), does honor an earlier release request.
+ void abandon(const char *context) { if (!locked()) doAbandon(context); }
/// May the caller commit to treating this [previously locked]
/// entry as a cache hit?
virtual void flush();
protected:
+ typedef Store::EntryGuard EntryGuard;
+
void transientsAbandonmentCheck();
+ /// does nothing except throwing if disk-associated data members are inconsistent
+ void checkDisk() const;
private:
+ void doAbandon(const char *context);
bool checkTooBig() const;
void forcePublicKey(const cache_key *newkey);
- void adjustVary();
+ StoreEntry *adjustVary();
const cache_key *calcPublicKey(const KeyScope keyScope);
static MemAllocator *pool;
std::ostream &operator <<(std::ostream &os, const StoreEntry &e);
/// \ingroup StoreAPI
-class NullStoreEntry:public StoreEntry
-{
+typedef void (*STOREGETCLIENT) (StoreEntry *, void *cbdata);
+namespace Store {
+
+/// a smart pointer similar to std::unique_ptr<> that automatically
+/// release()s and unlock()s the guarded Entry on stack-unwinding failures
+class EntryGuard {
public:
- static NullStoreEntry *getInstance();
- bool isNull() {
- return true;
+ /// \param entry either nil or a locked Entry to manage
+ /// \param context default unlock() message
+ EntryGuard(Entry *entry, const char *context):
+ entry_(entry), context_(context) {
+ assert(!entry_ || entry_->locked());
}
- const char *getMD5Text() const;
- HttpReply const *getReply() const { return NULL; }
- void write (StoreIOBuffer) {}
+ ~EntryGuard() {
+ if (entry_) {
+ // something went wrong -- the caller did not unlockAndReset() us
+ onException();
+ }
+ }
- bool isEmpty () const {return true;}
+ EntryGuard(EntryGuard &&) = delete; // no copying or moving (for now)
- virtual size_t bytesWanted(Range<size_t> const aRange, bool) const { return aRange.end; }
+ /// like std::unique_ptr::get()
+ /// \returns nil or the guarded (locked) entry
+ Entry *get() {
+ return entry_;
+ }
- void operator delete(void *address);
- void complete() {}
+ /// like std::unique_ptr::reset()
+ /// stops guarding the entry
+ /// unlocks the entry (which may destroy it)
+ void unlockAndReset(const char *resetContext = nullptr) {
+ if (entry_) {
+ entry_->unlock(resetContext ? resetContext : context_);
+ entry_ = nullptr;
+ }
+ }
private:
- store_client_t storeClientType() const {return STORE_MEM_CLIENT;}
-
- char const *getSerialisedMetaData();
- virtual bool mayStartSwapOut() { return false; }
+ void onException() noexcept;
- void trimMemory(const bool) {}
-
- static NullStoreEntry _instance;
+ Entry *entry_; ///< the guarded Entry or nil
+ const char *context_; ///< default unlock() message
};
-/// \ingroup StoreAPI
-typedef void (*STOREGETCLIENT) (StoreEntry *, void *cbdata);
-
-namespace Store {
void Stats(StoreEntry *output);
void Maintain(void *unused);
-};
+}; // namespace Store
/// \ingroup StoreAPI
size_t storeEntryInUse();
/// \ingroup StoreAPI
/// Creates a new StoreEntry with mem_obj and sets initial flags/states.
-StoreEntry *storeCreatePureEntry(const char *storeId, const char *logUrl, const RequestFlags &, const HttpRequestMethod&);
+StoreEntry *storeCreatePureEntry(const char *storeId, const char *logUrl, const HttpRequestMethod&);
/// \ingroup StoreAPI
void storeInit(void);
/// \ingroup StoreAPI
void storeReplAdd(const char *, REMOVALPOLICYCREATE *);
-/// \ingroup StoreAPI
+/// One of the three methods to get rid of an unlocked StoreEntry object.
+/// This low-level method ignores lock()ing and release() promises. It never
+/// leaves the entry in the local store_table.
+/// TODO: Hide by moving its functionality into the StoreEntry destructor.
extern FREE destroyStoreEntry;
/// \ingroup StoreAPI