ceph-mirror/src/osd/ECCommonL.cc
Matan Breizman 23c33f69ff src: sed -i 's/WITH_SEASTAR/WITH_CRIMSON/'
Signed-off-by: Matan Breizman <mbreizma@redhat.com>
2025-04-03 07:53:15 +00:00

1106 lines
32 KiB
C++

// -*- 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) 2013 Inktank Storage, Inc.
*
* 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 <iostream>
#include <sstream>
#include "ECCommonL.h"
#include "ECInject.h"
#include "messages/MOSDPGPush.h"
#include "messages/MOSDPGPushReply.h"
#include "messages/MOSDECSubOpWrite.h"
#include "messages/MOSDECSubOpWriteReply.h"
#include "messages/MOSDECSubOpRead.h"
#include "messages/MOSDECSubOpReadReply.h"
#include "ECMsgTypes.h"
#include "PGLog.h"
#include "osd_tracer.h"
#define dout_context cct
#define dout_subsys ceph_subsys_osd
#define DOUT_PREFIX_ARGS this
#undef dout_prefix
#define dout_prefix _prefix(_dout, this)
using std::dec;
using std::hex;
using std::less;
using std::list;
using std::make_pair;
using std::map;
using std::pair;
using std::ostream;
using std::set;
using std::string;
using std::unique_ptr;
using std::vector;
using ceph::bufferhash;
using ceph::bufferlist;
using ceph::bufferptr;
using ceph::ErasureCodeInterfaceRef;
using ceph::Formatter;
namespace ECLegacy {
static ostream& _prefix(std::ostream *_dout, ECCommonL::RMWPipeline *rmw_pipeline) {
return rmw_pipeline->get_parent()->gen_dbg_prefix(*_dout) << "ECCommonL ";
}
static ostream& _prefix(std::ostream *_dout, ECCommonL::ReadPipeline *read_pipeline) {
return read_pipeline->get_parent()->gen_dbg_prefix(*_dout) << "ECCommonL ";
}
static ostream& _prefix(std::ostream *_dout,
ECCommonL::UnstableHashInfoRegistry *unstable_hash_info_registry) {
// TODO: backref to ECListener?
return *_dout << "ECCommonL ";
}
static ostream& _prefix(std::ostream *_dout, struct ClientReadCompleter *read_completer);
ostream &operator<<(ostream &lhs, const ECCommonL::RMWPipeline::pipeline_state_t &rhs) {
switch (rhs.pipeline_state) {
case ECCommonL::RMWPipeline::pipeline_state_t::CACHE_VALID:
return lhs << "CACHE_VALID";
case ECCommonL::RMWPipeline::pipeline_state_t::CACHE_INVALID:
return lhs << "CACHE_INVALID";
default:
ceph_abort_msg("invalid pipeline state");
}
return lhs; // unreachable
}
ostream &operator<<(ostream &lhs, const ECCommonL::ec_extent_t &rhs)
{
return lhs << rhs.err << ","
<< rhs.emap;
}
ostream &operator<<(ostream &lhs, const ECCommonL::read_request_t &rhs)
{
return lhs << "read_request_t(to_read=[" << rhs.to_read << "]"
<< ", need=" << rhs.need
<< ", want_attrs=" << rhs.want_attrs
<< ")";
}
ostream &operator<<(ostream &lhs, const ECCommonL::read_result_t &rhs)
{
lhs << "read_result_t(r=" << rhs.r
<< ", errors=" << rhs.errors;
if (rhs.attrs) {
lhs << ", attrs=" << *(rhs.attrs);
} else {
lhs << ", noattrs";
}
return lhs << ", returned=" << rhs.returned << ")";
}
ostream &operator<<(ostream &lhs, const ECCommonL::ReadOp &rhs)
{
lhs << "ReadOp(tid=" << rhs.tid;
#ifndef WITH_CRIMSON
if (rhs.op && rhs.op->get_req()) {
lhs << ", op=";
rhs.op->get_req()->print(lhs);
}
#endif
return lhs << ", to_read=" << rhs.to_read
<< ", complete=" << rhs.complete
<< ", priority=" << rhs.priority
<< ", obj_to_source=" << rhs.obj_to_source
<< ", source_to_obj=" << rhs.source_to_obj
<< ", want_to_read" << rhs.want_to_read
<< ", in_progress=" << rhs.in_progress << ")";
}
void ECCommonL::ReadOp::dump(Formatter *f) const
{
f->dump_unsigned("tid", tid);
#ifndef WITH_CRIMSON
if (op && op->get_req()) {
f->dump_stream("op") << *(op->get_req());
}
#endif
f->dump_stream("to_read") << to_read;
f->dump_stream("complete") << complete;
f->dump_int("priority", priority);
f->dump_stream("obj_to_source") << obj_to_source;
f->dump_stream("source_to_obj") << source_to_obj;
f->dump_stream("want_to_read") << want_to_read;
f->dump_stream("in_progress") << in_progress;
}
ostream &operator<<(ostream &lhs, const ECCommonL::RMWPipeline::Op &rhs)
{
lhs << "Op(" << rhs.hoid
<< " v=" << rhs.version
<< " tt=" << rhs.trim_to
<< " tid=" << rhs.tid
<< " reqid=" << rhs.reqid;
#ifndef WITH_CRIMSON
if (rhs.client_op && rhs.client_op->get_req()) {
lhs << " client_op=";
rhs.client_op->get_req()->print(lhs);
}
#endif
lhs << " pg_committed_to=" << rhs.pg_committed_to
<< " temp_added=" << rhs.temp_added
<< " temp_cleared=" << rhs.temp_cleared
<< " pending_read=" << rhs.pending_read
<< " remote_read=" << rhs.remote_read
<< " remote_read_result=" << rhs.remote_read_result
<< " pending_apply=" << rhs.pending_apply
<< " pending_commit=" << rhs.pending_commit
<< " plan.to_read=" << rhs.plan.to_read
<< " plan.will_write=" << rhs.plan.will_write
<< ")";
return lhs;
}
void ECCommonL::ReadPipeline::complete_read_op(ReadOp &rop)
{
dout(20) << __func__ << " completing " << rop << dendl;
map<hobject_t, read_request_t>::iterator req_iter =
rop.to_read.begin();
map<hobject_t, read_result_t>::iterator resiter =
rop.complete.begin();
ceph_assert(rop.to_read.size() == rop.complete.size());
for (; req_iter != rop.to_read.end(); ++req_iter, ++resiter) {
ceph_assert(rop.want_to_read.contains(req_iter->first));
rop.on_complete->finish_single_request(
req_iter->first,
resiter->second,
req_iter->second.to_read,
rop.want_to_read[req_iter->first]);
}
ceph_assert(rop.on_complete);
std::move(*rop.on_complete).finish(rop.priority);
rop.on_complete = nullptr;
// if the read op is over. clean all the data of this tid.
for (set<pg_shard_t>::iterator iter = rop.in_progress.begin();
iter != rop.in_progress.end();
iter++) {
shard_to_read_map[*iter].erase(rop.tid);
}
rop.in_progress.clear();
tid_to_read_map.erase(rop.tid);
}
void ECCommonL::ReadPipeline::on_change()
{
for (map<ceph_tid_t, ReadOp>::iterator i = tid_to_read_map.begin();
i != tid_to_read_map.end();
++i) {
dout(10) << __func__ << ": cancelling " << i->second << dendl;
}
tid_to_read_map.clear();
shard_to_read_map.clear();
in_progress_client_reads.clear();
}
void ECCommonL::ReadPipeline::get_all_avail_shards(
const hobject_t &hoid,
const set<pg_shard_t> &error_shards,
set<int> &have,
map<shard_id_t, pg_shard_t> &shards,
bool for_recovery)
{
for (set<pg_shard_t>::const_iterator i =
get_parent()->get_acting_shards().begin();
i != get_parent()->get_acting_shards().end();
++i) {
dout(10) << __func__ << ": checking acting " << *i << dendl;
const pg_missing_t &missing = get_parent()->get_shard_missing(*i);
if (error_shards.contains(*i)) {
continue;
}
if (cct->_conf->bluestore_debug_inject_read_err &&
ECInject::test_read_error1(ghobject_t(hoid, ghobject_t::NO_GEN, i->shard))) {
dout(0) << __func__ << " Error inject - Missing shard " << i->shard << dendl;
continue;
}
if (!missing.is_missing(hoid)) {
ceph_assert(!have.count(static_cast<int>(i->shard)));
have.insert(static_cast<int>(i->shard));
ceph_assert(!shards.count(i->shard));
shards.insert(make_pair(i->shard, *i));
}
}
if (for_recovery) {
for (set<pg_shard_t>::const_iterator i =
get_parent()->get_backfill_shards().begin();
i != get_parent()->get_backfill_shards().end();
++i) {
if (error_shards.find(*i) != error_shards.end())
continue;
if (have.count(static_cast<int>(i->shard))) {
ceph_assert(shards.count(i->shard));
continue;
}
dout(10) << __func__ << ": checking backfill " << *i << dendl;
ceph_assert(!shards.count(i->shard));
const pg_info_t &info = get_parent()->get_shard_info(*i);
const pg_missing_t &missing = get_parent()->get_shard_missing(*i);
if (hoid < info.last_backfill &&
!missing.is_missing(hoid)) {
have.insert(static_cast<int>(i->shard));
shards.insert(make_pair(i->shard, *i));
}
}
map<hobject_t, set<pg_shard_t>>::const_iterator miter =
get_parent()->get_missing_loc_shards().find(hoid);
if (miter != get_parent()->get_missing_loc_shards().end()) {
for (set<pg_shard_t>::iterator i = miter->second.begin();
i != miter->second.end();
++i) {
dout(10) << __func__ << ": checking missing_loc " << *i << dendl;
auto m = get_parent()->maybe_get_shard_missing(*i);
if (m) {
ceph_assert(!(*m).is_missing(hoid));
}
if (error_shards.find(*i) != error_shards.end())
continue;
have.insert(static_cast<int>(i->shard));
shards.insert(make_pair(i->shard, *i));
}
}
}
}
int ECCommonL::ReadPipeline::get_min_avail_to_read_shards(
const hobject_t &hoid,
const set<int> &want,
bool for_recovery,
bool do_redundant_reads,
map<pg_shard_t, vector<pair<int, int>>> *to_read)
{
// Make sure we don't do redundant reads for recovery
ceph_assert(!for_recovery || !do_redundant_reads);
set<int> have;
map<shard_id_t, pg_shard_t> shards;
set<pg_shard_t> error_shards;
get_all_avail_shards(hoid, error_shards, have, shards, for_recovery);
map<int, vector<pair<int, int>>> need;
int r = ec_impl->minimum_to_decode(want, have, &need);
if (r < 0)
return r;
if (do_redundant_reads) {
vector<pair<int, int>> subchunks_list;
subchunks_list.push_back(make_pair(0, ec_impl->get_sub_chunk_count()));
for (auto &&i: have) {
need[i] = subchunks_list;
}
}
if (!to_read)
return 0;
for (auto &&i:need) {
ceph_assert(shards.count(shard_id_t(i.first)));
to_read->insert(make_pair(shards[shard_id_t(i.first)], i.second));
}
return 0;
}
// a static for the sake of unittesting
void ECCommonL::ReadPipeline::get_min_want_to_read_shards(
const uint64_t offset,
const uint64_t length,
const ECUtilL::stripe_info_t& sinfo,
set<int> *want_to_read)
{
const auto [left_chunk_index, right_chunk_index] =
sinfo.offset_length_to_data_chunk_indices(offset, length);
const auto distance =
std::min(right_chunk_index - left_chunk_index, (uint64_t)sinfo.get_k());
for(uint64_t i = 0; i < distance; i++) {
auto raw_shard = (left_chunk_index + i) % sinfo.get_k();
want_to_read->insert(sinfo.get_shard(raw_shard));
}
}
void ECCommonL::ReadPipeline::get_min_want_to_read_shards(
const uint64_t offset,
const uint64_t length,
set<int> *want_to_read)
{
get_min_want_to_read_shards(offset, length, sinfo, want_to_read);
dout(20) << __func__ << ": offset " << offset << " length " << length
<< " want_to_read " << *want_to_read << dendl;
}
int ECCommonL::ReadPipeline::get_remaining_shards(
const hobject_t &hoid,
const set<int> &avail,
const set<int> &want,
const read_result_t &result,
map<pg_shard_t, vector<pair<int, int>>> *to_read,
bool for_recovery)
{
ceph_assert(to_read);
set<int> have;
map<shard_id_t, pg_shard_t> shards;
set<pg_shard_t> error_shards;
for (auto &p : result.errors) {
error_shards.insert(p.first);
}
get_all_avail_shards(hoid, error_shards, have, shards, for_recovery);
map<int, vector<pair<int, int>>> need;
int r = ec_impl->minimum_to_decode(want, have, &need);
if (r < 0) {
dout(0) << __func__ << " not enough shards left to try for " << hoid
<< " read result was " << result << dendl;
return -EIO;
}
set<int> shards_left;
for (auto p : need) {
if (avail.find(p.first) == avail.end()) {
shards_left.insert(p.first);
}
}
vector<pair<int, int>> subchunks;
subchunks.push_back(make_pair(0, ec_impl->get_sub_chunk_count()));
for (set<int>::iterator i = shards_left.begin();
i != shards_left.end();
++i) {
ceph_assert(shards.count(shard_id_t(*i)));
ceph_assert(avail.find(*i) == avail.end());
to_read->insert(make_pair(shards[shard_id_t(*i)], subchunks));
}
return 0;
}
void ECCommonL::ReadPipeline::start_read_op(
int priority,
map<hobject_t, set<int>> &want_to_read,
map<hobject_t, read_request_t> &to_read,
OpRequestRef _op,
bool do_redundant_reads,
bool for_recovery,
std::unique_ptr<ECCommonL::ReadCompleter> on_complete)
{
ceph_tid_t tid = get_parent()->get_tid();
ceph_assert(!tid_to_read_map.count(tid));
auto &op = tid_to_read_map.emplace(
tid,
ReadOp(
priority,
tid,
do_redundant_reads,
for_recovery,
std::move(on_complete),
_op,
std::move(want_to_read),
std::move(to_read))).first->second;
dout(10) << __func__ << ": starting " << op << dendl;
if (_op) {
#ifndef WITH_CRIMSON
op.trace = _op->pg_trace;
#endif
op.trace.event("start ec read");
}
do_read_op(op);
}
void ECCommonL::ReadPipeline::do_read_op(ReadOp &op)
{
int priority = op.priority;
ceph_tid_t tid = op.tid;
dout(10) << __func__ << ": starting read " << op << dendl;
map<pg_shard_t, ECSubRead> messages;
for (map<hobject_t, read_request_t>::iterator i = op.to_read.begin();
i != op.to_read.end();
++i) {
bool need_attrs = i->second.want_attrs;
for (auto j = i->second.need.begin();
j != i->second.need.end();
++j) {
if (need_attrs) {
messages[j->first].attrs_to_read.insert(i->first);
need_attrs = false;
}
messages[j->first].subchunks[i->first] = j->second;
op.obj_to_source[i->first].insert(j->first);
op.source_to_obj[j->first].insert(i->first);
}
for (const auto& read : i->second.to_read) {
auto p = make_pair(read.offset, read.size);
pair<uint64_t, uint64_t> chunk_off_len = sinfo.chunk_aligned_offset_len_to_chunk(p);
for (auto k = i->second.need.begin();
k != i->second.need.end();
++k) {
messages[k->first].to_read[i->first].push_back(
boost::make_tuple(
chunk_off_len.first,
chunk_off_len.second,
read.flags));
}
ceph_assert(!need_attrs);
}
}
std::vector<std::pair<int, Message*>> m;
m.reserve(messages.size());
for (map<pg_shard_t, ECSubRead>::iterator i = messages.begin();
i != messages.end();
++i) {
op.in_progress.insert(i->first);
shard_to_read_map[i->first].insert(op.tid);
i->second.tid = tid;
MOSDECSubOpRead *msg = new MOSDECSubOpRead;
msg->set_priority(priority);
msg->pgid = spg_t(
get_info().pgid.pgid,
i->first.shard);
msg->map_epoch = get_osdmap_epoch();
msg->min_epoch = get_parent()->get_interval_start_epoch();
msg->op = i->second;
msg->op.from = get_parent()->whoami_shard();
msg->op.tid = tid;
if (op.trace) {
// initialize a child span for this shard
msg->trace.init("ec sub read", nullptr, &op.trace);
msg->trace.keyval("shard", i->first.shard.id);
}
m.push_back(std::make_pair(i->first.osd, msg));
}
if (!m.empty()) {
get_parent()->send_message_osd_cluster(m, get_osdmap_epoch());
}
dout(10) << __func__ << ": started " << op << dendl;
}
void ECCommonL::ReadPipeline::get_want_to_read_shards(
std::set<int> *want_to_read) const
{
for (int i = 0; i < (int)sinfo.get_k(); ++i) {
want_to_read->insert(sinfo.get_shard(i));
}
}
struct ClientReadCompleter : ECCommonL::ReadCompleter {
ClientReadCompleter(ECCommonL::ReadPipeline &read_pipeline,
ECCommonL::ClientAsyncReadStatus *status)
: read_pipeline(read_pipeline),
status(status) {}
void finish_single_request(
const hobject_t &hoid,
ECCommonL::read_result_t &res,
list<ec_align_t> to_read,
set<int> wanted_to_read) override
{
auto* cct = read_pipeline.cct;
dout(20) << __func__ << " completing hoid=" << hoid
<< " res=" << res << " to_read=" << to_read << dendl;
extent_map result;
if (res.r != 0)
goto out;
ceph_assert(res.returned.size() == to_read.size());
ceph_assert(res.errors.empty());
for (auto &&read: to_read) {
const auto bounds = make_pair(read.offset, read.size);
const auto aligned =
read_pipeline.sinfo.offset_len_to_chunk_bounds(bounds);
ceph_assert(res.returned.front().get<0>() == aligned.first);
ceph_assert(res.returned.front().get<1>() == aligned.second);
map<int, bufferlist> to_decode;
bufferlist bl;
for (map<pg_shard_t, bufferlist>::iterator j =
res.returned.front().get<2>().begin();
j != res.returned.front().get<2>().end();
++j) {
to_decode[static_cast<int>(j->first.shard)] = std::move(j->second);
}
dout(20) << __func__ << " going to decode: "
<< " wanted_to_read=" << wanted_to_read
<< " to_decode=" << to_decode
<< dendl;
int r = ECUtilL::decode(
read_pipeline.sinfo,
read_pipeline.ec_impl,
wanted_to_read,
to_decode,
&bl);
if (r < 0) {
dout(10) << __func__ << " error on ECUtilL::decode r=" << r << dendl;
res.r = r;
goto out;
}
bufferlist trimmed;
// If partial stripe reads are disabled aligned_offset_in_stripe will
// be 0 which will mean trim_offset is 0. When partial reads are enabled
// the shards read (wanted_to_read) is a union of the requirements for
// each stripe, each range being read may need to trim unneeded shards
uint64_t aligned_offset_in_stripe = aligned.first -
read_pipeline.sinfo.logical_to_prev_stripe_offset(aligned.first);
uint64_t chunk_size = read_pipeline.sinfo.get_chunk_size();
uint64_t trim_offset = 0;
for (auto shard : wanted_to_read) {
if (read_pipeline.sinfo.get_raw_shard(shard) * chunk_size <
aligned_offset_in_stripe) {
trim_offset += chunk_size;
} else {
break;
}
}
auto off = read.offset + trim_offset - aligned.first;
dout(20) << __func__ << " bl.length()=" << bl.length()
<< " off=" << off
<< " read.offset=" << read.offset
<< " read.size=" << read.size
<< " trim_offset="<< trim_offset << dendl;
ceph_assert(read.size <= bl.length() - off);
trimmed.substr_of(bl, off, read.size);
result.insert(
read.offset, trimmed.length(), std::move(trimmed));
res.returned.pop_front();
}
out:
dout(20) << __func__ << " calling complete_object with result="
<< result << dendl;
status->complete_object(hoid, res.r, std::move(result));
read_pipeline.kick_reads();
}
void finish(int priority) && override
{
// NOP
}
ECCommonL::ReadPipeline &read_pipeline;
ECCommonL::ClientAsyncReadStatus *status;
};
static ostream& _prefix(std::ostream *_dout, ClientReadCompleter *read_completer) {
return _prefix(_dout, &read_completer->read_pipeline) << "ECCommonL";
}
void ECCommonL::ReadPipeline::objects_read_and_reconstruct(
const map<hobject_t, std::list<ec_align_t>> &reads,
bool fast_read,
GenContextURef<ECCommonL::ec_extents_t &&> &&func)
{
in_progress_client_reads.emplace_back(
reads.size(), std::move(func));
if (!reads.size()) {
kick_reads();
return;
}
map<hobject_t, set<int>> obj_want_to_read;
map<hobject_t, read_request_t> for_read_op;
for (auto &&to_read: reads) {
set<int> want_to_read;
if (cct->_conf->osd_ec_partial_reads) {
for (const auto& single_region : to_read.second) {
get_min_want_to_read_shards(single_region.offset,
single_region.size,
&want_to_read);
}
} else {
get_want_to_read_shards(&want_to_read);
}
map<pg_shard_t, vector<pair<int, int>>> shards;
int r = get_min_avail_to_read_shards(
to_read.first,
want_to_read,
false,
fast_read,
&shards);
ceph_assert(r == 0);
int subchunk_size =
sinfo.get_chunk_size() / ec_impl->get_sub_chunk_count();
dout(20) << __func__
<< " subchunk_size=" << subchunk_size
<< " chunk_size=" << sinfo.get_chunk_size() << dendl;
for_read_op.insert(
make_pair(
to_read.first,
read_request_t(
to_read.second,
shards,
false)));
obj_want_to_read.insert(make_pair(to_read.first, want_to_read));
}
start_read_op(
CEPH_MSG_PRIO_DEFAULT,
obj_want_to_read,
for_read_op,
OpRequestRef(),
fast_read,
false,
std::make_unique<ClientReadCompleter>(*this, &(in_progress_client_reads.back())));
}
int ECCommonL::ReadPipeline::send_all_remaining_reads(
const hobject_t &hoid,
ReadOp &rop)
{
set<int> already_read;
const set<pg_shard_t>& ots = rop.obj_to_source[hoid];
for (set<pg_shard_t>::iterator i = ots.begin(); i != ots.end(); ++i)
already_read.insert(static_cast<int>(i->shard));
dout(10) << __func__ << " have/error shards=" << already_read << dendl;
map<pg_shard_t, vector<pair<int, int>>> shards;
int r = get_remaining_shards(hoid, already_read, rop.want_to_read[hoid],
rop.complete[hoid], &shards, rop.for_recovery);
if (r)
return r;
list<ec_align_t> to_read = rop.to_read.find(hoid)->second.to_read;
// (Note cuixf) If we need to read attrs and we read failed, try to read again.
bool want_attrs =
rop.to_read.find(hoid)->second.want_attrs &&
(!rop.complete[hoid].attrs || rop.complete[hoid].attrs->empty());
if (want_attrs) {
dout(10) << __func__ << " want attrs again" << dendl;
}
rop.to_read.erase(hoid);
rop.to_read.insert(make_pair(
hoid,
read_request_t(
to_read,
shards,
want_attrs)));
return 0;
}
void ECCommonL::ReadPipeline::kick_reads()
{
while (in_progress_client_reads.size() &&
in_progress_client_reads.front().is_complete()) {
in_progress_client_reads.front().run();
in_progress_client_reads.pop_front();
}
}
void ECCommonL::RMWPipeline::start_rmw(OpRef op)
{
ceph_assert(op);
dout(10) << __func__ << ": " << *op << dendl;
ceph_assert(!tid_to_op_map.count(op->tid));
waiting_state.push_back(*op);
tid_to_op_map[op->tid] = std::move(op);
check_ops();
}
bool ECCommonL::RMWPipeline::try_state_to_reads()
{
if (waiting_state.empty())
return false;
Op *op = &(waiting_state.front());
if (op->requires_rmw() && pipeline_state.cache_invalid()) {
ceph_assert(get_parent()->get_pool().allows_ecoverwrites());
dout(20) << __func__ << ": blocking " << *op
<< " because it requires an rmw and the cache is invalid "
<< pipeline_state
<< dendl;
return false;
}
if (!pipeline_state.caching_enabled()) {
op->using_cache = false;
} else if (op->invalidates_cache()) {
dout(20) << __func__ << ": invalidating cache after this op"
<< dendl;
pipeline_state.invalidate();
}
waiting_state.pop_front();
waiting_reads.push_back(*op);
if (op->using_cache) {
cache.open_write_pin(op->pin);
extent_set empty;
for (auto &&hpair: op->plan.will_write) {
auto to_read_plan_iter = op->plan.to_read.find(hpair.first);
const extent_set &to_read_plan =
to_read_plan_iter == op->plan.to_read.end() ?
empty :
to_read_plan_iter->second;
extent_set remote_read = cache.reserve_extents_for_rmw(
hpair.first,
op->pin,
hpair.second,
to_read_plan);
extent_set pending_read = to_read_plan;
pending_read.subtract(remote_read);
if (!remote_read.empty()) {
op->remote_read[hpair.first] = std::move(remote_read);
}
if (!pending_read.empty()) {
op->pending_read[hpair.first] = std::move(pending_read);
}
}
} else {
op->remote_read = op->plan.to_read;
}
dout(10) << __func__ << ": " << *op << dendl;
if (!op->remote_read.empty()) {
ceph_assert(get_parent()->get_pool().allows_ecoverwrites());
objects_read_async_no_cache(
op->remote_read,
[op, this](ec_extents_t &&results) {
for (auto &&i: results) {
op->remote_read_result.emplace(make_pair(i.first, i.second.emap));
}
check_ops();
});
}
return true;
}
bool ECCommonL::RMWPipeline::try_reads_to_commit()
{
if (waiting_reads.empty())
return false;
Op *op = &(waiting_reads.front());
if (op->read_in_progress())
return false;
waiting_reads.pop_front();
waiting_commit.push_back(*op);
dout(10) << __func__ << ": starting commit on " << *op << dendl;
dout(20) << __func__ << ": " << cache << dendl;
get_parent()->apply_stats(
op->hoid,
op->delta_stats);
if (op->using_cache) {
for (auto &&hpair: op->pending_read) {
op->remote_read_result[hpair.first].insert(
cache.get_remaining_extents_for_rmw(
hpair.first,
op->pin,
hpair.second));
}
op->pending_read.clear();
} else {
ceph_assert(op->pending_read.empty());
}
map<shard_id_t, ObjectStore::Transaction> trans;
for (set<pg_shard_t>::const_iterator i =
get_parent()->get_acting_recovery_backfill_shards().begin();
i != get_parent()->get_acting_recovery_backfill_shards().end();
++i) {
trans[i->shard];
}
op->trace.event("start ec write");
map<hobject_t,extent_map> written;
op->generate_transactions(
ec_impl,
get_parent()->get_info().pgid.pgid,
sinfo,
&written,
&trans,
get_parent()->get_dpp(),
get_osdmap()->require_osd_release);
dout(20) << __func__ << ": " << cache << dendl;
dout(20) << __func__ << ": written: " << written << dendl;
dout(20) << __func__ << ": op: " << *op << dendl;
if (!get_parent()->get_pool().allows_ecoverwrites()) {
for (auto &&i: op->log_entries) {
if (i.requires_kraken()) {
derr << __func__ << ": log entry " << i << " requires kraken"
<< " but overwrites are not enabled!" << dendl;
ceph_abort();
}
}
}
map<hobject_t,extent_set> written_set;
for (auto &&i: written) {
written_set[i.first] = i.second.get_interval_set();
}
dout(20) << __func__ << ": written_set: " << written_set << dendl;
ceph_assert(written_set == op->plan.will_write);
if (op->using_cache) {
for (auto &&hpair: written) {
dout(20) << __func__ << ": " << hpair << dendl;
cache.present_rmw_update(hpair.first, op->pin, hpair.second);
}
}
op->remote_read.clear();
op->remote_read_result.clear();
ObjectStore::Transaction empty;
bool should_write_local = false;
ECSubWrite local_write_op;
std::vector<std::pair<int, Message*>> messages;
messages.reserve(get_parent()->get_acting_recovery_backfill_shards().size());
set<pg_shard_t> backfill_shards = get_parent()->get_backfill_shards();
for (set<pg_shard_t>::const_iterator i =
get_parent()->get_acting_recovery_backfill_shards().begin();
i != get_parent()->get_acting_recovery_backfill_shards().end();
++i) {
op->pending_apply.insert(*i);
op->pending_commit.insert(*i);
map<shard_id_t, ObjectStore::Transaction>::iterator iter =
trans.find(i->shard);
ceph_assert(iter != trans.end());
bool should_send = get_parent()->should_send_op(*i, op->hoid);
const pg_stat_t &stats =
(should_send || !backfill_shards.count(*i)) ?
get_info().stats :
get_parent()->get_shard_info().find(*i)->second.stats;
ECSubWrite sop(
get_parent()->whoami_shard(),
op->tid,
op->reqid,
op->hoid,
stats,
should_send ? iter->second : empty,
op->version,
op->trim_to,
op->pg_committed_to,
op->log_entries,
op->updated_hit_set_history,
op->temp_added,
op->temp_cleared,
!should_send);
ZTracer::Trace trace;
if (op->trace) {
// initialize a child span for this shard
trace.init("ec sub write", nullptr, &op->trace);
trace.keyval("shard", i->shard.id);
}
if (*i == get_parent()->whoami_shard()) {
should_write_local = true;
local_write_op.claim(sop);
} else if (cct->_conf->bluestore_debug_inject_read_err &&
ECInject::test_write_error1(ghobject_t(op->hoid,
ghobject_t::NO_GEN, i->shard))) {
dout(0) << " Error inject - Dropping write message to shard " <<
i->shard << dendl;
} else {
MOSDECSubOpWrite *r = new MOSDECSubOpWrite(sop);
r->pgid = spg_t(get_parent()->primary_spg_t().pgid, i->shard);
r->map_epoch = get_osdmap_epoch();
r->min_epoch = get_parent()->get_interval_start_epoch();
r->trace = trace;
messages.push_back(std::make_pair(i->osd, r));
}
}
if (!messages.empty()) {
get_parent()->send_message_osd_cluster(messages, get_osdmap_epoch());
}
if (should_write_local) {
handle_sub_write(
get_parent()->whoami_shard(),
op->client_op,
local_write_op,
op->trace);
}
for (auto i = op->on_write.begin();
i != op->on_write.end();
op->on_write.erase(i++)) {
(*i)();
}
return true;
}
struct ECDummyOp : ECCommonL::RMWPipeline::Op {
void generate_transactions(
ceph::ErasureCodeInterfaceRef &ecimpl,
pg_t pgid,
const ECUtilL::stripe_info_t &sinfo,
std::map<hobject_t,extent_map> *written,
std::map<shard_id_t, ObjectStore::Transaction> *transactions,
DoutPrefixProvider *dpp,
const ceph_release_t require_osd_release) final
{
// NOP, as -- in constrast to ECClassicalOp -- there is no
// transaction involved
}
};
bool ECCommonL::RMWPipeline::try_finish_rmw()
{
if (waiting_commit.empty())
return false;
Op *op = &(waiting_commit.front());
if (op->write_in_progress())
return false;
waiting_commit.pop_front();
dout(10) << __func__ << ": " << *op << dendl;
dout(20) << __func__ << ": " << cache << dendl;
if (op->pg_committed_to > completed_to)
completed_to = op->pg_committed_to;
if (op->version > committed_to)
committed_to = op->version;
if (get_osdmap()->require_osd_release >= ceph_release_t::kraken) {
if (op->version > get_parent()->get_log().get_can_rollback_to() &&
waiting_reads.empty() &&
waiting_commit.empty()) {
// submit a dummy, transaction-empty op to kick the rollforward
auto tid = get_parent()->get_tid();
auto nop = std::make_unique<ECDummyOp>();
nop->hoid = op->hoid;
nop->trim_to = op->trim_to;
nop->pg_committed_to = op->version;
nop->tid = tid;
nop->reqid = op->reqid;
waiting_reads.push_back(*nop);
tid_to_op_map[tid] = std::move(nop);
}
}
if (op->using_cache) {
cache.release_write_pin(op->pin);
}
tid_to_op_map.erase(op->tid);
if (waiting_reads.empty() &&
waiting_commit.empty()) {
pipeline_state.clear();
dout(20) << __func__ << ": clearing pipeline_state "
<< pipeline_state
<< dendl;
}
return true;
}
void ECCommonL::RMWPipeline::check_ops()
{
while (try_state_to_reads() ||
try_reads_to_commit() ||
try_finish_rmw());
}
void ECCommonL::RMWPipeline::on_change()
{
dout(10) << __func__ << dendl;
completed_to = eversion_t();
committed_to = eversion_t();
pipeline_state.clear();
waiting_reads.clear();
waiting_state.clear();
waiting_commit.clear();
for (auto &&op: tid_to_op_map) {
cache.release_write_pin(op.second->pin);
}
tid_to_op_map.clear();
}
void ECCommonL::RMWPipeline::call_write_ordered(std::function<void(void)> &&cb) {
if (!waiting_state.empty()) {
waiting_state.back().on_write.emplace_back(std::move(cb));
} else if (!waiting_reads.empty()) {
waiting_reads.back().on_write.emplace_back(std::move(cb));
} else {
// Nothing earlier in the pipeline, just call it
cb();
}
}
ECUtilL::HashInfoRef ECCommonL::UnstableHashInfoRegistry::maybe_put_hash_info(
const hobject_t &hoid,
ECUtilL::HashInfo &&hinfo)
{
return registry.lookup_or_create(hoid, hinfo);
}
ECUtilL::HashInfoRef ECCommonL::UnstableHashInfoRegistry::get_hash_info(
const hobject_t &hoid,
bool create,
const map<string, bufferlist, less<>>& attrs,
uint64_t size)
{
dout(10) << __func__ << ": Getting attr on " << hoid << dendl;
ECUtilL::HashInfoRef ref = registry.lookup(hoid);
if (!ref) {
dout(10) << __func__ << ": not in cache " << hoid << dendl;
ECUtilL::HashInfo hinfo(ec_impl->get_chunk_count());
bufferlist bl;
map<string, bufferlist>::const_iterator k = attrs.find(ECUtilL::get_hinfo_key());
if (k == attrs.end()) {
dout(5) << __func__ << " " << hoid << " missing hinfo attr" << dendl;
} else {
bl = k->second;
}
if (bl.length() > 0) {
auto bp = bl.cbegin();
try {
decode(hinfo, bp);
} catch(...) {
dout(0) << __func__ << ": Can't decode hinfo for " << hoid << dendl;
return ECUtilL::HashInfoRef();
}
if (hinfo.get_total_chunk_size() != size) {
dout(0) << __func__ << ": Mismatch of total_chunk_size "
<< hinfo.get_total_chunk_size() << dendl;
return ECUtilL::HashInfoRef();
} else {
create = true;
}
} else if (size == 0) { // If empty object and no hinfo, create it
create = true;
}
if (create) {
ref = registry.lookup_or_create(hoid, hinfo);
}
}
return ref;
}
}