blob: 6722bc36476cc0567a0598f97ebe8c5d073c9240 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_STORAGE_BLOBFS_BLOB_CACHE_H_
#define SRC_STORAGE_BLOBFS_BLOB_CACHE_H_
#ifndef __Fuchsia__
#error Fuchsia-only Header
#endif
#include <lib/fit/function.h>
#include <fbl/condition_variable.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/mutex.h>
#include <fbl/ref_ptr.h>
#include "src/lib/digest/digest.h"
#include "src/lib/storage/vfs/cpp/vnode.h"
#include "src/storage/blobfs/cache_node.h"
#include "src/storage/blobfs/cache_policy.h"
namespace blobfs {
using digest::Digest;
// BlobCache contains a collection of weak pointers to vnodes.
//
// This cache also helps manage the lifecycle of these vnodes, controlling what is cached when there
// are no more external references.
//
// Internally, the BlobCache contains a "live set" and "closed set" of vnodes. The "live set"
// contains all Vnodes with a strong reference. The "closed set" contains references to Vnodes which
// are not used, but which exist on-disk. These Vnodes may be stored in a "low-memory" state until
// they are requested.
//
// This class is thread-safe.
class BlobCache {
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(BlobCache);
BlobCache();
~BlobCache();
// Empties the cache, evicting all open nodes and deleting all closed nodes.
void Reset();
// Sets the internal cache policy dealing with blob eviction.
//
// Refer to the declaration of |CachePolicy| for more information.
void SetCachePolicy(CachePolicy policy) { cache_policy_ = policy; }
// Iterates over all non-evicted cached nodes with strong references, invoking |callback| on each
// one.
//
// If a node is inserted into the "live set" via a concurrent call to |Add|, or evicted with a
// concurrent call to |Evict|, it is undefined if that node will be returned.
//
// Returns the first error encountered, or ZX_OK once finished. |callback| respects flow control
// status codes (i.e. ZX_ERR_STOP, ZX_ERR_NEXT).
using NextNodeCallback = fit::function<zx_status_t(fbl::RefPtr<CacheNode>)>;
zx_status_t ForAllOpenNodes(NextNodeCallback callback);
// Searches for a blob by |digest|.
//
// If a readable blob with the same name exists, it is (optionally) placed in |out|. If no such
// blob is found, ZX_ERR_NOT_FOUND is returned.
//
// |out| may be null. The same error code will be returned as if it was a valid pointer. If |out|
// is not null, then the returned-by-strong-reference Vnode will exist in the "live set".
zx_status_t Lookup(const Digest& digest, fbl::RefPtr<CacheNode>* out) __WARN_UNUSED_RESULT;
// Adds a blob to the "live set" of the cache. If |vnode->ShouldCache()| is true, then this node
// will remain discoverable using |Lookup()|, even if no strong references remain.
//
// Returns ZX_ERR_ALREADY_EXISTS if this blob could not be added because a node with the same
// key already exists in the cache.
zx_status_t Add(const fbl::RefPtr<CacheNode>& vnode) __WARN_UNUSED_RESULT;
// Deletes a blob from the cache. When the last strong reference is removed, it is put into a
// low-memory state, but not placed into the "closed set" of the cache. Future calls to "Lookup"
// will not be able to observe this node.
//
// Returns ZX_OK if the node was evicted from the cache.
// Returns ZX_ERR_NOT_FOUND if the node was not in the cache.
zx_status_t Evict(const fbl::RefPtr<CacheNode>& vnode) __WARN_UNUSED_RESULT;
private:
// Resurrects a Vnode with no strong references, and relocate it from the "live set" to the
// "closed set".
//
// Precondition: The blob must have no strong references.
// This function is currently only safe to call from CacheNode::fbl_recycle.
void Downgrade(CacheNode* vn);
friend class CacheNode;
// Identical to |Evict|, but utilizing a raw pointer.
//
// This function is only safe to call from:
// - |Evict|, where the strong reference guarantees that the node will exist in the |open_hash_|
// or not at all, or
// - |fbl_recycle|, where the refcount of zero will prevent other nodes from concurrently
// acquiring a reference. In this case, an argument is passed, identifying that other nodes
// observing the |open_hash_| via lookup should be signalled if this node is removed.
zx_status_t EvictUnsafe(CacheNode* vnode, bool from_recycle = false);
// Returns a strong reference to a node, if it exists. May relocate the node from the
// |closed_hash_| to the |open_hash_| if no strong references actively exist. |out| must not be
// nullptr.
//
// Returns ZX_OK if the node is found and returned.
// Returns ZX_ERR_NOT_FOUND if the node doesn't exist in the cache.
zx_status_t LookupLocked(const digest::Digest& key, fbl::RefPtr<CacheNode>* out)
__TA_REQUIRES(hash_lock_);
// Upgrades a Vnode which exists in the |closed_hash_| into |open_hash_|, and acquire the strong
// reference the Vnode which was leaked by |Downgrade()|, if it exists.
//
// Precondition: The Vnode must not exist in |open_hash_|.
fbl::RefPtr<CacheNode> UpgradeLocked(const digest::Digest& key) __TA_REQUIRES(hash_lock_);
// Resets the cache by deleting all members |closed_hash_|.
void ResetLocked() __TA_REQUIRES(hash_lock_);
// We need to define this structure to allow the CacheNodes to be indexable by a key which is
// larger than a primitive type: the keys are 'digest::kSha256Length' bytes long.
struct MerkleRootTraits {
static const digest::Digest& GetKey(const CacheNode& obj) { return obj.digest(); }
static bool LessThan(const digest::Digest& d1, const digest::Digest& d2) { return d1 < d2; }
static bool EqualTo(const digest::Digest& d1, const digest::Digest& d2) { return d1 == d2; }
};
// CacheNodes exist in the WAVLTree as long as one or more reference exists; when the Vnode is
// deleted, it is immediately removed from the WAVL tree.
using WAVLTreeByMerkle = fbl::WAVLTree<const digest::Digest&, CacheNode*, MerkleRootTraits>;
CachePolicy cache_policy_ = CachePolicy::EvictImmediately;
fbl::Mutex hash_lock_{};
// All 'in use' blobs.
WAVLTreeByMerkle open_hash_ __TA_GUARDED(hash_lock_){};
// All 'closed' blobs.
WAVLTreeByMerkle closed_hash_ __TA_GUARDED(hash_lock_){};
// A condition variable which is signalled whenever a CacheNode has been removed from the
// |open_hash_|. When a CacheNode runs out of references, it exists in the |open_hash_| with no
// strong references for a short period of time before being removed and either resurrected or
// destroyed. This means, however, that a concurrent caller trying to |Lookup()| that node may see
// it, but be unable to acquire it. This variable lets those callers wait until SOME node has been
// removed from the |open_hash_|, at which point their |Lookup()| may have a different result.
fbl::ConditionVariable release_cvar_;
};
} // namespace blobfs
#endif // SRC_STORAGE_BLOBFS_BLOB_CACHE_H_