| // Copyright 2017 The Chromium 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 NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_ |
| #define NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_ |
| |
| #include <algorithm> |
| #include <list> |
| #include <memory> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "base/files/file.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/synchronization/lock.h" |
| #include "net/base/net_export.h" |
| #include "net/disk_cache/simple/simple_entry_format.h" |
| #include "starboard/types.h" |
| |
| namespace disk_cache { |
| |
| class SimpleSynchronousEntry; |
| |
| // This keeps track of all the files SimpleCache has open, across all the |
| // backend instancess, in order to prevent us from running out of file |
| // descriptors. |
| // TODO(morlovich): Actually implement closing and re-opening of things if we |
| // run out. |
| // |
| // This class is thread-safe. |
| class NET_EXPORT_PRIVATE SimpleFileTracker { |
| public: |
| enum class SubFile { FILE_0, FILE_1, FILE_SPARSE }; |
| |
| // A RAII helper that guards access to a file grabbed for use from |
| // SimpleFileTracker::Acquire(). While it's still alive, if IsOK() is true, |
| // then using the underlying base::File via get() or the -> operator will be |
| // safe. |
| // |
| // This class is movable but not copyable. It should only be used from a |
| // single logical sequence of execution, and should not outlive the |
| // corresponding SimpleSynchronousEntry. |
| class NET_EXPORT_PRIVATE FileHandle { |
| public: |
| FileHandle(); |
| FileHandle(FileHandle&& other); |
| ~FileHandle(); |
| FileHandle& operator=(FileHandle&& other); |
| base::File* operator->() const; |
| base::File* get() const; |
| // Returns true if this handle points to a valid file. This should normally |
| // be the first thing called on the object, after getting it from |
| // SimpleFileTracker::Acquire. |
| bool IsOK() const; |
| |
| private: |
| friend class SimpleFileTracker; |
| FileHandle(SimpleFileTracker* file_tracker, |
| const SimpleSynchronousEntry* entry, |
| SimpleFileTracker::SubFile subfile, |
| base::File* file); |
| |
| // All the pointer fields are nullptr in the default/moved away from form. |
| SimpleFileTracker* file_tracker_; |
| const SimpleSynchronousEntry* entry_; |
| SimpleFileTracker::SubFile subfile_; |
| base::File* file_; |
| DISALLOW_COPY_AND_ASSIGN(FileHandle); |
| }; |
| |
| struct EntryFileKey { |
| EntryFileKey() : entry_hash(0), doom_generation(0) {} |
| explicit EntryFileKey(uint64_t hash) |
| : entry_hash(hash), doom_generation(0) {} |
| |
| uint64_t entry_hash; |
| |
| // 0 means this a non-doomed, active entry, for its backend that will be |
| // checked on OpenEntry(key) where hash(key) = entry_hash. Other values of |
| // |doom_generation| are used to generate distinct file names for entries |
| // that have been Doom()ed, either by explicit API call by the client or |
| // internal operation (eviction, collisions, etc.) |
| uint64_t doom_generation; |
| }; |
| |
| // The default limit here is half of what's available on our target OS where |
| // Chrome has the lowest limit. |
| SimpleFileTracker(int file_limit = 512); |
| ~SimpleFileTracker(); |
| |
| // Established |file| as what's backing |subfile| for |owner|. This is |
| // intended to be called when SimpleSynchronousEntry first sets up the file to |
| // transfer its ownership to SimpleFileTracker. Any Register() call must be |
| // eventually followed by a corresponding Close() call before the |owner| is |
| // destroyed. |file->IsValid()| must be true. |
| void Register(const SimpleSynchronousEntry* owner, |
| SubFile subfile, |
| std::unique_ptr<base::File> file); |
| |
| // Lends out a file to SimpleSynchronousEntry for use. SimpleFileTracker |
| // will ensure that it doesn't close the file until the handle is destroyed. |
| // The caller should check .IsOK() on the returned value before using it, as |
| // it's possible that the file had to be closed and re-opened due to FD |
| // pressure, and that open may have failed. This should not be called twice |
| // with the exact same arguments until the handle returned from the previous |
| // such call is destroyed. |
| FileHandle Acquire(const SimpleSynchronousEntry* owner, SubFile subfile); |
| |
| // Tells SimpleFileTracker that SimpleSynchronousEntry will not be interested |
| // in the file further, so it can be closed and forgotten about. It's OK to |
| // call this while a handle to the file is alive, in which case the effect |
| // takes place after the handle is destroyed. |
| // If Close() has been called and the handle to the file is no longer alive, |
| // a new backing file can be established by calling Register() again. |
| void Close(const SimpleSynchronousEntry* owner, SubFile file); |
| |
| // Updates key->doom_generation to one not in use for the hash; it's the |
| // caller's responsibility to update file names accordingly, and to prevent |
| // others from stomping on things while this is going on. SimpleBackendImpl's |
| // entries_pending_doom_ is the mechanism for protecting this action from |
| // races. |
| void Doom(const SimpleSynchronousEntry* owner, EntryFileKey* key); |
| |
| // Returns true if there is no in-memory state around, e.g. everything got |
| // cleaned up. This is a test-only method since this object is expected to be |
| // shared between multiple threads, in which case its return value may be |
| // outdated the moment it's returned. |
| bool IsEmptyForTesting(); |
| |
| private: |
| struct TrackedFiles { |
| // We can potentially run through this state machine multiple times for |
| // FILE_1, as that's often missing, so SimpleSynchronousEntry can sometimes |
| // close and remove the file for an empty stream, then re-open it on actual |
| // data. |
| enum State { |
| TF_NO_REGISTRATION = 0, |
| TF_REGISTERED = 1, |
| TF_ACQUIRED = 2, |
| TF_ACQUIRED_PENDING_CLOSE = 3, |
| }; |
| |
| NET_EXPORT_PRIVATE TrackedFiles(); |
| NET_EXPORT_PRIVATE ~TrackedFiles(); |
| |
| // True if this isn't keeping track of anything any more. |
| bool Empty() const; |
| |
| // True if this has open files. Note that this is not the same as !Empty() |
| // as this may be false when an entry had its files temporarily closed, but |
| // is still relevant. |
| bool HasOpenFiles() const; |
| |
| // We use pointers to SimpleSynchronousEntry two ways: |
| // 1) As opaque keys. This is handy as it avoids having to compare paths in |
| // case multiple backends use the same key. Since we access the |
| // bookkeeping under |lock_| |
| // |
| // 2) To get info on the caller of our operation. |
| // Accessing |owner| from any other TrackedFiles would be unsafe (as it |
| // may be doing its own thing in a different thread). |
| const SimpleSynchronousEntry* owner; |
| EntryFileKey key; |
| |
| // Some of these may be nullptr, if they are not open. Non-null pointers |
| // to files that are not valid will not be stored here. |
| // Note that these are stored indirect since we hand out pointers to these, |
| // and we don't want those to become invalid if some other thread appends |
| // things here. |
| std::unique_ptr<base::File> files[kSimpleEntryTotalFileCount]; |
| |
| State state[kSimpleEntryTotalFileCount]; |
| std::list<TrackedFiles*>::iterator position_in_lru; |
| |
| // true if position_in_lru is valid. For entries where we closed everything, |
| // we try not to keep them in the LRU so that we don't have to constantly |
| // rescan them. |
| bool in_lru; |
| }; |
| |
| // Marks the file that was previously returned by Acquire as eligible for |
| // closing again. Called by ~FileHandle. |
| void Release(const SimpleSynchronousEntry* owner, SubFile subfile); |
| |
| // Precondition: entry for given |owner| must already be in tracked_files_ |
| TrackedFiles* Find(const SimpleSynchronousEntry* owner); |
| |
| // Handles state transition of closing file (when we are not deferring it), |
| // and moves the file out. Note that this may delete |*owners_files|. |
| std::unique_ptr<base::File> PrepareClose(TrackedFiles* owners_files, |
| int file_index); |
| |
| // If too many files are open, picks some to close, and moves them to |
| // |*files_to_close|, updating other state as appropriate. |
| void CloseFilesIfTooManyOpen( |
| std::vector<std::unique_ptr<base::File>>* files_to_close); |
| |
| // Tries to reopen given file, updating |*owners_files| if successful. |
| void ReopenFile(TrackedFiles* owners_files, SubFile subfile); |
| |
| // Makes sure the entry is marked as most recently used, adding it to LRU |
| // if needed. |
| void EnsureInFrontOfLRU(TrackedFiles* owners_files); |
| |
| base::Lock lock_; |
| std::unordered_map<uint64_t, std::vector<std::unique_ptr<TrackedFiles>>> |
| tracked_files_; |
| std::list<TrackedFiles*> lru_; |
| |
| int file_limit_; |
| |
| // How many actually open files we are using. |
| // Note that when a thread commits to closing a file, but hasn't actually |
| // executed the close yet, the file is no longer counted as open here, so this |
| // might be a little off. This should be OK as long as file_limit_ is set |
| // conservatively, considering SimpleCache's parallelism is bounded by a low |
| // number of threads, and getting it exact would require re-acquiring the |
| // lock after closing the file. |
| int open_files_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SimpleFileTracker); |
| }; |
| |
| } // namespace disk_cache |
| |
| #endif // NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_ |