#include "buffer.h"
#include "byteorder.h"
-template<typename T, typename VVV=void>
+template<typename T, typename=void>
struct denc_traits {
static constexpr bool supported = false;
static constexpr bool featured = false;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = true;
};
static constexpr bool supported = true;
static constexpr bool bounded = false;
static constexpr bool featured = false;
+ static constexpr bool need_contiguous = true;
static void bound_encode(const T &o, size_t& p, uint64_t f=0);
static void encode(const T &o, buffer::list::contiguous_appender& p,
uint64_t f=0);
static constexpr bool supported = true;
static constexpr bool bounded = false;
static constexpr bool featured = true;
+ static constexpr bool need_contiguous = true;
static void bound_encode(const T &o, size_t& p, uint64_t f);
static void encode(const T &o, buffer::list::contiguous_appender& p,
uint64_t f);
declared via WRITE_CLASS_DENC, although you can also invoke them explicitly
in your code.
+ - These methods are optimised for contiguous buffer, but denc() will try
+ rebuild a contigous one if the decoded bufferlist is segmented. If you are
+ concerned about the cost, you might want to define yet another method:
+
+ void decode(buffer::list::iterator &p);
+
- These can be defined either explicitly (as above), or can be "magically"
defined all in one go using the DENC macro and DENC_{START,FINISH} helpers
(which work like the legacy {ENCODE,DECODE}_{START,FINISH} macros):
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = true;
+ static constexpr bool need_contiguous = false;
static void bound_encode(const T &o, size_t& p, uint64_t f=0) {
p += sizeof(T);
}
uint64_t f=0) {
o = *(T *)p.get_pos_add(sizeof(o));
}
+ static void decode(T& o, buffer::list::iterator &p) {
+ p.copy(sizeof(T), reinterpret_cast<char*>(&o));
+ }
};
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = true;
+ static constexpr bool need_contiguous = false;
using etype = typename _denc::ExtType<T>::type;
static void bound_encode(const T &o, size_t& p, uint64_t f=0) {
p += sizeof(etype);
uint64_t f=0) {
o = *(etype*)p.get_pos_add(sizeof(etype));
}
+ static void decode(T& o, buffer::list::iterator &p) {
+ etype e;
+ p.copy(sizeof(etype), reinterpret_cast<char*>(&e));
+ o = e;
+ }
};
// varint
traits::decode(o, p, features);
}
+namespace _denc {
+ template<typename T, typename=void>
+ struct has_legacy_denc : std::false_type
+ {};
+ template<typename T>
+ struct has_legacy_denc<T,
+ decltype(std::declval<T&>()
+ .decode(std::declval<bufferlist::iterator&>()))> : std::true_type
+ {
+ static void decode(T& v, bufferlist::iterator& p) {
+ v.decode(p);
+ }
+ };
+ template<typename T>
+ struct has_legacy_denc<T,
+ typename std::enable_if<
+ !denc_traits<T>::need_contiguous>::type> : std::true_type
+ {
+ static void decode(T& v, bufferlist::iterator& p) {
+ denc_traits<T>::decode(v, p);
+ }
+ };
+}
+
+template<typename T,
+ typename traits=denc_traits<T>,
+ typename has_legacy_denc=_denc::has_legacy_denc<T>>
+inline typename std::enable_if<traits::supported &&
+ has_legacy_denc::value>::type denc(
+ T& o,
+ buffer::list::iterator& p)
+{
+ has_legacy_denc::decode(o, p);
+}
// ---------------------------------------------------------------------
// base types and containers
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = false;
static void bound_encode(const value_type& s, size_t& p, uint64_t f=0) {
p += sizeof(uint32_t) + s.size();
::denc(len, p);
decode_nohead(len, s, p);
}
+ static void decode(value_type& s, buffer::list::iterator& p)
+ {
+ uint32_t len;
+ ::denc(len, p);
+ s.clear();
+ p.copy(len, s);
+ }
static void decode_nohead(size_t len, value_type& s,
buffer::ptr::iterator& p) {
s.clear();
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = false;
static void bound_encode(const bufferptr& v, size_t& p, uint64_t f=0) {
p += sizeof(uint32_t) + v.length();
}
::denc(len, p);
v = p.get_ptr(len);
}
+ static void decode(bufferptr& v, buffer::list::iterator& p) {
+ uint32_t len;
+ ::denc(len, p);
+ bufferlist s;
+ p.copy(len, s);
+ if (len) {
+ if (s.get_num_buffers() == 1)
+ v = s.front();
+ else
+ v = buffer::copy(s.c_str(), s.length());
+ }
+ }
};
//
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = false;
static void bound_encode(const bufferlist& v, size_t& p, uint64_t f=0) {
p += sizeof(uint32_t) + v.length();
}
v.clear();
v.push_back(p.get_ptr(len));
}
+ static void decode(bufferlist& v, buffer::list::iterator& p) {
+ uint32_t len;
+ ::denc(len, p);
+ v.clear();
+ p.copy(len, v);
+ }
static void encode_nohead(const bufferlist& v,
buffer::list::contiguous_appender& p) {
p.append(v);
static constexpr bool supported = true;
static constexpr bool featured = a_traits::featured || b_traits::featured ;
static constexpr bool bounded = a_traits::bounded && b_traits::bounded;
+ static constexpr bool need_contiguous = (a_traits::need_contiguous ||
+ b_traits::need_contiguous);
template<typename AA=A>
static typename std::enable_if<sizeof(AA) &&
denc(v.first, p, f);
denc(v.second, p, f);
}
+ template<typename AA=A>
+ static typename std::enable_if<sizeof(AA) && !need_contiguous>::type
+ decode(std::pair<A,B>& v, buffer::list::iterator& p,
+ uint64_t f = 0) {
+ denc(v.first, p);
+ denc(v.second, p);
+ }
};
namespace _denc {
static constexpr bool supported = true;
static constexpr bool featured = traits::featured;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = traits::need_contiguous;
template<typename U=T>
static typename std::enable_if<sizeof(U) &&
denc(num, p);
decode_nohead(num, s, p, f);
}
+ template<typename U=T>
+ static typename std::enable_if<sizeof(U) && !need_contiguous>::type
+ decode(container& s, buffer::list::iterator& p) {
+ uint32_t num;
+ denc(num, p);
+ decode_nohead(num, s, p);
+ }
// nohead
template<typename U=T>
Details::insert(s, std::move(t));
}
}
+ template<typename U=T>
+ static typename std::enable_if<sizeof(U) && !need_contiguous>::type
+ decode_nohead(size_t num, container& s,
+ buffer::list::iterator& p) {
+ s.clear();
+ Details::reserve(s, num);
+ while (num--) {
+ T t;
+ denc(t, p);
+ Details::insert(s, std::move(t));
+ }
+ }
};
template<typename T>
static constexpr bool supported = true;
static constexpr bool featured = traits::featured;
static constexpr bool bounded = traits::bounded;
+ static constexpr bool need_contiguous = traits::need_contiguous;
template<typename U=T>
static typename std::enable_if<sizeof(U) &&
for (auto& e : s)
denc(e, p, f);
}
+ template<typename U=T>
+ static typename std::enable_if<sizeof(U) &&
+ !need_contiguous>::type
+ decode(container& s, buffer::list::iterator& p) {
+ for (auto& e : s) {
+ denc(e, p);
+ }
+ }
};
namespace _denc {
tuple_traits<Ts...>::bounded);
static constexpr bool featured = (denc_traits<T>::featured ||
tuple_traits<Ts...>::featured);
+ static constexpr bool need_contiguous =
+ (denc_traits<T>::need_contiguous ||
+ tuple_traits<Ts...>::need_contiguous);
};
template<>
struct tuple_traits<> {
static constexpr bool supported = true;
static constexpr bool bounded = true;
static constexpr bool featured = false;
+ static constexpr bool need_contiguous = false;
};
}
_denc::indices<I>) {
denc(std::get<I>(s), p);
}
+ template<typename T, size_t I, size_t J, size_t ...Is>
+ static void decode_helper(T& s, buffer::list::iterator& p,
+ _denc::indices<I, J, Is...>) {
+ denc(std::get<I>(s), p);
+ decode_helper(s, p, _denc::indices<J, Is...>{});
+ }
+ template<typename T, size_t I>
+ static void decode_helper(T& s, buffer::list::iterator& p,
+ _denc::indices<I>) {
+ denc(std::get<I>(s), p);
+ }
public:
using traits = _denc::tuple_traits<Ts...>;
static constexpr bool supported = true;
static constexpr bool featured = traits::featured;
static constexpr bool bounded = traits::bounded;
+ static constexpr bool need_contiguous = traits::need_contiguous;
template<typename U = traits>
static void decode(container& s, buffer::ptr::iterator& p, uint64_t f = 0) {
decode_helper(s, p, _denc::build_indices_t<sizeof...(Ts)>{});
}
+ template<typename U = traits>
+ static typename std::enable_if<U::supported &&
+ !need_contiguous>::type
+ decode(container& s, buffer::list::iterator& p, uint64_t f = 0) {
+ decode_helper(s, p, _denc::build_indices_t<sizeof...(Ts)>{});
+ }
};
//
static constexpr bool supported = true;
static constexpr bool featured = traits::featured;
static constexpr bool bounded = false;
+ static constexpr bool need_contiguous = traits::need_contiguous;
template<typename U = T>
static typename std::enable_if<sizeof(U) && !featured>::type
}
}
+ template<typename U = T>
+ static typename std::enable_if<sizeof(U) && !need_contiguous>::type
+ decode(boost::optional<T>& v, buffer::list::iterator& p) {
+ bool x;
+ denc(x, p);
+ if (x) {
+ v = T{};
+ denc(*v, p);
+ } else {
+ v = boost::none;
+ }
+ }
+
template<typename U = T>
static typename std::enable_if<sizeof(U) && !featured>::type
encode_nohead(const boost::optional<T>& v,
static constexpr bool supported = true;
static constexpr bool featured = false;
static constexpr bool bounded = true;
+ static constexpr bool need_contiguous = false;
static void bound_encode(const boost::none_t& v, size_t& p) {
p += sizeof(bool);
static constexpr bool supported = true; \
static constexpr bool featured = false; \
static constexpr bool bounded = b; \
+ static constexpr bool need_contiguous = !_denc::has_legacy_denc<T>::value;\
static void bound_encode(const T& v, size_t& p, uint64_t f=0) { \
v.bound_encode(p); \
} \
static constexpr bool supported = true; \
static constexpr bool featured = true; \
static constexpr bool bounded = b; \
+ static constexpr bool need_contiguous = !_denc::has_legacy_denc<T>::value;\
static void bound_encode(const T& v, size_t& p, uint64_t f) { \
v.bound_encode(p, f); \
} \
traits::encode(o, a, features);
}
-template<typename T, typename traits=denc_traits<T>>
+template<typename T,
+ typename traits=denc_traits<T>>
inline typename std::enable_if<traits::supported &&
- !traits::featured>::type decode(
+ !traits::need_contiguous>::type decode(
T& o,
bufferlist::iterator& p)
{
if (p.end())
throw buffer::end_of_buffer();
- // ensure we get a contigous buffer... until the end of the
- // bufferlist. we don't really know how much we'll need here,
- // unfortunately. hopefully it is already contiguous and we're just
- // bumping the raw ref and initializing the ptr tmp fields.
- bufferptr tmp;
- bufferlist::iterator t = p;
- t.copy_shallow(p.get_bl().length() - p.get_off(), tmp);
- auto cp = tmp.begin();
- traits::decode(o, cp);
- p.advance((ssize_t)cp.get_offset());
+ const auto& bl = p.get_bl();
+ const auto remaining = bl.length() - p.get_off();
+ // it is expensive to rebuild a contigous buffer and drop it, so avoid this.
+ if (p.get_current_ptr().get_raw() != bl.back().get_raw() &&
+ remaining > CEPH_PAGE_SIZE) {
+ traits::decode(o, p);
+ } else {
+ // ensure we get a contigous buffer... until the end of the
+ // bufferlist. we don't really know how much we'll need here,
+ // unfortunately. hopefully it is already contiguous and we're just
+ // bumping the raw ref and initializing the ptr tmp fields.
+ bufferptr tmp;
+ bufferlist::iterator t = p;
+ t.copy_shallow(remaining, tmp);
+ auto cp = tmp.begin();
+ traits::decode(o, cp);
+ p.advance((ssize_t)cp.get_offset());
+ }
}
-template<typename T, typename traits=denc_traits<T>>
+template<typename T,
+ typename traits=denc_traits<T>>
inline typename std::enable_if<traits::supported &&
- traits::featured>::type decode(
+ traits::need_contiguous>::type decode(
T& o,
bufferlist::iterator& p)
{
if (p.end())
throw buffer::end_of_buffer();
+ // ensure we get a contigous buffer... until the end of the
+ // bufferlist. we don't really know how much we'll need here,
+ // unfortunately. hopefully it is already contiguous and we're just
+ // bumping the raw ref and initializing the ptr tmp fields.
bufferptr tmp;
bufferlist::iterator t = p;
t.copy_shallow(p.get_bl().length() - p.get_off(), tmp);
*/
#include <stdio.h>
+#include <numeric>
#include "global/global_init.h"
#include "common/ceph_argparse.h"
ASSERT_EQ(bpi.get_pos(), bl.c_str() + bl.length());
}
}
+
+// unlike legacy_t, Legacy supports denc() also.
+struct Legacy {
+ static unsigned n_denc;
+ static unsigned n_decode;
+ uint8_t value = 0;
+ DENC(Legacy, v, p) {
+ n_denc++;
+ denc(v.value, p);
+ }
+ void decode(buffer::list::iterator& p) {
+ n_decode++;
+ ::decode(value, p);
+ }
+ static void reset() {
+ n_denc = n_decode = 0;
+ }
+ static bufferlist encode_n(unsigned n, const vector<unsigned>& segments);
+};
+WRITE_CLASS_DENC(Legacy)
+unsigned Legacy::n_denc = 0;
+unsigned Legacy::n_decode = 0;
+
+bufferlist Legacy::encode_n(unsigned n, const vector<unsigned>& segments) {
+ vector<Legacy> v;
+ for (unsigned i = 0; i < n; i++) {
+ v.push_back(Legacy());
+ }
+ bufferlist bl(n * sizeof(uint8_t));
+ ::encode(v, bl);
+ bufferlist segmented;
+ auto p = bl.begin();
+
+ auto sum = std::accumulate(segments.begin(), segments.end(), 0u);
+ for (auto i : segments) {
+ buffer::ptr seg;
+ p.copy_deep(bl.length() * i / sum, seg);
+ segmented.push_back(seg);
+ }
+ p.copy_all(segmented);
+ return segmented;
+}
+
+TEST(denc, no_copy_if_segmented_and_lengthy)
+{
+ static_assert(_denc::has_legacy_denc<Legacy>::value,
+ "Legacy do have legacy denc");
+ {
+ // use denc() which shallow_copy() if the buffer is small
+ constexpr unsigned N_COPIES = 42;
+ const vector<unsigned> segs{50, 50}; // half-half
+ bufferlist segmented = Legacy::encode_n(N_COPIES, segs);
+ ASSERT_GT(segmented.get_num_buffers(), 1u);
+ ASSERT_LT(segmented.length(), CEPH_PAGE_SIZE);
+ auto p = segmented.begin();
+ vector<Legacy> v;
+ // denc() is shared by encode() and decode(), so reset() right before
+ // decode()
+ Legacy::reset();
+ ::decode(v, p);
+ ASSERT_EQ(N_COPIES, v.size());
+ ASSERT_EQ(N_COPIES, Legacy::n_denc);
+ ASSERT_EQ(0u, Legacy::n_decode);
+ }
+ {
+ // use denc() which shallow_copy() if the buffer is not segmented and large
+ const unsigned N_COPIES = CEPH_PAGE_SIZE * 2;
+ const vector<unsigned> segs{100};
+ bufferlist segmented = Legacy::encode_n(N_COPIES, segs);
+ ASSERT_EQ(segmented.get_num_buffers(), 1u);
+ ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE);
+ auto p = segmented.begin();
+ vector<Legacy> v;
+ Legacy::reset();
+ ::decode(v, p);
+ ASSERT_EQ(N_COPIES, v.size());
+ ASSERT_EQ(N_COPIES, Legacy::n_denc);
+ ASSERT_EQ(0u, Legacy::n_decode);
+ }
+ {
+ // use denc() which shallow_copy() if the buffer is segmented and large,
+ // but the total size of the chunks to be decoded is smallish.
+ bufferlist large_bl = Legacy::encode_n(CEPH_PAGE_SIZE * 2, {50, 50});
+ bufferlist small_bl = Legacy::encode_n(100, {50, 50});
+ bufferlist segmented;
+ segmented.append(large_bl);
+ segmented.append(small_bl);
+ ASSERT_GT(segmented.get_num_buffers(), 1);
+ ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE);
+ auto p = segmented.begin();
+ p.advance(large_bl.length());
+ ASSERT_LT(segmented.length() - p.get_off(), CEPH_PAGE_SIZE);
+ vector<Legacy> v;
+ Legacy::reset();
+ ::decode(v, p);
+ ASSERT_EQ(Legacy::n_denc, 100u);
+ ASSERT_EQ(0u, Legacy::n_decode);
+ }
+ {
+ // use decode() which avoids deep copy if the buffer is segmented and large
+ bufferlist small_bl = Legacy::encode_n(100, {50, 50});
+ bufferlist large_bl = Legacy::encode_n(CEPH_PAGE_SIZE * 2, {50, 50});
+ bufferlist segmented;
+ segmented.append(small_bl);
+ segmented.append(large_bl);
+ ASSERT_GT(segmented.get_num_buffers(), 1);
+ ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE);
+ auto p = segmented.begin();
+ p.advance(small_bl.length());
+ ASSERT_GT(segmented.length() - p.get_off(), CEPH_PAGE_SIZE);
+ vector<Legacy> v;
+ Legacy::reset();
+ ::decode(v, p);
+ ASSERT_EQ(0u, Legacy::n_denc);
+ ASSERT_EQ(CEPH_PAGE_SIZE * 2, Legacy::n_decode);
+ }
+}