Skip to content

Commit 7b9b842

Browse files
committed
Add unordered_map_with_ttl
1 parent e95393b commit 7b9b842

File tree

4 files changed

+272
-1
lines changed

4 files changed

+272
-1
lines changed

.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"type": "cppdbg",
1010
"request": "launch",
1111
"program": "${workspaceFolder}/build/test/test_utxx",
12-
"args": ["-t", "test_variant_tree"],
12+
"args": ["-t", "test_unordered_map_with_ttl"],
1313
"stopAtEntry": false,
1414
"cwd": "${workspaceFolder}",
1515
"environment": [],
+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//----------------------------------------------------------------------------
2+
/// \file unordered_map_with_ttl.hpp
3+
/// \author Serge Aleynikov
4+
//----------------------------------------------------------------------------
5+
/// \brief A Key/Value hashmap with TTL eviction.
6+
///
7+
/// An insert of a Key/Value pair in the map will store the timestamp of the
8+
/// maybe_add. Additionally a queue of maybe_adds is maintained by this container,
9+
/// which is checked on each insert and the expired Key/Value pairs are
10+
/// evicted from the map.
11+
///
12+
/// For performance reasons it's desirable to call the `refresh()` method
13+
/// every time the caller is idle.
14+
//----------------------------------------------------------------------------
15+
// Copyright (c) 2016 Serge Aleynikov <[email protected]>
16+
// Created: 2023-04-14
17+
//----------------------------------------------------------------------------
18+
/*
19+
***** BEGIN LICENSE BLOCK *****
20+
21+
This file is part of the utxx open-source project.
22+
23+
Copyright (C) 2016 Serge Aleynikov <[email protected]>
24+
25+
This library is free software; you can redistribute it and/or
26+
modify it under the terms of the GNU Lesser General Public
27+
License as published by the Free Software Foundation; either
28+
version 2.1 of the License, or (at your option) any later version.
29+
30+
This library is distributed in the hope that it will be useful,
31+
but WITHOUT ANY WARRANTY; without even the implied warranty of
32+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
33+
Lesser General Public License for more details.
34+
35+
You should have received a copy of the GNU Lesser General Public
36+
License along with this library; if not, write to the Free Software
37+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
38+
39+
***** END LICENSE BLOCK *****
40+
*/
41+
#pragma once
42+
43+
#include <type_traits>
44+
#include <memory>
45+
#include <unordered_map>
46+
#include <list>
47+
#include <cassert>
48+
49+
namespace utxx {
50+
namespace {
51+
template <typename T>
52+
struct val_node {
53+
val_node() {}
54+
val_node(T&& v, uint64_t time) : value(v), time(time) {}
55+
56+
T value;
57+
uint64_t time; // Last maybe_add time
58+
};
59+
60+
template <class T>
61+
struct val_assigner {
62+
bool operator()(
63+
[[maybe_unused]] val_node<T>& old,
64+
[[maybe_unused]] const T& new_val,
65+
[[maybe_unused]] uint64_t time
66+
) {
67+
return false;
68+
}
69+
};
70+
}
71+
72+
//--------------------------------------------------------------------------
73+
/// Unordered map with TTL for each key
74+
/// @tparam K key type
75+
/// @tparam T value type
76+
/// @tparam Alloc custom allocator
77+
///
78+
/// Short vectors up to \a MaxItems don't involve memory allocations.
79+
/// Short vector can be set to have NULL value by using "set_null()" method.
80+
/// NULL vectors are represented by having size() = -1.
81+
//--------------------------------------------------------------------------
82+
template <
83+
class K,
84+
class T,
85+
class Hash = std::hash<K>,
86+
class KeyEqual = std::equal_to<K>,
87+
class ValUpdate = val_assigner<T>,
88+
class Allocator = std::allocator<std::pair<const K, T>>
89+
>
90+
struct unordered_map_with_ttl {
91+
struct ttl_node {
92+
uint64_t time;
93+
K key;
94+
};
95+
96+
using hash_map = std::unordered_map<K, val_node<T>, Hash, KeyEqual, Allocator>;
97+
using lru_alloc = typename std::allocator_traits<Allocator>::template rebind_alloc<ttl_node>;
98+
using lru_list = std::list<ttl_node, lru_alloc>;
99+
using iterator = typename hash_map::iterator;
100+
using const_iterator = typename hash_map::const_iterator;
101+
102+
explicit unordered_map_with_ttl(uint64_t ttl)
103+
: m_ttl(ttl)
104+
, m_map()
105+
, m_lru()
106+
, m_assign(val_assigner<T>())
107+
{}
108+
109+
unordered_map_with_ttl(uint64_t ttl, const Allocator& alloc)
110+
: m_ttl(ttl)
111+
, m_map(alloc)
112+
, m_assign(val_assigner<T>())
113+
{}
114+
115+
unordered_map_with_ttl(
116+
uint64_t ttl,
117+
size_t bucket_count,
118+
const Hash& hash = Hash(),
119+
const KeyEqual& equal = KeyEqual(),
120+
const Allocator& alloc = Allocator()
121+
)
122+
: m_ttl(ttl)
123+
, m_map(bucket_count, hash, equal, alloc)
124+
, m_lru()
125+
, m_assign(val_assigner<T>())
126+
{}
127+
128+
/// @brief Try to add a given key/value to the map.
129+
/// @return true if the value was added
130+
bool try_add(const K& key, T&& value, uint64_t now);
131+
132+
size_t size() const { return m_map.size(); }
133+
134+
iterator begin() noexcept { return m_map.begin(); }
135+
const_iterator begin() const noexcept { return m_map.begin(); }
136+
const_iterator cbegin() const noexcept { return m_map.cbegin(); }
137+
138+
iterator end() noexcept { return m_map.end(); }
139+
const_iterator end() const noexcept { return m_map.end(); }
140+
const_iterator cend() const noexcept { return m_map.cend(); }
141+
142+
/// @brief Erase the given key from the map
143+
/// @return true when the key was evicted
144+
template <typename Key>
145+
bool erase(Key&& k) { return m_map.erase(k) > 0; }
146+
147+
/// @brief Clear the map
148+
void clear() { m_map.clear(); m_lru.clear(); }
149+
150+
/// @brief Evict expired key/value pairs
151+
/// @param now - current timestamp
152+
/// @return the number of evicted items
153+
size_t refresh(uint64_t now);
154+
155+
iterator find(const K& k) { return m_map.find(k); }
156+
const_iterator find(const K& k) const { return m_map.find(k); }
157+
158+
private:
159+
uint64_t m_ttl;
160+
hash_map m_map;
161+
lru_list m_lru;
162+
ValUpdate m_assign;
163+
};
164+
165+
//--------------------------------------------------------------------------
166+
// IMPLEMENTATION
167+
//--------------------------------------------------------------------------
168+
169+
template <class K, class T, class Hash, class KeyEq, class ValUpdate, class Allocator>
170+
size_t unordered_map_with_ttl<K,T,Hash,KeyEq,ValUpdate,Allocator>
171+
::refresh(uint64_t now)
172+
{
173+
assert(now > m_ttl);
174+
175+
size_t res = 0;
176+
auto ttl = now - m_ttl;
177+
178+
for (auto i = m_lru.begin(); i != m_lru.end() && i->time <= ttl; i = m_lru.begin(), ++res) {
179+
m_map.erase(i->key); // Evict expired node
180+
m_lru.pop_front();
181+
}
182+
183+
return res;
184+
}
185+
186+
template <class K, class T, class Hash, class KeyEq, class ValUpdate, class Allocator>
187+
bool unordered_map_with_ttl<K,T,Hash,KeyEq,ValUpdate,Allocator>
188+
::try_add(const K& key, T&& value, uint64_t now)
189+
{
190+
assert(now > m_ttl);
191+
192+
auto ttl = now - m_ttl;
193+
194+
for (auto i = m_lru.begin(); i != m_lru.end() && i->time <= ttl; i = m_lru.begin()) {
195+
m_map.erase(i->key); // Evict expired node
196+
m_lru.pop_front();
197+
}
198+
199+
auto it = m_map.find(key);
200+
auto not_found = it == m_map.end();
201+
202+
if (not_found)
203+
m_map.emplace(key, val_node<T>(std::move(value), now));
204+
else // maybe_add the existing entry
205+
not_found = m_assign(it->second, std::move(value), now);
206+
207+
m_lru.push_back(ttl_node{now, key});
208+
209+
return not_found;
210+
}
211+
212+
213+
} // namespace utxx

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ list(APPEND TEST_SRCS
7777
test_timestamp.cpp
7878
test_type_traits.cpp
7979
test_url.cpp
80+
test_unordered_map_with_ttl.cpp
8081
test_utxx.cpp
8182
test_variant.cpp
8283
test_variant_tree_scon_parser.cpp

test/test_unordered_map_with_ttl.cpp

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//----------------------------------------------------------------------------
2+
/// \file test_unordered_map_with_ttl.cpp
3+
//----------------------------------------------------------------------------
4+
/// \brief Test cases for classes in the unordered_map_with_ttl.hpp file.
5+
//----------------------------------------------------------------------------
6+
// Copyright (c) 2010 Serge Aleynikov <[email protected]>
7+
// Created: 2022-04-15
8+
//----------------------------------------------------------------------------
9+
/*
10+
***** BEGIN LICENSE BLOCK *****
11+
12+
This file is part of the utxx open-source projects
13+
14+
Copyright (C) 2010 Serge Aleynikov <[email protected]>
15+
16+
This library is free software; you can redistribute it and/or
17+
modify it under the terms of the GNU Lesser General Public
18+
License as published by the Free Software Foundation; either
19+
version 2.1 of the License, or (at your option) any later version.
20+
21+
This library is distributed in the hope that it will be useful,
22+
but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24+
Lesser General Public License for more details.
25+
26+
You should have received a copy of the GNU Lesser General Public
27+
License along with this library; if not, write to the Free Software
28+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29+
30+
***** END LICENSE BLOCK *****
31+
*/
32+
33+
#include <boost/test/unit_test.hpp>
34+
#include <utxx/unordered_map_with_ttl.hpp>
35+
#include <utxx/string.hpp>
36+
37+
using namespace utxx;
38+
39+
BOOST_AUTO_TEST_CASE( test_unordered_map_with_ttl )
40+
{
41+
unordered_map_with_ttl<int, int> map(1000);
42+
43+
BOOST_REQUIRE(map.try_add(1, 123, 10000));
44+
BOOST_REQUIRE(map.try_add(2, 234, 10000));
45+
BOOST_REQUIRE_EQUAL(2, map.size());
46+
47+
BOOST_CHECK( map.try_add(1, 123, 11000));
48+
BOOST_REQUIRE_EQUAL(1, map.size());
49+
BOOST_CHECK(!map.try_add(1, 123, 11500));
50+
BOOST_REQUIRE_EQUAL(1, map.size());
51+
BOOST_REQUIRE_EQUAL(1, map.refresh(12000));
52+
BOOST_REQUIRE_EQUAL(0, map.size());
53+
BOOST_CHECK( map.try_add(1, 123, 12000));
54+
BOOST_REQUIRE_EQUAL(1, map.size());
55+
}
56+
57+

0 commit comments

Comments
 (0)