Skip to content

Commit 8ef109e

Browse files
authored
Improvements to glz::async_map (#1465)
* Adding operator[] to async_map and simplifying map_subscriptable constraint * Add operator* for value_proxy * proxy_value now returns V& * serialization and deserialization for async_map
1 parent 7cee9ac commit 8ef109e

File tree

3 files changed

+125
-43
lines changed

3 files changed

+125
-43
lines changed

include/glaze/concepts/container_concepts.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ namespace glz::detail
155155
concept map_subscriptable = requires(T container) {
156156
{
157157
container[std::declval<typename T::key_type>()]
158-
} -> std::same_as<typename T::mapped_type&>;
158+
};
159159
};
160160

161161
template <typename T>

include/glaze/thread/async_map.hpp

+107-42
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
#include <utility>
1515
#include <vector>
1616

17-
#include "glaze/core/common.hpp"
18-
#include "glaze/util/type_traits.hpp"
19-
2017
// This async_map is intended to hold thread safe value types (V)
2118

2219
namespace glz
@@ -138,22 +135,22 @@ namespace glz
138135
pointer operator->() const { return (*item_it).get(); }
139136

140137
// Equality Comparison
141-
bool operator==(const iterator& other) const { return item_it == other.item_it; }
138+
bool operator==(const iterator& other) const noexcept { return item_it == other.item_it; }
142139

143140
// Inequality Comparison
144-
bool operator!=(const iterator& other) const { return !(*this == other); }
141+
bool operator!=(const iterator& other) const noexcept { return !(*this == other); }
145142

146143
// Comparison operators with iterator
147-
bool operator<(const iterator& other) const { return item_it < other.item_it; }
148-
bool operator>(const iterator& other) const { return item_it > other.item_it; }
149-
bool operator<=(const iterator& other) const { return item_it <= other.item_it; }
144+
bool operator<(const iterator& other) const noexcept { return item_it < other.item_it; }
145+
bool operator>(const iterator& other) const noexcept { return item_it > other.item_it; }
146+
bool operator<=(const iterator& other) const noexcept { return item_it <= other.item_it; }
150147
bool operator>=(const iterator& other) const { return item_it >= other.item_it; }
151148

152149
// Comparison operators with const_iterator
153-
bool operator<(const const_iterator& other) const { return item_it < other.item_it; }
154-
bool operator>(const const_iterator& other) const { return item_it > other.item_it; }
155-
bool operator<=(const const_iterator& other) const { return item_it <= other.item_it; }
156-
bool operator>=(const const_iterator& other) const { return item_it >= other.item_it; }
150+
bool operator<(const const_iterator& other) const noexcept { return item_it < other.item_it; }
151+
bool operator>(const const_iterator& other) const noexcept { return item_it > other.item_it; }
152+
bool operator<=(const const_iterator& other) const noexcept { return item_it <= other.item_it; }
153+
bool operator>=(const const_iterator& other) const noexcept { return item_it >= other.item_it; }
157154
};
158155

159156
class const_iterator
@@ -223,41 +220,43 @@ namespace glz
223220
pointer operator->() const { return (*item_it).get(); }
224221

225222
// Equality Comparison
226-
bool operator==(const const_iterator& other) const { return item_it == other.item_it; }
223+
bool operator==(const const_iterator& other) const noexcept { return item_it == other.item_it; }
227224

228225
// Inequality Comparison
229-
bool operator!=(const const_iterator& other) const { return !(*this == other); }
226+
bool operator!=(const const_iterator& other) const noexcept { return !(*this == other); }
230227

231228
// Comparison operators with const_iterator
232-
bool operator<(const const_iterator& other) const { return item_it < other.item_it; }
233-
bool operator>(const const_iterator& other) const { return item_it > other.item_it; }
234-
bool operator<=(const const_iterator& other) const { return item_it <= other.item_it; }
235-
bool operator>=(const const_iterator& other) const { return item_it >= other.item_it; }
229+
bool operator<(const const_iterator& other) const noexcept { return item_it < other.item_it; }
230+
bool operator>(const const_iterator& other) const noexcept { return item_it > other.item_it; }
231+
bool operator<=(const const_iterator& other) const noexcept { return item_it <= other.item_it; }
232+
bool operator>=(const const_iterator& other) const noexcept { return item_it >= other.item_it; }
236233

237234
// Comparison operators with iterator
238-
bool operator<(const iterator& other) const { return item_it < other.item_it; }
239-
bool operator>(const iterator& other) const { return item_it > other.item_it; }
240-
bool operator<=(const iterator& other) const { return item_it <= other.item_it; }
241-
bool operator>=(const iterator& other) const { return item_it >= other.item_it; }
235+
bool operator<(const iterator& other) const noexcept { return item_it < other.item_it; }
236+
bool operator>(const iterator& other) const noexcept { return item_it > other.item_it; }
237+
bool operator<=(const iterator& other) const noexcept { return item_it <= other.item_it; }
238+
bool operator>=(const iterator& other) const noexcept { return item_it >= other.item_it; }
242239
};
243240

244241
// Value Proxy Class Definition
245242
class value_proxy
246243
{
247244
private:
248-
value_type& value_ref;
245+
V& value_ref;
249246
std::shared_ptr<std::shared_lock<std::shared_mutex>> shared_lock_ptr;
250247
std::shared_ptr<std::unique_lock<std::shared_mutex>> unique_lock_ptr;
251248

252249
public:
253-
value_proxy(value_type& value_ref,
250+
value_proxy(V& value_ref,
254251
std::shared_ptr<std::shared_lock<std::shared_mutex>> existing_shared_lock = nullptr,
255252
std::shared_ptr<std::unique_lock<std::shared_mutex>> existing_unique_lock = nullptr)
256253
: value_ref(value_ref), shared_lock_ptr(existing_shared_lock), unique_lock_ptr(existing_unique_lock)
257254
{
258255
// Ensure that a lock is provided
259256
assert(shared_lock_ptr || unique_lock_ptr);
260257
}
258+
259+
static constexpr bool glaze_value_proxy = true;
261260

262261
// Disable Copy and Move
263262
value_proxy(const value_proxy&) = delete;
@@ -266,15 +265,29 @@ namespace glz
266265
value_proxy& operator=(value_proxy&&) = delete;
267266

268267
// Access the value
269-
V& value() { return value_ref.second; }
268+
V& value() { return value_ref; }
270269

271-
const V& value() const { return value_ref.second; }
270+
const V& value() const { return value_ref; }
272271

273272
// Arrow Operator
274-
value_type* operator->() { return &value_ref; }
273+
V* operator->() { return &value_ref; }
274+
275+
const V* operator->() const { return &value_ref; }
276+
277+
V& operator*() { return value_ref; }
278+
279+
const V& operator*() const { return value_ref; }
275280

276281
// Implicit Conversion to V&
277-
operator V&() { return value_ref.second; }
282+
operator V&() { return value_ref; }
283+
284+
operator const V&() const { return value_ref; }
285+
286+
template <class T>
287+
value_proxy& operator=(const T& other) {
288+
value_ref = other;
289+
return *this;
290+
}
278291

279292
bool operator==(const V& other) const { return value() == other; }
280293
};
@@ -283,11 +296,11 @@ namespace glz
283296
class const_value_proxy
284297
{
285298
private:
286-
const_value_type& value_ref;
299+
const V& value_ref;
287300
std::shared_ptr<std::shared_lock<std::shared_mutex>> shared_lock_ptr;
288301

289302
public:
290-
const_value_proxy(const_value_type& value_ref,
303+
const_value_proxy(const V& value_ref,
291304
std::shared_ptr<std::shared_lock<std::shared_mutex>> existing_shared_lock)
292305
: value_ref(value_ref), shared_lock_ptr(existing_shared_lock)
293306
{
@@ -302,24 +315,60 @@ namespace glz
302315
const_value_proxy& operator=(const_value_proxy&&) = delete;
303316

304317
// Access the value
305-
const V& value() const { return value_ref.second; }
318+
const V& value() const { return value_ref; }
306319

307320
// Arrow Operator
308-
const_value_type* operator->() const { return &value_ref; }
321+
const V* operator->() const { return &value_ref; }
322+
323+
const V& operator*() const { return value_ref; }
309324

310325
// Implicit Conversion to const V&
311-
operator const V&() const { return value_ref.second; }
326+
operator const V&() const { return value_ref; }
312327

313328
bool operator==(const V& other) const { return value() == other; }
314329
};
315-
316-
template <class KeyType>
317-
[[deprecated(
318-
"operator[] is not allowed with async_map because it would require expensive unique locks")]] value_proxy
319-
operator[](const KeyType&)
330+
331+
value_proxy operator[](const K& key)
320332
{
321-
static_assert(false_v<KeyType>);
322-
};
333+
// Acquire a shared lock to search for the key
334+
std::shared_lock<std::shared_mutex> shared_lock(mutex);
335+
auto [it, found] = binary_search_key(key);
336+
337+
if (found) {
338+
auto shared_lock_ptr = std::make_shared<std::shared_lock<std::shared_mutex>>(std::move(shared_lock));
339+
return value_proxy((*it)->second, shared_lock_ptr);
340+
}
341+
else {
342+
// Key doesn't exist; release the shared_lock
343+
shared_lock.unlock();
344+
// Acquire a unique lock to modify the map
345+
std::unique_lock<std::shared_mutex> unique_lock(mutex);
346+
347+
// Double-check if the key was inserted by another thread
348+
std::tie(it, found) = binary_search_key(key);
349+
350+
if (!found) {
351+
// Insert a new element with default-constructed value
352+
it = items.insert(it, std::make_unique<std::pair<K, V>>(
353+
std::piecewise_construct,
354+
std::forward_as_tuple(key),
355+
std::forward_as_tuple()));
356+
}
357+
358+
unique_lock.unlock();
359+
shared_lock.lock();
360+
361+
// Find the value once again in case a modification occurred
362+
std::tie(it, found) = binary_search_key(key);
363+
364+
if (!found) {
365+
throw std::out_of_range("Key was removed by another thread");
366+
}
367+
368+
auto shared_lock_ptr = std::make_shared<std::shared_lock<std::shared_mutex>>(std::move(shared_lock));
369+
return value_proxy((*it)->second, shared_lock_ptr);
370+
}
371+
}
323372

324373
// Insert method behaves like std::map::insert
325374
std::pair<iterator, bool> insert(const std::pair<K, V>& pair)
@@ -447,7 +496,7 @@ namespace glz
447496
auto [it, found] = binary_search_key(key);
448497

449498
if (found) {
450-
return value_proxy(*(*it), shared_lock_ptr);
499+
return value_proxy((*it)->second, shared_lock_ptr);
451500
}
452501
else {
453502
throw std::out_of_range("Key not found");
@@ -463,7 +512,7 @@ namespace glz
463512
auto [it, found] = binary_search_key(key);
464513

465514
if (found) {
466-
return const_value_proxy(*(*it), shared_lock_ptr);
515+
return const_value_proxy((*it)->second, shared_lock_ptr);
467516
}
468517
else {
469518
throw std::out_of_range("Key not found");
@@ -521,3 +570,19 @@ namespace glz
521570
}
522571
};
523572
}
573+
574+
namespace glz::detail
575+
{
576+
template <class T>
577+
concept is_value_proxy = requires { T::glaze_value_proxy; };
578+
579+
template <is_value_proxy T>
580+
struct from<JSON, T>
581+
{
582+
template <auto Opts>
583+
static void op(auto&& value, is_context auto&& ctx, auto&& it, auto&& end)
584+
{
585+
read<JSON>::op<Opts>(value.value(), ctx, it, end);
586+
}
587+
};
588+
}

tests/exceptions_test/exceptions_test.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,23 @@ suite async_map_tests = [] {
250250
std::cout << it->second << '\n';
251251
}
252252
};
253+
254+
"async_map write_json"_test = [] {
255+
static_assert(glz::detail::readable_map_t<glz::async_map<std::string, std::atomic<int>>>);
256+
257+
glz::async_map<std::string, std::atomic<int>> map;
258+
map["one"] = 1;
259+
map["two"] = 2;
260+
261+
std::string buffer{};
262+
expect(not glz::write_json(map, buffer));
263+
expect(buffer == R"({"one":1,"two":2})") << buffer;
264+
265+
map.clear();
266+
expect(not glz::read_json(map, buffer));
267+
expect(map.at("one").value() == 1);
268+
expect(map.at("two").value() == 2);
269+
};
253270
};
254271

255272
int main() { return 0; }

0 commit comments

Comments
 (0)