OOR-CORS: Introduce an entry limit to the CORS preflight cache

Since CORS preflight cache can live for a longer time in OOR-CORS mode,
we introduce an entry limit to the CORS preflight cache.
Also this patch adds a metric to count cache entries.

Bug: 736308
Change-Id: Icd0ed0664d4bc592e921a632126ba88d1425f50b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1573519
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Ilya Sherman <isherman@chromium.org>
Reviewed-by: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652458}
diff --git a/services/network/public/cpp/cors/preflight_cache.cc b/services/network/public/cpp/cors/preflight_cache.cc
index bf30102..57327ad 100644
--- a/services/network/public/cpp/cors/preflight_cache.cc
+++ b/services/network/public/cpp/cors/preflight_cache.cc
@@ -4,13 +4,26 @@
 
 #include "services/network/public/cpp/cors/preflight_cache.h"
 
+#include <iterator>
+
+#include "base/metrics/histogram_functions.h"
+#include "base/rand_util.h"
+#include "base/time/time.h"
 #include "url/gurl.h"
 
 namespace network {
 
 namespace cors {
 
-PreflightCache::PreflightCache() = default;
+namespace {
+constexpr size_t kMaxCacheEntries = 4096;
+}  // namespace
+
+PreflightCache::PreflightCache() {
+  timer_.Start(FROM_HERE, base::TimeDelta::FromHours(1), this,
+               &PreflightCache::ReportMetrics);
+}
+
 PreflightCache::~PreflightCache() = default;
 
 void PreflightCache::AppendEntry(
@@ -18,6 +31,12 @@
     const GURL& url,
     std::unique_ptr<PreflightResult> preflight_result) {
   DCHECK(preflight_result);
+
+  // Since one new entry is always added below, let's purge one cache entry
+  // if cache size is larger than kMaxCacheEntries - 1 so that the size to be
+  // kMaxCacheEntries at maximum.
+  MayPurge(kMaxCacheEntries - 1);
+
   cache_[origin][url.spec()] = std::move(preflight_result);
 }
 
@@ -58,12 +77,40 @@
 }
 
 size_t PreflightCache::CountEntriesForTesting() const {
+  return CountEntries();
+}
+
+void PreflightCache::MayPurgeForTesting(size_t max_entries) {
+  MayPurge(max_entries);
+}
+
+size_t PreflightCache::CountEntries() const {
   size_t entries = 0;
   for (auto const& cache_per_origin : cache_)
     entries += cache_per_origin.second.size();
   return entries;
 }
 
+void PreflightCache::MayPurge(size_t max_entries) {
+  if (CountEntries() <= max_entries)
+    return;
+  auto target_origin_cache = cache_.begin();
+  std::advance(target_origin_cache, base::RandInt(0, cache_.size() - 1));
+  if (target_origin_cache->second.size() == 1) {
+    cache_.erase(target_origin_cache);
+  } else {
+    auto target_cache_entry = target_origin_cache->second.begin();
+    std::advance(target_cache_entry,
+                 base::RandInt(0, target_origin_cache->second.size() - 1));
+    target_origin_cache->second.erase(target_cache_entry);
+  }
+}
+
+void PreflightCache::ReportMetrics() {
+  base::UmaHistogramCounts10000("Net.Cors.PreflightCacheEntries",
+                                CountEntries());
+}
+
 }  // namespace cors
 
 }  // namespace network
diff --git a/services/network/public/cpp/cors/preflight_cache.h b/services/network/public/cpp/cors/preflight_cache.h
index 58337b9..b77be4f9 100644
--- a/services/network/public/cpp/cors/preflight_cache.h
+++ b/services/network/public/cpp/cors/preflight_cache.h
@@ -11,6 +11,7 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/timer/timer.h"
 #include "net/http/http_request_headers.h"
 #include "services/network/public/cpp/cors/preflight_result.h"
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
@@ -53,13 +54,24 @@
   // Counts cached entries for testing.
   size_t CountEntriesForTesting() const;
 
+  // Purges one cache entry if number of entries is larger than |max_entries|
+  // for testing.
+  void MayPurgeForTesting(size_t max_entries);
+
  private:
+  size_t CountEntries() const;
+  void MayPurge(size_t max_entries);
+  void ReportMetrics();
+
   // A map for caching. The outer map takes an origin to find a per-origin
   // cache map, and the inner map takes an URL to find a cached entry.
   std::map<std::string /* origin */,
            std::map<std::string /* url */, std::unique_ptr<PreflightResult>>>
       cache_;
 
+  // RepeatingTimer to report metrics.
+  base::RepeatingTimer timer_;
+
   DISALLOW_COPY_AND_ASSIGN(PreflightCache);
 };
 
diff --git a/services/network/public/cpp/cors/preflight_cache_unittest.cc b/services/network/public/cpp/cors/preflight_cache_unittest.cc
index e1a3f33..3f3fe54 100644
--- a/services/network/public/cpp/cors/preflight_cache_unittest.cc
+++ b/services/network/public/cpp/cors/preflight_cache_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/public/cpp/cors/preflight_cache.h"
 
+#include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
 #include "net/http/http_request_headers.h"
@@ -23,6 +24,7 @@
  protected:
   size_t CountOrigins() const { return cache_.CountOriginsForTesting(); }
   size_t CountEntries() const { return cache_.CountEntriesForTesting(); }
+  void MayPurge(size_t max_entries) { cache_.MayPurgeForTesting(max_entries); }
   PreflightCache* cache() { return &cache_; }
 
   void AppendEntry(const std::string& origin, const GURL& url) {
@@ -47,6 +49,7 @@
   void SetUp() override { PreflightResult::SetTickClockForTesting(&clock_); }
   void TearDown() override { PreflightResult::SetTickClockForTesting(nullptr); }
 
+  base::test::ScopedTaskEnvironment env_;
   PreflightCache cache_;
   base::SimpleTestTickClock clock_;
 };
@@ -74,6 +77,11 @@
 
   EXPECT_EQ(2u, CountOrigins());
   EXPECT_EQ(3u, CountEntries());
+
+  for (size_t max_entries : {3, 2, 1, 0}) {
+    MayPurge(max_entries);
+    EXPECT_EQ(max_entries, CountEntries());
+  }
 }
 
 TEST_F(PreflightCacheTest, CacheTimeout) {
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ac766a1..92b858c1f 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -63887,6 +63887,16 @@
   </summary>
 </histogram>
 
+<histogram name="Net.Cors.PreflightCacheEntries" units="entries"
+    expires_after="M77">
+  <owner>toyoshim@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <summary>
+    The distribution of the number of cache entries in the CORS preflight cache.
+    This counts the cache entries every hour.
+  </summary>
+</histogram>
+
 <histogram name="Net.CountOfAlternateProtocolServers" units="servers"
     expires_after="M77">
   <owner>bnc@chromium.org</owner>