mirror of
https://github.com/ceph/ceph
synced 2026-08-02 07:03:18 +00:00
osd: Introduce optimized EC
Signed-off-by: Alex Ainscow <aainscow@uk.ibm.com>
This commit is contained in:
parent
df60540889
commit
9e2841ab16
@ -48,7 +48,7 @@ set(osd_srcs
|
||||
ECUtilL.cc
|
||||
ECCommon.cc
|
||||
ECBackend.cc
|
||||
ExtentCache.cc
|
||||
ECExtentCache.cc
|
||||
ECTransaction.cc
|
||||
ECUtil.cc
|
||||
ECInject.cc
|
||||
|
||||
1457
src/osd/ECBackend.cc
1457
src/osd/ECBackend.cc
File diff suppressed because it is too large
Load Diff
@ -14,22 +14,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/intrusive/set.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
|
||||
#include "ECCommon.h"
|
||||
#include "ECExtentCache.h"
|
||||
#include "ECListener.h"
|
||||
#include "ECTypes.h"
|
||||
#include "ECUtil.h"
|
||||
#include "OSD.h"
|
||||
#include "PGBackend.h"
|
||||
#include "erasure-code/ErasureCodeInterface.h"
|
||||
#include "ECUtil.h"
|
||||
#include "ECTransaction.h"
|
||||
#include "ExtentCache.h"
|
||||
#include "ECListener.h"
|
||||
#include "include/buffer.h"
|
||||
#include "osd/scrubber/scrub_backend.h"
|
||||
|
||||
/* This file is soon going to be replaced (before next release), so we are going
|
||||
* to simply ignore all deprecated warnings.
|
||||
* */
|
||||
IGNORE_DEPRECATED
|
||||
|
||||
//forward declaration
|
||||
struct ECSubWrite;
|
||||
@ -39,63 +40,61 @@ struct ECSubReadReply;
|
||||
class ECSwitch;
|
||||
|
||||
struct RecoveryMessages;
|
||||
class ECSwitch;
|
||||
|
||||
class ECBackend : public ECCommon {
|
||||
public:
|
||||
PGBackend::RecoveryHandle *open_recovery_op();
|
||||
public:
|
||||
PGBackend::RecoveryHandle *open_recovery_op();
|
||||
|
||||
void run_recovery_op(
|
||||
PGBackend::RecoveryHandle *h,
|
||||
int priority
|
||||
);
|
||||
int priority
|
||||
);
|
||||
|
||||
int recover_object(
|
||||
const hobject_t &hoid,
|
||||
eversion_t v,
|
||||
ObjectContextRef head,
|
||||
ObjectContextRef obc,
|
||||
const hobject_t &hoid,
|
||||
eversion_t v,
|
||||
ObjectContextRef head,
|
||||
ObjectContextRef obc,
|
||||
PGBackend::RecoveryHandle *h
|
||||
);
|
||||
);
|
||||
|
||||
bool _handle_message(
|
||||
OpRequestRef op
|
||||
);
|
||||
bool can_handle_while_inactive(
|
||||
OpRequestRef op
|
||||
);
|
||||
bool _handle_message(OpRequestRef op);
|
||||
bool can_handle_while_inactive(OpRequestRef op);
|
||||
friend struct SubWriteApplied;
|
||||
friend struct SubWriteCommitted;
|
||||
void sub_write_committed(
|
||||
ceph_tid_t tid,
|
||||
eversion_t version,
|
||||
eversion_t last_complete,
|
||||
const ZTracer::Trace &trace);
|
||||
ceph_tid_t tid,
|
||||
eversion_t version,
|
||||
eversion_t last_complete,
|
||||
const ZTracer::Trace &trace
|
||||
);
|
||||
void handle_sub_write(
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace,
|
||||
ECListener& eclistener
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace,
|
||||
ECListener &eclistener
|
||||
) override;
|
||||
void handle_sub_read(
|
||||
pg_shard_t from,
|
||||
const ECSubRead &op,
|
||||
ECSubReadReply *reply,
|
||||
const ZTracer::Trace &trace
|
||||
pg_shard_t from,
|
||||
const ECSubRead &op,
|
||||
ECSubReadReply *reply,
|
||||
const ZTracer::Trace &trace
|
||||
);
|
||||
void handle_sub_write_reply(
|
||||
pg_shard_t from,
|
||||
const ECSubWriteReply &op,
|
||||
const ZTracer::Trace &trace
|
||||
pg_shard_t from,
|
||||
const ECSubWriteReply &op,
|
||||
const ZTracer::Trace &trace
|
||||
);
|
||||
void handle_sub_read_reply(
|
||||
pg_shard_t from,
|
||||
ECSubReadReply &op,
|
||||
const ZTracer::Trace &trace
|
||||
pg_shard_t from,
|
||||
ECSubReadReply &op,
|
||||
const ZTracer::Trace &trace
|
||||
);
|
||||
|
||||
/// @see ReadOp below
|
||||
void check_recovery_sources(const OSDMapRef& osdmap);
|
||||
void check_recovery_sources(const OSDMapRef &osdmap);
|
||||
|
||||
void on_change();
|
||||
void clear_recovery_state();
|
||||
@ -107,26 +106,27 @@ public:
|
||||
}
|
||||
|
||||
void submit_transaction(
|
||||
const hobject_t &hoid,
|
||||
const object_stat_sum_t &delta_stats,
|
||||
const eversion_t &at_version,
|
||||
PGTransactionUPtr &&t,
|
||||
const eversion_t &trim_to,
|
||||
const eversion_t &pg_committed_to,
|
||||
std::vector<pg_log_entry_t>&& log_entries,
|
||||
std::optional<pg_hit_set_history_t> &hset_history,
|
||||
Context *on_all_commit,
|
||||
ceph_tid_t tid,
|
||||
osd_reqid_t reqid,
|
||||
OpRequestRef op
|
||||
const hobject_t &hoid,
|
||||
const object_stat_sum_t &delta_stats,
|
||||
const eversion_t &at_version,
|
||||
PGTransactionUPtr &&t,
|
||||
const eversion_t &trim_to,
|
||||
const eversion_t &pg_committed_to,
|
||||
std::vector<pg_log_entry_t> &&log_entries,
|
||||
std::optional<pg_hit_set_history_t> &hset_history,
|
||||
Context *on_all_commit,
|
||||
ceph_tid_t tid,
|
||||
osd_reqid_t reqid,
|
||||
OpRequestRef op
|
||||
);
|
||||
|
||||
int objects_read_sync(
|
||||
const hobject_t &hoid,
|
||||
uint64_t off,
|
||||
uint64_t len,
|
||||
uint32_t op_flags,
|
||||
ceph::buffer::list *bl);
|
||||
const hobject_t &hoid,
|
||||
uint64_t off,
|
||||
uint64_t len,
|
||||
uint32_t op_flags,
|
||||
ceph::buffer::list *bl
|
||||
);
|
||||
|
||||
/**
|
||||
* Async read mechanism
|
||||
@ -147,19 +147,35 @@ public:
|
||||
* check_recovery_sources.
|
||||
*/
|
||||
void objects_read_and_reconstruct(
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
GenContextURef<ECCommon::ec_extents_t &&> &&func) override;
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
uint64_t object_size,
|
||||
GenContextURef<ECCommon::ec_extents_t&&> &&func
|
||||
) override;
|
||||
|
||||
/**
|
||||
* Async read mechanism for read-modify-write (RMW) code paths. Here wthe
|
||||
* client already knows the set of shard reads that are required, so these
|
||||
* can be passed in directly. The "fast_read" mechanism is not needed.
|
||||
*
|
||||
* Otherwise this is the same as objects_read_and_reconstruct.
|
||||
*/
|
||||
void objects_read_and_reconstruct_for_rmw(
|
||||
std::map<hobject_t, read_request_t> &&reads,
|
||||
GenContextURef<ECCommon::ec_extents_t&&> &&func
|
||||
) override;
|
||||
|
||||
void objects_read_async(
|
||||
const hobject_t &hoid,
|
||||
uint64_t object_size,
|
||||
const std::list<std::pair<ec_align_t,
|
||||
std::pair<ceph::buffer::list*, Context*>>> &to_read,
|
||||
Context *on_complete,
|
||||
bool fast_read = false);
|
||||
const hobject_t &hoid,
|
||||
uint64_t object_size,
|
||||
const std::list<std::pair<ec_align_t,
|
||||
std::pair<ceph::buffer::list*, Context*>>> &
|
||||
to_read,
|
||||
Context *on_complete,
|
||||
bool fast_read = false
|
||||
);
|
||||
|
||||
private:
|
||||
private:
|
||||
friend struct ECRecoveryHandle;
|
||||
|
||||
void kick_reads();
|
||||
@ -194,159 +210,185 @@ private:
|
||||
* Transaction, and reads in a RecoveryMessages object which is passed
|
||||
* among the recovery methods.
|
||||
*/
|
||||
public:
|
||||
public:
|
||||
struct RecoveryBackend {
|
||||
CephContext* cct;
|
||||
CephContext *cct;
|
||||
const coll_t &coll;
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
const ECUtil::stripe_info_t& sinfo;
|
||||
ReadPipeline& read_pipeline;
|
||||
UnstableHashInfoRegistry& unstable_hashinfo_registry;
|
||||
const ECUtil::stripe_info_t &sinfo;
|
||||
ReadPipeline &read_pipeline;
|
||||
UnstableHashInfoRegistry &unstable_hashinfo_registry;
|
||||
// TODO: lay an interface down here
|
||||
ECListener* parent;
|
||||
ECBackend* ecbackend;
|
||||
ECListener *parent;
|
||||
ECBackend *ecbackend;
|
||||
|
||||
ECListener *get_parent() const { return parent; }
|
||||
const OSDMapRef& get_osdmap() const { return get_parent()->pgb_get_osdmap(); }
|
||||
epoch_t get_osdmap_epoch() const { return get_parent()->pgb_get_osdmap_epoch(); }
|
||||
|
||||
const OSDMapRef &get_osdmap() const {
|
||||
return get_parent()->pgb_get_osdmap();
|
||||
}
|
||||
|
||||
epoch_t get_osdmap_epoch() const {
|
||||
return get_parent()->pgb_get_osdmap_epoch();
|
||||
}
|
||||
|
||||
const pg_info_t &get_info() { return get_parent()->get_info(); }
|
||||
void add_temp_obj(const hobject_t &oid) { get_parent()->add_temp_obj(oid); }
|
||||
void clear_temp_obj(const hobject_t &oid) { get_parent()->clear_temp_obj(oid); }
|
||||
|
||||
RecoveryBackend(CephContext* cct,
|
||||
const coll_t &coll,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t& sinfo,
|
||||
ReadPipeline& read_pipeline,
|
||||
UnstableHashInfoRegistry& unstable_hashinfo_registry,
|
||||
ECListener* parent,
|
||||
ECBackend* ecbackend);
|
||||
struct RecoveryOp {
|
||||
hobject_t hoid;
|
||||
eversion_t v;
|
||||
std::set<pg_shard_t> missing_on;
|
||||
std::set<shard_id_t> missing_on_shards;
|
||||
void clear_temp_obj(const hobject_t &oid) {
|
||||
get_parent()->clear_temp_obj(oid);
|
||||
}
|
||||
|
||||
ObjectRecoveryInfo recovery_info;
|
||||
ObjectRecoveryProgress recovery_progress;
|
||||
RecoveryBackend(CephContext *cct,
|
||||
const coll_t &coll,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
ReadPipeline &read_pipeline,
|
||||
UnstableHashInfoRegistry &unstable_hashinfo_registry,
|
||||
ECListener *parent,
|
||||
ECBackend *ecbackend);
|
||||
|
||||
enum state_t { IDLE, READING, WRITING, COMPLETE } state;
|
||||
struct RecoveryOp {
|
||||
hobject_t hoid;
|
||||
eversion_t v;
|
||||
std::set<pg_shard_t> missing_on;
|
||||
std::set<shard_id_t> missing_on_shards;
|
||||
|
||||
static const char* tostr(state_t state) {
|
||||
switch (state) {
|
||||
case RecoveryOp::IDLE:
|
||||
return "IDLE";
|
||||
case RecoveryOp::READING:
|
||||
return "READING";
|
||||
case RecoveryOp::WRITING:
|
||||
return "WRITING";
|
||||
case RecoveryOp::COMPLETE:
|
||||
return "COMPLETE";
|
||||
default:
|
||||
ceph_abort();
|
||||
return "";
|
||||
ObjectRecoveryInfo recovery_info;
|
||||
ObjectRecoveryProgress recovery_progress;
|
||||
|
||||
enum state_t { IDLE, READING, WRITING, COMPLETE } state;
|
||||
|
||||
static const char *tostr(state_t state) {
|
||||
switch (state) {
|
||||
case RecoveryOp::IDLE:
|
||||
return "IDLE";
|
||||
case RecoveryOp::READING:
|
||||
return "READING";
|
||||
case RecoveryOp::WRITING:
|
||||
return "WRITING";
|
||||
case RecoveryOp::COMPLETE:
|
||||
return "COMPLETE";
|
||||
default:
|
||||
ceph_abort();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// must be filled if state == WRITING
|
||||
std::optional<ECUtil::shard_extent_map_t> returned_data;
|
||||
std::map<std::string, ceph::buffer::list, std::less<>> xattrs;
|
||||
ECUtil::HashInfoRef hinfo;
|
||||
ObjectContextRef obc;
|
||||
std::set<pg_shard_t> waiting_on_pushes;
|
||||
|
||||
void dump(ceph::Formatter *f) const;
|
||||
|
||||
RecoveryOp() : state(IDLE) {}
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "RecoveryOp("
|
||||
<< "hoid=" << hoid
|
||||
<< " v=" << v
|
||||
<< " missing_on=" << missing_on
|
||||
<< " missing_on_shards=" << missing_on_shards
|
||||
<< " recovery_info=" << recovery_info
|
||||
<< " recovery_progress=" << recovery_progress
|
||||
<< " obc refcount=" << obc.use_count()
|
||||
<< " state=" << ECBackend::RecoveryBackend::RecoveryOp::tostr(state)
|
||||
<< " waiting_on_pushes=" << waiting_on_pushes
|
||||
<< ")";
|
||||
}
|
||||
};
|
||||
|
||||
std::map<hobject_t, RecoveryOp> recovery_ops;
|
||||
|
||||
uint64_t get_recovery_chunk_size() const {
|
||||
return round_up_to(cct->_conf->osd_recovery_max_chunk,
|
||||
sinfo.get_stripe_width());
|
||||
}
|
||||
|
||||
// must be filled if state == WRITING
|
||||
std::map<int, ceph::buffer::list> returned_data;
|
||||
std::map<std::string, ceph::buffer::list, std::less<>> xattrs;
|
||||
ECUtil::HashInfoRef hinfo;
|
||||
ObjectContextRef obc;
|
||||
std::set<pg_shard_t> waiting_on_pushes;
|
||||
virtual ~RecoveryBackend() = default;
|
||||
virtual void commit_txn_send_replies(
|
||||
ceph::os::Transaction &&txn,
|
||||
std::map<int, MOSDPGPushReply*> replies) = 0;
|
||||
void dispatch_recovery_messages(RecoveryMessages &m, int priority);
|
||||
|
||||
// valid in state READING
|
||||
std::pair<uint64_t, uint64_t> extent_requested;
|
||||
|
||||
void dump(ceph::Formatter *f) const;
|
||||
|
||||
RecoveryOp() : state(IDLE) {}
|
||||
PGBackend::RecoveryHandle *open_recovery_op();
|
||||
void run_recovery_op(
|
||||
struct ECRecoveryHandle &h,
|
||||
int priority);
|
||||
int recover_object(
|
||||
const hobject_t &hoid,
|
||||
eversion_t v,
|
||||
ObjectContextRef head,
|
||||
ObjectContextRef obc,
|
||||
PGBackend::RecoveryHandle *h);
|
||||
void continue_recovery_op(
|
||||
RecoveryBackend::RecoveryOp &op,
|
||||
RecoveryMessages *m);
|
||||
void handle_recovery_read_complete(
|
||||
const hobject_t &hoid,
|
||||
ECUtil::shard_extent_map_t &&buffers_read,
|
||||
std::optional<std::map<std::string, ceph::buffer::list, std::less<>>>
|
||||
attrs,
|
||||
const ECUtil::shard_extent_set_t &want_to_read,
|
||||
RecoveryMessages *m);
|
||||
void handle_recovery_push(
|
||||
const PushOp &op,
|
||||
RecoveryMessages *m,
|
||||
bool is_repair);
|
||||
void handle_recovery_push_reply(
|
||||
const PushReplyOp &op,
|
||||
pg_shard_t from,
|
||||
RecoveryMessages *m);
|
||||
friend struct RecoveryMessages;
|
||||
void _failed_push(const hobject_t &hoid, ECCommon::read_result_t &res);
|
||||
};
|
||||
friend ostream &operator<<(ostream &lhs, const RecoveryOp &rhs);
|
||||
std::map<hobject_t, RecoveryOp> recovery_ops;
|
||||
|
||||
uint64_t get_recovery_chunk_size() const {
|
||||
return round_up_to(cct->_conf->osd_recovery_max_chunk,
|
||||
sinfo.get_stripe_width());
|
||||
}
|
||||
|
||||
virtual ~RecoveryBackend() = default;
|
||||
virtual void commit_txn_send_replies(
|
||||
ceph::os::Transaction&& txn,
|
||||
std::map<int, MOSDPGPushReply*> replies) = 0;
|
||||
void dispatch_recovery_messages(RecoveryMessages &m, int priority);
|
||||
|
||||
PGBackend::RecoveryHandle *open_recovery_op();
|
||||
void run_recovery_op(
|
||||
struct ECRecoveryHandle &h,
|
||||
int priority);
|
||||
int recover_object(
|
||||
const hobject_t &hoid,
|
||||
eversion_t v,
|
||||
ObjectContextRef head,
|
||||
ObjectContextRef obc,
|
||||
PGBackend::RecoveryHandle *h);
|
||||
void continue_recovery_op(
|
||||
RecoveryBackend::RecoveryOp &op,
|
||||
RecoveryMessages *m);
|
||||
void handle_recovery_read_complete(
|
||||
const hobject_t &hoid,
|
||||
boost::tuple<uint64_t, uint64_t, std::map<pg_shard_t, ceph::buffer::list> > &to_read,
|
||||
std::optional<std::map<std::string, ceph::buffer::list, std::less<>> > attrs,
|
||||
RecoveryMessages *m);
|
||||
void handle_recovery_push(
|
||||
const PushOp &op,
|
||||
RecoveryMessages *m,
|
||||
bool is_repair);
|
||||
void handle_recovery_push_reply(
|
||||
const PushReplyOp &op,
|
||||
pg_shard_t from,
|
||||
RecoveryMessages *m);
|
||||
friend struct RecoveryMessages;
|
||||
int get_ec_data_chunk_count() const {
|
||||
return ec_impl->get_data_chunk_count();
|
||||
}
|
||||
void _failed_push(const hobject_t &hoid, ECCommon::read_result_t &res);
|
||||
};
|
||||
struct ECRecoveryBackend : RecoveryBackend {
|
||||
ECRecoveryBackend(CephContext* cct,
|
||||
const coll_t &coll,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t& sinfo,
|
||||
ReadPipeline& read_pipeline,
|
||||
UnstableHashInfoRegistry& unstable_hashinfo_registry,
|
||||
PGBackend::Listener* parent,
|
||||
ECBackend* ecbackend)
|
||||
: RecoveryBackend(cct, coll, std::move(ec_impl), sinfo, read_pipeline, unstable_hashinfo_registry, parent->get_eclistener(), ecbackend),
|
||||
parent(parent) {
|
||||
}
|
||||
ECRecoveryBackend(CephContext *cct,
|
||||
const coll_t &coll,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
ReadPipeline &read_pipeline,
|
||||
UnstableHashInfoRegistry &unstable_hashinfo_registry,
|
||||
PGBackend::Listener *parent,
|
||||
ECBackend *ecbackend)
|
||||
: RecoveryBackend(cct, coll, std::move(ec_impl), sinfo, read_pipeline,
|
||||
unstable_hashinfo_registry, parent->get_eclistener(),
|
||||
ecbackend),
|
||||
parent(parent) {}
|
||||
|
||||
void commit_txn_send_replies(
|
||||
ceph::os::Transaction&& txn,
|
||||
std::map<int, MOSDPGPushReply*> replies) override;
|
||||
ceph::os::Transaction &&txn,
|
||||
std::map<int, MOSDPGPushReply*> replies) override;
|
||||
|
||||
PGBackend::Listener *get_parent() const { return parent; }
|
||||
|
||||
private:
|
||||
private:
|
||||
PGBackend::Listener *parent;
|
||||
};
|
||||
friend ostream &operator<<(ostream &lhs, const RecoveryBackend::RecoveryOp &rhs);
|
||||
|
||||
friend ostream &operator<<(ostream &lhs,
|
||||
const RecoveryBackend::RecoveryOp &rhs
|
||||
);
|
||||
friend struct RecoveryMessages;
|
||||
friend struct OnRecoveryReadComplete;
|
||||
friend struct RecoveryReadCompleter;
|
||||
|
||||
void handle_recovery_push(
|
||||
const PushOp &op,
|
||||
RecoveryMessages *m,
|
||||
bool is_repair);
|
||||
const PushOp &op,
|
||||
RecoveryMessages *m,
|
||||
bool is_repair
|
||||
);
|
||||
|
||||
public:
|
||||
PGBackend::Listener *parent;
|
||||
CephContext *cct;
|
||||
ECSwitch *switcher;
|
||||
struct ReadPipeline read_pipeline;
|
||||
struct RMWPipeline rmw_pipeline;
|
||||
struct ECRecoveryBackend recovery_backend;
|
||||
PGBackend::Listener *parent;
|
||||
CephContext *cct;
|
||||
ECSwitch *switcher;
|
||||
ReadPipeline read_pipeline;
|
||||
RMWPipeline rmw_pipeline;
|
||||
ECRecoveryBackend recovery_backend;
|
||||
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
|
||||
@ -355,46 +397,55 @@ public:
|
||||
/**
|
||||
* ECRecPred
|
||||
*
|
||||
* Determines the whether _have is sufficient to recover an object
|
||||
* Determines whether _have is sufficient to recover an object
|
||||
*/
|
||||
class ECRecPred : public IsPGRecoverablePredicate {
|
||||
std::set<int> want;
|
||||
shard_id_set want;
|
||||
const ECUtil::stripe_info_t *sinfo;
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
public:
|
||||
explicit ECRecPred(ceph::ErasureCodeInterfaceRef ec_impl) : ec_impl(ec_impl) {
|
||||
for (unsigned i = 0; i < ec_impl->get_chunk_count(); ++i) {
|
||||
want.insert(i);
|
||||
}
|
||||
}
|
||||
bool operator()(const std::set<pg_shard_t> &_have) const override {
|
||||
std::set<int> have;
|
||||
for (std::set<pg_shard_t>::const_iterator i = _have.begin();
|
||||
i != _have.end();
|
||||
++i) {
|
||||
have.insert(static_cast<int>(i->shard));
|
||||
}
|
||||
std::map<int, std::vector<std::pair<int, int>>> min;
|
||||
|
||||
return ec_impl->minimum_to_decode(want, have, &min) == 0;
|
||||
public:
|
||||
explicit ECRecPred(const ECUtil::stripe_info_t *sinfo,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl) :
|
||||
sinfo(sinfo), ec_impl(ec_impl) {
|
||||
want.insert_range(shard_id_t(0), sinfo->get_k_plus_m());
|
||||
}
|
||||
|
||||
bool operator()(const std::set<pg_shard_t> &_have) const override {
|
||||
shard_id_set have;
|
||||
for (pg_shard_t p: _have) {
|
||||
have.insert(p.shard);
|
||||
}
|
||||
std::unique_ptr<shard_id_map<std::vector<std::pair<int, int>>>>
|
||||
min_sub_chunks = nullptr;
|
||||
if (sinfo->supports_sub_chunks()) {
|
||||
min_sub_chunks = std::make_unique<shard_id_map<std::vector<std::pair<
|
||||
int, int>>>>(sinfo->get_k_plus_m());
|
||||
}
|
||||
shard_id_set min;
|
||||
|
||||
return ec_impl->minimum_to_decode(want, have, min, min_sub_chunks.get())
|
||||
== 0;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ECRecPred> get_is_recoverable_predicate() const {
|
||||
return std::make_unique<ECRecPred>(ec_impl);
|
||||
return std::make_unique<ECRecPred>(&sinfo, ec_impl);
|
||||
}
|
||||
|
||||
unsigned get_ec_data_chunk_count() const {
|
||||
return ec_impl->get_data_chunk_count();
|
||||
unsigned get_ec_data_chunk_count() const {
|
||||
return sinfo.get_k();
|
||||
}
|
||||
|
||||
int get_ec_stripe_chunk_size() const {
|
||||
return sinfo.get_chunk_size();
|
||||
}
|
||||
uint64_t object_size_to_shard_size(const uint64_t size,
|
||||
shard_id_t shard) const {
|
||||
if (size == std::numeric_limits<uint64_t>::max()) {
|
||||
return size;
|
||||
}
|
||||
return sinfo.logical_to_next_chunk_offset(size);
|
||||
|
||||
uint64_t object_size_to_shard_size(const uint64_t size, shard_id_t shard
|
||||
) const {
|
||||
return sinfo.object_size_to_shard_size(size, shard);
|
||||
}
|
||||
|
||||
/**
|
||||
* ECReadPred
|
||||
*
|
||||
@ -403,16 +454,21 @@ public:
|
||||
class ECReadPred : public IsPGReadablePredicate {
|
||||
pg_shard_t whoami;
|
||||
ECRecPred rec_pred;
|
||||
public:
|
||||
|
||||
public:
|
||||
ECReadPred(
|
||||
pg_shard_t whoami,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl) : whoami(whoami), rec_pred(ec_impl) {}
|
||||
pg_shard_t whoami,
|
||||
const ECUtil::stripe_info_t *sinfo,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl) : whoami(whoami), rec_pred(sinfo, ec_impl) {}
|
||||
|
||||
bool operator()(const std::set<pg_shard_t> &_have) const override {
|
||||
return _have.count(whoami) && rec_pred(_have);
|
||||
}
|
||||
};
|
||||
std::unique_ptr<ECReadPred> get_is_readable_predicate(pg_shard_t whoami) const {
|
||||
return std::make_unique<ECReadPred>(whoami, ec_impl);
|
||||
|
||||
std::unique_ptr<ECReadPred>
|
||||
get_is_readable_predicate(pg_shard_t whoami) const {
|
||||
return std::make_unique<ECReadPred>(whoami, &sinfo, ec_impl);
|
||||
}
|
||||
|
||||
const ECUtil::stripe_info_t sinfo;
|
||||
@ -424,34 +480,41 @@ public:
|
||||
int,
|
||||
std::map<std::string, ceph::bufferlist, std::less<>>,
|
||||
size_t
|
||||
> get_attrs_n_size_from_disk(const hobject_t& hoid);
|
||||
> get_attrs_n_size_from_disk(const hobject_t &hoid);
|
||||
|
||||
public:
|
||||
int object_stat(const hobject_t &hoid, struct stat* st);
|
||||
ECUtil::HashInfoRef get_hinfo_from_disk(hobject_t oid);
|
||||
|
||||
std::optional<object_info_t> get_object_info_from_obc(
|
||||
ObjectContextRef &obc_map
|
||||
);
|
||||
|
||||
public:
|
||||
int object_stat(const hobject_t &hoid, struct stat *st);
|
||||
ECBackend(
|
||||
PGBackend::Listener *pg,
|
||||
CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
uint64_t stripe_width,
|
||||
ECSwitch *s,
|
||||
ECExtentCache::LRU &ignored);
|
||||
PGBackend::Listener *pg,
|
||||
CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
uint64_t stripe_width,
|
||||
ECSwitch *s,
|
||||
ECExtentCache::LRU &ec_extent_cache_lru
|
||||
);
|
||||
|
||||
int objects_get_attrs(
|
||||
const hobject_t &hoid,
|
||||
std::map<std::string, ceph::buffer::list, std::less<>> *out);
|
||||
const hobject_t &hoid,
|
||||
std::map<std::string, ceph::buffer::list, std::less<>> *out
|
||||
);
|
||||
|
||||
bool auto_repair_supported() const { return true; }
|
||||
|
||||
int be_deep_scrub(
|
||||
const hobject_t &poid,
|
||||
ScrubMap &map,
|
||||
ScrubMapBuilder &pos,
|
||||
ScrubMap::object &o);
|
||||
const hobject_t &poid,
|
||||
ScrubMap &map,
|
||||
ScrubMapBuilder &pos,
|
||||
ScrubMap::object &o
|
||||
);
|
||||
|
||||
uint64_t be_get_ondisk_size(uint64_t logical_size, shard_id_t ignored) const {
|
||||
return sinfo.logical_to_next_chunk_offset(logical_size);
|
||||
uint64_t be_get_ondisk_size(uint64_t logical_size, shard_id_t shard_id
|
||||
) const {
|
||||
return object_size_to_shard_size(logical_size, shard_id);
|
||||
}
|
||||
};
|
||||
ostream &operator<<(ostream &lhs, const ECBackend::RMWPipeline::pipeline_state_t &rhs);
|
||||
|
||||
END_IGNORE_DEPRECATED
|
||||
|
||||
1304
src/osd/ECCommon.cc
1304
src/osd/ECCommon.cc
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,6 @@
|
||||
#include "ECUtil.h"
|
||||
#include "ECTypes.h"
|
||||
#if WITH_CRIMSON
|
||||
#include "ExtentCache.h"
|
||||
#include "crimson/osd/object_context.h"
|
||||
#include "os/Transaction.h"
|
||||
#include "osd/OSDMap.h"
|
||||
@ -32,7 +31,7 @@ struct ECTransaction {
|
||||
struct WritePlan {
|
||||
bool invalidates_cache = false; // Yes, both are possible
|
||||
std::map<hobject_t,extent_set> to_read;
|
||||
std::map<hobject_t,extent_set> will_write; // superset of to_read
|
||||
std::map<hobject_t,extent_set> will_write;
|
||||
|
||||
std::map<hobject_t,ECUtil::HashInfoRef> hash_infos;
|
||||
};
|
||||
@ -45,8 +44,9 @@ typedef crimson::osd::ObjectContextRef ObjectContextRef;
|
||||
#endif
|
||||
|
||||
#include "ECTransaction.h"
|
||||
#include "ExtentCache.h"
|
||||
#include "ECExtentCache.h"
|
||||
#include "ECListener.h"
|
||||
#include "common/dout.h"
|
||||
|
||||
//forward declaration
|
||||
struct ECSubWrite;
|
||||
@ -56,36 +56,86 @@ struct ECCommon {
|
||||
struct ec_extent_t {
|
||||
int err;
|
||||
extent_map emap;
|
||||
ECUtil::shard_extent_map_t shard_extent_map;
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << err << "," << emap;
|
||||
}
|
||||
};
|
||||
friend std::ostream &operator<<(std::ostream &lhs, const ec_extent_t &rhs);
|
||||
|
||||
using ec_extents_t = std::map<hobject_t, ec_extent_t>;
|
||||
|
||||
virtual ~ECCommon() = default;
|
||||
|
||||
virtual void handle_sub_write(
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace,
|
||||
ECListener& eclistener
|
||||
) = 0;
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace,
|
||||
ECListener &eclistener) = 0;
|
||||
|
||||
virtual void objects_read_and_reconstruct(
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
GenContextURef<ec_extents_t &&> &&func) = 0;
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
uint64_t object_size,
|
||||
GenContextURef<ec_extents_t&&> &&func) = 0;
|
||||
|
||||
struct shard_read_t {
|
||||
extent_set extents;
|
||||
std::optional<std::vector<std::pair<int, int>>> subchunk;
|
||||
pg_shard_t pg_shard;
|
||||
bool operator==(const shard_read_t &other) const;
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "shard_read_t(extents=[" << extents << "]"
|
||||
<< ", subchunk=" << subchunk
|
||||
<< ", pg_shard=" << pg_shard
|
||||
<< ")";
|
||||
}
|
||||
};
|
||||
|
||||
struct read_request_t {
|
||||
const std::list<ec_align_t> to_read;
|
||||
std::map<pg_shard_t, std::vector<std::pair<int, int>>> need;
|
||||
bool want_attrs;
|
||||
const uint32_t flags = 0;
|
||||
const ECUtil::shard_extent_set_t shard_want_to_read;
|
||||
shard_id_map<shard_read_t> shard_reads;
|
||||
bool want_attrs = false;
|
||||
uint64_t object_size;
|
||||
|
||||
read_request_t(
|
||||
const std::list<ec_align_t> &to_read,
|
||||
const std::map<pg_shard_t, std::vector<std::pair<int, int>>> &need,
|
||||
bool want_attrs)
|
||||
: to_read(to_read), need(need), want_attrs(want_attrs) {}
|
||||
const std::list<ec_align_t> &to_read,
|
||||
const ECUtil::shard_extent_set_t &shard_want_to_read,
|
||||
bool want_attrs, uint64_t object_size) :
|
||||
to_read(to_read),
|
||||
flags(to_read.front().flags),
|
||||
shard_want_to_read(shard_want_to_read),
|
||||
shard_reads(shard_want_to_read.get_max_shards()),
|
||||
want_attrs(want_attrs),
|
||||
object_size(object_size) {}
|
||||
|
||||
read_request_t(const ECUtil::shard_extent_set_t &shard_want_to_read,
|
||||
bool want_attrs, uint64_t object_size) :
|
||||
shard_want_to_read(shard_want_to_read),
|
||||
shard_reads(shard_want_to_read.get_max_shards()),
|
||||
want_attrs(want_attrs),
|
||||
object_size(object_size) {}
|
||||
|
||||
bool operator==(const read_request_t &other) const;
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "read_request_t(to_read=[" << to_read << "]"
|
||||
<< ", flags=" << flags
|
||||
<< ", shard_want_to_read=" << shard_want_to_read
|
||||
<< ", shard_reads=" << shard_reads
|
||||
<< ", want_attrs=" << want_attrs
|
||||
<< ")";
|
||||
}
|
||||
};
|
||||
friend std::ostream &operator<<(std::ostream &lhs, const read_request_t &rhs);
|
||||
|
||||
virtual void objects_read_and_reconstruct_for_rmw(
|
||||
std::map<hobject_t, read_request_t> &&to_read,
|
||||
GenContextURef<ec_extents_t&&> &&func) = 0;
|
||||
|
||||
struct ReadOp;
|
||||
/**
|
||||
* Low level async read mechanism
|
||||
@ -111,19 +161,30 @@ struct ECCommon {
|
||||
struct read_result_t {
|
||||
int r;
|
||||
std::map<pg_shard_t, int> errors;
|
||||
std::optional<std::map<std::string, ceph::buffer::list, std::less<>> > attrs;
|
||||
std::list<
|
||||
boost::tuple<
|
||||
uint64_t, uint64_t, std::map<pg_shard_t, ceph::buffer::list> > > returned;
|
||||
read_result_t() : r(0) {}
|
||||
std::optional<std::map<std::string, ceph::buffer::list, std::less<>>> attrs;
|
||||
ECUtil::shard_extent_map_t buffers_read;
|
||||
ECUtil::shard_extent_set_t processed_read_requests;
|
||||
|
||||
read_result_t(const ECUtil::stripe_info_t *sinfo) :
|
||||
r(0), buffers_read(sinfo),
|
||||
processed_read_requests(sinfo->get_k_plus_m()) {}
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "read_result_t(r=" << r << ", errors=" << errors;
|
||||
if (attrs) {
|
||||
os << ", attrs=" << *(attrs);
|
||||
} else {
|
||||
os << ", noattrs";
|
||||
}
|
||||
os << ", buffers_read=" << buffers_read << ")";
|
||||
}
|
||||
};
|
||||
|
||||
struct ReadCompleter {
|
||||
virtual void finish_single_request(
|
||||
const hobject_t &hoid,
|
||||
read_result_t &res,
|
||||
std::list<ec_align_t> to_read,
|
||||
std::set<int> wanted_to_read) = 0;
|
||||
const hobject_t &hoid,
|
||||
read_result_t &&res,
|
||||
ECCommon::read_request_t &req) = 0;
|
||||
|
||||
virtual void finish(int priority) && = 0;
|
||||
|
||||
@ -131,26 +192,35 @@ struct ECCommon {
|
||||
};
|
||||
|
||||
friend struct CallClientContexts;
|
||||
|
||||
struct ClientAsyncReadStatus {
|
||||
unsigned objects_to_read;
|
||||
GenContextURef<ec_extents_t &&> func;
|
||||
GenContextURef<ec_extents_t&&> func;
|
||||
ec_extents_t results;
|
||||
|
||||
explicit ClientAsyncReadStatus(
|
||||
unsigned objects_to_read,
|
||||
GenContextURef<ec_extents_t &&> &&func)
|
||||
unsigned objects_to_read,
|
||||
GenContextURef<ec_extents_t&&> &&func)
|
||||
: objects_to_read(objects_to_read), func(std::move(func)) {}
|
||||
|
||||
void complete_object(
|
||||
const hobject_t &hoid,
|
||||
int err,
|
||||
extent_map &&buffers) {
|
||||
const hobject_t &hoid,
|
||||
int err,
|
||||
extent_map &&buffers,
|
||||
ECUtil::shard_extent_map_t &&shard_extent_map) {
|
||||
ceph_assert(objects_to_read);
|
||||
--objects_to_read;
|
||||
ceph_assert(!results.count(hoid));
|
||||
results.emplace(hoid, ec_extent_t{err, std::move(buffers)});
|
||||
ceph_assert(!results.contains(hoid));
|
||||
results.emplace(hoid, ec_extent_t{
|
||||
err, std::move(buffers),
|
||||
std::move(shard_extent_map)
|
||||
});
|
||||
}
|
||||
|
||||
bool is_complete() const {
|
||||
return objects_to_read == 0;
|
||||
}
|
||||
|
||||
void run() {
|
||||
func.release()->complete(std::move(results));
|
||||
}
|
||||
@ -171,113 +241,125 @@ struct ECCommon {
|
||||
|
||||
ZTracer::Trace trace;
|
||||
|
||||
std::map<hobject_t, std::set<int>> want_to_read;
|
||||
std::map<hobject_t, read_request_t> to_read;
|
||||
std::map<hobject_t, read_result_t> complete;
|
||||
|
||||
std::map<hobject_t, std::set<pg_shard_t>> obj_to_source;
|
||||
std::map<pg_shard_t, std::set<hobject_t> > source_to_obj;
|
||||
std::map<pg_shard_t, std::set<hobject_t>> source_to_obj;
|
||||
|
||||
void dump(ceph::Formatter *f) const;
|
||||
|
||||
std::set<pg_shard_t> in_progress;
|
||||
|
||||
std::list<ECUtil::log_entry_t> debug_log;
|
||||
|
||||
ReadOp(
|
||||
int priority,
|
||||
ceph_tid_t tid,
|
||||
bool do_redundant_reads,
|
||||
bool for_recovery,
|
||||
std::unique_ptr<ReadCompleter> _on_complete,
|
||||
OpRequestRef op,
|
||||
std::map<hobject_t, std::set<int>> &&_want_to_read,
|
||||
std::map<hobject_t, read_request_t> &&_to_read)
|
||||
int priority,
|
||||
ceph_tid_t tid,
|
||||
bool do_redundant_reads,
|
||||
bool for_recovery,
|
||||
std::unique_ptr<ReadCompleter> _on_complete,
|
||||
std::map<hobject_t, read_request_t> &&_to_read)
|
||||
: priority(priority),
|
||||
tid(tid),
|
||||
op(op),
|
||||
do_redundant_reads(do_redundant_reads),
|
||||
for_recovery(for_recovery),
|
||||
on_complete(std::move(_on_complete)),
|
||||
want_to_read(std::move(_want_to_read)),
|
||||
to_read(std::move(_to_read)) {
|
||||
for (auto &&hpair: to_read) {
|
||||
auto &returned = complete[hpair.first].returned;
|
||||
for (auto &&extent: hpair.second.to_read) {
|
||||
returned.push_back(
|
||||
boost::make_tuple(
|
||||
extent.offset,
|
||||
extent.size,
|
||||
std::map<pg_shard_t, ceph::buffer::list>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
to_read(std::move(_to_read)) {}
|
||||
|
||||
ReadOp() = delete;
|
||||
ReadOp(const ReadOp &) = delete; // due to on_complete being unique_ptr
|
||||
ReadOp(ReadOp &&) = default;
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "ReadOp(tid=" << tid;
|
||||
#ifndef WITH_CRIMSON
|
||||
if (op && op->get_req()) {
|
||||
os << ", op=";
|
||||
op->get_req()->print(os);
|
||||
}
|
||||
#endif
|
||||
os << ", to_read=" << to_read << ", complete=" << complete
|
||||
<< ", priority=" << priority << ", obj_to_source=" << obj_to_source
|
||||
<< ", source_to_obj=" << source_to_obj << ", in_progress=" <<
|
||||
in_progress
|
||||
<< ", debug_log=" << debug_log << ")";
|
||||
}
|
||||
};
|
||||
|
||||
struct ReadPipeline {
|
||||
void objects_read_and_reconstruct(
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
GenContextURef<ec_extents_t &&> &&func);
|
||||
const std::map<hobject_t, std::list<ec_align_t>> &reads,
|
||||
bool fast_read,
|
||||
uint64_t object_size,
|
||||
GenContextURef<ec_extents_t&&> &&func);
|
||||
|
||||
void objects_read_and_reconstruct_for_rmw(
|
||||
std::map<hobject_t, read_request_t> &&to_read,
|
||||
GenContextURef<ECCommon::ec_extents_t&&> &&func);
|
||||
|
||||
template <class F, class G>
|
||||
void filter_read_op(
|
||||
const OSDMapRef& osdmap,
|
||||
ReadOp &op,
|
||||
F&& on_erase,
|
||||
G&& on_schedule_recovery);
|
||||
const OSDMapRef &osdmap,
|
||||
ReadOp &op,
|
||||
F &&on_erase,
|
||||
G &&on_schedule_recovery);
|
||||
|
||||
template <class F, class G>
|
||||
void check_recovery_sources(
|
||||
const OSDMapRef& osdmap,
|
||||
F&& on_erase,
|
||||
G&& on_schedule_recovery);
|
||||
const OSDMapRef &osdmap,
|
||||
F &&on_erase,
|
||||
G &&on_schedule_recovery);
|
||||
|
||||
void complete_read_op(ReadOp &rop);
|
||||
void complete_read_op(ReadOp &&rop);
|
||||
|
||||
void start_read_op(
|
||||
int priority,
|
||||
std::map<hobject_t, std::set<int>> &want_to_read,
|
||||
std::map<hobject_t, read_request_t> &to_read,
|
||||
OpRequestRef op,
|
||||
bool do_redundant_reads,
|
||||
bool for_recovery,
|
||||
std::unique_ptr<ReadCompleter> on_complete);
|
||||
int priority,
|
||||
std::map<hobject_t, read_request_t> &to_read,
|
||||
bool do_redundant_reads,
|
||||
bool for_recovery,
|
||||
std::unique_ptr<ReadCompleter> on_complete);
|
||||
|
||||
void do_read_op(ReadOp &rop);
|
||||
|
||||
int send_all_remaining_reads(
|
||||
const hobject_t &hoid,
|
||||
ReadOp &rop);
|
||||
const hobject_t &hoid,
|
||||
ReadOp &rop);
|
||||
|
||||
void on_change();
|
||||
|
||||
void kick_reads();
|
||||
|
||||
std::map<ceph_tid_t, ReadOp> tid_to_read_map;
|
||||
std::map<pg_shard_t, std::set<ceph_tid_t> > shard_to_read_map;
|
||||
std::map<pg_shard_t, std::set<ceph_tid_t>> shard_to_read_map;
|
||||
std::list<ClientAsyncReadStatus> in_progress_client_reads;
|
||||
|
||||
CephContext* cct;
|
||||
CephContext *cct;
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
const ECUtil::stripe_info_t& sinfo;
|
||||
const ECUtil::stripe_info_t &sinfo;
|
||||
// TODO: lay an interface down here
|
||||
ECListener* parent;
|
||||
ECListener *parent;
|
||||
|
||||
ECListener *get_parent() const { return parent; }
|
||||
const OSDMapRef& get_osdmap() const { return get_parent()->pgb_get_osdmap(); }
|
||||
epoch_t get_osdmap_epoch() const { return get_parent()->pgb_get_osdmap_epoch(); }
|
||||
const pg_info_t &get_info() { return get_parent()->get_info(); }
|
||||
|
||||
ReadPipeline(CephContext* cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t& sinfo,
|
||||
ECListener* parent)
|
||||
const OSDMapRef &get_osdmap() const {
|
||||
return get_parent()->pgb_get_osdmap();
|
||||
}
|
||||
|
||||
epoch_t get_osdmap_epoch() const {
|
||||
return get_parent()->pgb_get_osdmap_epoch();
|
||||
}
|
||||
|
||||
const pg_info_t &get_info() const { return get_parent()->get_info(); }
|
||||
|
||||
ReadPipeline(CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
ECListener *parent)
|
||||
: cct(cct),
|
||||
ec_impl(std::move(ec_impl)),
|
||||
sinfo(sinfo),
|
||||
parent(parent) {
|
||||
}
|
||||
parent(parent) {}
|
||||
|
||||
/**
|
||||
* While get_want_to_read_shards creates a want_to_read based on the EC
|
||||
@ -289,47 +371,42 @@ struct ECCommon {
|
||||
*
|
||||
*/
|
||||
void get_min_want_to_read_shards(
|
||||
uint64_t offset, ///< [in]
|
||||
uint64_t length, ///< [in]
|
||||
std::set<int> *want_to_read ///< [out]
|
||||
);
|
||||
static void get_min_want_to_read_shards(
|
||||
const uint64_t offset,
|
||||
const uint64_t length,
|
||||
const ECUtil::stripe_info_t& sinfo,
|
||||
std::set<int> *want_to_read);
|
||||
const ec_align_t &to_read, ///< [in]
|
||||
ECUtil::shard_extent_set_t &want_shard_reads); ///< [out]
|
||||
|
||||
int get_remaining_shards(
|
||||
const hobject_t &hoid,
|
||||
const std::set<int> &avail,
|
||||
const std::set<int> &want,
|
||||
const read_result_t &result,
|
||||
std::map<pg_shard_t, std::vector<std::pair<int, int>>> *to_read,
|
||||
bool for_recovery);
|
||||
const hobject_t &hoid,
|
||||
read_result_t &read_result,
|
||||
read_request_t &read_request,
|
||||
bool for_recovery,
|
||||
bool fast_read);
|
||||
|
||||
void get_all_avail_shards(
|
||||
const hobject_t &hoid,
|
||||
const std::set<pg_shard_t> &error_shards,
|
||||
std::set<int> &have,
|
||||
std::map<shard_id_t, pg_shard_t> &shards,
|
||||
bool for_recovery);
|
||||
const hobject_t &hoid,
|
||||
shard_id_set &have,
|
||||
shard_id_map<pg_shard_t> &shards,
|
||||
bool for_recovery,
|
||||
const std::optional<std::set<pg_shard_t>> &error_shards = std::nullopt);
|
||||
|
||||
std::pair<const shard_id_set, const shard_id_set> get_readable_writable_shard_id_sets();
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &lhs, const ReadOp &rhs);
|
||||
friend struct FinishReadOp;
|
||||
|
||||
void get_want_to_read_shards(std::set<int> *want_to_read) const;
|
||||
void get_want_to_read_shards(
|
||||
const std::list<ec_align_t> &to_read,
|
||||
ECUtil::shard_extent_set_t &want_shard_reads);
|
||||
|
||||
/// Returns to_read replicas sufficient to reconstruct want
|
||||
int get_min_avail_to_read_shards(
|
||||
const hobject_t &hoid, ///< [in] object
|
||||
const std::set<int> &want, ///< [in] desired shards
|
||||
bool for_recovery, ///< [in] true if we may use non-acting replicas
|
||||
bool do_redundant_reads, ///< [in] true if we want to issue redundant reads to reduce latency
|
||||
std::map<pg_shard_t, std::vector<std::pair<int, int>>> *to_read ///< [out] shards, corresponding subchunks to read
|
||||
const hobject_t &hoid, ///< [in] object
|
||||
bool for_recovery, ///< [in] true if we may use non-acting replicas
|
||||
bool do_redundant_reads,
|
||||
///< [in] true if we want to issue redundant reads to reduce latency
|
||||
read_request_t &read_request,
|
||||
///< [out] shard_reads, corresponding subchunks / other sub reads to read
|
||||
const std::optional<std::set<pg_shard_t>> &error_shards = std::nullopt
|
||||
//< [in] Shards where reads have failed (optional)
|
||||
); ///< @return error code, 0 on success
|
||||
|
||||
void schedule_recovery_work();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -346,7 +423,7 @@ struct ECCommon {
|
||||
* on the writing std::list.
|
||||
*/
|
||||
|
||||
struct RMWPipeline {
|
||||
struct RMWPipeline : ECExtentCache::BackendReadListener {
|
||||
struct Op : boost::intrusive::list_base_hook<> {
|
||||
/// From submit_transaction caller, describes operation
|
||||
hobject_t hoid;
|
||||
@ -374,171 +451,176 @@ struct ECCommon {
|
||||
/// Ancillary also provided from submit_transaction caller
|
||||
std::map<hobject_t, ObjectContextRef> obc_map;
|
||||
|
||||
/// see call_write_ordered
|
||||
std::list<std::function<void(void)> > on_write;
|
||||
|
||||
/// Generated internally
|
||||
std::set<hobject_t> temp_added;
|
||||
std::set<hobject_t> temp_cleared;
|
||||
|
||||
ECTransaction::WritePlan plan;
|
||||
bool requires_rmw() const { return !plan.to_read.empty(); }
|
||||
bool invalidates_cache() const { return plan.invalidates_cache; }
|
||||
bool requires_rmw() const { return !plan.want_read; }
|
||||
|
||||
// must be true if requires_rmw(), must be false if invalidates_cache()
|
||||
bool using_cache = true;
|
||||
|
||||
/// In progress read state;
|
||||
std::map<hobject_t,extent_set> pending_read; // subset already being read
|
||||
std::map<hobject_t,extent_set> remote_read; // subset we must read
|
||||
std::map<hobject_t,extent_map> remote_read_result;
|
||||
bool read_in_progress() const {
|
||||
return !remote_read.empty() && remote_read_result.empty();
|
||||
}
|
||||
int pending_cache_ops = 0;
|
||||
std::map<hobject_t, ECUtil::shard_extent_map_t> remote_shard_extent_map;
|
||||
|
||||
/// In progress write state.
|
||||
std::set<pg_shard_t> pending_commit;
|
||||
// we need pending_apply for pre-mimic peers so that we don't issue a
|
||||
// read on a remote shard before it has applied a previous write. We can
|
||||
// remove this after nautilus.
|
||||
std::set<pg_shard_t> pending_apply;
|
||||
int pending_commits = 0;
|
||||
|
||||
bool write_in_progress() const {
|
||||
return !pending_commit.empty() || !pending_apply.empty();
|
||||
return pending_commits != 0;
|
||||
}
|
||||
|
||||
/// optional, may be null, for tracking purposes
|
||||
OpRequestRef client_op;
|
||||
|
||||
/// pin for cache
|
||||
ExtentCache::write_pin pin;
|
||||
std::list<ECExtentCache::OpRef> cache_ops;
|
||||
RMWPipeline *pipeline;
|
||||
|
||||
Op() : tid(), plan(), pipeline(nullptr) {}
|
||||
|
||||
/// Callbacks
|
||||
Context *on_all_commit = nullptr;
|
||||
|
||||
virtual ~Op() {
|
||||
delete on_all_commit;
|
||||
}
|
||||
|
||||
virtual void generate_transactions(
|
||||
ceph::ErasureCodeInterfaceRef &ecimpl,
|
||||
pg_t pgid,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
std::map<hobject_t,extent_map> *written,
|
||||
std::map<shard_id_t, ceph::os::Transaction> *transactions,
|
||||
DoutPrefixProvider *dpp,
|
||||
const ceph_release_t require_osd_release = ceph_release_t::unknown) = 0;
|
||||
ceph::ErasureCodeInterfaceRef &ec_impl,
|
||||
pg_t pgid,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
std::map<hobject_t, ECUtil::shard_extent_map_t> *written,
|
||||
shard_id_map<ceph::os::Transaction> *transactions,
|
||||
DoutPrefixProvider *dpp,
|
||||
const OSDMapRef &osdmap) = 0;
|
||||
|
||||
virtual bool skip_transaction(
|
||||
std::set<shard_id_t> &pending_roll_forward,
|
||||
shard_id_t shard,
|
||||
ceph::os::Transaction &transaction) = 0;
|
||||
|
||||
void cache_ready(const hobject_t &oid, const ECUtil::shard_extent_map_t &result) {
|
||||
if (!result.empty()) {
|
||||
remote_shard_extent_map.insert(std::pair(oid, result));
|
||||
}
|
||||
|
||||
if (!--pending_cache_ops) {
|
||||
pipeline->cache_ready(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << "Op(" << hoid << " v=" << version << " tt=" << trim_to
|
||||
<< " tid=" << tid << " reqid=" << reqid;
|
||||
#ifndef WITH_CRIMSON
|
||||
if (client_op && client_op->get_req()) {
|
||||
os << " client_op=";
|
||||
client_op->get_req()->print(os);
|
||||
}
|
||||
#endif
|
||||
os << " pg_committed_to=" << pg_committed_to
|
||||
<< " temp_added=" << temp_added
|
||||
<< " temp_cleared=" << temp_cleared
|
||||
<< " remote_read_result=" << remote_shard_extent_map
|
||||
<< " pending_commits=" << pending_commits
|
||||
<< " plan.to_read=" << plan
|
||||
<< ")";
|
||||
}
|
||||
};
|
||||
using OpRef = std::unique_ptr<Op>;
|
||||
using op_list = boost::intrusive::list<Op>;
|
||||
friend std::ostream &operator<<(std::ostream &lhs, const Op &rhs);
|
||||
|
||||
ExtentCache cache;
|
||||
void backend_read(hobject_t oid, ECUtil::shard_extent_set_t const &request,
|
||||
uint64_t object_size) override {
|
||||
std::map<hobject_t, read_request_t> to_read;
|
||||
to_read.emplace(oid, read_request_t(request, false, object_size));
|
||||
|
||||
objects_read_async_no_cache(
|
||||
std::move(to_read),
|
||||
[this](ec_extents_t &&results) {
|
||||
for (auto &&[oid, result]: results) {
|
||||
extent_cache.read_done(oid, std::move(result.shard_extent_map));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
using OpRef = std::shared_ptr<Op>;
|
||||
|
||||
std::map<ceph_tid_t, OpRef> tid_to_op_map; /// Owns Op structure
|
||||
/**
|
||||
* We model the possible rmw states as a std::set of waitlists.
|
||||
* All writes at this time complete in order, so a write blocked
|
||||
* at waiting_state blocks all writes behind it as well (same for
|
||||
* other states).
|
||||
*
|
||||
* Future work: We can break this up into a per-object pipeline
|
||||
* (almost). First, provide an ordering token to submit_transaction
|
||||
* and require that all operations within a single transaction take
|
||||
* place on a subset of hobject_t space partitioned by that token
|
||||
* (the hashid seem about right to me -- even works for temp objects
|
||||
* if you recall that a temp object created for object head foo will
|
||||
* only ever be referenced by other transactions on foo and aren't
|
||||
* reused). Next, factor this part into a class and maintain one per
|
||||
* ordering token. Next, fixup PrimaryLogPG's repop queue to be
|
||||
* partitioned by ordering token. Finally, refactor the op pipeline
|
||||
* so that the log entries passed into submit_transaction aren't
|
||||
* versioned. We can't assign versions to them until we actually
|
||||
* submit the operation. That's probably going to be the hard part.
|
||||
*/
|
||||
class pipeline_state_t {
|
||||
enum {
|
||||
CACHE_VALID = 0,
|
||||
CACHE_INVALID = 1
|
||||
} pipeline_state = CACHE_VALID;
|
||||
public:
|
||||
bool caching_enabled() const {
|
||||
return pipeline_state == CACHE_VALID;
|
||||
}
|
||||
bool cache_invalid() const {
|
||||
return !caching_enabled();
|
||||
}
|
||||
void invalidate() {
|
||||
pipeline_state = CACHE_INVALID;
|
||||
}
|
||||
void clear() {
|
||||
pipeline_state = CACHE_VALID;
|
||||
}
|
||||
friend std::ostream &operator<<(std::ostream &lhs, const pipeline_state_t &rhs);
|
||||
} pipeline_state;
|
||||
std::map<hobject_t, eversion_t> oid_to_version;
|
||||
|
||||
op_list waiting_state; /// writes waiting on pipe_state
|
||||
op_list waiting_reads; /// writes waiting on partial stripe reads
|
||||
op_list waiting_commit; /// writes waiting on initial commit
|
||||
std::list<OpRef> waiting_commit;
|
||||
eversion_t completed_to;
|
||||
eversion_t committed_to;
|
||||
void start_rmw(OpRef op);
|
||||
bool try_state_to_reads();
|
||||
bool try_reads_to_commit();
|
||||
bool try_finish_rmw();
|
||||
void check_ops();
|
||||
void cache_ready(Op &op);
|
||||
void try_finish_rmw();
|
||||
void finish_rmw(OpRef const &op);
|
||||
|
||||
void on_change();
|
||||
void on_change2();
|
||||
void call_write_ordered(std::function<void(void)> &&cb);
|
||||
|
||||
CephContext* cct;
|
||||
CephContext *cct;
|
||||
ECListener *get_parent() const { return parent; }
|
||||
const OSDMapRef& get_osdmap() const { return get_parent()->pgb_get_osdmap(); }
|
||||
epoch_t get_osdmap_epoch() const { return get_parent()->pgb_get_osdmap_epoch(); }
|
||||
const pg_info_t &get_info() { return get_parent()->get_info(); }
|
||||
|
||||
const OSDMapRef &get_osdmap() const {
|
||||
return get_parent()->pgb_get_osdmap();
|
||||
}
|
||||
|
||||
epoch_t get_osdmap_epoch() const {
|
||||
return get_parent()->pgb_get_osdmap_epoch();
|
||||
}
|
||||
|
||||
const pg_info_t &get_info() const { return get_parent()->get_info(); }
|
||||
|
||||
template <typename Func>
|
||||
void objects_read_async_no_cache(
|
||||
const std::map<hobject_t,extent_set> &to_read,
|
||||
Func &&on_complete
|
||||
) {
|
||||
std::map<hobject_t, std::list<ec_align_t>> _to_read;
|
||||
for (auto &&hpair: to_read) {
|
||||
auto &l = _to_read[hpair.first];
|
||||
for (auto extent: hpair.second) {
|
||||
l.emplace_back(ec_align_t{extent.first, extent.second, 0});
|
||||
}
|
||||
}
|
||||
ec_backend.objects_read_and_reconstruct(
|
||||
_to_read,
|
||||
false,
|
||||
std::map<hobject_t, read_request_t> &&to_read,
|
||||
Func &&on_complete) {
|
||||
ec_backend.objects_read_and_reconstruct_for_rmw(
|
||||
std::move(to_read),
|
||||
make_gen_lambda_context<
|
||||
ECCommon::ec_extents_t &&, Func>(
|
||||
std::forward<Func>(on_complete)));
|
||||
ECCommon::ec_extents_t&&, Func>(
|
||||
std::forward<Func>(on_complete)));
|
||||
}
|
||||
|
||||
void handle_sub_write(
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace
|
||||
) {
|
||||
ec_backend.handle_sub_write(from, std::move(msg), op, trace, *get_parent());
|
||||
pg_shard_t from,
|
||||
OpRequestRef msg,
|
||||
ECSubWrite &op,
|
||||
const ZTracer::Trace &trace) const {
|
||||
ec_backend.handle_sub_write(from, std::move(msg), op, trace,
|
||||
*get_parent());
|
||||
}
|
||||
|
||||
// end of iface
|
||||
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
const ECUtil::stripe_info_t& sinfo;
|
||||
ECListener* parent;
|
||||
ECCommon& ec_backend;
|
||||
// Set of shards that will need a dummy transaction for the final
|
||||
// roll forward
|
||||
std::set<shard_id_t> pending_roll_forward;
|
||||
|
||||
RMWPipeline(CephContext* cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
const ECUtil::stripe_info_t &sinfo;
|
||||
ECListener *parent;
|
||||
ECCommon &ec_backend;
|
||||
ECExtentCache extent_cache;
|
||||
uint64_t ec_pdw_write_mode;
|
||||
|
||||
RMWPipeline(CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl,
|
||||
const ECUtil::stripe_info_t& sinfo,
|
||||
ECListener* parent,
|
||||
ECCommon& ec_backend)
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
ECListener *parent,
|
||||
ECCommon &ec_backend,
|
||||
ECExtentCache::LRU &ec_extent_cache_lru)
|
||||
: cct(cct),
|
||||
ec_impl(std::move(ec_impl)),
|
||||
sinfo(sinfo),
|
||||
parent(parent),
|
||||
ec_backend(ec_backend) {
|
||||
}
|
||||
ec_backend(ec_backend),
|
||||
extent_cache(*this, ec_extent_cache_lru, sinfo, cct),
|
||||
ec_pdw_write_mode(cct->_conf.get_val<uint64_t>("ec_pdw_write_mode")) {}
|
||||
};
|
||||
|
||||
class UnstableHashInfoRegistry {
|
||||
@ -547,47 +629,35 @@ struct ECCommon {
|
||||
/// If modified, ensure that the ref is held until the update is applied
|
||||
SharedPtrRegistry<hobject_t, ECUtil::HashInfo> registry;
|
||||
|
||||
public:
|
||||
public:
|
||||
UnstableHashInfoRegistry(
|
||||
CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl)
|
||||
CephContext *cct,
|
||||
ceph::ErasureCodeInterfaceRef ec_impl)
|
||||
: cct(cct),
|
||||
ec_impl(std::move(ec_impl)) {}
|
||||
ec_impl(std::move(ec_impl)) {}
|
||||
|
||||
ECUtil::HashInfoRef maybe_put_hash_info(
|
||||
const hobject_t &hoid,
|
||||
ECUtil::HashInfo &&hinfo);
|
||||
const hobject_t &hoid,
|
||||
ECUtil::HashInfo &&hinfo);
|
||||
|
||||
ECUtil::HashInfoRef get_hash_info(
|
||||
const hobject_t &hoid,
|
||||
bool create,
|
||||
const std::map<std::string, ceph::buffer::list, std::less<>>& attr,
|
||||
uint64_t size);
|
||||
const hobject_t &hoid,
|
||||
bool create,
|
||||
const std::map<std::string, ceph::buffer::list, std::less<>> &attrs,
|
||||
uint64_t size);
|
||||
};
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &lhs,
|
||||
const ECCommon::RMWPipeline::pipeline_state_t &rhs);
|
||||
std::ostream &operator<<(std::ostream &lhs,
|
||||
const ECCommon::read_request_t &rhs);
|
||||
std::ostream &operator<<(std::ostream &lhs,
|
||||
const ECCommon::read_result_t &rhs);
|
||||
std::ostream &operator<<(std::ostream &lhs,
|
||||
const ECCommon::ReadOp &rhs);
|
||||
std::ostream &operator<<(std::ostream &lhs,
|
||||
const ECCommon::RMWPipeline::Op &rhs);
|
||||
|
||||
template <class F, class G>
|
||||
void ECCommon::ReadPipeline::check_recovery_sources(
|
||||
const OSDMapRef& osdmap,
|
||||
F&& on_erase,
|
||||
G&& on_schedule_recovery)
|
||||
{
|
||||
const OSDMapRef &osdmap,
|
||||
F &&on_erase,
|
||||
G &&on_schedule_recovery
|
||||
) {
|
||||
std::set<ceph_tid_t> tids_to_filter;
|
||||
for (std::map<pg_shard_t, std::set<ceph_tid_t> >::iterator
|
||||
for (std::map<pg_shard_t, std::set<ceph_tid_t>>::iterator
|
||||
i = shard_to_read_map.begin();
|
||||
i != shard_to_read_map.end();
|
||||
) {
|
||||
i != shard_to_read_map.end();) {
|
||||
if (osdmap->is_down(i->first.osd)) {
|
||||
tids_to_filter.insert(i->second.begin(), i->second.end());
|
||||
shard_to_read_map.erase(i++);
|
||||
@ -606,53 +676,45 @@ void ECCommon::ReadPipeline::check_recovery_sources(
|
||||
|
||||
template <class F, class G>
|
||||
void ECCommon::ReadPipeline::filter_read_op(
|
||||
const OSDMapRef& osdmap,
|
||||
ReadOp &op,
|
||||
F&& on_erase,
|
||||
G&& on_schedule_recovery)
|
||||
{
|
||||
const OSDMapRef &osdmap,
|
||||
ReadOp &op,
|
||||
F &&on_erase,
|
||||
G &&on_schedule_recovery
|
||||
) {
|
||||
std::set<hobject_t> to_cancel;
|
||||
for (std::map<pg_shard_t, std::set<hobject_t> >::iterator i = op.source_to_obj.begin();
|
||||
i != op.source_to_obj.end();
|
||||
++i) {
|
||||
if (osdmap->is_down(i->first.osd)) {
|
||||
to_cancel.insert(i->second.begin(), i->second.end());
|
||||
op.in_progress.erase(i->first);
|
||||
continue;
|
||||
for (auto &&[pg_shard, hoid_set] : op.source_to_obj) {
|
||||
if (osdmap->is_down(pg_shard.osd)) {
|
||||
to_cancel.insert(hoid_set.begin(), hoid_set.end());
|
||||
op.in_progress.erase(pg_shard);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_cancel.empty())
|
||||
return;
|
||||
|
||||
for (std::map<pg_shard_t, std::set<hobject_t> >::iterator i = op.source_to_obj.begin();
|
||||
i != op.source_to_obj.end();
|
||||
) {
|
||||
for (std::set<hobject_t>::iterator j = i->second.begin();
|
||||
j != i->second.end();
|
||||
) {
|
||||
if (to_cancel.count(*j))
|
||||
i->second.erase(j++);
|
||||
else
|
||||
++j;
|
||||
for (auto iter = op.source_to_obj.begin();
|
||||
iter != op.source_to_obj.end();) {
|
||||
auto &[pg_shard, hoid_set] = *iter;
|
||||
for (auto &hoid : hoid_set) {
|
||||
if (to_cancel.contains(hoid)) {
|
||||
hoid_set.erase(hoid);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
op.source_to_obj.erase(i++);
|
||||
if (hoid_set.empty()) {
|
||||
op.source_to_obj.erase(iter++);
|
||||
} else {
|
||||
ceph_assert(!osdmap->is_down(i->first.osd));
|
||||
++i;
|
||||
ceph_assert(!osdmap->is_down(pg_shard.osd));
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::set<hobject_t>::iterator i = to_cancel.begin();
|
||||
i != to_cancel.end();
|
||||
++i) {
|
||||
get_parent()->cancel_pull(*i);
|
||||
for (auto hoid : to_cancel) {
|
||||
get_parent()->cancel_pull(hoid);
|
||||
|
||||
ceph_assert(op.to_read.count(*i));
|
||||
op.to_read.erase(*i);
|
||||
op.complete.erase(*i);
|
||||
on_erase(*i);
|
||||
ceph_assert(op.to_read.contains(hoid));
|
||||
op.to_read.erase(hoid);
|
||||
op.complete.erase(hoid);
|
||||
on_erase(hoid);
|
||||
}
|
||||
|
||||
if (op.in_progress.empty()) {
|
||||
@ -675,8 +737,14 @@ void ECCommon::ReadPipeline::filter_read_op(
|
||||
}
|
||||
}
|
||||
|
||||
template <> struct fmt::formatter<ECCommon::RMWPipeline::pipeline_state_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<ECCommon::read_request_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<ECCommon::read_result_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<ECCommon::ReadOp> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<ECCommon::RMWPipeline::Op> : fmt::ostream_formatter {};
|
||||
template <>
|
||||
struct fmt::formatter<ECCommon::read_request_t> : fmt::ostream_formatter {};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<ECCommon::read_result_t> : fmt::ostream_formatter {};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<ECCommon::ReadOp> : fmt::ostream_formatter {};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<ECCommon::RMWPipeline::Op> : fmt::ostream_formatter {};
|
||||
|
||||
480
src/osd/ECExtentCache.cc
Normal file
480
src/osd/ECExtentCache.cc
Normal file
@ -0,0 +1,480 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
|
||||
#include "ECExtentCache.h"
|
||||
#include "ECUtil.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace std;
|
||||
using namespace ECUtil;
|
||||
|
||||
void ECExtentCache::Object::request(OpRef &op) {
|
||||
/* After a cache invalidation, we allow through a single cache-invalidating
|
||||
* IO.
|
||||
*/
|
||||
if (op->invalidates_cache) {
|
||||
if (cache_invalidated) {
|
||||
op->invalidates_cache = false;
|
||||
} else {
|
||||
cache_invalidate_expected = true;
|
||||
}
|
||||
}
|
||||
cache_invalidated = false;
|
||||
|
||||
extent_set eset = op->get_pin_eset(line_size);
|
||||
|
||||
for (auto &&[start, len] : eset) {
|
||||
for (uint64_t to_pin = start; to_pin < start + len; to_pin += line_size) {
|
||||
LineRef l;
|
||||
if (!lines.contains(to_pin)) {
|
||||
l = make_shared<Line>(*this, to_pin);
|
||||
if (!l->cache->empty()) {
|
||||
l->cache->to_shard_extent_set(do_not_read);
|
||||
}
|
||||
lines.emplace(to_pin, weak_ptr(l));
|
||||
} else {
|
||||
l = lines.at(to_pin).lock();
|
||||
}
|
||||
op->lines.emplace_back(l);
|
||||
}
|
||||
}
|
||||
|
||||
bool read_required = false;
|
||||
|
||||
/* Deal with reads if there are any.
|
||||
* If any cache invalidation ops have been added, there is no point adding any
|
||||
* reads as they are all going to be thrown away before any of the
|
||||
* post-invalidate ops are honoured.
|
||||
*/
|
||||
if (op->reads && !cache_invalidate_expected) {
|
||||
for (auto &&[shard, eset] : *(op->reads)) {
|
||||
extent_set request = eset;
|
||||
if (do_not_read.contains(shard)) {
|
||||
request.subtract(do_not_read.at(shard));
|
||||
}
|
||||
|
||||
if (!request.empty()) {
|
||||
requesting[shard].union_of(request);
|
||||
read_required = true;
|
||||
requesting_ops.emplace_back(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Calculate the range of the object which no longer need to be written. This
|
||||
* will include:
|
||||
* - Any reads being issued by this IO.
|
||||
* - Any writes being issued (these will be cached)
|
||||
* - any unwritten regions in an append - these can assumed to be zero.
|
||||
*/
|
||||
if (read_required) {
|
||||
do_not_read.insert(requesting);
|
||||
}
|
||||
do_not_read.insert(op->writes);
|
||||
if (op->projected_size > projected_size) {
|
||||
/* This write is growing the size of the object. This essentially counts
|
||||
* as a write (although the cache will not get populated). Future reads
|
||||
* to this area will be skipped, but this makes them essentially zero
|
||||
* reads.
|
||||
*/
|
||||
shard_extent_set_t obj_hole(pg.sinfo.get_k_plus_m());
|
||||
shard_extent_set_t read_mask(pg.sinfo.get_k_plus_m());
|
||||
|
||||
pg.sinfo.ro_size_to_read_mask(op->projected_size, obj_hole);
|
||||
pg.sinfo.ro_size_to_read_mask(projected_size, read_mask);
|
||||
obj_hole.subtract(read_mask);
|
||||
do_not_read.insert(obj_hole);
|
||||
} else if (op->projected_size < projected_size) {
|
||||
// Invalidate the object's cache when we see any object reduce in size.
|
||||
op->invalidates_cache = true;
|
||||
}
|
||||
|
||||
projected_size = op->projected_size;
|
||||
|
||||
if (read_required) send_reads();
|
||||
else op->read_done = true;
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::send_reads() {
|
||||
if (reading || requesting.empty())
|
||||
return; // Read busy
|
||||
|
||||
reading_ops.swap(requesting_ops);
|
||||
pg.backend_read.backend_read(oid, requesting, current_size);
|
||||
requesting.clear();
|
||||
reading = true;
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::read_done(shard_extent_map_t const &buffers) {
|
||||
reading = false;
|
||||
for (auto &&op : reading_ops) {
|
||||
op->read_done = true;
|
||||
}
|
||||
reading_ops.clear();
|
||||
insert(buffers);
|
||||
}
|
||||
|
||||
uint64_t ECExtentCache::Object::line_align(uint64_t x) const {
|
||||
return x - (x % line_size);
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::insert(shard_extent_map_t const &buffers) const {
|
||||
if (buffers.empty()) return;
|
||||
|
||||
/* The following gets quite inefficient for writes which write to the start
|
||||
* and the end of a very large object, since we iterated over the middle.
|
||||
* This seems like a strange use case, so currently this is not being
|
||||
* optimised.
|
||||
*/
|
||||
for (uint64_t slice_start = line_align(buffers.get_start_offset());
|
||||
slice_start < buffers.get_end_offset();
|
||||
slice_start += line_size) {
|
||||
shard_extent_map_t slice = buffers.slice_map(slice_start, line_size);
|
||||
if (!slice.empty()) {
|
||||
LineRef l = lines.at(slice_start).lock();
|
||||
/* The line should have been created already! */
|
||||
l->cache->insert(slice);
|
||||
uint64_t old_size = l->size;
|
||||
l->size = l->cache->size();
|
||||
ceph_assert(l->size >= old_size);
|
||||
update_mempool(0, l->size - old_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::write_done(shard_extent_map_t const &buffers,
|
||||
uint64_t new_size) {
|
||||
insert(buffers);
|
||||
current_size = new_size;
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::unpin(Op &op) const {
|
||||
op.lines.clear();
|
||||
delete_maybe();
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::delete_maybe() const {
|
||||
if (lines.empty() && active_ios == 0) {
|
||||
pg.objects.erase(oid);
|
||||
}
|
||||
}
|
||||
|
||||
void check_seset_empty_for_range(shard_extent_set_t s, uint64_t off,
|
||||
uint64_t len) {
|
||||
for (auto &[shard, eset] : s) {
|
||||
ceph_assert(!eset.intersects(off, len));
|
||||
}
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::erase_line(uint64_t offset) {
|
||||
check_seset_empty_for_range(requesting, offset, line_size);
|
||||
do_not_read.erase_stripe(offset, line_size);
|
||||
lines.erase(offset);
|
||||
delete_maybe();
|
||||
}
|
||||
|
||||
void ECExtentCache::Object::invalidate(const OpRef &invalidating_op) {
|
||||
for (auto &l : std::views::values(lines)) {
|
||||
auto line = l.lock();
|
||||
line->cache->clear();
|
||||
update_mempool(0, -line->size);
|
||||
line->size = 0;
|
||||
}
|
||||
|
||||
/* Remove all entries from the LRU */
|
||||
pg.lru.remove_object(oid);
|
||||
|
||||
ceph_assert(!reading);
|
||||
do_not_read.clear();
|
||||
requesting.clear();
|
||||
requesting_ops.clear();
|
||||
reading_ops.clear();
|
||||
|
||||
/* Current size should reflect the actual size of the object, which was set
|
||||
* by the previous write. We are going to replay all the writes now, so set
|
||||
* the projected size to that of this op.
|
||||
*/
|
||||
projected_size = invalidating_op->projected_size;
|
||||
|
||||
// Cache can now be replayed and invalidate teh cache!
|
||||
invalidating_op->invalidates_cache = false;
|
||||
|
||||
cache_invalidated = true;
|
||||
cache_invalidate_expected = false;
|
||||
|
||||
/* We now need to reply all outstanding ops, so as to regenerate the read */
|
||||
for (auto &op : pg.waiting_ops) {
|
||||
if (op->object.oid == oid) {
|
||||
op->read_done = false;
|
||||
request(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ECExtentCache::cache_maybe_ready() {
|
||||
while (!waiting_ops.empty()) {
|
||||
OpRef op = waiting_ops.front();
|
||||
if (op->invalidates_cache) {
|
||||
/* We must wait for any outstanding reads to complete. The cache replans
|
||||
* all reads as part of invalidate. If an in-flight read completes after
|
||||
* the invalidate, it will potentially corrupt it, leading to data
|
||||
* corruption at the host.
|
||||
*/
|
||||
if (op->object.reading) {
|
||||
return;
|
||||
}
|
||||
op->object.invalidate(op);
|
||||
ceph_assert(!op->invalidates_cache);
|
||||
}
|
||||
/* If reads_done finds all reads complete it will call the completion
|
||||
* callback. Typically, this will cause the client to execute the
|
||||
* transaction and pop the front of waiting_ops. So we abort if either
|
||||
* reads are not ready, or the client chooses not to complete the op
|
||||
*/
|
||||
if (!op->complete_if_reads_cached(op)) {
|
||||
return;
|
||||
}
|
||||
|
||||
waiting_ops.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
ECExtentCache::OpRef ECExtentCache::prepare(GenContextURef<OpRef&> &&ctx,
|
||||
hobject_t const &oid,
|
||||
std::optional<shard_extent_set_t>
|
||||
const &to_read,
|
||||
shard_extent_set_t const &write,
|
||||
uint64_t orig_size,
|
||||
uint64_t projected_size,
|
||||
bool invalidates_cache) {
|
||||
|
||||
auto object_iter = objects.find(oid);
|
||||
if (object_iter == objects.end()) {
|
||||
auto p = objects.emplace(oid, Object(*this, oid, orig_size));
|
||||
object_iter = p.first;
|
||||
}
|
||||
OpRef op = std::make_shared<Op>(
|
||||
std::move(ctx), object_iter->second, to_read, write, projected_size,
|
||||
invalidates_cache);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
void ECExtentCache::read_done(hobject_t const &oid,
|
||||
shard_extent_map_t const &update) {
|
||||
objects.at(oid).read_done(update);
|
||||
cache_maybe_ready();
|
||||
objects.at(oid).send_reads();
|
||||
}
|
||||
|
||||
void ECExtentCache::write_done(OpRef const &op,
|
||||
shard_extent_map_t const &update) {
|
||||
op->write_done(std::move(update));
|
||||
}
|
||||
|
||||
uint64_t ECExtentCache::get_projected_size(hobject_t const &oid) const {
|
||||
return objects.at(oid).get_projected_size();
|
||||
}
|
||||
|
||||
bool ECExtentCache::contains_object(hobject_t const &oid) const {
|
||||
return objects.contains(oid);
|
||||
}
|
||||
|
||||
ECExtentCache::Op::~Op() {
|
||||
ceph_assert(object.active_ios > 0);
|
||||
object.active_ios--;
|
||||
ceph_assert(object.pg.active_ios > 0);
|
||||
object.pg.active_ios--;
|
||||
|
||||
object.unpin(*this);
|
||||
}
|
||||
|
||||
/* ECExtent cache cleanup on occurs in two parts. The first performs cleanup
|
||||
* of the ops currently managed by the extent cache. At this point, however
|
||||
* the cache will be waiting for other parts of EC to clean up (for example
|
||||
* any outstanding reads). on_change2() executes once all of this cleanup has
|
||||
* occurred.
|
||||
*/
|
||||
void ECExtentCache::on_change() {
|
||||
for (auto &&o : std::views::values(objects)) {
|
||||
o.reading_ops.clear();
|
||||
o.requesting_ops.clear();
|
||||
o.requesting.clear();
|
||||
}
|
||||
for (auto &&op : waiting_ops) {
|
||||
op->cancel();
|
||||
}
|
||||
waiting_ops.clear();
|
||||
}
|
||||
|
||||
/* This must be run toward the end of EC on_change handling. It asserts that
|
||||
* any object which is automatically self-destructs when idle has done so.
|
||||
* Additionally, it discards the entire LRU cache. This must be done after all
|
||||
* in-flight reads/writes have completed, or we risk attempting to insert data
|
||||
* into the cache after it has been cleared.
|
||||
*
|
||||
* Note that the LRU will end up being called multiple times. With some
|
||||
* additional code complexity this could be fixed for a small (probably
|
||||
* insignificant) performance improvement.
|
||||
*/
|
||||
void ECExtentCache::on_change2() const {
|
||||
lru.discard();
|
||||
/* If this assert fires in a unit test, make sure that all ops have completed
|
||||
* and cleared any extent cache ops they contain */
|
||||
ceph_assert(objects.empty());
|
||||
ceph_assert(active_ios == 0);
|
||||
ceph_assert(idle());
|
||||
}
|
||||
|
||||
void ECExtentCache::execute(list<OpRef> &op_list) {
|
||||
for (auto &op : op_list) {
|
||||
op->object.request(op);
|
||||
}
|
||||
waiting_ops.insert(waiting_ops.end(), op_list.begin(), op_list.end());
|
||||
counter++;
|
||||
cache_maybe_ready();
|
||||
}
|
||||
|
||||
bool ECExtentCache::idle() const {
|
||||
return active_ios == 0;
|
||||
}
|
||||
|
||||
uint32_t ECExtentCache::get_and_reset_counter() {
|
||||
uint32_t ret = counter;
|
||||
counter = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
list<ECExtentCache::LRU::Key>::iterator ECExtentCache::LRU::erase(
|
||||
const list<Key>::iterator &it,
|
||||
bool do_update_mempool) {
|
||||
uint64_t size_change = map.at(*it).second->size();
|
||||
if (do_update_mempool) {
|
||||
update_mempool(-1, 0 - size_change);
|
||||
}
|
||||
size -= size_change;
|
||||
map.erase(*it);
|
||||
return lru.erase(it);
|
||||
}
|
||||
|
||||
void ECExtentCache::LRU::add(const Line &line) {
|
||||
if (line.size == 0) {
|
||||
update_mempool(-1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const Key k(line.offset, line.object.oid);
|
||||
|
||||
shared_ptr<shard_extent_map_t> cache = line.cache;
|
||||
|
||||
mutex.lock();
|
||||
ceph_assert(!map.contains(k));
|
||||
auto i = lru.insert(lru.end(), k);
|
||||
auto j = make_pair(std::move(i), std::move(cache));
|
||||
map.insert(std::pair(std::move(k), std::move(j)));
|
||||
size += line.size; // This is already accounted for in mempool.
|
||||
free_maybe();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
shared_ptr<shard_extent_map_t> ECExtentCache::LRU::find(
|
||||
const hobject_t &oid, uint64_t offset) {
|
||||
Key k(offset, oid);
|
||||
shared_ptr<shard_extent_map_t> cache = nullptr;
|
||||
mutex.lock();
|
||||
if (map.contains(k)) {
|
||||
auto &&[lru_iter, c] = map.at(k);
|
||||
cache = c;
|
||||
auto it = lru_iter; // Intentional copy.
|
||||
erase(it, false);
|
||||
}
|
||||
mutex.unlock();
|
||||
return cache;
|
||||
}
|
||||
|
||||
void ECExtentCache::LRU::remove_object(const hobject_t &oid) {
|
||||
mutex.lock();
|
||||
for (auto it = lru.begin(); it != lru.end();) {
|
||||
if (it->oid == oid) {
|
||||
it = erase(it, true);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void ECExtentCache::LRU::free_maybe() {
|
||||
while (max_size < size) {
|
||||
auto it = lru.begin();
|
||||
erase(it, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ECExtentCache::LRU::discard() {
|
||||
mutex.lock();
|
||||
lru.clear();
|
||||
update_mempool(0 - map.size(), 0 - size);
|
||||
map.clear();
|
||||
size = 0;
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
const extent_set ECExtentCache::Op::get_pin_eset(uint64_t alignment) const {
|
||||
extent_set eset = writes.get_extent_superset();
|
||||
if (reads) {
|
||||
reads->get_extent_superset(eset);
|
||||
}
|
||||
eset.align(alignment);
|
||||
|
||||
return eset;
|
||||
}
|
||||
|
||||
ECExtentCache::Op::Op(GenContextURef<OpRef&> &&cache_ready_cb,
|
||||
Object &object,
|
||||
std::optional<shard_extent_set_t> const &to_read,
|
||||
shard_extent_set_t const &write,
|
||||
uint64_t projected_size,
|
||||
bool invalidates_cache) :
|
||||
object(object),
|
||||
reads(to_read),
|
||||
writes(write),
|
||||
result(&object.pg.sinfo),
|
||||
invalidates_cache(invalidates_cache),
|
||||
projected_size(projected_size),
|
||||
cache_ready_cb(std::move(cache_ready_cb)) {
|
||||
object.active_ios++;
|
||||
object.pg.active_ios++;
|
||||
}
|
||||
|
||||
shard_extent_map_t ECExtentCache::Object::get_cache(
|
||||
std::optional<shard_extent_set_t> const &set) const {
|
||||
if (!set) {
|
||||
return shard_extent_map_t(&pg.sinfo);
|
||||
}
|
||||
|
||||
shard_id_map<extent_map> res(pg.sinfo.get_k_plus_m());
|
||||
for (auto &&[shard, eset] : *set) {
|
||||
for (auto [off, len] : eset) {
|
||||
for (uint64_t slice_start = line_align(off);
|
||||
slice_start < off + len;
|
||||
slice_start += line_size) {
|
||||
uint64_t offset = max(slice_start, off);
|
||||
uint64_t length = min(slice_start + line_size, off + len) - offset;
|
||||
// This line must exist, as it was created when the op was created.
|
||||
LineRef l = lines.at(slice_start).lock();
|
||||
if (l->cache->contains_shard(shard)) {
|
||||
extent_map m = l->cache->get_extent_map(shard).intersect(
|
||||
offset, length);
|
||||
if (!m.empty()) {
|
||||
if (!res.contains(shard)) res.emplace(shard, std::move(m));
|
||||
else res.at(shard).insert(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return shard_extent_map_t(&pg.sinfo, std::move(res));
|
||||
}
|
||||
@ -1,10 +1,383 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
|
||||
/* EC "extent" cache. This extent cache attempts to improve performance,
|
||||
* particularly for small sequential writes, by caching the results of recent
|
||||
* reads and writes.
|
||||
*
|
||||
* The cache has two parts: The main cache which is active while an IO is
|
||||
* outstanding to an object and an "LRU" which stashes recent IO according to
|
||||
* a least-recently-used scheme.
|
||||
*
|
||||
* The cache keeps all caches indexed by shard, shard_offset. That is it
|
||||
* independently tracks caches for each shard of an EC. It will keep a cache
|
||||
* even for shards which are currently offline or missing, since the cache
|
||||
* is formed from the result of reads and writes, which are required to always
|
||||
* calculate missing shards.
|
||||
*
|
||||
* The cache allows for a single read to be outstanding per PG at a time. If
|
||||
* multiple writes are received while a read is active, the next read will
|
||||
* contain all necessary reads, so as to catch up. Early on in development, a
|
||||
* more parallel read mechanism was explored but was found to have no benefit.
|
||||
*
|
||||
* This cache will never re-order IO.
|
||||
*
|
||||
* The LRU
|
||||
*
|
||||
* The LRU is a per-OSD-shard (not to be confused with an EC shard). Since the
|
||||
* OSD-shard can have multiple threads, the LRU must have a mutex. This should
|
||||
* not be required for crimson-based pools, since each osd shard has a single
|
||||
* reactor. Some effort has been made to limit the frequency that this mutex is
|
||||
* taken.
|
||||
*
|
||||
* The LRU has a maximum size (defined in the constructor) and will keep its
|
||||
* usage below this amount.
|
||||
*
|
||||
* Cache Lines
|
||||
*
|
||||
* The LRU tracks extents of recent writes with cache Lines. These are
|
||||
* simple-to-track ranges of offsets across all shards. Each line represents 32K
|
||||
* of address space on each shard.
|
||||
*
|
||||
* A cache line can be owned by:
|
||||
* - No-one (i.e. it is not instantiated)
|
||||
* - Object - an IO is inflight for this cache line
|
||||
* - LRU - A recent IO touched this cache line.
|
||||
*
|
||||
* This simple ownership model means that the locking required for the LRU does
|
||||
* not leak out into the wider extent cache and allows for the entire cache
|
||||
* to be built from reference-counters.
|
||||
*
|
||||
* Client API
|
||||
*
|
||||
* The client has a number of required interactions:
|
||||
* 1. prepare(...). This creates a cache op. All cache ops required for a single
|
||||
* parent op must be prepared before any are executed.
|
||||
* 2. execute(...). Execute an IO. This gives the cache permission to perform
|
||||
* the IO. This function can (and frequently does) call back
|
||||
* re-entrantly, so the caller must be aware that this can
|
||||
* happen.
|
||||
*
|
||||
* The client must provide a mechanism for the extent cache to read. It does
|
||||
* this by extending the ECExtentCache::BackendRead class.
|
||||
*
|
||||
* Once a read is complete, the client must call cache.read_done().
|
||||
*
|
||||
* When the cache is ready, it will call back the lambda passed to execute.
|
||||
* The client is expected to populate the write data, including any parity
|
||||
* data, by calling the cache.write_done() method.
|
||||
*
|
||||
* Finally, there is an on_change() and on_change2() methods. The first of these
|
||||
* instructs the extent cache to discard any ops it has queued. The second
|
||||
* simply asserts that the cache is now idle, this is to ensure that the calling
|
||||
* code has performed the required clean up to clear the extent cache.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Temporary stubs
|
||||
#include "ECUtil.h"
|
||||
#include "include/Context.h"
|
||||
|
||||
class ECExtentCache {
|
||||
class Address;
|
||||
class Line;
|
||||
class Object;
|
||||
typedef std::shared_ptr<Line> LineRef;
|
||||
typedef std::list<LineRef>::iterator LineIter;
|
||||
|
||||
public:
|
||||
class LRU;
|
||||
class Op;
|
||||
typedef std::shared_ptr<Op> OpRef;
|
||||
|
||||
struct BackendReadListener {
|
||||
virtual void backend_read(hobject_t oid,
|
||||
ECUtil::shard_extent_set_t const &request,
|
||||
uint64_t object_size) = 0;
|
||||
virtual ~BackendReadListener() = default;
|
||||
};
|
||||
|
||||
static void update_mempool(int items, int64_t bytes) {
|
||||
mempool::get_pool(mempool::pool_index_t(mempool::mempool_ec_extent_cache)).
|
||||
adjust_count(items, bytes);
|
||||
}
|
||||
|
||||
class LRU {
|
||||
public:
|
||||
LRU(uint64_t) {}
|
||||
class Key {
|
||||
public:
|
||||
uint64_t offset;
|
||||
hobject_t oid;
|
||||
|
||||
Key(uint64_t offset, const hobject_t &oid) : offset(offset), oid(oid) {};
|
||||
|
||||
friend bool operator==(const Key &lhs, const Key &rhs) {
|
||||
return lhs.offset == rhs.offset
|
||||
&& lhs.oid == rhs.oid;
|
||||
}
|
||||
|
||||
friend bool operator!=(const Key &lhs, const Key &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyHash {
|
||||
std::size_t operator()(const Key &obj) const {
|
||||
std::size_t seed = 0x625610ED;
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x1E665363 + static_cast<
|
||||
std::size_t>(obj.offset);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x51343C80 + obj.oid.get_hash();
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend class Object;
|
||||
friend class ECExtentCache;
|
||||
std::unordered_map<Key, std::pair<
|
||||
std::list<Key>::iterator, std::shared_ptr<
|
||||
ECUtil::shard_extent_map_t>>, KeyHash> map;
|
||||
std::list<Key> lru;
|
||||
uint64_t max_size = 0;
|
||||
uint64_t size = 0;
|
||||
ceph::mutex mutex = ceph::make_mutex("ECExtentCache::LRU");
|
||||
|
||||
void free_maybe();
|
||||
void discard();
|
||||
void add(const Line &line);
|
||||
void erase(const Key &k);
|
||||
std::list<Key>::iterator erase(const std::list<Key>::iterator &it,
|
||||
bool update_mempool);
|
||||
std::shared_ptr<ECUtil::shard_extent_map_t> find(
|
||||
const hobject_t &oid, uint64_t offset);
|
||||
void remove_object(const hobject_t &oid);
|
||||
|
||||
public:
|
||||
explicit LRU(uint64_t max_size) : map(), max_size(max_size) {}
|
||||
};
|
||||
};
|
||||
|
||||
class Op {
|
||||
friend class Object;
|
||||
friend class ECExtentCache;
|
||||
|
||||
Object &object;
|
||||
std::optional<ECUtil::shard_extent_set_t> const reads;
|
||||
ECUtil::shard_extent_set_t const writes;
|
||||
ECUtil::shard_extent_map_t result;
|
||||
bool complete = false;
|
||||
bool invalidates_cache = false;
|
||||
bool reading = false;
|
||||
bool read_done = false;
|
||||
uint64_t projected_size = 0;
|
||||
GenContextURef<OpRef&> cache_ready_cb;
|
||||
std::list<LineRef> lines;
|
||||
|
||||
// List of callbacks to be executed on write completion (not commit)
|
||||
std::list<std::function<void(void)>> on_write;
|
||||
|
||||
const extent_set get_pin_eset(uint64_t alignment) const;
|
||||
|
||||
public:
|
||||
explicit Op(
|
||||
GenContextURef<OpRef&> &&cache_ready_cb,
|
||||
Object &object,
|
||||
std::optional<ECUtil::shard_extent_set_t> const &to_read,
|
||||
ECUtil::shard_extent_set_t const &write,
|
||||
uint64_t projected_size,
|
||||
bool invalidates_cache);
|
||||
|
||||
~Op();
|
||||
void cancel() { delete cache_ready_cb.release(); }
|
||||
const ECUtil::shard_extent_set_t &get_writes() const { return writes; }
|
||||
const Object &get_object() const { return object; }
|
||||
const hobject_t &get_hoid() const { return object.oid; }
|
||||
const ECUtil::shard_extent_map_t &get_result() { return result; }
|
||||
|
||||
void add_on_write(std::function<void(void)> &&cb) {
|
||||
on_write.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
bool complete_if_reads_cached(OpRef &op_ref) {
|
||||
if (!read_done) {
|
||||
return false;
|
||||
}
|
||||
result = object.get_cache(reads);
|
||||
complete = true;
|
||||
cache_ready_cb.release()->complete(op_ref);
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_done(ECUtil::shard_extent_map_t const &update) const {
|
||||
object.write_done(update, projected_size);
|
||||
for (auto &cb: on_write) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define MIN_LINE_SIZE (32UL*1024UL)
|
||||
|
||||
private:
|
||||
class Object {
|
||||
friend class Op;
|
||||
friend class LRU;
|
||||
friend class Line;
|
||||
friend class ECExtentCache;
|
||||
|
||||
ECExtentCache &pg;
|
||||
ECUtil::shard_extent_set_t requesting;
|
||||
ECUtil::shard_extent_set_t do_not_read;
|
||||
std::list<OpRef> reading_ops;
|
||||
std::list<OpRef> requesting_ops;
|
||||
// Map of the byte-offset of the start of the line to the line.
|
||||
std::map<uint64_t, std::weak_ptr<Line>> lines;
|
||||
int active_ios = 0;
|
||||
uint64_t current_size = 0;
|
||||
uint64_t projected_size = 0;
|
||||
uint64_t line_size = 0;
|
||||
bool reading = false;
|
||||
bool cache_invalidated = false;
|
||||
bool cache_invalidate_expected = false;
|
||||
|
||||
void request(OpRef &op);
|
||||
void send_reads();
|
||||
void unpin(Op &op) const;
|
||||
void delete_maybe() const;
|
||||
void erase_line(uint64_t offset);
|
||||
void invalidate(const OpRef &invalidating_op);
|
||||
|
||||
public:
|
||||
hobject_t oid;
|
||||
|
||||
Object(ECExtentCache &pg, hobject_t const &oid, uint64_t size) :
|
||||
pg(pg),
|
||||
requesting(pg.sinfo.get_k_plus_m()),
|
||||
do_not_read(pg.sinfo.get_k_plus_m()),
|
||||
current_size(size),
|
||||
projected_size(size),
|
||||
oid(oid) {
|
||||
line_size = std::max(MIN_LINE_SIZE, pg.sinfo.get_chunk_size());
|
||||
}
|
||||
|
||||
void insert(ECUtil::shard_extent_map_t const &buffers) const;
|
||||
void write_done(ECUtil::shard_extent_map_t const &buffers, uint64_t new_size);
|
||||
void read_done(ECUtil::shard_extent_map_t const &result);
|
||||
[[nodiscard]] uint64_t get_projected_size() const { return projected_size; }
|
||||
ECUtil::shard_extent_map_t get_cache(
|
||||
std::optional<ECUtil::shard_extent_set_t> const &set) const;
|
||||
uint64_t line_align(uint64_t line) const;
|
||||
};
|
||||
|
||||
|
||||
class Line {
|
||||
public:
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
std::shared_ptr<ECUtil::shard_extent_map_t> cache;
|
||||
Object &object;
|
||||
|
||||
Line(Object &object,
|
||||
uint64_t offset) :
|
||||
offset(offset),
|
||||
object(object) {
|
||||
std::shared_ptr<ECUtil::shard_extent_map_t> c = object.pg.lru.find(
|
||||
object.oid, offset);
|
||||
|
||||
if (c == nullptr) {
|
||||
cache = std::make_shared<ECUtil::shard_extent_map_t>(&object.pg.sinfo);
|
||||
size = 0;
|
||||
/* We are creating an empty cache line */
|
||||
update_mempool(1, 0);
|
||||
} else {
|
||||
cache = c;
|
||||
size = c->size();
|
||||
}
|
||||
}
|
||||
|
||||
~Line() {
|
||||
object.pg.lru.add(*this);
|
||||
object.erase_line(offset);
|
||||
}
|
||||
|
||||
friend bool operator==(const Line &lhs, const Line &rhs) {
|
||||
return lhs.offset == rhs.offset
|
||||
&& lhs.object.oid == rhs.object.oid;
|
||||
}
|
||||
|
||||
friend bool operator!=(const Line &lhs, const Line &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
std::map<hobject_t, Object> objects;
|
||||
BackendReadListener &backend_read;
|
||||
LRU &lru;
|
||||
const ECUtil::stripe_info_t &sinfo;
|
||||
std::list<OpRef> waiting_ops;
|
||||
void cache_maybe_ready();
|
||||
uint32_t counter = 0;
|
||||
uint32_t active_ios = 0;
|
||||
CephContext *cct;
|
||||
|
||||
OpRef prepare(GenContextURef<OpRef&> &&ctx,
|
||||
hobject_t const &oid,
|
||||
std::optional<ECUtil::shard_extent_set_t> const &to_read,
|
||||
ECUtil::shard_extent_set_t const &write,
|
||||
uint64_t orig_size,
|
||||
uint64_t projected_size,
|
||||
bool invalidates_cache);
|
||||
|
||||
public:
|
||||
~ECExtentCache() {
|
||||
// This should really only be needed in failed tests, as the PG should
|
||||
// clear up any IO before it gets destructed. However, here we make sure
|
||||
// to clean up any outstanding IO.
|
||||
on_change();
|
||||
on_change2();
|
||||
}
|
||||
|
||||
explicit ECExtentCache(BackendReadListener &backend_read,
|
||||
LRU &lru, const ECUtil::stripe_info_t &sinfo,
|
||||
CephContext *cct
|
||||
) :
|
||||
backend_read(backend_read),
|
||||
lru(lru),
|
||||
sinfo(sinfo),
|
||||
cct(cct) {}
|
||||
|
||||
// Insert some data into the cache.
|
||||
void read_done(hobject_t const &oid, ECUtil::shard_extent_map_t const &update);
|
||||
void write_done(OpRef const &op, ECUtil::shard_extent_map_t const &update);
|
||||
void on_change();
|
||||
void on_change2() const;
|
||||
[[nodiscard]] bool contains_object(hobject_t const &oid) const;
|
||||
[[nodiscard]] uint64_t get_projected_size(hobject_t const &oid) const;
|
||||
|
||||
template <typename CacheReadyCb>
|
||||
OpRef prepare(hobject_t const &oid,
|
||||
std::optional<ECUtil::shard_extent_set_t> const &to_read,
|
||||
ECUtil::shard_extent_set_t const &write,
|
||||
uint64_t orig_size,
|
||||
uint64_t projected_size,
|
||||
bool invalidates_cache,
|
||||
CacheReadyCb &&ready_cb) {
|
||||
GenContextURef<OpRef&> ctx =
|
||||
make_gen_lambda_context<OpRef&, CacheReadyCb>(
|
||||
std::forward<CacheReadyCb>(ready_cb));
|
||||
|
||||
return prepare(std::move(ctx), oid, to_read, write, orig_size,
|
||||
projected_size, invalidates_cache);
|
||||
}
|
||||
|
||||
void execute(std::list<OpRef> &op_list);
|
||||
[[nodiscard]] bool idle() const;
|
||||
uint32_t get_and_reset_counter();
|
||||
|
||||
void add_on_write(std::function<void(void)> &&cb) const {
|
||||
if (waiting_ops.empty()) {
|
||||
cb();
|
||||
} else {
|
||||
waiting_ops.back()->add_on_write(std::move(cb));
|
||||
}
|
||||
}
|
||||
}; // ECExtentCaches
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,184 +12,133 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ECTRANSACTION_H
|
||||
#define ECTRANSACTION_H
|
||||
#pragma once
|
||||
|
||||
#include "common/dout.h"
|
||||
#include "ECUtil.h"
|
||||
#include "ExtentCache.h"
|
||||
#include "erasure-code/ErasureCodeInterface.h"
|
||||
#include "os/Transaction.h"
|
||||
#include "PGTransaction.h"
|
||||
|
||||
namespace ECTransaction {
|
||||
struct WritePlan {
|
||||
bool invalidates_cache = false; // Yes, both are possible
|
||||
std::map<hobject_t,extent_set> to_read;
|
||||
std::map<hobject_t,extent_set> will_write; // superset of to_read
|
||||
class WritePlanObj {
|
||||
public:
|
||||
const hobject_t hoid;
|
||||
std::optional<ECUtil::shard_extent_set_t> to_read;
|
||||
ECUtil::shard_extent_set_t will_write;
|
||||
const ECUtil::HashInfoRef hinfo;
|
||||
const ECUtil::HashInfoRef shinfo;
|
||||
const uint64_t orig_size;
|
||||
uint64_t projected_size;
|
||||
bool invalidates_cache;
|
||||
bool do_parity_delta_write = false;
|
||||
|
||||
std::map<hobject_t,ECUtil::HashInfoRef> hash_infos;
|
||||
};
|
||||
WritePlanObj(
|
||||
const hobject_t &hoid,
|
||||
const PGTransaction::ObjectOperation &op,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
const shard_id_set readable_shards,
|
||||
const shard_id_set writable_shards,
|
||||
const bool object_in_cache,
|
||||
uint64_t orig_size,
|
||||
const std::optional<object_info_t> &oi,
|
||||
const std::optional<object_info_t> &soi,
|
||||
const ECUtil::HashInfoRef &&hinfo,
|
||||
const ECUtil::HashInfoRef &&shinfo,
|
||||
const unsigned pdw_write_mode);
|
||||
|
||||
template <typename F>
|
||||
WritePlan get_write_plan(
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
PGTransaction& t,
|
||||
F &&get_hinfo,
|
||||
DoutPrefixProvider *dpp) {
|
||||
WritePlan plan;
|
||||
t.safe_create_traverse(
|
||||
[&](std::pair<const hobject_t, PGTransaction::ObjectOperation> &i) {
|
||||
const auto& [obj, op] = i;
|
||||
ECUtil::HashInfoRef hinfo = get_hinfo(obj);
|
||||
plan.hash_infos[obj] = hinfo;
|
||||
|
||||
uint64_t projected_size =
|
||||
hinfo->get_projected_total_logical_size(sinfo);
|
||||
|
||||
if (op.deletes_first()) {
|
||||
ldpp_dout(dpp, 20) << __func__ << ": delete, setting projected size"
|
||||
<< " to 0" << dendl;
|
||||
projected_size = 0;
|
||||
}
|
||||
|
||||
hobject_t source;
|
||||
if (op.has_source(&source)) {
|
||||
// typically clone or mv
|
||||
plan.invalidates_cache = true;
|
||||
|
||||
ECUtil::HashInfoRef shinfo = get_hinfo(source);
|
||||
projected_size = shinfo->get_projected_total_logical_size(sinfo);
|
||||
plan.hash_infos[source] = shinfo;
|
||||
}
|
||||
|
||||
auto &will_write = plan.will_write[obj];
|
||||
if (op.truncate &&
|
||||
op.truncate->first < projected_size) {
|
||||
if (!(sinfo.logical_offset_is_stripe_aligned(
|
||||
op.truncate->first))) {
|
||||
plan.to_read[obj].union_insert(
|
||||
sinfo.logical_to_prev_stripe_offset(op.truncate->first),
|
||||
sinfo.get_stripe_width());
|
||||
|
||||
ldpp_dout(dpp, 20) << __func__ << ": unaligned truncate" << dendl;
|
||||
|
||||
will_write.union_insert(
|
||||
sinfo.logical_to_prev_stripe_offset(op.truncate->first),
|
||||
sinfo.get_stripe_width());
|
||||
}
|
||||
projected_size = sinfo.logical_to_next_stripe_offset(
|
||||
op.truncate->first);
|
||||
}
|
||||
|
||||
extent_set raw_write_set;
|
||||
for (auto &&extent: op.buffer_updates) {
|
||||
using BufferUpdate = PGTransaction::ObjectOperation::BufferUpdate;
|
||||
if (boost::get<BufferUpdate::CloneRange>(&(extent.get_val()))) {
|
||||
ceph_assert(
|
||||
0 ==
|
||||
"CloneRange is not allowed, do_op should have returned ENOTSUPP");
|
||||
}
|
||||
raw_write_set.insert(extent.get_off(), extent.get_len());
|
||||
}
|
||||
|
||||
auto orig_size = projected_size;
|
||||
for (auto extent = raw_write_set.begin();
|
||||
extent != raw_write_set.end();
|
||||
++extent) {
|
||||
uint64_t head_start =
|
||||
sinfo.logical_to_prev_stripe_offset(extent.get_start());
|
||||
uint64_t head_finish =
|
||||
sinfo.logical_to_next_stripe_offset(extent.get_start());
|
||||
if (head_start > projected_size) {
|
||||
head_start = projected_size;
|
||||
}
|
||||
if (head_start != head_finish &&
|
||||
head_start < orig_size) {
|
||||
ceph_assert(head_finish <= orig_size);
|
||||
ceph_assert(head_finish - head_start == sinfo.get_stripe_width());
|
||||
ldpp_dout(dpp, 20) << __func__ << ": reading partial head stripe "
|
||||
<< head_start << "~" << sinfo.get_stripe_width()
|
||||
<< dendl;
|
||||
plan.to_read[obj].union_insert(
|
||||
head_start, sinfo.get_stripe_width());
|
||||
}
|
||||
|
||||
uint64_t tail_start =
|
||||
sinfo.logical_to_prev_stripe_offset(
|
||||
extent.get_start() + extent.get_len());
|
||||
uint64_t tail_finish =
|
||||
sinfo.logical_to_next_stripe_offset(
|
||||
extent.get_start() + extent.get_len());
|
||||
if (tail_start != tail_finish &&
|
||||
(head_start == head_finish || tail_start != head_start) &&
|
||||
tail_start < orig_size) {
|
||||
ceph_assert(tail_finish <= orig_size);
|
||||
ceph_assert(tail_finish - tail_start == sinfo.get_stripe_width());
|
||||
ldpp_dout(dpp, 20) << __func__ << ": reading partial tail stripe "
|
||||
<< tail_start << "~" << sinfo.get_stripe_width()
|
||||
<< dendl;
|
||||
plan.to_read[obj].union_insert(
|
||||
tail_start, sinfo.get_stripe_width());
|
||||
}
|
||||
|
||||
if (head_start != tail_finish) {
|
||||
ceph_assert(
|
||||
sinfo.logical_offset_is_stripe_aligned(
|
||||
tail_finish - head_start)
|
||||
);
|
||||
will_write.union_insert(
|
||||
head_start, tail_finish - head_start);
|
||||
if (tail_finish > projected_size)
|
||||
projected_size = tail_finish;
|
||||
} else {
|
||||
ceph_assert(tail_finish <= projected_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (op.truncate && op.truncate->second > projected_size) {
|
||||
uint64_t truncating_to =
|
||||
sinfo.logical_to_next_stripe_offset(op.truncate->second);
|
||||
ldpp_dout(dpp, 20) << __func__ << ": truncating out to "
|
||||
<< truncating_to
|
||||
<< dendl;
|
||||
will_write.union_insert(projected_size,
|
||||
truncating_to - projected_size);
|
||||
projected_size = truncating_to;
|
||||
}
|
||||
|
||||
ldpp_dout(dpp, 20) << __func__ << ": " << obj
|
||||
<< " projected size "
|
||||
<< projected_size
|
||||
<< dendl;
|
||||
hinfo->set_projected_total_logical_size(
|
||||
sinfo,
|
||||
projected_size);
|
||||
|
||||
/* validate post conditions:
|
||||
* to_read should have an entry for `obj` if it isn't empty
|
||||
* and if we are reading from `obj`, we can't be renaming or
|
||||
* cloning it */
|
||||
ceph_assert(plan.to_read.count(obj) == 0 ||
|
||||
(!plan.to_read.at(obj).empty() &&
|
||||
!i.second.has_source()));
|
||||
});
|
||||
return plan;
|
||||
void print(std::ostream &os) const {
|
||||
os << "to_read: " << to_read
|
||||
<< " will_write: " << will_write
|
||||
<< " hinfo: " << hinfo
|
||||
<< " shinfo: " << shinfo
|
||||
<< " orig_size: " << orig_size
|
||||
<< " projected_size: " << projected_size
|
||||
<< " invalidates_cache: " << invalidates_cache
|
||||
<< " do_pdw: " << do_parity_delta_write;
|
||||
}
|
||||
};
|
||||
|
||||
void generate_transactions(
|
||||
PGTransaction* _t,
|
||||
struct WritePlan {
|
||||
bool want_read;
|
||||
std::list<WritePlanObj> plans;
|
||||
|
||||
void print(std::ostream &os) const {
|
||||
os << " { plans : ";
|
||||
bool first = true;
|
||||
for (auto && p : plans) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
os << ", ";
|
||||
}
|
||||
os << p;
|
||||
}
|
||||
os << "}";
|
||||
}
|
||||
};
|
||||
|
||||
class Generate {
|
||||
PGTransaction &t;
|
||||
const ErasureCodeInterfaceRef &ec_impl;
|
||||
const pg_t &pgid;
|
||||
const ECUtil::stripe_info_t &sinfo;
|
||||
shard_id_map<ceph::os::Transaction> &transactions;
|
||||
DoutPrefixProvider *dpp;
|
||||
const OSDMapRef &osdmap;
|
||||
pg_log_entry_t *entry;
|
||||
const hobject_t &oid;
|
||||
PGTransaction::ObjectOperation& op;
|
||||
ObjectContextRef obc;
|
||||
std::map<std::string, std::optional<bufferlist>> xattr_rollback;
|
||||
const WritePlanObj &plan;
|
||||
std::optional<ECUtil::shard_extent_map_t> read_sem;
|
||||
ECUtil::shard_extent_map_t to_write;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> rollback_extents;
|
||||
std::vector<shard_id_set> rollback_shards;
|
||||
uint32_t fadvise_flags = 0;
|
||||
|
||||
void all_shards_written();
|
||||
void shard_written(const shard_id_t shard);
|
||||
void shards_written(const shard_id_set &shards);
|
||||
void delete_first();
|
||||
void zero_truncate_to_delete();
|
||||
void process_init();
|
||||
void encode_and_write();
|
||||
void truncate();
|
||||
void overlay_writes();
|
||||
void appends_and_clone_ranges();
|
||||
void written_and_present_shards();
|
||||
void attr_updates();
|
||||
void handle_deletes();
|
||||
|
||||
public:
|
||||
Generate(PGTransaction &t,
|
||||
ErasureCodeInterfaceRef &ec_impl, pg_t &pgid,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
const std::map<hobject_t, ECUtil::shard_extent_map_t> &partial_extents,
|
||||
std::map<hobject_t, ECUtil::shard_extent_map_t> *written_map,
|
||||
shard_id_map<ceph::os::Transaction> &transactions,
|
||||
const OSDMapRef &osdmap,
|
||||
const hobject_t &oid, PGTransaction::ObjectOperation &op,
|
||||
WritePlanObj &plan,
|
||||
DoutPrefixProvider *dpp,
|
||||
pg_log_entry_t *entry);
|
||||
};
|
||||
|
||||
void generate_transactions(
|
||||
PGTransaction *_t,
|
||||
WritePlan &plan,
|
||||
ceph::ErasureCodeInterfaceRef &ecimpl,
|
||||
ceph::ErasureCodeInterfaceRef &ec_impl,
|
||||
pg_t pgid,
|
||||
const ECUtil::stripe_info_t &sinfo,
|
||||
const std::map<hobject_t,extent_map> &partial_extents,
|
||||
const std::map<hobject_t, ECUtil::shard_extent_map_t> &partial_extents,
|
||||
std::vector<pg_log_entry_t> &entries,
|
||||
std::map<hobject_t,extent_map> *written,
|
||||
std::map<shard_id_t, ceph::os::Transaction> *transactions,
|
||||
std::map<hobject_t, ECUtil::shard_extent_map_t> *written_map,
|
||||
shard_id_map<ceph::os::Transaction> *transactions,
|
||||
std::set<hobject_t> *temp_added,
|
||||
std::set<hobject_t> *temp_removed,
|
||||
DoutPrefixProvider *dpp,
|
||||
const ceph_release_t require_osd_release = ceph_release_t::unknown);
|
||||
};
|
||||
|
||||
#endif
|
||||
const OSDMapRef &osdmap
|
||||
);
|
||||
}
|
||||
|
||||
1235
src/osd/ECUtil.cc
1235
src/osd/ECUtil.cc
File diff suppressed because it is too large
Load Diff
1018
src/osd/ECUtil.h
1018
src/osd/ECUtil.h
File diff suppressed because it is too large
Load Diff
@ -1,245 +0,0 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
/*
|
||||
* Ceph - scalable distributed file system
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat
|
||||
*
|
||||
* This is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software
|
||||
* Foundation. See file COPYING.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ExtentCache.h"
|
||||
|
||||
using std::ostream;
|
||||
|
||||
using ceph::bufferlist;
|
||||
|
||||
void ExtentCache::extent::_link_pin_state(pin_state &pin_state)
|
||||
{
|
||||
ceph_assert(parent_extent_set);
|
||||
ceph_assert(!parent_pin_state);
|
||||
parent_pin_state = &pin_state;
|
||||
pin_state.pin_list.push_back(*this);
|
||||
}
|
||||
|
||||
void ExtentCache::extent::_unlink_pin_state()
|
||||
{
|
||||
ceph_assert(parent_extent_set);
|
||||
ceph_assert(parent_pin_state);
|
||||
auto liter = pin_state::list::s_iterator_to(*this);
|
||||
parent_pin_state->pin_list.erase(liter);
|
||||
parent_pin_state = nullptr;
|
||||
}
|
||||
|
||||
void ExtentCache::extent::unlink()
|
||||
{
|
||||
ceph_assert(parent_extent_set);
|
||||
ceph_assert(parent_pin_state);
|
||||
|
||||
_unlink_pin_state();
|
||||
|
||||
// remove from extent set
|
||||
{
|
||||
auto siter = object_extent_set::set::s_iterator_to(*this);
|
||||
auto &set = object_extent_set::set::container_from_iterator(siter);
|
||||
ceph_assert(&set == &(parent_extent_set->extent_set));
|
||||
set.erase(siter);
|
||||
}
|
||||
|
||||
parent_extent_set = nullptr;
|
||||
ceph_assert(!parent_pin_state);
|
||||
}
|
||||
|
||||
void ExtentCache::extent::link(
|
||||
object_extent_set &extent_set,
|
||||
pin_state &pin_state)
|
||||
{
|
||||
ceph_assert(!parent_extent_set);
|
||||
parent_extent_set = &extent_set;
|
||||
extent_set.extent_set.insert(*this);
|
||||
|
||||
_link_pin_state(pin_state);
|
||||
}
|
||||
|
||||
void ExtentCache::extent::move(
|
||||
pin_state &to)
|
||||
{
|
||||
_unlink_pin_state();
|
||||
_link_pin_state(to);
|
||||
}
|
||||
|
||||
void ExtentCache::remove_and_destroy_if_empty(object_extent_set &eset)
|
||||
{
|
||||
if (eset.extent_set.empty()) {
|
||||
auto siter = cache_set::s_iterator_to(eset);
|
||||
auto &set = cache_set::container_from_iterator(siter);
|
||||
ceph_assert(&set == &per_object_caches);
|
||||
|
||||
// per_object_caches owns eset
|
||||
per_object_caches.erase(eset);
|
||||
delete &eset;
|
||||
}
|
||||
}
|
||||
|
||||
ExtentCache::object_extent_set &ExtentCache::get_or_create(
|
||||
const hobject_t &oid)
|
||||
{
|
||||
cache_set::insert_commit_data data;
|
||||
auto p = per_object_caches.insert_check(oid, Cmp(), data);
|
||||
if (p.second) {
|
||||
auto *eset = new object_extent_set(oid);
|
||||
per_object_caches.insert_commit(*eset, data);
|
||||
return *eset;
|
||||
} else {
|
||||
return *(p.first);
|
||||
}
|
||||
}
|
||||
|
||||
ExtentCache::object_extent_set *ExtentCache::get_if_exists(
|
||||
const hobject_t &oid)
|
||||
{
|
||||
cache_set::insert_commit_data data;
|
||||
auto p = per_object_caches.insert_check(oid, Cmp(), data);
|
||||
if (p.second) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &*(p.first);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<
|
||||
ExtentCache::object_extent_set::set::iterator,
|
||||
ExtentCache::object_extent_set::set::iterator
|
||||
> ExtentCache::object_extent_set::get_containing_range(
|
||||
uint64_t off, uint64_t len)
|
||||
{
|
||||
// fst is first iterator with end after off (may be end)
|
||||
auto fst = extent_set.upper_bound(off, uint_cmp());
|
||||
if (fst != extent_set.begin())
|
||||
--fst;
|
||||
if (fst != extent_set.end() && off >= (fst->offset + fst->get_length()))
|
||||
++fst;
|
||||
|
||||
// lst is first iterator with start >= off + len (may be end)
|
||||
auto lst = extent_set.lower_bound(off + len, uint_cmp());
|
||||
return std::make_pair(fst, lst);
|
||||
}
|
||||
|
||||
extent_set ExtentCache::reserve_extents_for_rmw(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_set &to_write,
|
||||
const extent_set &to_read)
|
||||
{
|
||||
if (to_write.empty() && to_read.empty()) {
|
||||
return extent_set();
|
||||
}
|
||||
extent_set must_read;
|
||||
auto &eset = get_or_create(oid);
|
||||
extent_set missing;
|
||||
for (auto &&res: to_write) {
|
||||
eset.traverse_update(
|
||||
pin,
|
||||
res.first,
|
||||
res.second,
|
||||
[&](uint64_t off, uint64_t len,
|
||||
extent *ext, object_extent_set::update_action *action) {
|
||||
action->action = object_extent_set::update_action::UPDATE_PIN;
|
||||
if (!ext) {
|
||||
missing.insert(off, len);
|
||||
}
|
||||
});
|
||||
}
|
||||
must_read.intersection_of(
|
||||
to_read,
|
||||
missing);
|
||||
return must_read;
|
||||
}
|
||||
|
||||
extent_map ExtentCache::get_remaining_extents_for_rmw(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_set &to_get)
|
||||
{
|
||||
if (to_get.empty()) {
|
||||
return extent_map();
|
||||
}
|
||||
extent_map ret;
|
||||
auto &eset = get_or_create(oid);
|
||||
for (auto &&res: to_get) {
|
||||
bufferlist bl;
|
||||
uint64_t cur = res.first;
|
||||
eset.traverse_update(
|
||||
pin,
|
||||
res.first,
|
||||
res.second,
|
||||
[&](uint64_t off, uint64_t len,
|
||||
extent *ext, object_extent_set::update_action *action) {
|
||||
ceph_assert(off == cur);
|
||||
cur = off + len;
|
||||
action->action = object_extent_set::update_action::NONE;
|
||||
ceph_assert(ext && ext->bl && ext->pinned_by_write());
|
||||
bl.substr_of(
|
||||
*(ext->bl),
|
||||
off - ext->offset,
|
||||
len);
|
||||
ret.insert(off, len, bl);
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ExtentCache::present_rmw_update(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_map &extents)
|
||||
{
|
||||
if (extents.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &eset = get_or_create(oid);
|
||||
for (auto &&res: extents) {
|
||||
eset.traverse_update(
|
||||
pin,
|
||||
res.get_off(),
|
||||
res.get_len(),
|
||||
[&](uint64_t off, uint64_t len,
|
||||
extent *ext, object_extent_set::update_action *action) {
|
||||
action->action = object_extent_set::update_action::NONE;
|
||||
ceph_assert(ext && ext->pinned_by_write());
|
||||
action->bl = bufferlist();
|
||||
action->bl->substr_of(
|
||||
res.get_val(),
|
||||
off - res.get_off(),
|
||||
len);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ostream &ExtentCache::print(ostream &out) const
|
||||
{
|
||||
out << "ExtentCache(" << std::endl;
|
||||
for (auto esiter = per_object_caches.begin();
|
||||
esiter != per_object_caches.end();
|
||||
++esiter) {
|
||||
out << " Extents(" << esiter->oid << ")[" << std::endl;
|
||||
for (auto exiter = esiter->extent_set.begin();
|
||||
exiter != esiter->extent_set.end();
|
||||
++exiter) {
|
||||
out << " Extent(" << exiter->offset
|
||||
<< "~" << exiter->get_length()
|
||||
<< ":" << exiter->pin_tid()
|
||||
<< ")" << std::endl;
|
||||
}
|
||||
}
|
||||
return out << ")" << std::endl;
|
||||
}
|
||||
|
||||
ostream &operator<<(ostream &lhs, const ExtentCache &cache)
|
||||
{
|
||||
return cache.print(lhs);
|
||||
}
|
||||
@ -1,486 +0,0 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
/*
|
||||
* Ceph - scalable distributed file system
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat
|
||||
*
|
||||
* This is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software
|
||||
* Foundation. See file COPYING.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include "include/interval_set.h"
|
||||
#include "common/interval_map.h"
|
||||
#include "include/buffer.h"
|
||||
#include "common/hobject.h"
|
||||
|
||||
/**
|
||||
ExtentCache
|
||||
|
||||
The main purpose of this cache is to ensure that we can pipeline
|
||||
overlapping partial overwrites.
|
||||
|
||||
To that end we need to ensure that an extent pinned for an operation is
|
||||
live until that operation completes. However, a particular extent
|
||||
might be pinned by multiple operations (several pipelined writes
|
||||
on the same object).
|
||||
|
||||
1) When we complete an operation, we only look at extents owned only
|
||||
by that operation.
|
||||
2) Per-extent overhead is fixed size.
|
||||
2) Per-operation metadata is fixed size.
|
||||
|
||||
This is simple enough to realize with two main structures:
|
||||
- extent: contains a pointer to the pin owning it and intrusive list
|
||||
pointers to other extents owned by the same pin
|
||||
- pin_state: contains the list head for extents owned by it
|
||||
|
||||
This works as long as we only need to remember one "owner" for
|
||||
each extent. To make this work, we'll need to leverage some
|
||||
invariants guaranteed by higher layers:
|
||||
|
||||
1) Writes on a particular object must be ordered
|
||||
2) A particular object will have outstanding reads or writes, but not
|
||||
both (note that you can have a read while a write is committed, but
|
||||
not applied).
|
||||
|
||||
Our strategy therefore will be to have whichever in-progress op will
|
||||
finish "last" be the owner of a particular extent. For now, we won't
|
||||
cache reads, so 2) simply means that we can assume that reads and
|
||||
recovery operations imply no unstable extents on the object in
|
||||
question.
|
||||
|
||||
Write: WaitRead -> WaitCommit -> Complete
|
||||
|
||||
Invariant 1) above actually indicates that we can't have writes
|
||||
bypassing the WaitRead state while there are writes waiting on
|
||||
Reads. Thus, the set of operations pinning a particular extent
|
||||
must always complete in order or arrival.
|
||||
|
||||
This suggests that a particular extent may be in only the following
|
||||
states:
|
||||
|
||||
|
||||
0) Empty (not in the map at all)
|
||||
1) Write Pending N
|
||||
- Some write with reqid <= N is currently fetching the data for
|
||||
this extent
|
||||
- The extent must persist until Write reqid N completes
|
||||
- All ops pinning this extent are writes in the WaitRead state of
|
||||
the Write pipeline (there must be an in progress write, so no
|
||||
reads can be in progress).
|
||||
2) Write Pinned N:
|
||||
- This extent has data corresponding to some reqid M <= N
|
||||
- The extent must persist until Write reqid N commits
|
||||
- All ops pinning this extent are writes in some Write
|
||||
state (all are possible). Reads are not possible
|
||||
in this state (or the others) due to 2).
|
||||
|
||||
All of the above suggests that there are 3 things users can
|
||||
ask of the cache corresponding to the 3 Write pipelines
|
||||
states.
|
||||
*/
|
||||
|
||||
/// If someone wants these types, but not ExtentCache, move to another file
|
||||
struct bl_split_merge {
|
||||
ceph::buffer::list split(
|
||||
uint64_t offset,
|
||||
uint64_t length,
|
||||
ceph::buffer::list &bl) const {
|
||||
ceph::buffer::list out;
|
||||
out.substr_of(bl, offset, length);
|
||||
return out;
|
||||
}
|
||||
bool can_merge(const ceph::buffer::list &left, const ceph::buffer::list &right) const {
|
||||
return true;
|
||||
}
|
||||
ceph::buffer::list merge(ceph::buffer::list &&left, ceph::buffer::list &&right) const {
|
||||
ceph::buffer::list bl{std::move(left)};
|
||||
bl.claim_append(right);
|
||||
return bl;
|
||||
}
|
||||
uint64_t length(const ceph::buffer::list &b) const { return b.length(); }
|
||||
};
|
||||
using extent_set = interval_set<uint64_t>;
|
||||
using extent_map = interval_map<uint64_t, ceph::buffer::list, bl_split_merge>;
|
||||
|
||||
class ExtentCache {
|
||||
struct object_extent_set;
|
||||
struct pin_state;
|
||||
private:
|
||||
|
||||
struct extent {
|
||||
object_extent_set *parent_extent_set = nullptr;
|
||||
pin_state *parent_pin_state = nullptr;
|
||||
boost::intrusive::set_member_hook<> extent_set_member;
|
||||
boost::intrusive::list_member_hook<> pin_list_member;
|
||||
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
std::optional<ceph::buffer::list> bl;
|
||||
|
||||
uint64_t get_length() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
bool is_pending() const {
|
||||
return bl == std::nullopt;
|
||||
}
|
||||
|
||||
bool pinned_by_write() const {
|
||||
ceph_assert(parent_pin_state);
|
||||
return parent_pin_state->is_write();
|
||||
}
|
||||
|
||||
uint64_t pin_tid() const {
|
||||
ceph_assert(parent_pin_state);
|
||||
return parent_pin_state->tid;
|
||||
}
|
||||
|
||||
extent(uint64_t offset, ceph::buffer::list _bl)
|
||||
: offset(offset), length(_bl.length()), bl(_bl) {}
|
||||
|
||||
extent(uint64_t offset, uint64_t length)
|
||||
: offset(offset), length(length) {}
|
||||
|
||||
bool operator<(const extent &rhs) const {
|
||||
return offset < rhs.offset;
|
||||
}
|
||||
private:
|
||||
// can briefly violate the two link invariant, used in unlink() and move()
|
||||
void _link_pin_state(pin_state &pin_state);
|
||||
void _unlink_pin_state();
|
||||
public:
|
||||
void unlink();
|
||||
void link(object_extent_set &parent_extent_set, pin_state &pin_state);
|
||||
void move(pin_state &to);
|
||||
};
|
||||
|
||||
struct object_extent_set : boost::intrusive::set_base_hook<> {
|
||||
hobject_t oid;
|
||||
explicit object_extent_set(const hobject_t &oid) : oid(oid) {}
|
||||
|
||||
using set_member_options = boost::intrusive::member_hook<
|
||||
extent,
|
||||
boost::intrusive::set_member_hook<>,
|
||||
&extent::extent_set_member>;
|
||||
using set = boost::intrusive::set<extent, set_member_options>;
|
||||
set extent_set;
|
||||
|
||||
bool operator<(const object_extent_set &rhs) const {
|
||||
return oid < rhs.oid;
|
||||
}
|
||||
|
||||
struct uint_cmp {
|
||||
bool operator()(uint64_t lhs, const extent &rhs) const {
|
||||
return lhs < rhs.offset;
|
||||
}
|
||||
bool operator()(const extent &lhs, uint64_t rhs) const {
|
||||
return lhs.offset < rhs;
|
||||
}
|
||||
};
|
||||
std::pair<set::iterator, set::iterator> get_containing_range(
|
||||
uint64_t offset, uint64_t length);
|
||||
|
||||
void erase(uint64_t offset, uint64_t length);
|
||||
|
||||
struct update_action {
|
||||
enum type {
|
||||
NONE,
|
||||
UPDATE_PIN
|
||||
};
|
||||
type action = NONE;
|
||||
std::optional<ceph::buffer::list> bl;
|
||||
};
|
||||
template <typename F>
|
||||
void traverse_update(
|
||||
pin_state &pin,
|
||||
uint64_t offset,
|
||||
uint64_t length,
|
||||
F &&f) {
|
||||
auto range = get_containing_range(offset, length);
|
||||
|
||||
if (range.first == range.second || range.first->offset > offset) {
|
||||
uint64_t extlen = range.first == range.second ?
|
||||
length : range.first->offset - offset;
|
||||
|
||||
update_action action;
|
||||
f(offset, extlen, nullptr, &action);
|
||||
ceph_assert(!action.bl || action.bl->length() == extlen);
|
||||
if (action.action == update_action::UPDATE_PIN) {
|
||||
extent *ext = action.bl ?
|
||||
new extent(offset, *action.bl) :
|
||||
new extent(offset, extlen);
|
||||
ext->link(*this, pin);
|
||||
} else {
|
||||
ceph_assert(!action.bl);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto p = range.first; p != range.second;) {
|
||||
extent *ext = &*p;
|
||||
++p;
|
||||
|
||||
uint64_t extoff = std::max(ext->offset, offset);
|
||||
uint64_t extlen = std::min(
|
||||
ext->length - (extoff - ext->offset),
|
||||
offset + length - extoff);
|
||||
|
||||
update_action action;
|
||||
f(extoff, extlen, ext, &action);
|
||||
ceph_assert(!action.bl || action.bl->length() == extlen);
|
||||
extent *final_extent = nullptr;
|
||||
if (action.action == update_action::NONE) {
|
||||
final_extent = ext;
|
||||
} else {
|
||||
pin_state *ps = ext->parent_pin_state;
|
||||
ext->unlink();
|
||||
if ((ext->offset < offset) &&
|
||||
(ext->offset + ext->get_length() > offset)) {
|
||||
extent *head = nullptr;
|
||||
if (ext->bl) {
|
||||
ceph::buffer::list bl;
|
||||
bl.substr_of(
|
||||
*(ext->bl),
|
||||
0,
|
||||
offset - ext->offset);
|
||||
head = new extent(ext->offset, bl);
|
||||
} else {
|
||||
head = new extent(
|
||||
ext->offset, offset - ext->offset);
|
||||
}
|
||||
head->link(*this, *ps);
|
||||
}
|
||||
if ((ext->offset + ext->length > offset + length) &&
|
||||
(offset + length > ext->offset)) {
|
||||
uint64_t nlen =
|
||||
(ext->offset + ext->get_length()) - (offset + length);
|
||||
extent *tail = nullptr;
|
||||
if (ext->bl) {
|
||||
ceph::buffer::list bl;
|
||||
bl.substr_of(
|
||||
*(ext->bl),
|
||||
ext->get_length() - nlen,
|
||||
nlen);
|
||||
tail = new extent(offset + length, bl);
|
||||
} else {
|
||||
tail = new extent(offset + length, nlen);
|
||||
}
|
||||
tail->link(*this, *ps);
|
||||
}
|
||||
if (action.action == update_action::UPDATE_PIN) {
|
||||
if (ext->bl) {
|
||||
ceph::buffer::list bl;
|
||||
bl.substr_of(
|
||||
*(ext->bl),
|
||||
extoff - ext->offset,
|
||||
extlen);
|
||||
final_extent = new ExtentCache::extent(
|
||||
extoff,
|
||||
bl);
|
||||
} else {
|
||||
final_extent = new ExtentCache::extent(
|
||||
extoff, extlen);
|
||||
}
|
||||
final_extent->link(*this, pin);
|
||||
}
|
||||
delete ext;
|
||||
}
|
||||
|
||||
if (action.bl) {
|
||||
ceph_assert(final_extent);
|
||||
ceph_assert(final_extent->length == action.bl->length());
|
||||
final_extent->bl = *(action.bl);
|
||||
}
|
||||
|
||||
uint64_t next_off = p == range.second ?
|
||||
offset + length : p->offset;
|
||||
if (extoff + extlen < next_off) {
|
||||
uint64_t tailoff = extoff + extlen;
|
||||
uint64_t taillen = next_off - tailoff;
|
||||
|
||||
update_action action;
|
||||
f(tailoff, taillen, nullptr, &action);
|
||||
ceph_assert(!action.bl || action.bl->length() == taillen);
|
||||
if (action.action == update_action::UPDATE_PIN) {
|
||||
extent *ext = action.bl ?
|
||||
new extent(tailoff, *action.bl) :
|
||||
new extent(tailoff, taillen);
|
||||
ext->link(*this, pin);
|
||||
} else {
|
||||
ceph_assert(!action.bl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
struct Cmp {
|
||||
bool operator()(const hobject_t &oid, const object_extent_set &rhs) const {
|
||||
return oid < rhs.oid;
|
||||
}
|
||||
bool operator()(const object_extent_set &lhs, const hobject_t &oid) const {
|
||||
return lhs.oid < oid;
|
||||
}
|
||||
};
|
||||
|
||||
object_extent_set &get_or_create(const hobject_t &oid);
|
||||
object_extent_set *get_if_exists(const hobject_t &oid);
|
||||
|
||||
void remove_and_destroy_if_empty(object_extent_set &set);
|
||||
using cache_set = boost::intrusive::set<object_extent_set>;
|
||||
cache_set per_object_caches;
|
||||
|
||||
uint64_t next_write_tid = 1;
|
||||
uint64_t next_read_tid = 1;
|
||||
struct pin_state {
|
||||
uint64_t tid = 0;
|
||||
enum pin_type_t {
|
||||
NONE,
|
||||
WRITE,
|
||||
};
|
||||
pin_type_t pin_type = NONE;
|
||||
bool is_write() const { return pin_type == WRITE; }
|
||||
|
||||
pin_state(const pin_state &other) = delete;
|
||||
pin_state &operator=(const pin_state &other) = delete;
|
||||
pin_state(pin_state &&other) = delete;
|
||||
pin_state() = default;
|
||||
|
||||
using list_member_options = boost::intrusive::member_hook<
|
||||
extent,
|
||||
boost::intrusive::list_member_hook<>,
|
||||
&extent::pin_list_member>;
|
||||
using list = boost::intrusive::list<extent, boost::intrusive::constant_time_size<false>, list_member_options>;
|
||||
list pin_list;
|
||||
~pin_state() {
|
||||
ceph_assert(pin_list.empty());
|
||||
ceph_assert(tid == 0);
|
||||
ceph_assert(pin_type == NONE);
|
||||
}
|
||||
void _open(uint64_t in_tid, pin_type_t in_type) {
|
||||
ceph_assert(pin_type == NONE);
|
||||
ceph_assert(in_tid > 0);
|
||||
tid = in_tid;
|
||||
pin_type = in_type;
|
||||
}
|
||||
};
|
||||
|
||||
void release_pin(pin_state &p) {
|
||||
for (auto iter = p.pin_list.begin(); iter != p.pin_list.end(); ) {
|
||||
std::unique_ptr<extent> extent(&*iter); // we now own this
|
||||
iter++; // unlink will invalidate
|
||||
ceph_assert(extent->parent_extent_set);
|
||||
auto &eset = *(extent->parent_extent_set);
|
||||
extent->unlink();
|
||||
remove_and_destroy_if_empty(eset);
|
||||
}
|
||||
p.tid = 0;
|
||||
p.pin_type = pin_state::NONE;
|
||||
}
|
||||
|
||||
public:
|
||||
class write_pin : private pin_state {
|
||||
friend class ExtentCache;
|
||||
private:
|
||||
void open(uint64_t in_tid) {
|
||||
_open(in_tid, pin_state::WRITE);
|
||||
}
|
||||
public:
|
||||
write_pin() : pin_state() {}
|
||||
};
|
||||
|
||||
void open_write_pin(write_pin &pin) {
|
||||
pin.open(next_write_tid++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves extents required for rmw, and learn
|
||||
* which need to be read
|
||||
*
|
||||
* Pins all extents in to_write. Returns subset of to_read not
|
||||
* currently present in the cache. Caller must obtain those
|
||||
* extents before calling get_remaining_extents_for_rmw.
|
||||
*
|
||||
* Transition table:
|
||||
* - Empty -> Write Pending pin.reqid
|
||||
* - Write Pending N -> Write Pending pin.reqid
|
||||
* - Write Pinned N -> Write Pinned pin.reqid
|
||||
*
|
||||
* @param oid [in] object undergoing rmw
|
||||
* @param pin [in,out] pin to use (obtained from create_write_pin)
|
||||
* @param to_write [in] extents which will be written
|
||||
* @param to_read [in] extents to read prior to write (must be subset
|
||||
* of to_write)
|
||||
* @return subset of to_read which isn't already present or pending
|
||||
*/
|
||||
extent_set reserve_extents_for_rmw(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_set &to_write,
|
||||
const extent_set &to_read);
|
||||
|
||||
/**
|
||||
* Gets extents required for rmw not returned from
|
||||
* reserve_extents_for_rmw
|
||||
*
|
||||
* Requested extents (to_get) must be the set to_read \ the set
|
||||
* returned from reserve_extents_for_rmw. No transition table,
|
||||
* all extents at this point must be present and already pinned
|
||||
* for this pin by reserve_extents_for_rmw.
|
||||
*
|
||||
* @param oid [in] object
|
||||
* @param pin [in,out] pin associated with this IO
|
||||
* @param to_get [in] extents to get (see above for restrictions)
|
||||
* @return map of buffers from to_get
|
||||
*/
|
||||
extent_map get_remaining_extents_for_rmw(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_set &to_get);
|
||||
|
||||
/**
|
||||
* Updates the cache to reflect the rmw write
|
||||
*
|
||||
* All presented extents must already have been specified in
|
||||
* reserve_extents_for_rmw under to_write.
|
||||
*
|
||||
* Transition table:
|
||||
* - Empty -> invalid, must call reserve_extents_for_rmw first
|
||||
* - Write Pending N -> Write Pinned N, update buffer
|
||||
* (assert N >= pin.reqid)
|
||||
* - Write Pinned N -> Update buffer (assert N >= pin.reqid)
|
||||
*
|
||||
* @param oid [in] object
|
||||
* @param pin [in,out] pin associated with this IO
|
||||
* @param extents [in] map of buffers to update
|
||||
* @return void
|
||||
*/
|
||||
void present_rmw_update(
|
||||
const hobject_t &oid,
|
||||
write_pin &pin,
|
||||
const extent_map &extents);
|
||||
|
||||
/**
|
||||
* Release all buffers pinned by pin
|
||||
*/
|
||||
void release_write_pin(
|
||||
write_pin &pin) {
|
||||
release_pin(pin);
|
||||
}
|
||||
|
||||
std::ostream &print(std::ostream &out) const;
|
||||
};
|
||||
|
||||
std::ostream &operator <<(std::ostream &lhs, const ExtentCache &cache);
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "erasure-code/ErasureCodePlugin.h"
|
||||
#include "log/Log.h"
|
||||
#include "global/global_context.h"
|
||||
@ -62,6 +63,80 @@ TEST(ErasureCodePlugin, factory)
|
||||
}
|
||||
}
|
||||
|
||||
bufferptr create_bufferptr(uint64_t value) {
|
||||
bufferlist bl;
|
||||
bl.append_zero(4096);
|
||||
memcpy(bl.c_str(), &value, sizeof(value));
|
||||
return bl.begin().get_current_ptr();
|
||||
}
|
||||
|
||||
TEST(ErasureCodePlugin, parity_delta_write) {
|
||||
ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance();
|
||||
ErasureCodeInterfaceRef erasure_code;
|
||||
ErasureCodeProfile profile;
|
||||
profile["technique"] = "reed_sol_van";
|
||||
profile["k"] = "5";
|
||||
int k=5;
|
||||
profile["m"] = "3";
|
||||
int m=3;
|
||||
EXPECT_EQ(0, instance.factory("jerasure",
|
||||
g_conf().get_val<std::string>("erasure_code_dir"),
|
||||
profile,
|
||||
&erasure_code, &cerr));
|
||||
shard_id_map<bufferptr> data(8);
|
||||
shard_id_map<bufferptr> coding(8);
|
||||
shard_id_map<bufferptr> coding2(8);
|
||||
shard_id_map<bufferptr> decode_in(8);
|
||||
shard_id_map<bufferptr> decode_out(8);
|
||||
|
||||
uint32_t seeds[] = {100, 101, 102, 103, 104};
|
||||
uint32_t overwrite3 = 1032;
|
||||
|
||||
for (shard_id_t s; s < k; ++s) {
|
||||
data[s] = create_bufferptr(seeds[int(s)]);
|
||||
}
|
||||
for (shard_id_t s(k); s < k + m; ++s) {
|
||||
coding[s] = create_bufferptr(-1);
|
||||
coding2[s] = create_bufferptr(-1);
|
||||
}
|
||||
|
||||
// Do a normal encode.
|
||||
erasure_code->encode_chunks(data, coding);
|
||||
|
||||
shard_id_map<bufferptr> delta(8);
|
||||
delta[shard_id_t(3)] = create_bufferptr(-1);
|
||||
|
||||
bufferptr overwrite_bp = create_bufferptr(overwrite3);
|
||||
|
||||
erasure_code->encode_delta(data[shard_id_t(3)], overwrite_bp, &delta[shard_id_t(3)]);
|
||||
erasure_code->apply_delta(delta, coding);
|
||||
data[shard_id_t(3)] = overwrite_bp;
|
||||
|
||||
erasure_code->encode_chunks(data, coding2);
|
||||
|
||||
for (shard_id_t s(k); s < k + m; ++s) {
|
||||
ASSERT_EQ(*(uint32_t*)coding[s].c_str(), *(uint32_t*)coding2[s].c_str());
|
||||
}
|
||||
|
||||
data.erase(shard_id_t(4));
|
||||
data.emplace(shard_id_t(4), (char*)malloc(4096), 4096);
|
||||
shard_id_set want;
|
||||
want.insert_range(shard_id_t(0), 5);
|
||||
decode_in[shard_id_t(0)] = data[shard_id_t(0)];
|
||||
decode_in[shard_id_t(1)] = data[shard_id_t(1)];
|
||||
decode_in[shard_id_t(2)] = data[shard_id_t(2)];
|
||||
decode_in[shard_id_t(3)] = data[shard_id_t(3)];
|
||||
decode_out[shard_id_t(4)] = data[shard_id_t(4)];
|
||||
decode_in[shard_id_t(6)] = coding[shard_id_t(6)];
|
||||
|
||||
ASSERT_EQ(0, erasure_code->decode_chunks(want, decode_in, decode_out));
|
||||
|
||||
seeds[3] = overwrite3;
|
||||
for (shard_id_t s(0); s < k; ++s) {
|
||||
ASSERT_EQ(seeds[int(s)], *(uint32_t*)data[s].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* compile-command: "cd ../.. ; make -j4 &&
|
||||
|
||||
@ -76,6 +76,14 @@ add_executable(unittest_ecbackend
|
||||
add_ceph_unittest(unittest_ecbackend)
|
||||
target_link_libraries(unittest_ecbackend osd global)
|
||||
|
||||
# unittest_ecutil
|
||||
add_executable(unittest_ecutil
|
||||
TestECUtil.cc
|
||||
$<TARGET_OBJECTS:unit-main>
|
||||
)
|
||||
add_ceph_unittest(unittest_ecutil)
|
||||
target_link_libraries(unittest_ecutil osd global)
|
||||
|
||||
# unittest_osdscrub
|
||||
add_executable(unittest_osdscrub
|
||||
TestOSDScrub.cc
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1034
src/test/osd/TestECUtil.cc
Normal file
1034
src/test/osd/TestECUtil.cc
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
#include "osd/PGTransaction.h"
|
||||
#include "osd/ECTransaction.h"
|
||||
#include "common/debug.h"
|
||||
#include "osd/ECBackend.h"
|
||||
|
||||
#include "test/unit.cc"
|
||||
|
||||
@ -27,99 +28,344 @@ struct mydpp : public DoutPrefixProvider {
|
||||
|
||||
#define dout_context g_ceph_context
|
||||
|
||||
TEST(ectransaction, two_writes_separated)
|
||||
struct ECTestOp : ECCommon::RMWPipeline::Op {
|
||||
PGTransactionUPtr t;
|
||||
};
|
||||
|
||||
TEST(ectransaction, two_writes_separated_append)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransactionUPtr t(new PGTransaction);
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a, b;
|
||||
t->create(h);
|
||||
a.append_zero(565760);
|
||||
t->write(h, 0, a.length(), a, 0);
|
||||
op.buffer_updates.insert(0, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
b.append_zero(2437120);
|
||||
t->write(h, 669856, b.length(), b, 0);
|
||||
op.buffer_updates.insert(669856, b.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{b, 0});
|
||||
|
||||
ECUtil::stripe_info_t sinfo(2, 2, 8192);
|
||||
auto plan = ECTransaction::get_write_plan(
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 2, 8192, &pool);
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 4);
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
*t,
|
||||
[&](const hobject_t &i) {
|
||||
ECUtil::HashInfoRef ref(new ECUtil::HashInfo(1));
|
||||
return ref;
|
||||
},
|
||||
&dpp);
|
||||
generic_derr << "to_read " << plan.to_read << dendl;
|
||||
generic_derr << "will_write " << plan.will_write << dendl;
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
0,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
ASSERT_EQ(0u, plan.to_read.size());
|
||||
ASSERT_EQ(1u, plan.will_write.size());
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
ASSERT_FALSE(plan.to_read);
|
||||
ASSERT_EQ(4u, plan.will_write.shard_count());
|
||||
}
|
||||
|
||||
TEST(ectransaction, two_writes_nearby)
|
||||
TEST(ectransaction, two_writes_separated_misaligned_overwrite)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransactionUPtr t(new PGTransaction);
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a, b;
|
||||
t->create(h);
|
||||
|
||||
// two nearby writes, both partly touching the same 8192-byte stripe
|
||||
ECUtil::stripe_info_t sinfo(2, 2, 8192);
|
||||
a.append_zero(565760);
|
||||
t->write(h, 0, a.length(), a, 0);
|
||||
op.buffer_updates.insert(0, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
b.append_zero(2437120);
|
||||
t->write(h, 569856, b.length(), b, 0);
|
||||
op.buffer_updates.insert(669856, b.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{b, 0});
|
||||
|
||||
auto plan = ECTransaction::get_write_plan(
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 2, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 3112960;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 4);
|
||||
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
*t,
|
||||
[&](const hobject_t &i) {
|
||||
ECUtil::HashInfoRef ref(new ECUtil::HashInfo(1));
|
||||
return ref;
|
||||
},
|
||||
&dpp);
|
||||
generic_derr << "to_read " << plan.to_read << dendl;
|
||||
generic_derr << "will_write " << plan.will_write << dendl;
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
oi.size,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
ASSERT_EQ(0u, plan.to_read.size());
|
||||
ASSERT_EQ(1u, plan.will_write.size());
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
ASSERT_EQ(2u, (*plan.to_read).shard_count());
|
||||
ASSERT_EQ(4u, plan.will_write.shard_count());
|
||||
}
|
||||
|
||||
TEST(ectransaction, many_writes)
|
||||
// Test writing to an object at an offset which is beyond the end of the
|
||||
// current object.
|
||||
TEST(ectransaction, partial_write)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransactionUPtr t(new PGTransaction);
|
||||
bufferlist a, b;
|
||||
a.append_zero(512);
|
||||
b.append_zero(4096);
|
||||
t->create(h);
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a;
|
||||
|
||||
ECUtil::stripe_info_t sinfo(2, 2, 8192);
|
||||
// write 2801664~512
|
||||
// write 2802176~512
|
||||
// write 2802688~512
|
||||
// write 2803200~512
|
||||
t->write(h, 2801664, a.length(), a, 0);
|
||||
t->write(h, 2802176, a.length(), a, 0);
|
||||
t->write(h, 2802688, a.length(), a, 0);
|
||||
t->write(h, 2803200, a.length(), a, 0);
|
||||
// Start by writing 8 bytes to the start of an object.
|
||||
a.append_zero(8);
|
||||
op.buffer_updates.insert(0, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
// write 2805760~4096
|
||||
// write 2809856~4096
|
||||
// write 2813952~4096
|
||||
t->write(h, 2805760, b.length(), b, 0);
|
||||
t->write(h, 2809856, b.length(), b, 0);
|
||||
t->write(h, 2813952, b.length(), b, 0);
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 8;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 3);
|
||||
|
||||
auto plan = ECTransaction::get_write_plan(
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
*t,
|
||||
[&](const hobject_t &i) {
|
||||
ECUtil::HashInfoRef ref(new ECUtil::HashInfo(1));
|
||||
return ref;
|
||||
},
|
||||
&dpp);
|
||||
generic_derr << "to_read " << plan.to_read << dendl;
|
||||
generic_derr << "will_write " << plan.will_write << dendl;
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
0,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
ASSERT_EQ(0u, plan.to_read.size());
|
||||
ASSERT_EQ(1u, plan.will_write.size());
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// The object is empty, so we should have no reads and an 4k write.
|
||||
ASSERT_FALSE(plan.to_read);
|
||||
extent_set ref_write;
|
||||
ref_write.insert(0, 4096);
|
||||
ASSERT_EQ(2u, plan.will_write.shard_count());
|
||||
ASSERT_EQ(ref_write, plan.will_write.at(shard_id_t(0)));
|
||||
ASSERT_EQ(ref_write, plan.will_write.at(shard_id_t(2)));
|
||||
}
|
||||
|
||||
TEST(ectransaction, overlapping_write_non_aligned)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a;
|
||||
|
||||
// Start by writing 8 bytes to the start of an object.
|
||||
a.append_zero(8);
|
||||
op.buffer_updates.insert(0, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 8;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 4);
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
8,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// There should be no overlap of this read.
|
||||
ASSERT_EQ(1u, (*plan.to_read).shard_count());
|
||||
extent_set ref;
|
||||
ref.insert(0, 4096);
|
||||
ASSERT_EQ(2u, plan.will_write.shard_count());
|
||||
ASSERT_EQ(1u, (*plan.to_read).shard_count());
|
||||
ASSERT_EQ(ref, plan.will_write.at(shard_id_t(0)));
|
||||
ASSERT_EQ(ref, plan.will_write.at(shard_id_t(2)));
|
||||
}
|
||||
|
||||
TEST(ectransaction, test_appending_write_non_aligned)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a;
|
||||
|
||||
// Start by writing 8 bytes to the start of an object.
|
||||
a.append_zero(4096);
|
||||
op.buffer_updates.insert(3*4096, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 4*4096;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 4);
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
8,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// We are growing an option from zero with a hole.
|
||||
ASSERT_FALSE(plan.to_read);
|
||||
|
||||
// The writes will cover not cover the zero parts
|
||||
ECUtil::shard_extent_set_t ref_write(sinfo.get_k_plus_m());
|
||||
ref_write[shard_id_t(1)].insert(4096, 4096);
|
||||
ref_write[shard_id_t(2)].insert(4096, 4096);
|
||||
ASSERT_EQ(ref_write, plan.will_write);
|
||||
}
|
||||
|
||||
TEST(ectransaction, append_with_large_hole)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a;
|
||||
|
||||
// We have a 4k write quite a way after the current limit of a 4k object
|
||||
a.append_zero(4096);
|
||||
op.buffer_updates.insert(24*4096, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 25*4096;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 4);
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
4096,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// Should not require any reads.
|
||||
ASSERT_FALSE(plan.to_read);
|
||||
|
||||
// The writes will cover the new zero parts.
|
||||
ECUtil::shard_extent_set_t ref_write(sinfo.get_k_plus_m());
|
||||
ref_write[shard_id_t(0)].insert(12*4096, 4096);
|
||||
ref_write[shard_id_t(2)].insert(12*4096, 4096);
|
||||
ASSERT_EQ(ref_write, plan.will_write);
|
||||
}
|
||||
|
||||
TEST(ectransaction, test_append_not_page_aligned_with_large_hole)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransaction::ObjectOperation op;
|
||||
bufferlist a;
|
||||
|
||||
// We have a 4k write quite a way after the current limit of a 4k object
|
||||
a.append_zero(2048);
|
||||
op.buffer_updates.insert(24*4096 + 1024, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 25*4096;
|
||||
shard_id_set shards;
|
||||
shards.insert_range(shard_id_t(), 3);
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
4096,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// No reads (because not yet written)
|
||||
ASSERT_FALSE(plan.to_read);
|
||||
|
||||
// Writes should grow to 4k
|
||||
ECUtil::shard_extent_set_t ref_write(sinfo.get_k_plus_m());
|
||||
ref_write[shard_id_t(0)].insert(12*4096, 4096);
|
||||
ref_write[shard_id_t(2)].insert(12*4096, 4096);
|
||||
ASSERT_EQ(ref_write, plan.will_write);
|
||||
}
|
||||
|
||||
TEST(ectransaction, test_overwrite_with_missing)
|
||||
{
|
||||
hobject_t h;
|
||||
PGTransaction::ObjectOperation op, op2;
|
||||
bufferlist a;
|
||||
|
||||
// We have a 4k write quite a way after the current limit of a 4k object
|
||||
a.append_zero(14*1024);
|
||||
op.buffer_updates.insert(0, a.length(), PGTransaction::ObjectOperation::BufferUpdate::Write{a, 0});
|
||||
|
||||
pg_pool_t pool;
|
||||
pool.set_flag(pg_pool_t::FLAG_EC_OPTIMIZATIONS);
|
||||
ECUtil::stripe_info_t sinfo(2, 1, 8192, &pool, std::vector<shard_id_t>(0));
|
||||
object_info_t oi;
|
||||
oi.size = 42*1024;
|
||||
shard_id_set shards;
|
||||
shards.insert(shard_id_t(0));
|
||||
shards.insert(shard_id_t(1));
|
||||
|
||||
ECTransaction::WritePlanObj plan(
|
||||
h,
|
||||
op,
|
||||
sinfo,
|
||||
shards,
|
||||
shards,
|
||||
false,
|
||||
42*1024,
|
||||
oi,
|
||||
std::nullopt,
|
||||
ECUtil::HashInfoRef(new ECUtil::HashInfo(1)),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
generic_derr << "plan " << plan << dendl;
|
||||
|
||||
// No reads (because not yet written)
|
||||
ASSERT_TRUE(plan.to_read);
|
||||
ECUtil::shard_extent_set_t ref_read(sinfo.get_k_plus_m());
|
||||
ref_read[shard_id_t(1)].insert(4096, 4096);
|
||||
ASSERT_EQ(ref_read, plan.to_read);
|
||||
|
||||
// Writes should grow to 4k
|
||||
ECUtil::shard_extent_set_t ref_write(sinfo.get_k_plus_m());
|
||||
ref_write[shard_id_t(0)].insert(0, 8192);
|
||||
ref_write[shard_id_t(1)].insert(0, 8192);
|
||||
ASSERT_EQ(ref_write, plan.will_write);
|
||||
}
|
||||
@ -14,269 +14,682 @@
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "osd/ExtentCache.h"
|
||||
#include <iostream>
|
||||
#include "osd/ECExtentCache.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ECUtil;
|
||||
|
||||
extent_map imap_from_vector(vector<pair<uint64_t, uint64_t> > &&in)
|
||||
shard_extent_map_t imap_from_vector(vector<vector<pair<uint64_t, uint64_t>>> &&in, stripe_info_t const *sinfo)
|
||||
{
|
||||
extent_map out;
|
||||
for (auto &&tup: in) {
|
||||
bufferlist bl;
|
||||
bl.append_zero(tup.second);
|
||||
out.insert(tup.first, bl.length(), bl);
|
||||
shard_extent_map_t out(sinfo);
|
||||
for (int shard = 0; shard < (int)in.size(); shard++) {
|
||||
for (auto &&tup: in[shard]) {
|
||||
bufferlist bl;
|
||||
bl.append_zero(tup.second);
|
||||
out.insert_in_shard(shard_id_t(shard), tup.first, bl);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
extent_map imap_from_iset(const extent_set &set)
|
||||
shard_extent_map_t imap_from_iset(const shard_extent_set_t &sset, stripe_info_t *sinfo)
|
||||
{
|
||||
extent_map out;
|
||||
for (auto &&iter: set) {
|
||||
bufferlist bl;
|
||||
bl.append_zero(iter.second);
|
||||
out.insert(iter.first, iter.second, bl);
|
||||
shard_extent_map_t out(sinfo);
|
||||
|
||||
for (auto &&[shard, set]: sset) {
|
||||
for (auto &&iter: set) {
|
||||
bufferlist bl;
|
||||
bl.append_zero(iter.second);
|
||||
out.insert_in_shard(shard, iter.first, bl);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
extent_set iset_from_vector(vector<pair<uint64_t, uint64_t> > &&in)
|
||||
shard_extent_set_t iset_from_vector(vector<vector<pair<uint64_t, uint64_t>>> &&in, const stripe_info_t *sinfo)
|
||||
{
|
||||
extent_set out;
|
||||
for (auto &&tup: in) {
|
||||
out.insert(tup.first, tup.second);
|
||||
shard_extent_set_t out(sinfo->get_k_plus_m());
|
||||
for (int shard = 0; shard < (int)in.size(); shard++) {
|
||||
for (auto &&tup: in[shard]) {
|
||||
out[shard_id_t(shard)].insert(tup.first, tup.second);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
TEST(extentcache, simple_write)
|
||||
struct Client : public ECExtentCache::BackendReadListener
|
||||
{
|
||||
hobject_t oid;
|
||||
hobject_t oid = hobject_t().make_temp_hobject("My first object");
|
||||
stripe_info_t sinfo;
|
||||
ECExtentCache::LRU lru;
|
||||
ECExtentCache cache;
|
||||
optional<shard_extent_set_t> active_reads;
|
||||
list<shard_extent_map_t> results;
|
||||
|
||||
ExtentCache c;
|
||||
ExtentCache::write_pin pin;
|
||||
c.open_write_pin(pin);
|
||||
Client(uint64_t chunk_size, int k, int m, uint64_t cache_size) :
|
||||
sinfo(k, m, k*chunk_size, vector<shard_id_t>(0)),
|
||||
lru(cache_size), cache(*this, lru, sinfo, g_ceph_context) {};
|
||||
|
||||
auto to_read = iset_from_vector(
|
||||
{{0, 2}, {8, 2}, {20, 2}});
|
||||
auto to_write = iset_from_vector(
|
||||
{{0, 10}, {20, 4}});
|
||||
auto must_read = c.reserve_extents_for_rmw(
|
||||
oid, pin, to_write, to_read);
|
||||
ASSERT_EQ(
|
||||
must_read,
|
||||
to_read);
|
||||
void backend_read(hobject_t _oid, const shard_extent_set_t& request,
|
||||
uint64_t object_size) override {
|
||||
ceph_assert(oid == _oid);
|
||||
active_reads = request;
|
||||
}
|
||||
|
||||
c.print(std::cerr);
|
||||
void cache_ready(const hobject_t& _oid, const shard_extent_map_t& _result)
|
||||
{
|
||||
ceph_assert(oid == _oid);
|
||||
results.emplace_back(_result);
|
||||
}
|
||||
|
||||
auto got = imap_from_iset(must_read);
|
||||
auto pending_read = to_read;
|
||||
pending_read.subtract(must_read);
|
||||
void complete_read()
|
||||
{
|
||||
auto reads_done = imap_from_iset(*active_reads, &sinfo);
|
||||
active_reads.reset(); // set before done, as may be called back.
|
||||
cache.read_done(oid, std::move(reads_done));
|
||||
}
|
||||
|
||||
auto pending = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin,
|
||||
pending_read);
|
||||
ASSERT_TRUE(pending.empty());
|
||||
void complete_write(ECExtentCache::OpRef &op)
|
||||
{
|
||||
shard_extent_map_t emap = imap_from_iset(op->get_writes(), &sinfo);
|
||||
//Fill in the parity. Parity correctness does not matter to the cache.
|
||||
emap.insert_parity_buffers();
|
||||
results.clear();
|
||||
cache.write_done(op, std::move(emap));
|
||||
}
|
||||
|
||||
auto write_map = imap_from_iset(to_write);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin,
|
||||
write_map);
|
||||
void cache_execute(ECExtentCache::OpRef &op)
|
||||
{
|
||||
list<ECExtentCache::OpRef> l;
|
||||
l.emplace_back(op);
|
||||
cache.execute(l);
|
||||
}
|
||||
|
||||
c.release_write_pin(pin);
|
||||
const stripe_info_t *get_stripe_info() const { return &sinfo; }
|
||||
};
|
||||
|
||||
TEST(ECExtentCache, double_write_done)
|
||||
{
|
||||
Client cl(32, 2, 1, 64);
|
||||
|
||||
auto to_write = iset_from_vector({{{0, 10}}, {{0, 10}}}, cl.get_stripe_info());
|
||||
|
||||
optional op = cl.cache.prepare(cl.oid, nullopt, to_write, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op);
|
||||
cl.complete_write(*op);
|
||||
}
|
||||
|
||||
TEST(extentcache, write_write_overlap)
|
||||
TEST(ECExtentCache, simple_write)
|
||||
{
|
||||
hobject_t oid;
|
||||
Client cl(32, 2, 1, 64);
|
||||
{
|
||||
auto to_read = iset_from_vector( {{{0, 2}}, {{0, 2}}}, cl.get_stripe_info());
|
||||
auto to_write = iset_from_vector({{{0, 10}}, {{0, 10}}}, cl.get_stripe_info());
|
||||
|
||||
ExtentCache c;
|
||||
ExtentCache::write_pin pin;
|
||||
c.open_write_pin(pin);
|
||||
/* OpRef request(hobject_t const &oid,
|
||||
std::optional<std::shard_extent_set_t> const &to_read,
|
||||
std::shard_extent_set_t const &write,
|
||||
uint64_t orig_size,
|
||||
uint64_t projected_size,
|
||||
CacheReadyCb &&ready_cb)
|
||||
*/
|
||||
|
||||
// start write 1
|
||||
auto to_read = iset_from_vector(
|
||||
{{0, 2}, {8, 2}, {20, 2}});
|
||||
auto to_write = iset_from_vector(
|
||||
{{0, 10}, {20, 4}});
|
||||
auto must_read = c.reserve_extents_for_rmw(
|
||||
oid, pin, to_write, to_read);
|
||||
ASSERT_EQ(
|
||||
must_read,
|
||||
to_read);
|
||||
optional op = cl.cache.prepare(cl.oid, to_read, to_write, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op);
|
||||
ASSERT_EQ(to_read, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
cl.complete_read();
|
||||
|
||||
c.print(std::cerr);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
ASSERT_EQ(1, cl.results.size());
|
||||
ASSERT_EQ(to_read, cl.results.front().get_extent_set());
|
||||
cl.complete_write(*op);
|
||||
|
||||
// start write 2
|
||||
ExtentCache::write_pin pin2;
|
||||
c.open_write_pin(pin2);
|
||||
auto to_read2 = iset_from_vector(
|
||||
{{2, 4}, {10, 4}, {18, 4}});
|
||||
auto to_write2 = iset_from_vector(
|
||||
{{2, 12}, {18, 12}});
|
||||
auto must_read2 = c.reserve_extents_for_rmw(
|
||||
oid, pin2, to_write2, to_read2);
|
||||
ASSERT_EQ(
|
||||
must_read2,
|
||||
iset_from_vector({{10, 4}, {18, 2}}));
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
op.reset();
|
||||
}
|
||||
|
||||
c.print(std::cerr);
|
||||
// Repeating the same read should complete without a backend read..
|
||||
{
|
||||
auto to_read = iset_from_vector( {{{0, 2}}, {{0, 2}}}, cl.get_stripe_info());
|
||||
auto to_write = iset_from_vector({{{0, 10}}, {{0, 10}}}, cl.get_stripe_info());
|
||||
optional op = cl.cache.prepare(cl.oid, to_read, to_write, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
ASSERT_EQ(1, cl.results.size());
|
||||
ASSERT_EQ(to_read, cl.results.front().get_extent_set());
|
||||
cl.complete_write(*op);
|
||||
op.reset();
|
||||
}
|
||||
|
||||
// complete read for write 1 and start commit
|
||||
auto got = imap_from_iset(must_read);
|
||||
auto pending_read = to_read;
|
||||
pending_read.subtract(must_read);
|
||||
auto pending = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin,
|
||||
pending_read);
|
||||
ASSERT_TRUE(pending.empty());
|
||||
// Perform a read overlapping with the previous write, but not hte previous read.
|
||||
// This should not result in any backend reads, since the cache can be honoured
|
||||
// from the previous write.
|
||||
{
|
||||
auto to_read = iset_from_vector( {{{2, 2}}, {{2, 2}}}, cl.get_stripe_info());
|
||||
auto to_write = iset_from_vector({{{0, 10}}, {{0, 10}}}, cl.get_stripe_info());
|
||||
optional op = cl.cache.prepare(cl.oid, to_read, to_write, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op);
|
||||
|
||||
auto write_map = imap_from_iset(to_write);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin,
|
||||
write_map);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
// complete read for write 2 and start commit
|
||||
auto pending_read2 = to_read2;
|
||||
pending_read2.subtract(must_read2);
|
||||
auto pending2 = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin2,
|
||||
pending_read2);
|
||||
ASSERT_EQ(
|
||||
pending2,
|
||||
imap_from_iset(pending_read2));
|
||||
|
||||
auto write_map2 = imap_from_iset(to_write2);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin2,
|
||||
write_map2);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
c.release_write_pin(pin);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
c.release_write_pin(pin2);
|
||||
// SHould have remained in LRU!
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
ASSERT_EQ(1, cl.results.size());
|
||||
ASSERT_EQ(to_read, cl.results.front().get_extent_set());
|
||||
cl.complete_write(*op);
|
||||
op.reset();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(extentcache, write_write_overlap2)
|
||||
{
|
||||
hobject_t oid;
|
||||
TEST(ECExtentCache, sequential_appends) {
|
||||
Client cl(32, 2, 1, 32);
|
||||
|
||||
ExtentCache c;
|
||||
ExtentCache::write_pin pin;
|
||||
c.open_write_pin(pin);
|
||||
auto to_write1 = iset_from_vector({{{0, 10}}}, cl.get_stripe_info());
|
||||
|
||||
// start write 1
|
||||
auto to_read = extent_set();
|
||||
auto to_write = iset_from_vector(
|
||||
{{659456, 4096}});
|
||||
auto must_read = c.reserve_extents_for_rmw(
|
||||
oid, pin, to_write, to_read);
|
||||
ASSERT_EQ(
|
||||
must_read,
|
||||
to_read);
|
||||
// The first write...
|
||||
optional op1 = cl.cache.prepare(cl.oid, nullopt, to_write1, 0, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
|
||||
c.print(std::cerr);
|
||||
// Write should have been honoured immediately.
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
auto to_write2 = iset_from_vector({{{10, 10}}}, cl.get_stripe_info());
|
||||
cl.complete_write(*op1);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// start write 2
|
||||
ExtentCache::write_pin pin2;
|
||||
c.open_write_pin(pin2);
|
||||
auto to_read2 = extent_set();
|
||||
auto to_write2 = iset_from_vector(
|
||||
{{663552, 4096}});
|
||||
auto must_read2 = c.reserve_extents_for_rmw(
|
||||
oid, pin2, to_write2, to_read2);
|
||||
ASSERT_EQ(
|
||||
must_read2,
|
||||
to_read2);
|
||||
// The first write...
|
||||
optional op2 = cl.cache.prepare(cl.oid, nullopt, to_write1, 10, 20, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op2);
|
||||
|
||||
// start write 3
|
||||
ExtentCache::write_pin pin3;
|
||||
c.open_write_pin(pin3);
|
||||
auto to_read3 = iset_from_vector({{659456, 8192}});
|
||||
auto to_write3 = iset_from_vector({{659456, 8192}});
|
||||
auto must_read3 = c.reserve_extents_for_rmw(
|
||||
oid, pin3, to_write3, to_read3);
|
||||
ASSERT_EQ(
|
||||
must_read3,
|
||||
extent_set());
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
// complete read for write 1 and start commit
|
||||
auto got = imap_from_iset(must_read);
|
||||
auto pending_read = to_read;
|
||||
pending_read.subtract(must_read);
|
||||
auto pending = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin,
|
||||
pending_read);
|
||||
ASSERT_TRUE(pending.empty());
|
||||
|
||||
auto write_map = imap_from_iset(to_write);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin,
|
||||
write_map);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
// complete read for write 2 and start commit
|
||||
auto pending_read2 = to_read2;
|
||||
pending_read2.subtract(must_read2);
|
||||
auto pending2 = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin2,
|
||||
pending_read2);
|
||||
ASSERT_EQ(
|
||||
pending2,
|
||||
imap_from_iset(pending_read2));
|
||||
|
||||
auto write_map2 = imap_from_iset(to_write2);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin2,
|
||||
write_map2);
|
||||
|
||||
// complete read for write 2 and start commit
|
||||
auto pending_read3 = to_read3;
|
||||
pending_read3.subtract(must_read3);
|
||||
auto pending3 = c.get_remaining_extents_for_rmw(
|
||||
oid,
|
||||
pin3,
|
||||
pending_read3);
|
||||
ASSERT_EQ(
|
||||
pending3,
|
||||
imap_from_iset(pending_read3));
|
||||
|
||||
auto write_map3 = imap_from_iset(to_write3);
|
||||
c.present_rmw_update(
|
||||
oid,
|
||||
pin3,
|
||||
write_map3);
|
||||
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
c.release_write_pin(pin);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
c.release_write_pin(pin2);
|
||||
|
||||
c.print(std::cerr);
|
||||
|
||||
c.release_write_pin(pin3);
|
||||
}
|
||||
|
||||
TEST(ECExtentCache, multiple_writes)
|
||||
{
|
||||
Client cl(32, 2, 1, 32);
|
||||
|
||||
auto to_read1 = iset_from_vector( {{{0, 2}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 10}}}, cl.get_stripe_info());
|
||||
|
||||
// This should drive a request for this IO, which we do not yet honour.
|
||||
optional op1 = cl.cache.prepare(cl.oid, to_read1, to_write1, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Perform another request. We should not see any change in the read requests.
|
||||
auto to_read2 = iset_from_vector( {{{8, 4}}}, cl.get_stripe_info());
|
||||
auto to_write2 = iset_from_vector({{{10, 10}}}, cl.get_stripe_info());
|
||||
optional op2 = cl.cache.prepare(cl.oid, to_read2, to_write2, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Perform another request, this to check that reads are coalesced.
|
||||
auto to_read3 = iset_from_vector( {{{32, 6}}}, cl.get_stripe_info());
|
||||
auto to_write3 = iset_from_vector({}, cl.get_stripe_info());
|
||||
optional op3 = cl.cache.prepare(cl.oid, to_read3, to_write3, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op3);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Finally op4, with no reads.
|
||||
auto to_write4 = iset_from_vector({{{20, 10}}}, cl.get_stripe_info());
|
||||
optional op4 = cl.cache.prepare(cl.oid, nullopt, to_write4, 10, 10, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op4);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Completing the first read will allow the first write and start a batched read.
|
||||
// Note that the cache must not read what was written in op 1.
|
||||
cl.complete_read();
|
||||
auto expected_read = iset_from_vector({{{10,2}, {32,6}}}, cl.get_stripe_info());
|
||||
ASSERT_EQ(expected_read, cl.active_reads);
|
||||
ASSERT_EQ(1, cl.results.size());
|
||||
ASSERT_EQ(to_read1, cl.results.front().get_extent_set());
|
||||
cl.complete_write(*op1);
|
||||
|
||||
// The next write requires some more reads, so should not occur.
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// All reads complete, this should allow for op2 to be ready.
|
||||
cl.complete_read();
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
ASSERT_EQ(3, cl.results.size());
|
||||
auto result = cl.results.begin();
|
||||
ASSERT_EQ(to_read2, result++->get_extent_set());
|
||||
ASSERT_EQ(to_read3, result++->get_extent_set());
|
||||
ASSERT_TRUE(result++->empty());
|
||||
|
||||
cl.complete_write(*op2);
|
||||
cl.complete_write(*op3);
|
||||
cl.complete_write(*op4);
|
||||
|
||||
op1.reset();
|
||||
op2.reset();
|
||||
op3.reset();
|
||||
op4.reset();
|
||||
}
|
||||
|
||||
int dummies;
|
||||
struct Dummy
|
||||
{
|
||||
Dummy() {dummies++;}
|
||||
~Dummy() {dummies--;}
|
||||
};
|
||||
|
||||
TEST(ECExtentCache, on_change)
|
||||
{
|
||||
Client cl(32, 2, 1, 64);
|
||||
auto to_read1 = iset_from_vector( {{{0, 2}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 10}}}, cl.get_stripe_info());
|
||||
|
||||
optional<ECExtentCache::OpRef> op;
|
||||
optional<shared_ptr<Dummy>> dummy;
|
||||
|
||||
dummy.emplace(make_shared<Dummy>());
|
||||
ceph_assert(dummies == 1);
|
||||
{
|
||||
shared_ptr<Dummy> d = *dummy;
|
||||
/* Here we generate an op that we never expect to be completed. Note that
|
||||
* some static code analysis tools suggest deleting d here. DO NOT DO THIS
|
||||
* as we are relying on side effects from the destruction of d in this test.
|
||||
*/
|
||||
op.emplace(cl.cache.prepare(cl.oid, to_read1, to_write1, 10, 10, false,
|
||||
[d](ECExtentCache::OpRef &ignored)
|
||||
{
|
||||
ceph_abort("Should be cancelled");
|
||||
}));
|
||||
}
|
||||
cl.cache_execute(*op);
|
||||
|
||||
/* We now have the following graph of objects:
|
||||
* cache -- op -- lambda -- d
|
||||
* dummy --/
|
||||
*/
|
||||
ASSERT_EQ(1, dummies);
|
||||
|
||||
/* Executing the on_change will "cancel" this cache op. This will cause it
|
||||
* to release the lambda, reducing us down to dummy -- d
|
||||
*/
|
||||
cl.cache.on_change();
|
||||
ASSERT_EQ(1, dummies);
|
||||
|
||||
/* This emulates the rmw pipeline clearing outstanding IO. We now have no
|
||||
* references to d, so we should have destructed the object.
|
||||
* */
|
||||
dummy.reset();
|
||||
ASSERT_EQ(0, dummies);
|
||||
|
||||
/* Keeping the op alive here is emulating the dummy keeping a record of the
|
||||
* cache op. It will also be destroyed at this point by rmw pipeline.
|
||||
*/
|
||||
ASSERT_FALSE(cl.cache.idle());
|
||||
op.reset();
|
||||
ASSERT_TRUE(cl.cache.idle());
|
||||
|
||||
// The cache has its own asserts, which we should honour.
|
||||
cl.cache.on_change2();
|
||||
}
|
||||
|
||||
TEST(ECExtentCache, multiple_misaligned_writes)
|
||||
{
|
||||
Client cl(256*1024, 2, 1, 1024*1024);
|
||||
|
||||
// IO 1 is really a 6k write. The write is inflated to 8k, but the second 4k is
|
||||
// partial, so we read the second 4k to RMW
|
||||
auto to_read1 = iset_from_vector( {{{4*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 8*1024}}}, cl.get_stripe_info());
|
||||
|
||||
// IO 2 is the next 8k write, starting at 6k. So we have a 12k write, reading the
|
||||
// first and last pages. The first part of this read should be in the cache.
|
||||
auto to_read2 = iset_from_vector( {{{4*1024, 4*1024}, {12*4096, 4*4096}}}, cl.get_stripe_info());
|
||||
auto to_read2_exec = iset_from_vector( {{{12*4096, 4*4096}}}, cl.get_stripe_info());
|
||||
auto to_write2 = iset_from_vector({{{4*1024, 12*1024}}}, cl.get_stripe_info());
|
||||
|
||||
// IO 3 is the next misaligned 4k, very similar to IO 3.
|
||||
auto to_read3 = iset_from_vector( {{{12*1024, 4*1024}, {20*4096, 4*4096}}}, cl.get_stripe_info());
|
||||
auto to_read3_exec = iset_from_vector( {{{20*4096, 4*4096}}}, cl.get_stripe_info());
|
||||
auto to_write3 = iset_from_vector({{{12*1024, 12*1024}}}, cl.get_stripe_info());
|
||||
|
||||
//Perform the first write, which should result in a read.
|
||||
optional op1 = cl.cache.prepare(cl.oid, to_read1, to_write1, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Submit the second IO.
|
||||
optional op2 = cl.cache.prepare(cl.oid, to_read2, to_write2, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
// We should still be executing read 1.
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Allow the read to complete. We should now have op1 done...
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(to_read2_exec, cl.active_reads);
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op1);
|
||||
|
||||
// And move on to op3
|
||||
optional op3 = cl.cache.prepare(cl.oid, to_read3, to_write3, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op3);
|
||||
// We should still be executing read 1.
|
||||
ASSERT_EQ(to_read2_exec, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Allow the read to complete. We should now have op2 done...
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(to_read3_exec, cl.active_reads);
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op2);
|
||||
ASSERT_EQ(to_read3_exec, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
cl.complete_read();
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op3);
|
||||
|
||||
}
|
||||
|
||||
TEST(ECExtentCache, multiple_misaligned_writes2)
|
||||
{
|
||||
Client cl(256*1024, 2, 1, 1024*1024);
|
||||
|
||||
// IO 1 is really a 6k write. The write is inflated to 8k, but the second 4k is
|
||||
// partial, so we read the second 4k to RMW
|
||||
auto to_read1 = iset_from_vector( {{{4*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 8*1024}}}, cl.get_stripe_info());
|
||||
|
||||
// IO 2 is the next 8k write, starting at 6k. So we have a 12k write, reading the
|
||||
// first and last pages. The first part of this read should be in the cache.
|
||||
auto to_read2 = iset_from_vector( {{{4*1024, 4*1024}, {12*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_read2_exec = iset_from_vector( {{{12*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_write2 = iset_from_vector({{{4*1024, 12*1024}}}, cl.get_stripe_info());
|
||||
|
||||
// IO 3 is the next misaligned 4k, very similar to IO 3.
|
||||
auto to_read3 = iset_from_vector( {{{12*1024, 4*1024}, {20*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_read3_exec = iset_from_vector( {{{20*1024, 4*1024}}}, cl.get_stripe_info());
|
||||
auto to_write3 = iset_from_vector({{{12*1024, 12*1024}}}, cl.get_stripe_info());
|
||||
|
||||
//Perform the first write, which should result in a read.
|
||||
optional op1 = cl.cache.prepare(cl.oid, to_read1, to_write1, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Submit the second IO.
|
||||
optional op2 = cl.cache.prepare(cl.oid, to_read2, to_write2, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
// We should still be executing read 1.
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Allow the read to complete. We should now have op1 done...
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(to_read2_exec, cl.active_reads);
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op1);
|
||||
|
||||
// And move on to op3
|
||||
optional op3 = cl.cache.prepare(cl.oid, to_read3, to_write3, 22*1024, 22*1024, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op3);
|
||||
// We should still be executing read 1.
|
||||
ASSERT_EQ(to_read2_exec, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
// Allow the read to complete. We should now have op2 done...
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(to_read3_exec, cl.active_reads);
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op2);
|
||||
ASSERT_EQ(to_read3_exec, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
cl.complete_read();
|
||||
ASSERT_FALSE(cl.results.empty());
|
||||
cl.complete_write(*op3);
|
||||
|
||||
}
|
||||
|
||||
TEST(ECExtentCache, test_invalidate)
|
||||
{
|
||||
Client cl(256*1024, 2, 1, 1024*1024);
|
||||
|
||||
/* First attempt a write which does not do any reads */
|
||||
{
|
||||
auto to_read1 = iset_from_vector( {{{0, 4096}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 4096}}}, cl.get_stripe_info());
|
||||
optional op1 = cl.cache.prepare(cl.oid, to_read1, to_write1, 4096, 4096, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
ASSERT_EQ(to_read1, cl.active_reads);
|
||||
ASSERT_TRUE(cl.results.empty());
|
||||
|
||||
/* Now perform an invalidating cache write */
|
||||
optional op2 = cl.cache.prepare(cl.oid, nullopt, shard_extent_set_t(cl.sinfo.get_k_plus_m()), 4*1024, 0, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(2, cl.results.size());
|
||||
auto result = cl.results.begin();
|
||||
ASSERT_FALSE(result++->empty());
|
||||
ASSERT_TRUE(result++->empty());
|
||||
|
||||
cl.complete_write(*op1);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
cl.complete_write(*op2);
|
||||
|
||||
cl.cache.on_change();
|
||||
op1.reset();
|
||||
op2.reset();
|
||||
cl.cache.on_change2();
|
||||
ASSERT_TRUE(cl.cache.idle());
|
||||
}
|
||||
|
||||
/* Second test, modifies, deletes, creates, then modifies. */
|
||||
{
|
||||
auto to_read1 = iset_from_vector( {{{0, 8192}}}, cl.get_stripe_info());
|
||||
auto to_write1 = iset_from_vector({{{0, 8192}}}, cl.get_stripe_info());
|
||||
auto to_write2 = iset_from_vector({{{4096, 4096}}}, cl.get_stripe_info());
|
||||
auto to_read3 = iset_from_vector( {{{0, 4096}}}, cl.get_stripe_info());
|
||||
auto to_write3 = iset_from_vector({{{0, 4096}}}, cl.get_stripe_info());
|
||||
optional op1 = cl.cache.prepare(cl.oid, to_read1, to_write1, 8192, 8192, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
optional op2 = cl.cache.prepare(cl.oid, nullopt, shard_extent_set_t(cl.sinfo.get_k_plus_m()), 4*1024, 0, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
optional op3 = cl.cache.prepare(cl.oid, nullopt, to_write2, 0, 8192, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
optional op4 = cl.cache.prepare(cl.oid, to_read3, to_write3, 8192, 8192, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op1);
|
||||
cl.cache_execute(*op2);
|
||||
cl.cache_execute(*op3);
|
||||
cl.cache_execute(*op4);
|
||||
|
||||
/* The first result must actually read. */
|
||||
cl.complete_read();
|
||||
ASSERT_EQ(4, cl.results.size());
|
||||
auto result = cl.results.begin();
|
||||
ASSERT_FALSE(result++->empty());
|
||||
ASSERT_TRUE(result++->empty());
|
||||
ASSERT_TRUE(result++->empty());
|
||||
ASSERT_TRUE(result++->empty());
|
||||
cl.complete_write(*op1);
|
||||
cl.complete_write(*op2);
|
||||
cl.complete_write(*op3);
|
||||
cl.complete_write(*op4);
|
||||
|
||||
cl.cache.on_change();
|
||||
op1.reset();
|
||||
op2.reset();
|
||||
op3.reset();
|
||||
op4.reset();
|
||||
cl.cache.on_change2();
|
||||
ASSERT_TRUE(cl.cache.idle());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ECExtentCache, test_invalidate_lru)
|
||||
{
|
||||
uint64_t c = 4096;
|
||||
int k = 4;
|
||||
int m = 2;
|
||||
Client cl(c, k, m, 1024*c);
|
||||
|
||||
/* Populate the cache LRU and then invalidate the cache. */
|
||||
{
|
||||
uint64_t bs = 3767;
|
||||
auto io1 = iset_from_vector({{{align_page_prev(35*bs), align_page_next(36*bs) - align_page_prev(35*bs)}}}, cl.get_stripe_info());
|
||||
io1[shard_id_t(k)].insert(io1.get_extent_superset());
|
||||
io1[shard_id_t(k+1)].insert(io1.get_extent_superset());
|
||||
auto io2 = iset_from_vector({{{align_page_prev(18*bs), align_page_next(19*bs) - align_page_prev(18*bs)}}}, cl.get_stripe_info());
|
||||
io2[shard_id_t(k)].insert(io1.get_extent_superset());
|
||||
io2[shard_id_t(k+1)].insert(io1.get_extent_superset());
|
||||
// io 3 is the truncate
|
||||
auto io3 = shard_extent_set_t(cl.sinfo.get_k_plus_m());
|
||||
auto io4 = iset_from_vector({{{align_page_prev(30*bs), align_page_next(31*bs) - align_page_prev(30*bs)}}}, cl.get_stripe_info());
|
||||
io3[shard_id_t(k)].insert(io1.get_extent_superset());
|
||||
io3[shard_id_t(k+1)].insert(io1.get_extent_superset());
|
||||
auto io5 = iset_from_vector({{{align_page_prev(18*bs), align_page_next(19*bs) - align_page_prev(18*bs)}}}, cl.get_stripe_info());
|
||||
io4[shard_id_t(k)].insert(io1.get_extent_superset());
|
||||
io4[shard_id_t(k+1)].insert(io1.get_extent_superset());
|
||||
|
||||
optional op1 = cl.cache.prepare(cl.oid, nullopt, io1, 0, align_page_next(36*bs), false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
|
||||
cl.cache_execute(*op1);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
cl.complete_write(*op1);
|
||||
op1.reset();
|
||||
|
||||
optional op2 = cl.cache.prepare(cl.oid, io2, io2, align_page_next(36*bs), align_page_next(36*bs), false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op2);
|
||||
// We have active reads because the object was discarded fro the cache
|
||||
// and has forgotten about all the zero reads.
|
||||
ASSERT_TRUE(cl.active_reads);
|
||||
cl.complete_read();
|
||||
cl.complete_write(*op2);
|
||||
op2.reset();
|
||||
|
||||
optional op3 = cl.cache.prepare(cl.oid, nullopt, io3, align_page_next(36*bs), 0, false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op3);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
cl.complete_write(*op3);
|
||||
op3.reset();
|
||||
|
||||
optional op4 = cl.cache.prepare(cl.oid, nullopt, io4, 0, align_page_next(30*bs), false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op4);
|
||||
ASSERT_FALSE(cl.active_reads);
|
||||
cl.complete_write(*op4);
|
||||
op4.reset();
|
||||
|
||||
optional op5 = cl.cache.prepare(cl.oid, io5, io5, align_page_next(30*bs), align_page_next(30*bs), false,
|
||||
[&cl](ECExtentCache::OpRef &op)
|
||||
{
|
||||
cl.cache_ready(op->get_hoid(), op->get_result());
|
||||
});
|
||||
cl.cache_execute(*op5);
|
||||
ASSERT_TRUE(cl.active_reads);
|
||||
cl.complete_write(*op5);
|
||||
op5.reset();
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
#include "common/ceph_argparse.h"
|
||||
#include "common/config_proxy.h"
|
||||
#include "common/errno.h"
|
||||
#include "erasure-code/ErasureCode.h"
|
||||
#include "erasure-code/ErasureCodePlugin.h"
|
||||
#include "global/global_context.h"
|
||||
#include "global/global_init.h"
|
||||
@ -96,7 +95,7 @@ int ec_init(const std::string &profile_str,
|
||||
uint64_t stripe_size = atoi(profile["k"].c_str());
|
||||
ceph_assert(stripe_size > 0);
|
||||
uint64_t stripe_width = stripe_size * stripe_unit;
|
||||
sinfo->reset(new ECUtil::stripe_info_t(*ec_impl, stripe_width));
|
||||
sinfo->reset(new ECUtil::stripe_info_t(*ec_impl, nullptr, stripe_width));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -196,37 +195,36 @@ int do_encode(const std::vector<const char*> &args) {
|
||||
return r;
|
||||
}
|
||||
|
||||
std::set<int> want;
|
||||
ECUtil::shard_extent_map_t encoded_data(sinfo.get());
|
||||
std::vector<std::string> shards;
|
||||
boost::split(shards, args[2], boost::is_any_of(","));
|
||||
for (auto &shard : shards) {
|
||||
want.insert(atoi(shard.c_str()));
|
||||
}
|
||||
ceph::bufferlist decoded_data;
|
||||
ceph::bufferlist input_data;
|
||||
std::string fname = args[3];
|
||||
|
||||
std::string error;
|
||||
r = decoded_data.read_file(fname.c_str(), &error);
|
||||
r = input_data.read_file(fname.c_str(), &error);
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to read " << fname << ": " << error << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t stripe_width = sinfo->get_stripe_width();
|
||||
if (decoded_data.length() % stripe_width != 0) {
|
||||
uint64_t pad = stripe_width - decoded_data.length() % stripe_width;
|
||||
decoded_data.append_zero(pad);
|
||||
if (input_data.length() % stripe_width != 0) {
|
||||
uint64_t pad = stripe_width - input_data.length() % stripe_width;
|
||||
input_data.append_zero(pad);
|
||||
}
|
||||
|
||||
std::map<int, ceph::bufferlist> encoded_data;
|
||||
r = ECUtil::encode(*sinfo, ec_impl, decoded_data, want, &encoded_data);
|
||||
sinfo->ro_range_to_shard_extent_map(0, input_data.length(), input_data, encoded_data);
|
||||
r = encoded_data.encode(ec_impl, nullptr, encoded_data.get_ro_end());
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to encode: " << cpp_strerror(r) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (auto &[shard, bl] : encoded_data) {
|
||||
for (auto &[shard, _] : encoded_data.get_extent_maps()) {
|
||||
std::string name = fname + "." + stringify(shard);
|
||||
bufferlist bl;
|
||||
encoded_data.get_shard_first_buffer(shard, bl);
|
||||
r = bl.write_file(name.c_str());
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to write " << name << ": " << cpp_strerror(r)
|
||||
@ -247,40 +245,41 @@ int do_decode(const std::vector<const char*> &args) {
|
||||
ceph::ErasureCodeInterfaceRef ec_impl;
|
||||
std::unique_ptr<ECUtil::stripe_info_t> sinfo;
|
||||
int r = ec_init(args[0], args[1], &ec_impl, &sinfo);
|
||||
if (r < 0) {
|
||||
if (r) {
|
||||
return r;
|
||||
}
|
||||
|
||||
std::map<int, ceph::bufferlist> encoded_data;
|
||||
ECUtil::shard_extent_map_t encoded_data(sinfo.get());
|
||||
std::vector<std::string> shards;
|
||||
boost::split(shards, args[2], boost::is_any_of(","));
|
||||
for (auto &shard : shards) {
|
||||
encoded_data[atoi(shard.c_str())] = {};
|
||||
}
|
||||
ceph::bufferlist decoded_data;
|
||||
std::string fname = args[3];
|
||||
|
||||
std::set<int> want_to_read;
|
||||
const auto chunk_mapping = ec_impl->get_chunk_mapping();
|
||||
for (auto &[shard, bl] : encoded_data) {
|
||||
std::string name = fname + "." + stringify(shard);
|
||||
for (auto &shard_str : shards) {
|
||||
std::string name = fname + "." + shard_str;
|
||||
std::string error;
|
||||
bufferlist bl;
|
||||
r = bl.read_file(name.c_str(), &error);
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to read " << name << ": " << error << std::endl;
|
||||
return 1;
|
||||
}
|
||||
auto chunk = static_cast<ssize_t>(chunk_mapping.size()) > shard ?
|
||||
chunk_mapping[shard] : shard_id_t(shard);
|
||||
want_to_read.insert(static_cast<int>(chunk));
|
||||
shard_id_t shard = sinfo->get_shard(raw_shard_id_t(atoi(shard_str.c_str())));
|
||||
encoded_data.insert_in_shard(shard, 0, bl);
|
||||
}
|
||||
|
||||
r = ECUtil::decode(*sinfo, ec_impl, want_to_read, encoded_data, &decoded_data);
|
||||
ECUtil::shard_extent_set_t wanted(sinfo->get_k_plus_m());
|
||||
sinfo->ro_range_to_shard_extent_set(encoded_data.get_ro_start(),
|
||||
encoded_data.get_ro_end() - encoded_data.get_ro_start(), wanted);
|
||||
|
||||
r = encoded_data.decode(ec_impl, wanted, encoded_data.get_ro_end());
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to decode: " << cpp_strerror(r) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
bufferlist decoded_data = encoded_data.get_ro_buffer();
|
||||
r = decoded_data.write_file(fname.c_str());
|
||||
if (r < 0) {
|
||||
std::cerr << "failed to write " << fname << ": " << cpp_strerror(r)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user