in-memory-storage.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2024 Regents of the University of California.
4  *
5  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6  *
7  * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8  * terms of the GNU Lesser General Public License as published by the Free Software
9  * Foundation, either version 3 of the License, or (at your option) any later version.
10  *
11  * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13  * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14  *
15  * You should have received copies of the GNU General Public License and GNU Lesser
16  * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17  * <http://www.gnu.org/licenses/>.
18  *
19  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20  */
21 
24 
25 namespace ndn {
26 
27 constexpr size_t MIN_CAPACITY = 16;
29 
32 {
33  m_it++;
34  if (m_it != m_cache->get<byFullName>().end()) {
35  m_ptr = &((*m_it)->getData());
36  }
37  else {
38  m_ptr = nullptr;
39  }
40  return *this;
41 }
42 
44  : m_limit(limit)
45 {
46  init();
47 }
48 
49 InMemoryStorage::InMemoryStorage(boost::asio::io_context& ioCtx, size_t limit)
50  : m_limit(limit)
51 {
52  m_scheduler = make_unique<Scheduler>(ioCtx);
53  init();
54 }
55 
56 void
57 InMemoryStorage::init()
58 {
59  // TODO: consider a more suitable initial value
60  m_capacity = MIN_CAPACITY;
61 
62  if (m_limit != std::numeric_limits<size_t>::max() && m_capacity > m_limit) {
63  m_capacity = m_limit;
64  }
65 
66  for (size_t i = 0; i < m_capacity; i++) {
67  m_freeEntries.push(new InMemoryStorageEntry());
68  }
69 }
70 
72 {
73  // evict all items from cache
74  auto it = m_cache.begin();
75  while (it != m_cache.end()) {
76  it = freeEntry(it);
77  }
78 
79  BOOST_ASSERT(m_freeEntries.size() == m_capacity);
80 
81  while (!m_freeEntries.empty()) {
82  delete m_freeEntries.top();
83  m_freeEntries.pop();
84  }
85 }
86 
87 void
89 {
90  size_t oldCapacity = m_capacity;
91  m_capacity = std::max(capacity, MIN_CAPACITY);
92 
93  if (size() > m_capacity) {
94  ssize_t nAllowedFailures = size() - m_capacity;
95  while (size() > m_capacity) {
96  if (!evictItem() && --nAllowedFailures < 0) {
97  NDN_THROW(Error());
98  }
99  }
100  }
101 
102  if (m_capacity >= oldCapacity) {
103  for (size_t i = oldCapacity; i < m_capacity; i++) {
104  m_freeEntries.push(new InMemoryStorageEntry());
105  }
106  }
107  else {
108  for (size_t i = oldCapacity; i > m_capacity; i--) {
109  delete m_freeEntries.top();
110  m_freeEntries.pop();
111  }
112  }
113 
114  BOOST_ASSERT(size() + m_freeEntries.size() == m_capacity);
115 }
116 
117 void
118 InMemoryStorage::insert(const Data& data, const time::milliseconds& mustBeFreshProcessingWindow)
119 {
120  // check if identical Data/Name already exists
121  auto it = m_cache.get<byFullName>().find(data.getFullName());
122  if (it != m_cache.get<byFullName>().end())
123  return;
124 
125  // if full, double the capacity
126  bool doesReachLimit = (getLimit() == getCapacity());
127  if (isFull() && !doesReachLimit) {
128  // note: This is incorrect if 2*capacity overflows, but memory should run out before that
129  size_t newCapacity = std::min(2 * getCapacity(), getLimit());
130  setCapacity(newCapacity);
131  }
132 
133  // if full and reach limitation of the capacity, employ replacement policy
134  if (isFull() && doesReachLimit) {
135  evictItem();
136  }
137 
138  // insert to cache
139  BOOST_ASSERT(m_freeEntries.size() > 0);
140  // take entry for the memory pool
141  InMemoryStorageEntry* entry = m_freeEntries.top();
142  m_freeEntries.pop();
143  m_nPackets++;
144  entry->setData(data);
145  if (m_scheduler != nullptr && mustBeFreshProcessingWindow >= ZERO_WINDOW) {
146  entry->scheduleMarkStale(*m_scheduler, mustBeFreshProcessingWindow);
147  }
148  m_cache.insert(entry);
149 
150  // let derived class do something with the entry
151  afterInsert(entry);
152 }
153 
154 shared_ptr<const Data>
156 {
157  auto it = m_cache.get<byFullName>().lower_bound(name);
158 
159  // if not found, return null
160  if (it == m_cache.get<byFullName>().end()) {
161  return nullptr;
162  }
163 
164  // if the given name is not the prefix of the lower_bound, return null
165  if (!name.isPrefixOf((*it)->getFullName())) {
166  return nullptr;
167  }
168 
169  afterAccess(*it);
170  return ((*it)->getData()).shared_from_this();
171 }
172 
173 shared_ptr<const Data>
175 {
176  // if the interest contains implicit digest, it is possible to directly locate a packet.
177  auto it = m_cache.get<byFullName>().find(interest.getName());
178 
179  // if a packet is located by its full name, it must be the packet to return.
180  if (it != m_cache.get<byFullName>().end()) {
181  return ((*it)->getData()).shared_from_this();
182  }
183 
184  // if the packet is not discovered by last step, either the packet is not in the storage or
185  // the interest doesn't contains implicit digest.
186  it = m_cache.get<byFullName>().lower_bound(interest.getName());
187 
188  if (it == m_cache.get<byFullName>().end()) {
189  return nullptr;
190  }
191 
192  // to locate the element that has a just smaller name than the interest's
193  if (it != m_cache.get<byFullName>().begin()) {
194  it--;
195  }
196 
197  InMemoryStorageEntry* ret = selectChild(interest, it);
198  if (ret == nullptr) {
199  return nullptr;
200  }
201 
202  // let derived class do something with the entry
203  afterAccess(ret);
204  return ret->getData().shared_from_this();
205 }
206 
207 InMemoryStorage::Cache::index<InMemoryStorage::byFullName>::type::iterator
208 InMemoryStorage::findNextFresh(Cache::index<byFullName>::type::iterator it) const
209 {
210  for (; it != m_cache.get<byFullName>().end(); it++) {
211  if ((*it)->isFresh())
212  return it;
213  }
214 
215  return it;
216 }
217 
218 InMemoryStorageEntry*
219 InMemoryStorage::selectChild(const Interest& interest,
220  Cache::index<byFullName>::type::iterator startingPoint) const
221 {
222  BOOST_ASSERT(startingPoint != m_cache.get<byFullName>().end());
223 
224  if (startingPoint != m_cache.get<byFullName>().begin()) {
225  BOOST_ASSERT((*startingPoint)->getFullName() < interest.getName());
226  }
227 
228  // filter out non-fresh data
229  if (interest.getMustBeFresh()) {
230  startingPoint = findNextFresh(startingPoint);
231  }
232 
233  if (startingPoint == m_cache.get<byFullName>().end()) {
234  return nullptr;
235  }
236 
237  if (interest.matchesData((*startingPoint)->getData())) {
238  return *startingPoint;
239  }
240 
241  // iterate to the right
242  auto rightmostCandidate = startingPoint;
243  while (true) {
244  ++rightmostCandidate;
245  // filter out non-fresh data
246  if (interest.getMustBeFresh()) {
247  rightmostCandidate = findNextFresh(rightmostCandidate);
248  }
249 
250  bool isInPrefix = false;
251  if (rightmostCandidate != m_cache.get<byFullName>().end()) {
252  isInPrefix = interest.getName().isPrefixOf((*rightmostCandidate)->getFullName());
253  }
254  if (isInPrefix) {
255  if (interest.matchesData((*rightmostCandidate)->getData())) {
256  return *rightmostCandidate;
257  }
258  }
259  else {
260  break;
261  }
262  }
263 
264  return nullptr;
265 }
266 
267 InMemoryStorage::Cache::iterator
268 InMemoryStorage::freeEntry(Cache::iterator it)
269 {
270  // push the *empty* entry into mem pool
271  (*it)->release();
272  m_freeEntries.push(*it);
273  m_nPackets--;
274  return m_cache.erase(it);
275 }
276 
277 void
278 InMemoryStorage::erase(const Name& prefix, const bool isPrefix)
279 {
280  if (isPrefix) {
281  auto it = m_cache.get<byFullName>().lower_bound(prefix);
282  while (it != m_cache.get<byFullName>().end() && prefix.isPrefixOf((*it)->getName())) {
283  // let derived class do something with the entry
284  beforeErase(*it);
285  it = freeEntry(it);
286  }
287  }
288  else {
289  auto it = m_cache.get<byFullName>().find(prefix);
290  if (it == m_cache.get<byFullName>().end())
291  return;
292 
293  // let derived class do something with the entry
294  beforeErase(*it);
295  freeEntry(it);
296  }
297 
298  if (m_freeEntries.size() > (2 * size()))
299  setCapacity(getCapacity() / 2);
300 }
301 
302 void
304 {
305  auto it = m_cache.get<byFullName>().find(name);
306  if (it == m_cache.get<byFullName>().end())
307  return;
308 
309  freeEntry(it);
310 }
311 
314 {
315  auto it = m_cache.get<byFullName>().begin();
316  return const_iterator(&((*it)->getData()), &m_cache, it);
317 }
318 
321 {
322  auto it = m_cache.get<byFullName>().end();
323  return const_iterator(nullptr, &m_cache, it);
324 }
325 
326 void
328 {
329 }
330 
331 void
333 {
334 }
335 
336 void
338 {
339 }
340 
341 void
342 InMemoryStorage::printCache(std::ostream& os) const
343 {
344  // start from the upper layer towards bottom
345  for (const auto& elem : m_cache.get<byFullName>())
346  os << elem->getFullName() << std::endl;
347 }
348 
349 } // namespace ndn
Represents a Data packet.
Definition: data.hpp:39
const Name & getFullName() const
Get the full name (including implicit digest).
Definition: data.cpp:200
Represents an in-memory storage entry.
void scheduleMarkStale(Scheduler &sched, time::nanoseconds after)
Schedule an event to mark this entry as non-fresh.
const Data & getData() const
Returns the Data packet stored in the in-memory storage entry.
void setData(const Data &data)
Changes the content of in-memory storage entry.
Represents an error might be thrown during reduce the current capacity of the in-memory storage throu...
Represents a const_iterator for the in-memory storage.
InMemoryStorage(size_t limit=std::numeric_limits< size_t >::max())
Create a InMemoryStorage with up to limit entries.
void erase(const Name &prefix, const bool isPrefix=true)
Deletes in-memory storage entry by prefix by default.
virtual void afterAccess(InMemoryStorageEntry *entry)
Update the entry when the entry is returned by the find() function according to derived class impleme...
void insert(const Data &data, const time::milliseconds &mustBeFreshProcessingWindow=INFINITE_WINDOW)
Inserts a Data packet.
InMemoryStorage::const_iterator end() const
Returns end iterator of the in-memory storage ordering by name with digest.
void printCache(std::ostream &os) const
Prints contents of the in-memory storage.
virtual void beforeErase(InMemoryStorageEntry *entry)
Update the entry or other data structures before a entry is successfully erased according to derived ...
shared_ptr< const Data > find(const Interest &interest)
Finds the best match Data for an Interest.
virtual bool evictItem()=0
Removes one Data packet from in-memory storage based on derived class implemented replacement policy.
bool isFull() const
Returns true if the in-memory storage uses up the current capacity, false otherwise.
size_t getCapacity() const
Returns current capacity of in-memory storage (in packets).
InMemoryStorage::const_iterator begin() const
Returns begin iterator of the in-memory storage ordering by name with digest.
void eraseImpl(const Name &name)
Deletes in-memory storage entries by the Name with implicit digest.
virtual void afterInsert(InMemoryStorageEntry *entry)
Update the entry or other data structures after a entry is successfully inserted according to derived...
void setCapacity(size_t nMaxPackets)
Sets current capacity of in-memory storage (in packets).
Represents an Interest packet.
Definition: interest.hpp:50
const Name & getName() const noexcept
Get the Interest name.
Definition: interest.hpp:179
Represents an absolute name.
Definition: name.hpp:45
bool isPrefixOf(const Name &other) const noexcept
Check if this name is a prefix of another name.
Definition: name.cpp:275
#define NDN_THROW(e)
Definition: exception.hpp:56
::boost::chrono::milliseconds milliseconds
Definition: time.hpp:52
@ Interest
Definition: tlv.hpp:68
Definition: data.cpp:25
constexpr time::milliseconds ZERO_WINDOW
constexpr size_t MIN_CAPACITY