Line data Source code
1 : /*
2 : ___________________________________________
3 : | _ ___ _ |
4 : | | | |__ \ | | |
5 : | | |__ ) |__ _ __ _ ___ _ __ | |_ |
6 : | | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
7 : | | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
8 : | |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
9 : | __/ | |
10 : | |___/ |
11 : |___________________________________________|
12 :
13 : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
14 : SPDX-License-Identifier: MIT
15 : Copyright (c) 2021 Eduardo Ramos
16 :
17 : Permission is hereby granted, free of charge, to any person obtaining a copy
18 : of this software and associated documentation files (the "Software"), to deal
19 : in the Software without restriction, including without limitation the rights
20 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 : copies of the Software, and to permit persons to whom the Software is
22 : furnished to do so, subject to the following conditions:
23 :
24 : The above copyright notice and this permission notice shall be included in all
25 : copies or substantial portions of the Software.
26 :
27 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 : SOFTWARE.
34 : */
35 :
36 : #pragma once
37 :
38 : // Better unordered_map than map:
39 : // Slighly more memory consumption (not significative in load tests) due to the hash map.
40 : // But order is not important for us, and the size is not very big (prune is normally
41 : // applied in load test provisions), so the cache is not used.
42 : // As insertion and deletion are equally fast for both containers, we focus on search
43 : // (O(log2(n)) for map as binary tree, O(1) constant as average (O(n) in worst case)
44 : // for unordered map as hash table), so for our case, unordered_map seems to be the best choice.
45 : #include <unordered_map>
46 :
47 : #include <common.hpp>
48 :
49 : #include <nlohmann/json.hpp>
50 :
51 :
52 : namespace h2agent
53 : {
54 : namespace model
55 : {
56 :
57 : template<typename Key, typename Value>
58 : class Map {
59 :
60 : typedef typename std::unordered_map<Key, Value> map_t;
61 : using IterationCallback = std::function<void(const Key&, const Value&)>;
62 :
63 : mutable mutex_t mutex_{};
64 : map_t map_{};
65 :
66 : protected:
67 30 : bool clear_unsafe() noexcept {
68 30 : bool result = (map_.size() != 0); // same !
69 30 : map_.clear();
70 30 : return result;
71 : }
72 :
73 : public:
74 :
75 1428 : Map() {};
76 :
77 : /** copy constructor */
78 : Map(const Map& other) : map_{}
79 : {
80 : read_guard_t guard(other.mutex_);
81 : this->map_ = other.map_;
82 : }
83 :
84 1347 : ~Map() = default;
85 :
86 : // getters
87 :
88 117 : bool exists(const Key& key) const
89 : {
90 117 : read_guard_t guard(mutex_);
91 234 : return (map_.find(key) != map_.end());
92 117 : }
93 :
94 : /**
95 : * Searchs map key
96 : *
97 : * @param key key to find
98 : * @param exits written by reference
99 : * @return Value for provided key, or initizalized Value when key is missing
100 : */
101 477 : Value get(const Key& key, bool &exists) const
102 : {
103 477 : read_guard_t guard(mutex_);
104 477 : auto it = map_.find(key);
105 477 : exists = (it != map_.end());
106 954 : return (exists ? it->second : Value{}); // return copy
107 477 : }
108 :
109 : /**
110 : * Getter which avoid copy of empty value and is more readable than get()
111 : */
112 134 : bool tryGet(const Key& key, Value& out_value) const {
113 134 : read_guard_t guard(mutex_);
114 134 : auto it = map_.find(key);
115 134 : if (it != map_.end()) {
116 15 : out_value = it->second;
117 15 : return true;
118 : }
119 119 : return false;
120 134 : }
121 :
122 : //// Lvalue
123 : //bool insert_if_not_exists(const Key& key, const Value& value) {
124 : // write_guard_t guard(mutex_);
125 : // // try_emplace is the atomic equivalent to insert_if_not_exists
126 : // return map_.try_emplace(key, value).second;
127 : //}
128 :
129 : //// Rvalue (max performance)
130 : //bool insert_if_not_exists(const Key& key, Value&& value) {
131 : // write_guard_t guard(mutex_);
132 : // return map_.try_emplace(key, std::move(value)).second;
133 : //}
134 :
135 : //template <typename... Args>
136 : //bool emplace(const Key& key, Args&&... args) {
137 : // write_guard_t guard(mutex_);
138 : // return map_.try_emplace(key, std::forward<Args>(args)...).second;
139 : //}
140 :
141 : /** map size */
142 234 : size_t size() const
143 : {
144 234 : read_guard_t guard(mutex_);
145 468 : return map_.size();
146 234 : }
147 :
148 13 : bool empty() const
149 : {
150 13 : read_guard_t guard(mutex_);
151 26 : return (map_.size() == 0);
152 13 : }
153 :
154 : /**
155 : * @brief Iterates safely over all elements in the map.
156 : * * This method provides **read access** to the map's elements in a **thread-safe** manner
157 : * by applying a user-defined callback function to each key-value pair. The entire
158 : * iteration is performed atomically and under a read lock.
159 : *
160 : * @attention This method acquires a \c std::shared_lock (read lock) for its full duration.
161 : * To ensure high concurrency, avoid placing long-running operations (such as file I/O or
162 : * thread sleeping) inside the callback function.
163 : *
164 : * @tparam Key The type of the map's key (e.g., std::string).
165 : * @tparam Value The type of the map's value (e.g., std::shared_ptr<T>).
166 : *
167 : * @param callback A function (lambda or functor) that is applied to every key-value pair.
168 : * The signature must be compatible with:
169 : * \code
170 : * void(const Key&, const Value&)
171 : * \endcode
172 : *
173 : * @note This function uses the callback pattern to prevent the exposure of unsafe iterators
174 : * (\c dangling iterators) to external threads.
175 : */
176 30 : void forEach(const IterationCallback& callback) const {
177 30 : read_guard_t guard(mutex_);
178 63 : for (const auto& pair : map_) {
179 33 : callback(pair.first, pair.second);
180 : }
181 30 : }
182 :
183 : /**
184 : * @brief Safely converts the internal map content into a JSON object.
185 : * * This method provides a thread-safe way to serialize the data stored in the
186 : * map by acquiring a read lock for the entire duration of the conversion.
187 : * * @details The method acquires a \c std::shared_lock on the map's mutex. While
188 : * the lock is held, the internal \c std::unordered_map is copied and cast
189 : * into a \c nlohmann::json object. This ensures that the map cannot be
190 : * modified (added to or removed from) by writer threads during serialization.
191 : * * @tparam Key The type of the map's key.
192 : * @tparam Value The type of the map's value.
193 : * * @note This implementation relies on \c nlohmann::json's built-in support
194 : * for converting \c std::unordered_map. If \c Value is a complex type (e.g.,
195 : * a smart pointer or a custom struct), ensure the appropriate \c to_json()
196 : * function is implemented for automatic serialization.
197 : *
198 : * @return nlohmann::json A new, thread-safe copy of the map's content
199 : * as a JSON object.
200 : */
201 3 : nlohmann::json getJson() const {
202 3 : read_guard_t guard(mutex_);
203 3 : nlohmann::json j(map_);
204 6 : return j; // return copy
205 3 : }
206 :
207 : // setters
208 :
209 : /**
210 : * Adds a new value to the map
211 : * Lvalue variant
212 : *
213 : * @param key key to add
214 : * @param value stored
215 : */
216 474 : void add(const Key& key, const Value &value) {
217 474 : write_guard_t guard(mutex_);
218 474 : map_.insert_or_assign(key, value);
219 474 : }
220 :
221 : // Rvalue variant (std::move)
222 320 : void add(const Key& key, Value&& value) {
223 320 : write_guard_t guard(mutex_);
224 320 : map_.insert_or_assign(key, std::move(value));
225 320 : }
226 :
227 : /**
228 : * Adds another map of same kind to the map
229 : *
230 : * @param map map to add
231 : */
232 5 : void add(const map_t& m)
233 : {
234 5 : write_guard_t guard(mutex_);
235 13 : for (const auto& kv : m)
236 8 : map_.insert_or_assign(kv.first, kv.second);
237 5 : }
238 :
239 : /**
240 : * Removes key
241 : *
242 : * @param key key to remove
243 : */
244 6 : void remove(const Key& key, bool &exists)
245 : {
246 6 : write_guard_t guard(mutex_);
247 6 : exists = (map_.erase(key) > 0);
248 6 : }
249 :
250 : /** Clear map */
251 : // return if something was deleted
252 20 : bool clear()
253 : {
254 20 : write_guard_t guard(mutex_);
255 40 : return clear_unsafe(); // don't call Map::size() to avoid mutex deadlocks (this one uses map_.size())
256 20 : }
257 : };
258 :
259 : }
260 : }
261 :
|