| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SERVICES_NETWORK_PREFETCH_CACHE_H_ |
| #define SERVICES_NETWORK_PREFETCH_CACHE_H_ |
| |
| #include <map> |
| #include <set> |
| #include <utility> |
| |
| #include "base/component_export.h" |
| #include "base/containers/linked_list.h" |
| #include "base/containers/queue.h" |
| #include "base/containers/unique_ptr_adapters.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "net/base/network_isolation_key.h" |
| #include "url/gurl.h" |
| |
| namespace net { |
| class NetworkIsolationKey; |
| } // namespace net |
| |
| namespace network { |
| |
| class PrefetchURLLoaderClient; |
| struct ResourceRequest; |
| |
| // PrefetchCache implements caching of PrefetchURLLoaderClient objects for |
| // NetworkContexts. There is at most one created per NetworkContext. |
| class COMPONENT_EXPORT(NETWORK_SERVICE) PrefetchCache final { |
| public: |
| // The maximum age a prefetch is permitted to live for without being claimed |
| // by a render process. |
| // TODO(crbug.com/342445996): Allow this to be set dynamically by a feature |
| // param. |
| static constexpr auto kMaxAge = base::Minutes(5); |
| |
| // If several prefetches are created close together, it is wasteful to wake up |
| // once for each one, so permit prefetches that have reached |
| // `kMaxAge` - `kExpirySlack` to also be expired. |
| // TODO(crbug.com/342445996): Allow this to be set dynamically by a feature |
| // param. |
| static constexpr auto kExpirySlack = base::Seconds(1); |
| |
| PrefetchCache(); |
| |
| PrefetchCache(const PrefetchCache&) = delete; |
| PrefetchCache& operator=(const PrefetchCache&) = delete; |
| |
| ~PrefetchCache(); |
| |
| // Instantiates and inserts a new PrefetchURLLoaderClient. If a matching |
| // request (same NIK and URL) already exists in the cache, returns nullptr. |
| // The returned PrefetchURLLoaderClient is owned by this object. The returned |
| // pointer is safe to use synchronously as long as nothing calls Erase() on |
| // it, but it should not be used again after returning to the run loop as it |
| // might be deleted asynchronously. |
| PrefetchURLLoaderClient* Emplace(const ResourceRequest& request); |
| |
| // Finds a PrefetchURLLoaderClient matching `nik` and `url`. Returns nullptr |
| // if nothing is found. As with Emplace(), the returned pointer should only be |
| // used synchronously. |
| PrefetchURLLoaderClient* Lookup(const net::NetworkIsolationKey& nik, |
| const GURL& url); |
| |
| // Prevents `client` from being returned by future calls to Lookup() and |
| // permits a new request with the same key to be created by Emplace(). |
| // `client` must exist in the cache. |
| void Consume(PrefetchURLLoaderClient* client); |
| |
| // Removes `client` from the cache and deletes it. `client` must have been |
| // created by Emplace() and not already erased. |
| void Erase(PrefetchURLLoaderClient* client); |
| |
| // Erases `client` after `kEraseGraceTime` has expired. The purpose is to |
| // permit a new transaction from a renderer to reach the HttpCache code before |
| // this client is erased, so that it can take over the cache lock if possible, |
| // avoiding the entry being truncated. |
| // |
| // This is a temporary feature to maximise the chances of reusing the disk |
| // cache entry when the feature kNetworkContextPrefetchUseMatches is not |
| // enabled. |
| // |
| // TODO(crbug.com/342445996): Remove this method and associated code after |
| // kNetworkContextPrefetchUseMatches has been permanently enabled. |
| void DelayedErase(PrefetchURLLoaderClient* client); |
| |
| private: |
| // This is not a std::list because we want to be able to remove an item from |
| // the cache by pointer. |
| using ListType = base::LinkedList<PrefetchURLLoaderClient>; |
| |
| // The references in the KeyType point inside the PrefetchURLLoaderClient that |
| // is the value of the map, so they are guaranteed to be valid. |
| using KeyType = std::pair<const net::NetworkIsolationKey&, const GURL&>; |
| |
| // The value_type of the map is const to reduce the risk of accidentally |
| // changing it, since the key's references point into the value. |
| using MapType = std::map<KeyType, const raw_ptr<PrefetchURLLoaderClient>>; |
| |
| using ClientStorage = std::set<std::unique_ptr<PrefetchURLLoaderClient>, |
| base::UniquePtrComparator>; |
| |
| struct PendingErasure { |
| // The client is referred to by its key rather than a pointer, so that there |
| // is no dangling reference if something else erases the client first. |
| net::NetworkIsolationKey nik; |
| GURL url; |
| base::TimeTicks erase_time; |
| }; |
| |
| using EraseQueue = base::queue<PendingErasure>; |
| |
| // Deletes any expired cache entries and then restarts the timer if needed. |
| void OnTimer(); |
| |
| // Deletes anything in `delayed_erase_queue_` that is due for deletion, and |
| // then schedules another call if the queue is still non-empty. |
| void DoDelayedErases(); |
| |
| // Removes and deletes the oldest entry from the cache. |
| void EraseOldest(); |
| |
| // Removes an entry from the cache without deleting it. `client` must be in |
| // the cache. |
| void RemoveFromCache(PrefetchURLLoaderClient* client); |
| |
| // Removes `client` from `client_storage_`, deleting it. `client` must exist. |
| void EraseFromStorage(PrefetchURLLoaderClient* client); |
| |
| // Starts the timer to fire when the next cache entry will expire. `now` |
| // should be the current time. It is optional because some callers have it |
| // handy and some don't. |
| void StartTimer(base::TimeTicks now = base::TimeTicks::Now()); |
| |
| // Schedules a task to call DoDelayedErases() the next time something in |
| // `delayed_erase_queue_` needs to be erased. `now` should be the return value |
| // from a recent call to `base::TimeTicks::Now()`. |
| void SchedulePendingErases(base::TimeTicks now); |
| |
| // Performing a find() on `map_` is sufficiently messy that it's worth |
| // encapsulating in a separate method. |
| MapType::iterator FindInMap(const net::NetworkIsolationKey& nik, |
| const GURL& url); |
| |
| // Storage for all the PrefetchURLLoaderClients created by this object, |
| // regardless if Consume() has been called for them or not. `list_` and `map_` |
| // contain references into these objects, so must be destroyed first. |
| ClientStorage client_storage_; |
| |
| // The PrefetchURLLoaderClients are stored in a list to permit deletion and |
| // finding the oldest in O(1) time. `list_.head()` is the oldest item and |
| // `list_.tail()` is the newest. `list_` does not own the objects. They are |
| // owned by `client_storage_`. |
| ListType list_; |
| |
| // They are referenced from a map, permitting O(log N) lookup. |
| MapType map_; |
| |
| // Timer. If `list_` is non-empty, it is set to go off when the oldest item in |
| // `list_` will expire. |
| base::OneShotTimer expiry_timer_; |
| |
| // Queue for DelayedErase(). Entries will be deleted when their `erase_time` |
| // is reached. |
| EraseQueue delayed_erase_queue_; |
| |
| // Initialized from kNetworkContextPrefetchMaxLoaders feature flag. |
| const size_t max_size_; |
| |
| // Initialized from "erase_grace_time" parameter to "NetworkContextPrefetch" |
| // feature. |
| const base::TimeDelta erase_grace_time_; |
| |
| base::WeakPtrFactory<PrefetchCache> weak_factory_{this}; |
| }; |
| |
| } // namespace network |
| |
| #endif // SERVICES_NETWORK_PREFETCH_CACHE_H_ |