1// Copyright (c) 2009 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/base/host_cache.h"
6
7#include "base/logging.h"
8#include "net/base/net_errors.h"
9
10namespace net {
11
12//-----------------------------------------------------------------------------
13
14HostCache::Entry::Entry(int error,
15                        const AddressList& addrlist,
16                        base::TimeTicks expiration)
17    : error(error), addrlist(addrlist), expiration(expiration) {
18}
19
20HostCache::Entry::~Entry() {
21}
22
23//-----------------------------------------------------------------------------
24
25HostCache::HostCache(size_t max_entries,
26                     base::TimeDelta success_entry_ttl,
27                     base::TimeDelta failure_entry_ttl)
28    : max_entries_(max_entries),
29      success_entry_ttl_(success_entry_ttl),
30      failure_entry_ttl_(failure_entry_ttl) {
31}
32
33HostCache::~HostCache() {
34}
35
36const HostCache::Entry* HostCache::Lookup(const Key& key,
37                                          base::TimeTicks now) const {
38  DCHECK(CalledOnValidThread());
39  if (caching_is_disabled())
40    return NULL;
41
42  EntryMap::const_iterator it = entries_.find(key);
43  if (it == entries_.end())
44    return NULL;  // Not found.
45
46  Entry* entry = it->second.get();
47  if (CanUseEntry(entry, now))
48    return entry;
49
50  return NULL;
51}
52
53HostCache::Entry* HostCache::Set(const Key& key,
54                                 int error,
55                                 const AddressList& addrlist,
56                                 base::TimeTicks now) {
57  DCHECK(CalledOnValidThread());
58  if (caching_is_disabled())
59    return NULL;
60
61  base::TimeTicks expiration = now +
62      (error == OK ? success_entry_ttl_ : failure_entry_ttl_);
63
64  scoped_refptr<Entry>& entry = entries_[key];
65  if (!entry) {
66    // Entry didn't exist, creating one now.
67    Entry* ptr = new Entry(error, addrlist, expiration);
68    entry = ptr;
69
70    // Compact the cache if we grew it beyond limit -- exclude |entry| from
71    // being pruned though!
72    if (entries_.size() > max_entries_)
73      Compact(now, ptr);
74    return ptr;
75  } else {
76    // Update an existing cache entry.
77    entry->error = error;
78    entry->addrlist = addrlist;
79    entry->expiration = expiration;
80    return entry.get();
81  }
82}
83
84void HostCache::clear() {
85  DCHECK(CalledOnValidThread());
86  entries_.clear();
87}
88
89size_t HostCache::size() const {
90  DCHECK(CalledOnValidThread());
91  return entries_.size();
92}
93
94size_t HostCache::max_entries() const {
95  DCHECK(CalledOnValidThread());
96  return max_entries_;
97}
98
99base::TimeDelta HostCache::success_entry_ttl() const {
100  DCHECK(CalledOnValidThread());
101  return success_entry_ttl_;
102}
103
104base::TimeDelta HostCache::failure_entry_ttl() const {
105  DCHECK(CalledOnValidThread());
106  return failure_entry_ttl_;
107}
108
109// Note that this map may contain expired entries.
110const HostCache::EntryMap& HostCache::entries() const {
111  DCHECK(CalledOnValidThread());
112  return entries_;
113}
114
115// static
116bool HostCache::CanUseEntry(const Entry* entry, const base::TimeTicks now) {
117  return entry->expiration > now;
118}
119
120void HostCache::Compact(base::TimeTicks now, const Entry* pinned_entry) {
121  // Clear out expired entries.
122  for (EntryMap::iterator it = entries_.begin(); it != entries_.end(); ) {
123    Entry* entry = (it->second).get();
124    if (entry != pinned_entry && !CanUseEntry(entry, now)) {
125      entries_.erase(it++);
126    } else {
127      ++it;
128    }
129  }
130
131  if (entries_.size() <= max_entries_)
132    return;
133
134  // If we still have too many entries, start removing unexpired entries
135  // at random.
136  // TODO(eroman): this eviction policy could be better (access count FIFO
137  // or whatever).
138  for (EntryMap::iterator it = entries_.begin();
139       it != entries_.end() && entries_.size() > max_entries_; ) {
140    Entry* entry = (it->second).get();
141    if (entry != pinned_entry) {
142      entries_.erase(it++);
143    } else {
144      ++it;
145    }
146  }
147
148  if (entries_.size() > max_entries_)
149    DLOG(WARNING) << "Still above max entries limit";
150}
151
152}  // namespace net
153