Skip to content
This repository was archived by the owner on Mar 22, 2023. It is now read-only.

Commit 704cf5d

Browse files
yjrobinigchor
authored andcommitted
Add atomic_persistent_aware_ptr with read/write-optimized options.
Add UT to test atomic_persistent_aware_ptr with both options.
1 parent 2083ad3 commit 704cf5d

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
/* Copyright 2021, Intel Corporation */
3+
4+
/**
5+
* @file
6+
* Atomic specialization for persistent-aware self_relative_ptr.
7+
*/
8+
9+
#ifndef LIBPMEMOBJ_CPP_ATOMIC_PERSISTENT_AWARE_PTR_HPP
10+
#define LIBPMEMOBJ_CPP_ATOMIC_PERSISTENT_AWARE_PTR_HPP
11+
12+
#include <libpmemobj++/detail/atomic_backoff.hpp>
13+
#include <libpmemobj++/detail/common.hpp>
14+
#include <libpmemobj++/detail/self_relative_ptr_base_impl.hpp>
15+
#include <libpmemobj++/experimental/atomic_self_relative_ptr.hpp>
16+
#include <libpmemobj++/experimental/self_relative_ptr.hpp>
17+
#include <libpmemobj++/transaction.hpp>
18+
#include <libpmemobj++/utils.hpp>
19+
20+
#include <atomic>
21+
22+
namespace pmem
23+
{
24+
namespace obj
25+
{
26+
namespace experimental
27+
{
28+
29+
/**
30+
* Atomic specialization of a persistent ptr (self_relative_ptr) that manages
31+
* its persistence by itself.
32+
*
33+
* In a multi-threaded scenario, the persistence of this ptr is guaranteed when
34+
* it is visible to (or read by) other threads. Performance-wise, two different
35+
* options are provided: Read-optimized and Write-optimized. See corresponding
36+
* store/load functions for details.
37+
*/
38+
template <typename T, typename ReadOptimized>
39+
struct atomic_persistent_aware_ptr {
40+
private:
41+
using ptr_type = pmem::detail::self_relative_ptr_base_impl<
42+
std::atomic<std::ptrdiff_t>>;
43+
using accessor = pmem::detail::self_relative_accessor<
44+
std::atomic<std::ptrdiff_t>>;
45+
46+
static constexpr uintptr_t IS_DIRTY = 1;
47+
48+
public:
49+
using this_type = atomic_persistent_aware_ptr;
50+
using value_type = pmem::obj::experimental::self_relative_ptr<T>;
51+
using difference_type = typename value_type::difference_type;
52+
53+
constexpr atomic_persistent_aware_ptr() noexcept = default;
54+
55+
/**
56+
* Constructors
57+
*/
58+
atomic_persistent_aware_ptr(value_type value) : ptr()
59+
{
60+
store(value);
61+
}
62+
atomic_persistent_aware_ptr(const atomic_persistent_aware_ptr &) =
63+
delete;
64+
65+
/**
66+
* Read-optimized store does the flush in store function, and clear the
67+
* dirty marker after flush.
68+
*
69+
* @param[in] desired the self_relative_ptr (no dirty flag) to be stored
70+
*
71+
*/
72+
template <typename OPT = ReadOptimized>
73+
typename std::enable_if<std::is_same<OPT, std::true_type>::value>::type
74+
store(value_type desired,
75+
std::memory_order order = std::memory_order_seq_cst) noexcept
76+
{
77+
auto dirty_desired = mark_dirty(desired);
78+
ptr.store(dirty_desired, order);
79+
pool_by_vptr(this).persist(&ptr, sizeof(ptr));
80+
ptr.compare_exchange_strong(dirty_desired, clear_dirty(desired),
81+
order);
82+
/* Flushing is not necessary for correctness, it's enough that
83+
* dirty_desired is persistent */
84+
#if LIBPMEMOBJ_CPP_VG_PMEMCHECK_ENABLED
85+
VALGRIND_PMC_DO_FLUSH(&ptr, sizeof(ptr));
86+
#endif
87+
}
88+
89+
/**
90+
* Write-optimized store updates the ptr with the dirty flag, relies on
91+
* consequent load to do the flush.
92+
*
93+
* @param[in] desired the self_relative_ptr (no dirty flag) to be stored
94+
*
95+
*/
96+
template <typename OPT = ReadOptimized>
97+
typename std::enable_if<!std::is_same<OPT, std::true_type>::value>::type
98+
store(value_type desired,
99+
std::memory_order order = std::memory_order_seq_cst) noexcept
100+
{
101+
ptr.store(mark_dirty(desired), order);
102+
}
103+
104+
/**
105+
* Read-optimized load retries upon dirty ptr, relies on the store
106+
* function to clear the dirty before continue.
107+
* But for correctness, just flush the dirty ptr and return the clear
108+
* ptr for now.
109+
*
110+
* @return the self_relative_ptr (no dirty flag)
111+
*/
112+
template <typename OPT = ReadOptimized>
113+
typename std::enable_if<std::is_same<OPT, std::true_type>::value,
114+
value_type>::type
115+
load(std::memory_order order = std::memory_order_seq_cst) noexcept
116+
{
117+
auto val = ptr.load(order);
118+
if (is_dirty(val)) {
119+
pool_by_vptr(this).persist(&ptr, sizeof(ptr));
120+
}
121+
return clear_dirty(val);
122+
}
123+
124+
/**
125+
* Write-optimized load flushes the ptr with the dirty flag, clears the
126+
* flag using CAS after flush.
127+
* If CAS failed, simply return the old clear ptr, rely on later load to
128+
* clear the dirty flag.
129+
*
130+
* @return the self_relative_ptr (no dirty flag)
131+
*/
132+
template <typename OPT = ReadOptimized>
133+
typename std::enable_if<!std::is_same<OPT, std::true_type>::value,
134+
value_type>::type
135+
load(std::memory_order order = std::memory_order_seq_cst) noexcept
136+
{
137+
auto val = ptr.load(order);
138+
if (is_dirty(val)) {
139+
pool_by_vptr(this).persist(&ptr, sizeof(ptr));
140+
auto clear_val = clear_dirty(val);
141+
ptr.compare_exchange_strong(val, clear_val, order);
142+
#if LIBPMEMOBJ_CPP_VG_PMEMCHECK_ENABLED
143+
VALGRIND_PMC_DO_FLUSH(&ptr, sizeof(ptr));
144+
#endif
145+
return clear_val;
146+
}
147+
return clear_dirty(val);
148+
}
149+
150+
bool
151+
is_lock_free() const noexcept
152+
{
153+
return ptr.is_lock_free();
154+
}
155+
156+
/*
157+
* Operators
158+
*/
159+
operator value_type() const noexcept
160+
{
161+
return load();
162+
}
163+
164+
value_type
165+
operator=(value_type desired) noexcept
166+
{
167+
store(desired);
168+
return desired;
169+
}
170+
171+
private:
172+
value_type
173+
mark_dirty(value_type ptr) const
174+
{
175+
auto dirty_ptr =
176+
reinterpret_cast<uintptr_t>(ptr.get()) | IS_DIRTY;
177+
return value_type{reinterpret_cast<T *>(dirty_ptr)};
178+
}
179+
180+
value_type
181+
clear_dirty(value_type ptr) const
182+
{
183+
auto clear_ptr =
184+
reinterpret_cast<uintptr_t>(ptr.get()) & ~IS_DIRTY;
185+
return value_type{reinterpret_cast<T *>(clear_ptr)};
186+
}
187+
188+
bool
189+
is_dirty(value_type ptr) const
190+
{
191+
return reinterpret_cast<uintptr_t>(ptr.get()) & IS_DIRTY;
192+
}
193+
194+
std::atomic<self_relative_ptr<T>> ptr;
195+
};
196+
} // namespace experimental
197+
} // namespace obj
198+
} // namespace pmem
199+
200+
#endif // LIBPMEMOBJ_CPP_ATOMIC_PERSISTENT_AWARE_PTR_HPP

tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ if(TEST_SELF_RELATIVE_POINTER)
318318

319319
build_test(self_relative_ptr_atomic_pmem ptr/self_relative_ptr_atomic_pmem.cpp)
320320
add_test_generic(NAME self_relative_ptr_atomic_pmem TRACERS none memcheck pmemcheck drd helgrind)
321+
322+
build_test(atomic_persistent_aware_ptr_pmem ptr/atomic_persistent_aware_ptr_pmem.cpp)
323+
add_test_generic(NAME atomic_persistent_aware_ptr_pmem TRACERS none memcheck pmemcheck drd helgrind)
321324
endif()
322325

323326
add_subdirectory(external)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
/* Copyright 2021, Intel Corporation */
3+
4+
#include "thread_helpers.hpp"
5+
#include "unittest.hpp"
6+
7+
#include <libpmemobj++/experimental/atomic_persistent_aware_ptr.hpp>
8+
#include <libpmemobj++/experimental/self_relative_ptr.hpp>
9+
#include <libpmemobj++/make_persistent.hpp>
10+
#include <libpmemobj++/make_persistent_array_atomic.hpp>
11+
#include <libpmemobj++/make_persistent_atomic.hpp>
12+
#include <libpmemobj++/p.hpp>
13+
#include <libpmemobj++/persistent_ptr.hpp>
14+
#include <libpmemobj++/persistent_ptr_base.hpp>
15+
#include <libpmemobj++/pool.hpp>
16+
#include <libpmemobj++/transaction.hpp>
17+
18+
#define LAYOUT "cpp"
19+
20+
namespace nvobj = pmem::obj;
21+
22+
template <typename T>
23+
using self_relative_ptr = pmem::obj::experimental::self_relative_ptr<T>;
24+
template <typename T, typename ReadOptimized>
25+
using atomic_ptr =
26+
pmem::obj::experimental::atomic_persistent_aware_ptr<T, ReadOptimized>;
27+
28+
template <typename ReadOptimized>
29+
struct root {
30+
atomic_ptr<int, ReadOptimized> ptr;
31+
self_relative_ptr<int> non_atomic_ptr;
32+
};
33+
34+
namespace
35+
{
36+
37+
template <typename ReadOptimized>
38+
void
39+
test_ptr_allocation(nvobj::pool<root<ReadOptimized>> &pop)
40+
{
41+
auto r = pop.root();
42+
try {
43+
nvobj::transaction::run(pop, [&] {
44+
UT_ASSERT(r->ptr.load() == nullptr);
45+
r->non_atomic_ptr = nvobj::make_persistent<int>();
46+
});
47+
r->ptr.store(r->non_atomic_ptr);
48+
} catch (...) {
49+
ASSERT_UNREACHABLE;
50+
}
51+
52+
UT_ASSERT(r->ptr.load() != nullptr);
53+
54+
try {
55+
auto non_atomic_ptr = r->ptr.load();
56+
nvobj::transaction::run(pop, [&] {
57+
nvobj::delete_persistent<int>(non_atomic_ptr);
58+
});
59+
r->ptr.store(nullptr);
60+
} catch (...) {
61+
ASSERT_UNREACHABLE;
62+
}
63+
64+
UT_ASSERT(r->ptr.load() == nullptr);
65+
}
66+
67+
} /* namespace */
68+
69+
inline const char *const
70+
BoolToString(bool b)
71+
{
72+
return b ? "_ropt" : "_wopt";
73+
}
74+
75+
template <typename ReadOptimized>
76+
static void
77+
test(char *path)
78+
{
79+
std::string path_str(path);
80+
path_str += BoolToString(ReadOptimized::value);
81+
82+
nvobj::pool<root<ReadOptimized>> pop;
83+
84+
try {
85+
pop = nvobj::pool<root<ReadOptimized>>::create(
86+
path_str, LAYOUT, PMEMOBJ_MIN_POOL, S_IWUSR | S_IRUSR);
87+
} catch (pmem::pool_error &pe) {
88+
UT_FATAL("!pool::create: %s %s", pe.what(), path_str.c_str());
89+
}
90+
91+
test_ptr_allocation(pop);
92+
93+
pop.close();
94+
}
95+
96+
int
97+
main(int argc, char *argv[])
98+
{
99+
if (argc != 2)
100+
UT_FATAL("usage: %s file-name", argv[0]);
101+
auto ret_writeopt = run_test([&] { test<std::false_type>(argv[1]); });
102+
auto ret_readopt = run_test([&] { test<std::true_type>(argv[1]); });
103+
return (ret_writeopt && ret_readopt);
104+
}

0 commit comments

Comments
 (0)