remove ceph code
[stor4nfv.git] / src / ceph / src / osd / PrimaryLogPG.cc
diff --git a/src/ceph/src/osd/PrimaryLogPG.cc b/src/ceph/src/osd/PrimaryLogPG.cc
deleted file mode 100644 (file)
index 3dd4eff..0000000
+++ /dev/null
@@ -1,14587 +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) 2004-2006 Sage Weil <sage@newdream.net>
- * Copyright (C) 2013,2014 Cloudwatt <libre.licensing@cloudwatt.com>
- *
- * Author: Loic Dachary <loic@dachary.org>
- *
- * 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 "boost/tuple/tuple.hpp"
-#include "boost/intrusive_ptr.hpp"
-#include "PG.h"
-#include "PrimaryLogPG.h"
-#include "OSD.h"
-#include "OpRequest.h"
-#include "ScrubStore.h"
-#include "Session.h"
-#include "objclass/objclass.h"
-
-#include "common/errno.h"
-#include "common/scrub_types.h"
-#include "common/perf_counters.h"
-
-#include "messages/MOSDOp.h"
-#include "messages/MOSDBackoff.h"
-#include "messages/MOSDSubOp.h"
-#include "messages/MOSDSubOpReply.h"
-#include "messages/MOSDPGTrim.h"
-#include "messages/MOSDPGScan.h"
-#include "messages/MOSDRepScrub.h"
-#include "messages/MOSDPGBackfill.h"
-#include "messages/MOSDPGBackfillRemove.h"
-#include "messages/MOSDPGUpdateLogMissing.h"
-#include "messages/MOSDPGUpdateLogMissingReply.h"
-#include "messages/MCommandReply.h"
-#include "messages/MOSDScrubReserve.h"
-#include "mds/inode_backtrace.h" // Ugh
-#include "common/EventTrace.h"
-
-#include "common/config.h"
-#include "include/compat.h"
-#include "mon/MonClient.h"
-#include "osdc/Objecter.h"
-#include "json_spirit/json_spirit_value.h"
-#include "json_spirit/json_spirit_reader.h"
-#include "include/assert.h"  // json_spirit clobbers it
-#include "include/rados/rados_types.hpp"
-
-#ifdef WITH_LTTNG
-#include "tracing/osd.h"
-#else
-#define tracepoint(...)
-#endif
-
-#define dout_context cct
-#define dout_subsys ceph_subsys_osd
-#define DOUT_PREFIX_ARGS this, osd->whoami, get_osdmap()
-#undef dout_prefix
-#define dout_prefix _prefix(_dout, this)
-template <typename T>
-static ostream& _prefix(std::ostream *_dout, T *pg) {
-  return *_dout << pg->gen_prefix();
-}
-
-
-#include <sstream>
-#include <utility>
-
-#include <errno.h>
-
-MEMPOOL_DEFINE_OBJECT_FACTORY(PrimaryLogPG, replicatedpg, osd);
-
-PGLSFilter::PGLSFilter() : cct(nullptr)
-{
-}
-
-PGLSFilter::~PGLSFilter()
-{
-}
-
-struct PrimaryLogPG::C_OSD_OnApplied : Context {
-  PrimaryLogPGRef pg;
-  epoch_t epoch;
-  eversion_t v;
-  C_OSD_OnApplied(
-    PrimaryLogPGRef pg,
-    epoch_t epoch,
-    eversion_t v)
-    : pg(pg), epoch(epoch), v(v) {}
-  void finish(int) override {
-    pg->lock();
-    if (!pg->pg_has_reset_since(epoch))
-      pg->op_applied(v);
-    pg->unlock();
-  }
-};
-
-/**
- * The CopyCallback class defines an interface for completions to the
- * copy_start code. Users of the copy infrastructure must implement
- * one and give an instance of the class to start_copy.
- *
- * The implementer is responsible for making sure that the CopyCallback
- * can associate itself with the correct copy operation.
- */
-class PrimaryLogPG::CopyCallback : public GenContext<CopyCallbackResults> {
-protected:
-  CopyCallback() {}
-  /**
-   * results.get<0>() is the return code: 0 for success; -ECANCELED if
-   * the operation was cancelled by the local OSD; -errno for other issues.
-   * results.get<1>() is a pointer to a CopyResults object, which you are
-   * responsible for deleting.
-   */
-  void finish(CopyCallbackResults results_) override = 0;
-
-public:
-  /// Provide the final size of the copied object to the CopyCallback
-  ~CopyCallback() override {}
-};
-
-template <typename T>
-class PrimaryLogPG::BlessedGenContext : public GenContext<T> {
-  PrimaryLogPGRef pg;
-  unique_ptr<GenContext<T>> c;
-  epoch_t e;
-public:
-  BlessedGenContext(PrimaryLogPG *pg, GenContext<T> *c, epoch_t e)
-    : pg(pg), c(c), e(e) {}
-  void finish(T t) override {
-    pg->lock();
-    if (pg->pg_has_reset_since(e))
-      c.reset();
-    else
-      c.release()->complete(t);
-    pg->unlock();
-  }
-};
-
-GenContext<ThreadPool::TPHandle&> *PrimaryLogPG::bless_gencontext(
-  GenContext<ThreadPool::TPHandle&> *c) {
-  return new BlessedGenContext<ThreadPool::TPHandle&>(
-    this, c, get_osdmap()->get_epoch());
-}
-
-class PrimaryLogPG::BlessedContext : public Context {
-  PrimaryLogPGRef pg;
-  unique_ptr<Context> c;
-  epoch_t e;
-public:
-  BlessedContext(PrimaryLogPG *pg, Context *c, epoch_t e)
-    : pg(pg), c(c), e(e) {}
-  void finish(int r) override {
-    pg->lock();
-    if (pg->pg_has_reset_since(e))
-      c.reset();
-    else
-      c.release()->complete(r);
-    pg->unlock();
-  }
-};
-
-
-Context *PrimaryLogPG::bless_context(Context *c) {
-  return new BlessedContext(this, c, get_osdmap()->get_epoch());
-}
-
-class PrimaryLogPG::C_PG_ObjectContext : public Context {
-  PrimaryLogPGRef pg;
-  ObjectContext *obc;
-  public:
-  C_PG_ObjectContext(PrimaryLogPG *p, ObjectContext *o) :
-    pg(p), obc(o) {}
-  void finish(int r) override {
-    pg->object_context_destructor_callback(obc);
-  }
-};
-
-class PrimaryLogPG::C_OSD_OndiskWriteUnlock : public Context {
-  ObjectContextRef obc, obc2, obc3;
-  public:
-  C_OSD_OndiskWriteUnlock(
-    ObjectContextRef o,
-    ObjectContextRef o2 = ObjectContextRef(),
-    ObjectContextRef o3 = ObjectContextRef()) : obc(o), obc2(o2), obc3(o3) {}
-  void finish(int r) override {
-    obc->ondisk_write_unlock();
-    if (obc2)
-      obc2->ondisk_write_unlock();
-    if (obc3)
-      obc3->ondisk_write_unlock();
-  }
-};
-
-struct OnReadComplete : public Context {
-  PrimaryLogPG *pg;
-  PrimaryLogPG::OpContext *opcontext;
-  OnReadComplete(
-    PrimaryLogPG *pg,
-    PrimaryLogPG::OpContext *ctx) : pg(pg), opcontext(ctx) {}
-  void finish(int r) override {
-    opcontext->finish_read(pg);
-  }
-  ~OnReadComplete() override {}
-};
-
-class PrimaryLogPG::C_OSD_AppliedRecoveredObject : public Context {
-  PrimaryLogPGRef pg;
-  ObjectContextRef obc;
-  public:
-  C_OSD_AppliedRecoveredObject(PrimaryLogPG *p, ObjectContextRef o) :
-    pg(p), obc(o) {}
-  void finish(int r) override {
-    pg->_applied_recovered_object(obc);
-  }
-};
-
-class PrimaryLogPG::C_OSD_CommittedPushedObject : public Context {
-  PrimaryLogPGRef pg;
-  epoch_t epoch;
-  eversion_t last_complete;
-  public:
-  C_OSD_CommittedPushedObject(
-    PrimaryLogPG *p, epoch_t epoch, eversion_t lc) :
-    pg(p), epoch(epoch), last_complete(lc) {
-  }
-  void finish(int r) override {
-    pg->_committed_pushed_object(epoch, last_complete);
-  }
-};
-
-class PrimaryLogPG::C_OSD_AppliedRecoveredObjectReplica : public Context {
-  PrimaryLogPGRef pg;
-  public:
-  explicit C_OSD_AppliedRecoveredObjectReplica(PrimaryLogPG *p) :
-    pg(p) {}
-  void finish(int r) override {
-    pg->_applied_recovered_object_replica();
-  }
-};
-
-// OpContext
-void PrimaryLogPG::OpContext::start_async_reads(PrimaryLogPG *pg)
-{
-  inflightreads = 1;
-  list<pair<boost::tuple<uint64_t, uint64_t, unsigned>,
-           pair<bufferlist*, Context*> > > in;
-  in.swap(pending_async_reads);
-  pg->pgbackend->objects_read_async(
-    obc->obs.oi.soid,
-    in,
-    new OnReadComplete(pg, this), pg->get_pool().fast_read);
-}
-void PrimaryLogPG::OpContext::finish_read(PrimaryLogPG *pg)
-{
-  assert(inflightreads > 0);
-  --inflightreads;
-  if (async_reads_complete()) {
-    assert(pg->in_progress_async_reads.size());
-    assert(pg->in_progress_async_reads.front().second == this);
-    pg->in_progress_async_reads.pop_front();
-
-    // Restart the op context now that all reads have been
-    // completed. Read failures will be handled by the op finisher
-    pg->execute_ctx(this);
-  }
-}
-
-class CopyFromCallback : public PrimaryLogPG::CopyCallback {
-public:
-  PrimaryLogPG::CopyResults *results = nullptr;
-  PrimaryLogPG::OpContext *ctx;
-  OSDOp &osd_op;
-
-  CopyFromCallback(PrimaryLogPG::OpContext *ctx, OSDOp &osd_op)
-    : ctx(ctx), osd_op(osd_op) {
-  }
-  ~CopyFromCallback() override {}
-
-  void finish(PrimaryLogPG::CopyCallbackResults results_) override {
-    results = results_.get<1>();
-    int r = results_.get<0>();
-
-    // for finish_copyfrom
-    ctx->user_at_version = results->user_version;
-
-    if (r >= 0) {
-      ctx->pg->execute_ctx(ctx);
-    } else {
-      if (r != -ECANCELED) { // on cancel just toss it out; client resends
-       if (ctx->op)
-         ctx->pg->osd->reply_op_error(ctx->op, r);
-      } else if (results->should_requeue) {
-       if (ctx->op)
-         ctx->pg->requeue_op(ctx->op);
-      }
-      ctx->pg->close_op_ctx(ctx);
-    }
-  }
-
-  bool is_temp_obj_used() {
-    return results->started_temp_obj;
-  }
-  uint64_t get_data_size() {
-    return results->object_size;
-  }
-};
-
-struct CopyFromFinisher : public PrimaryLogPG::OpFinisher {
-  CopyFromCallback *copy_from_callback;
-
-  CopyFromFinisher(CopyFromCallback *copy_from_callback)
-    : copy_from_callback(copy_from_callback) {
-  }
-
-  int execute() override {
-    // instance will be destructed after this method completes
-    copy_from_callback->ctx->pg->finish_copyfrom(copy_from_callback);
-    return 0;
-  }
-};
-
-// ======================
-// PGBackend::Listener
-
-void PrimaryLogPG::on_local_recover(
-  const hobject_t &hoid,
-  const ObjectRecoveryInfo &_recovery_info,
-  ObjectContextRef obc,
-  bool is_delete,
-  ObjectStore::Transaction *t
-  )
-{
-  dout(10) << __func__ << ": " << hoid << dendl;
-
-  ObjectRecoveryInfo recovery_info(_recovery_info);
-  clear_object_snap_mapping(t, hoid);
-  if (!is_delete && recovery_info.soid.is_snap()) {
-    OSDriver::OSTransaction _t(osdriver.get_transaction(t));
-    set<snapid_t> snaps;
-    dout(20) << " snapset " << recovery_info.ss
-            << " legacy_snaps " << recovery_info.oi.legacy_snaps << dendl;
-    if (recovery_info.ss.is_legacy() ||
-       recovery_info.ss.seq == 0 /* jewel osd doesn't populate this */) {
-      assert(recovery_info.oi.legacy_snaps.size());
-      snaps.insert(recovery_info.oi.legacy_snaps.begin(),
-                  recovery_info.oi.legacy_snaps.end());
-    } else {
-      auto p = recovery_info.ss.clone_snaps.find(hoid.snap);
-      assert(p != recovery_info.ss.clone_snaps.end());  // hmm, should we warn?
-      snaps.insert(p->second.begin(), p->second.end());
-    }
-    dout(20) << " snaps " << snaps << dendl;
-    snap_mapper.add_oid(
-      recovery_info.soid,
-      snaps,
-      &_t);
-  }
-  if (!is_delete && pg_log.get_missing().is_missing(recovery_info.soid) &&
-      pg_log.get_missing().get_items().find(recovery_info.soid)->second.need > recovery_info.version) {
-    assert(is_primary());
-    const pg_log_entry_t *latest = pg_log.get_log().objects.find(recovery_info.soid)->second;
-    if (latest->op == pg_log_entry_t::LOST_REVERT &&
-       latest->reverting_to == recovery_info.version) {
-      dout(10) << " got old revert version " << recovery_info.version
-              << " for " << *latest << dendl;
-      recovery_info.version = latest->version;
-      // update the attr to the revert event version
-      recovery_info.oi.prior_version = recovery_info.oi.version;
-      recovery_info.oi.version = latest->version;
-      bufferlist bl;
-      ::encode(recovery_info.oi, bl,
-              get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-      assert(!pool.info.require_rollback());
-      t->setattr(coll, ghobject_t(recovery_info.soid), OI_ATTR, bl);
-      if (obc)
-       obc->attr_cache[OI_ATTR] = bl;
-    }
-  }
-
-  // keep track of active pushes for scrub
-  ++active_pushes;
-
-  if (recovery_info.version > pg_log.get_can_rollback_to()) {
-    /* This can only happen during a repair, and even then, it would
-     * be one heck of a race.  If we are repairing the object, the
-     * write in question must be fully committed, so it's not valid
-     * to roll it back anyway (and we'll be rolled forward shortly
-     * anyway) */
-    PGLogEntryHandler h{this, t};
-    pg_log.roll_forward_to(recovery_info.version, &h);
-  }
-  recover_got(recovery_info.soid, recovery_info.version);
-
-  if (is_primary()) {
-    if (!is_delete) {
-      obc->obs.exists = true;
-      obc->ondisk_write_lock();
-
-      bool got = obc->get_recovery_read();
-      assert(got);
-
-      assert(recovering.count(obc->obs.oi.soid));
-      recovering[obc->obs.oi.soid] = obc;
-      obc->obs.oi = recovery_info.oi;  // may have been updated above
-      t->register_on_applied_sync(new C_OSD_OndiskWriteUnlock(obc));
-    }
-
-    t->register_on_applied(new C_OSD_AppliedRecoveredObject(this, obc));
-
-    publish_stats_to_osd();
-    assert(missing_loc.needs_recovery(hoid));
-    if (!is_delete)
-      missing_loc.add_location(hoid, pg_whoami);
-    release_backoffs(hoid);
-    if (!is_unreadable_object(hoid)) {
-      auto unreadable_object_entry = waiting_for_unreadable_object.find(hoid);
-      if (unreadable_object_entry != waiting_for_unreadable_object.end()) {
-       dout(20) << " kicking unreadable waiters on " << hoid << dendl;
-       requeue_ops(unreadable_object_entry->second);
-       waiting_for_unreadable_object.erase(unreadable_object_entry);
-      }
-    }
-  } else {
-    t->register_on_applied(
-      new C_OSD_AppliedRecoveredObjectReplica(this));
-
-  }
-
-  t->register_on_commit(
-    new C_OSD_CommittedPushedObject(
-      this,
-      get_osdmap()->get_epoch(),
-      info.last_complete));
-
-  // update pg
-  dirty_info = true;
-  write_if_dirty(*t);
-}
-
-void PrimaryLogPG::on_global_recover(
-  const hobject_t &soid,
-  const object_stat_sum_t &stat_diff,
-  bool is_delete)
-{
-  info.stats.stats.sum.add(stat_diff);
-  missing_loc.recovered(soid);
-  publish_stats_to_osd();
-  dout(10) << "pushed " << soid << " to all replicas" << dendl;
-  map<hobject_t, ObjectContextRef>::iterator i = recovering.find(soid);
-  assert(i != recovering.end());
-
-  if (!is_delete) {
-    // recover missing won't have had an obc, but it gets filled in
-    // during on_local_recover
-    assert(i->second);
-    list<OpRequestRef> requeue_list;
-    i->second->drop_recovery_read(&requeue_list);
-    requeue_ops(requeue_list);
-  }
-
-  backfills_in_flight.erase(soid);
-
-  recovering.erase(i);
-  finish_recovery_op(soid);
-  release_backoffs(soid);
-  auto degraded_object_entry = waiting_for_degraded_object.find(soid);
-  if (degraded_object_entry != waiting_for_degraded_object.end()) {
-    dout(20) << " kicking degraded waiters on " << soid << dendl;
-    requeue_ops(degraded_object_entry->second);
-    waiting_for_degraded_object.erase(degraded_object_entry);
-  }
-  auto unreadable_object_entry = waiting_for_unreadable_object.find(soid);
-  if (unreadable_object_entry != waiting_for_unreadable_object.end()) {
-    dout(20) << " kicking unreadable waiters on " << soid << dendl;
-    requeue_ops(unreadable_object_entry->second);
-    waiting_for_unreadable_object.erase(unreadable_object_entry);
-  }
-  finish_degraded_object(soid);
-}
-
-void PrimaryLogPG::on_peer_recover(
-  pg_shard_t peer,
-  const hobject_t &soid,
-  const ObjectRecoveryInfo &recovery_info)
-{
-  publish_stats_to_osd();
-  // done!
-  peer_missing[peer].got(soid, recovery_info.version);
-}
-
-void PrimaryLogPG::begin_peer_recover(
-  pg_shard_t peer,
-  const hobject_t soid)
-{
-  peer_missing[peer].revise_have(soid, eversion_t());
-}
-
-void PrimaryLogPG::schedule_recovery_work(
-  GenContext<ThreadPool::TPHandle&> *c)
-{
-  osd->recovery_gen_wq.queue(c);
-}
-
-void PrimaryLogPG::send_message_osd_cluster(
-  int peer, Message *m, epoch_t from_epoch)
-{
-  osd->send_message_osd_cluster(peer, m, from_epoch);
-}
-
-void PrimaryLogPG::send_message_osd_cluster(
-  Message *m, Connection *con)
-{
-  osd->send_message_osd_cluster(m, con);
-}
-
-void PrimaryLogPG::send_message_osd_cluster(
-  Message *m, const ConnectionRef& con)
-{
-  osd->send_message_osd_cluster(m, con);
-}
-
-void PrimaryLogPG::on_primary_error(
-  const hobject_t &oid,
-  eversion_t v)
-{
-  dout(0) << __func__ << ": oid " << oid << " version " << v << dendl;
-  primary_failed(oid);
-  primary_error(oid, v);
-  backfills_in_flight.erase(oid);
-  missing_loc.add_missing(oid, v, eversion_t());
-}
-
-ConnectionRef PrimaryLogPG::get_con_osd_cluster(
-  int peer, epoch_t from_epoch)
-{
-  return osd->get_con_osd_cluster(peer, from_epoch);
-}
-
-PerfCounters *PrimaryLogPG::get_logger()
-{
-  return osd->logger;
-}
-
-
-// ====================
-// missing objects
-
-bool PrimaryLogPG::is_missing_object(const hobject_t& soid) const
-{
-  return pg_log.get_missing().get_items().count(soid);
-}
-
-void PrimaryLogPG::maybe_kick_recovery(
-  const hobject_t &soid)
-{
-  eversion_t v;
-  if (!missing_loc.needs_recovery(soid, &v))
-    return;
-
-  map<hobject_t, ObjectContextRef>::const_iterator p = recovering.find(soid);
-  if (p != recovering.end()) {
-    dout(7) << "object " << soid << " v " << v << ", already recovering." << dendl;
-  } else if (missing_loc.is_unfound(soid)) {
-    dout(7) << "object " << soid << " v " << v << ", is unfound." << dendl;
-  } else {
-    dout(7) << "object " << soid << " v " << v << ", recovering." << dendl;
-    PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op();
-    if (is_missing_object(soid)) {
-      recover_missing(soid, v, cct->_conf->osd_client_op_priority, h);
-    } else if (missing_loc.is_deleted(soid)) {
-      prep_object_replica_deletes(soid, v, h);
-    } else {
-      prep_object_replica_pushes(soid, v, h);
-    }
-    pgbackend->run_recovery_op(h, cct->_conf->osd_client_op_priority);
-  }
-}
-
-void PrimaryLogPG::wait_for_unreadable_object(
-  const hobject_t& soid, OpRequestRef op)
-{
-  assert(is_unreadable_object(soid));
-  maybe_kick_recovery(soid);
-  waiting_for_unreadable_object[soid].push_back(op);
-  op->mark_delayed("waiting for missing object");
-}
-
-bool PrimaryLogPG::is_degraded_or_backfilling_object(const hobject_t& soid)
-{
-  /* The conditions below may clear (on_local_recover, before we queue
-   * the transaction) before we actually requeue the degraded waiters
-   * in on_global_recover after the transaction completes.
-   */
-  if (waiting_for_degraded_object.count(soid))
-    return true;
-  if (pg_log.get_missing().get_items().count(soid))
-    return true;
-  assert(!actingbackfill.empty());
-  for (set<pg_shard_t>::iterator i = actingbackfill.begin();
-       i != actingbackfill.end();
-       ++i) {
-    if (*i == get_primary()) continue;
-    pg_shard_t peer = *i;
-    auto peer_missing_entry = peer_missing.find(peer);
-    if (peer_missing_entry != peer_missing.end() &&
-       peer_missing_entry->second.get_items().count(soid))
-      return true;
-
-    // Object is degraded if after last_backfill AND
-    // we are backfilling it
-    if (is_backfill_targets(peer) &&
-       peer_info[peer].last_backfill <= soid &&
-       last_backfill_started >= soid &&
-       backfills_in_flight.count(soid))
-      return true;
-  }
-  return false;
-}
-
-void PrimaryLogPG::wait_for_degraded_object(const hobject_t& soid, OpRequestRef op)
-{
-  assert(is_degraded_or_backfilling_object(soid));
-
-  maybe_kick_recovery(soid);
-  waiting_for_degraded_object[soid].push_back(op);
-  op->mark_delayed("waiting for degraded object");
-}
-
-void PrimaryLogPG::block_write_on_full_cache(
-  const hobject_t& _oid, OpRequestRef op)
-{
-  const hobject_t oid = _oid.get_head();
-  dout(20) << __func__ << ": blocking object " << oid
-          << " on full cache" << dendl;
-  objects_blocked_on_cache_full.insert(oid);
-  waiting_for_cache_not_full.push_back(op);
-  op->mark_delayed("waiting for cache not full");
-}
-
-void PrimaryLogPG::block_for_clean(
-  const hobject_t& oid, OpRequestRef op)
-{
-  dout(20) << __func__ << ": blocking object " << oid
-          << " on primary repair" << dendl;
-  waiting_for_clean_to_primary_repair.push_back(op);
-  op->mark_delayed("waiting for clean to repair");
-}
-
-void PrimaryLogPG::block_write_on_snap_rollback(
-  const hobject_t& oid, ObjectContextRef obc, OpRequestRef op)
-{
-  dout(20) << __func__ << ": blocking object " << oid.get_head()
-          << " on snap promotion " << obc->obs.oi.soid << dendl;
-  // otherwise, we'd have blocked in do_op
-  assert(oid.is_head());
-  assert(objects_blocked_on_snap_promotion.count(oid) == 0);
-  objects_blocked_on_snap_promotion[oid] = obc;
-  wait_for_blocked_object(obc->obs.oi.soid, op);
-}
-
-void PrimaryLogPG::block_write_on_degraded_snap(
-  const hobject_t& snap, OpRequestRef op)
-{
-  dout(20) << __func__ << ": blocking object " << snap.get_head()
-          << " on degraded snap " << snap << dendl;
-  // otherwise, we'd have blocked in do_op
-  assert(objects_blocked_on_degraded_snap.count(snap.get_head()) == 0);
-  objects_blocked_on_degraded_snap[snap.get_head()] = snap.snap;
-  wait_for_degraded_object(snap, op);
-}
-
-bool PrimaryLogPG::maybe_await_blocked_snapset(
-  const hobject_t &hoid,
-  OpRequestRef op)
-{
-  ObjectContextRef obc;
-  obc = object_contexts.lookup(hoid.get_head());
-  if (obc) {
-    if (obc->is_blocked()) {
-      wait_for_blocked_object(obc->obs.oi.soid, op);
-      return true;
-    } else {
-      return false;
-    }
-  }
-  obc = object_contexts.lookup(hoid.get_snapdir());
-  if (obc) {
-    if (obc->is_blocked()) {
-      wait_for_blocked_object(obc->obs.oi.soid, op);
-      return true;
-    } else {
-      return false;
-    }
-  }
-  return false;
-}
-
-void PrimaryLogPG::wait_for_blocked_object(const hobject_t& soid, OpRequestRef op)
-{
-  dout(10) << __func__ << " " << soid << " " << op << dendl;
-  waiting_for_blocked_object[soid].push_back(op);
-  op->mark_delayed("waiting for blocked object");
-}
-
-void PrimaryLogPG::maybe_force_recovery()
-{
-  // no force if not in degraded/recovery/backfill stats
-  if (!is_degraded() &&
-      !state_test(PG_STATE_RECOVERING |
-                  PG_STATE_RECOVERY_WAIT |
-                 PG_STATE_BACKFILLING |
-                 PG_STATE_BACKFILL_WAIT |
-                 PG_STATE_BACKFILL_TOOFULL))
-    return;
-
-  if (pg_log.get_log().approx_size() <
-      cct->_conf->osd_max_pg_log_entries *
-        cct->_conf->osd_force_recovery_pg_log_entries_factor)
-    return;
-
-  // find the oldest missing object
-  version_t min_version = 0;
-  hobject_t soid;
-  if (!pg_log.get_missing().get_items().empty()) {
-    min_version = pg_log.get_missing().get_rmissing().begin()->first;
-    soid = pg_log.get_missing().get_rmissing().begin()->second;
-  }
-  assert(!actingbackfill.empty());
-  for (set<pg_shard_t>::iterator it = actingbackfill.begin();
-       it != actingbackfill.end();
-       ++it) {
-    if (*it == get_primary()) continue;
-    pg_shard_t peer = *it;
-    if (peer_missing.count(peer) &&
-       !peer_missing[peer].get_items().empty() &&
-       min_version > peer_missing[peer].get_rmissing().begin()->first) {
-      min_version = peer_missing[peer].get_rmissing().begin()->first;
-      soid = peer_missing[peer].get_rmissing().begin()->second;
-    }
-  }
-
-  // recover it
-  if (soid != hobject_t())
-    maybe_kick_recovery(soid);
-}
-
-class PGLSPlainFilter : public PGLSFilter {
-  string val;
-public:
-  int init(bufferlist::iterator &params) override
-  {
-    try {
-      ::decode(xattr, params);
-      ::decode(val, params);
-    } catch (buffer::error &e) {
-      return -EINVAL;
-    }
-
-    return 0;
-  }
-  ~PGLSPlainFilter() override {}
-  bool filter(const hobject_t &obj, bufferlist& xattr_data,
-                      bufferlist& outdata) override;
-};
-
-class PGLSParentFilter : public PGLSFilter {
-  inodeno_t parent_ino;
-public:
-  CephContext* cct;
-  PGLSParentFilter(CephContext* cct) : cct(cct) {
-    xattr = "_parent";
-  }
-  int init(bufferlist::iterator &params) override
-  {
-    try {
-      ::decode(parent_ino, params);
-    } catch (buffer::error &e) {
-      return -EINVAL;
-    }
-    generic_dout(0) << "parent_ino=" << parent_ino << dendl;
-
-    return 0;
-  }
-  ~PGLSParentFilter() override {}
-  bool filter(const hobject_t &obj, bufferlist& xattr_data,
-                      bufferlist& outdata) override;
-};
-
-bool PGLSParentFilter::filter(const hobject_t &obj,
-                              bufferlist& xattr_data, bufferlist& outdata)
-{
-  bufferlist::iterator iter = xattr_data.begin();
-  inode_backtrace_t bt;
-
-  generic_dout(0) << "PGLSParentFilter::filter" << dendl;
-
-  ::decode(bt, iter);
-
-  vector<inode_backpointer_t>::iterator vi;
-  for (vi = bt.ancestors.begin(); vi != bt.ancestors.end(); ++vi) {
-    generic_dout(0) << "vi->dirino=" << vi->dirino << " parent_ino=" << parent_ino << dendl;
-    if (vi->dirino == parent_ino) {
-      ::encode(*vi, outdata);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool PGLSPlainFilter::filter(const hobject_t &obj,
-                             bufferlist& xattr_data, bufferlist& outdata)
-{
-  if (val.size() != xattr_data.length())
-    return false;
-
-  if (memcmp(val.c_str(), xattr_data.c_str(), val.size()))
-    return false;
-
-  return true;
-}
-
-bool PrimaryLogPG::pgls_filter(PGLSFilter *filter, hobject_t& sobj, bufferlist& outdata)
-{
-  bufferlist bl;
-
-  // If filter has expressed an interest in an xattr, load it.
-  if (!filter->get_xattr().empty()) {
-    int ret = pgbackend->objects_get_attr(
-      sobj,
-      filter->get_xattr(),
-      &bl);
-    dout(0) << "getattr (sobj=" << sobj << ", attr=" << filter->get_xattr() << ") returned " << ret << dendl;
-    if (ret < 0) {
-      if (ret != -ENODATA || filter->reject_empty_xattr()) {
-        return false;
-      }
-    }
-  }
-
-  return filter->filter(sobj, bl, outdata);
-}
-
-int PrimaryLogPG::get_pgls_filter(bufferlist::iterator& iter, PGLSFilter **pfilter)
-{
-  string type;
-  PGLSFilter *filter;
-
-  try {
-    ::decode(type, iter);
-  }
-  catch (buffer::error& e) {
-    return -EINVAL;
-  }
-
-  if (type.compare("parent") == 0) {
-    filter = new PGLSParentFilter(cct);
-  } else if (type.compare("plain") == 0) {
-    filter = new PGLSPlainFilter();
-  } else {
-    std::size_t dot = type.find(".");
-    if (dot == std::string::npos || dot == 0 || dot == type.size() - 1) {
-      return -EINVAL;
-    }
-
-    const std::string class_name = type.substr(0, dot);
-    const std::string filter_name = type.substr(dot + 1);
-    ClassHandler::ClassData *cls = NULL;
-    int r = osd->class_handler->open_class(class_name, &cls);
-    if (r != 0) {
-      derr << "Error opening class '" << class_name << "': "
-           << cpp_strerror(r) << dendl;
-      if (r != -EPERM) // propogate permission error
-        r = -EINVAL;
-      return r;
-    } else {
-      assert(cls);
-    }
-
-    ClassHandler::ClassFilter *class_filter = cls->get_filter(filter_name);
-    if (class_filter == NULL) {
-      derr << "Error finding filter '" << filter_name << "' in class "
-           << class_name << dendl;
-      return -EINVAL;
-    }
-    filter = class_filter->fn();
-    if (!filter) {
-      // Object classes are obliged to return us something, but let's
-      // give an error rather than asserting out.
-      derr << "Buggy class " << class_name << " failed to construct "
-              "filter " << filter_name << dendl;
-      return -EINVAL;
-    }
-  }
-
-  assert(filter);
-  int r = filter->init(iter);
-  if (r < 0) {
-    derr << "Error initializing filter " << type << ": "
-         << cpp_strerror(r) << dendl;
-    delete filter;
-    return -EINVAL;
-  } else {
-    // Successfully constructed and initialized, return it.
-    *pfilter = filter;
-    return 0;
-  }
-}
-
-
-// ==========================================================
-
-int PrimaryLogPG::do_command(
-  cmdmap_t cmdmap,
-  ostream& ss,
-  bufferlist& idata,
-  bufferlist& odata,
-  ConnectionRef con,
-  ceph_tid_t tid)
-{
-  const auto &missing = pg_log.get_missing();
-  string prefix;
-  string format;
-
-  cmd_getval(cct, cmdmap, "format", format);
-  boost::scoped_ptr<Formatter> f(Formatter::create(format, "json-pretty", "json"));
-
-  string command;
-  cmd_getval(cct, cmdmap, "cmd", command);
-  if (command == "query") {
-    f->open_object_section("pg");
-    f->dump_string("state", pg_state_string(get_state()));
-    f->dump_stream("snap_trimq") << snap_trimq;
-    f->dump_unsigned("epoch", get_osdmap()->get_epoch());
-    f->open_array_section("up");
-    for (vector<int>::iterator p = up.begin(); p != up.end(); ++p)
-      f->dump_unsigned("osd", *p);
-    f->close_section();
-    f->open_array_section("acting");
-    for (vector<int>::iterator p = acting.begin(); p != acting.end(); ++p)
-      f->dump_unsigned("osd", *p);
-    f->close_section();
-    if (!backfill_targets.empty()) {
-      f->open_array_section("backfill_targets");
-      for (set<pg_shard_t>::iterator p = backfill_targets.begin();
-          p != backfill_targets.end();
-          ++p)
-        f->dump_stream("shard") << *p;
-      f->close_section();
-    }
-    if (!actingbackfill.empty()) {
-      f->open_array_section("actingbackfill");
-      for (set<pg_shard_t>::iterator p = actingbackfill.begin();
-          p != actingbackfill.end();
-          ++p)
-        f->dump_stream("shard") << *p;
-      f->close_section();
-    }
-    f->open_object_section("info");
-    _update_calc_stats();
-    info.dump(f.get());
-    f->close_section();
-
-    f->open_array_section("peer_info");
-    for (map<pg_shard_t, pg_info_t>::iterator p = peer_info.begin();
-        p != peer_info.end();
-        ++p) {
-      f->open_object_section("info");
-      f->dump_stream("peer") << p->first;
-      p->second.dump(f.get());
-      f->close_section();
-    }
-    f->close_section();
-
-    f->open_array_section("recovery_state");
-    handle_query_state(f.get());
-    f->close_section();
-
-    f->open_object_section("agent_state");
-    if (agent_state)
-      agent_state->dump(f.get());
-    f->close_section();
-
-    f->close_section();
-    f->flush(odata);
-    return 0;
-  }
-  else if (command == "mark_unfound_lost") {
-    string mulcmd;
-    cmd_getval(cct, cmdmap, "mulcmd", mulcmd);
-    int mode = -1;
-    if (mulcmd == "revert") {
-      if (pool.info.ec_pool()) {
-       ss << "mode must be 'delete' for ec pool";
-       return -EINVAL;
-      }
-      mode = pg_log_entry_t::LOST_REVERT;
-    } else if (mulcmd == "delete") {
-      mode = pg_log_entry_t::LOST_DELETE;
-    } else {
-      ss << "mode must be 'revert' or 'delete'; mark not yet implemented";
-      return -EINVAL;
-    }
-    assert(mode == pg_log_entry_t::LOST_REVERT ||
-          mode == pg_log_entry_t::LOST_DELETE);
-
-    if (!is_primary()) {
-      ss << "not primary";
-      return -EROFS;
-    }
-
-    uint64_t unfound = missing_loc.num_unfound();
-    if (!unfound) {
-      ss << "pg has no unfound objects";
-      return 0;  // make command idempotent
-    }
-
-    if (!all_unfound_are_queried_or_lost(get_osdmap())) {
-      ss << "pg has " << unfound
-        << " unfound objects but we haven't probed all sources, not marking lost";
-      return -EINVAL;
-    }
-
-    mark_all_unfound_lost(mode, con, tid);
-    return -EAGAIN;
-  }
-  else if (command == "list_missing") {
-    hobject_t offset;
-    string offset_json;
-    if (cmd_getval(cct, cmdmap, "offset", offset_json)) {
-      json_spirit::Value v;
-      try {
-       if (!json_spirit::read(offset_json, v))
-         throw std::runtime_error("bad json");
-       offset.decode(v);
-      } catch (std::runtime_error& e) {
-       ss << "error parsing offset: " << e.what();
-       return -EINVAL;
-      }
-    }
-    f->open_object_section("missing");
-    {
-      f->open_object_section("offset");
-      offset.dump(f.get());
-      f->close_section();
-    }
-    f->dump_int("num_missing", missing.num_missing());
-    f->dump_int("num_unfound", get_num_unfound());
-    const map<hobject_t, pg_missing_item> &needs_recovery_map =
-      missing_loc.get_needs_recovery();
-    map<hobject_t, pg_missing_item>::const_iterator p =
-      needs_recovery_map.upper_bound(offset);
-    {
-      f->open_array_section("objects");
-      int32_t num = 0;
-      for (; p != needs_recovery_map.end() && num < cct->_conf->osd_command_max_records; ++p) {
-        if (missing_loc.is_unfound(p->first)) {
-         f->open_object_section("object");
-         {
-           f->open_object_section("oid");
-           p->first.dump(f.get());
-           f->close_section();
-         }
-          p->second.dump(f.get()); // have, need keys
-         {
-           f->open_array_section("locations");
-            for (set<pg_shard_t>::iterator r =
-                missing_loc.get_locations(p->first).begin();
-                r != missing_loc.get_locations(p->first).end();
-                ++r)
-              f->dump_stream("shard") << *r;
-           f->close_section();
-         }
-         f->close_section();
-         num++;
-        }
-      }
-      f->close_section();
-    }
-    f->dump_bool("more", p != needs_recovery_map.end());
-    f->close_section();
-    f->flush(odata);
-    return 0;
-  }
-
-  ss << "unknown pg command " << prefix;
-  return -EINVAL;
-}
-
-// ==========================================================
-
-void PrimaryLogPG::do_pg_op(OpRequestRef op)
-{
-  // NOTE: this is non-const because we modify the OSDOp.outdata in
-  // place
-  MOSDOp *m = static_cast<MOSDOp *>(op->get_nonconst_req());
-  assert(m->get_type() == CEPH_MSG_OSD_OP);
-  dout(10) << "do_pg_op " << *m << dendl;
-
-  op->mark_started();
-
-  int result = 0;
-  string cname, mname;
-  PGLSFilter *filter = NULL;
-  bufferlist filter_out;
-
-  snapid_t snapid = m->get_snapid();
-
-  vector<OSDOp> ops = m->ops;
-
-  for (vector<OSDOp>::iterator p = ops.begin(); p != ops.end(); ++p) {
-    OSDOp& osd_op = *p;
-    bufferlist::iterator bp = p->indata.begin();
-    switch (p->op.op) {
-    case CEPH_OSD_OP_PGNLS_FILTER:
-      try {
-       ::decode(cname, bp);
-       ::decode(mname, bp);
-      }
-      catch (const buffer::error& e) {
-       dout(0) << "unable to decode PGLS_FILTER description in " << *m << dendl;
-       result = -EINVAL;
-       break;
-      }
-      if (filter) {
-       delete filter;
-       filter = NULL;
-      }
-      result = get_pgls_filter(bp, &filter);
-      if (result < 0)
-        break;
-
-      assert(filter);
-
-      // fall through
-
-    case CEPH_OSD_OP_PGNLS:
-      if (snapid != CEPH_NOSNAP) {
-       result = -EINVAL;
-       break;
-      }
-      if (get_osdmap()->raw_pg_to_pg(m->get_pg()) != info.pgid.pgid) {
-        dout(10) << " pgnls pg=" << m->get_pg()
-                << " " << get_osdmap()->raw_pg_to_pg(m->get_pg())
-                << " != " << info.pgid << dendl;
-       result = 0; // hmm?
-      } else {
-       unsigned list_size = MIN(cct->_conf->osd_max_pgls, p->op.pgls.count);
-
-        dout(10) << " pgnls pg=" << m->get_pg() << " count " << list_size << dendl;
-       // read into a buffer
-        vector<hobject_t> sentries;
-        pg_nls_response_t response;
-       try {
-         ::decode(response.handle, bp);
-       }
-       catch (const buffer::error& e) {
-         dout(0) << "unable to decode PGNLS handle in " << *m << dendl;
-         result = -EINVAL;
-         break;
-       }
-
-       hobject_t next;
-       hobject_t lower_bound = response.handle;
-       hobject_t pg_start = info.pgid.pgid.get_hobj_start();
-       hobject_t pg_end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num());
-        dout(10) << " pgnls lower_bound " << lower_bound
-                << " pg_end " << pg_end << dendl;
-       if (((!lower_bound.is_max() && lower_bound >= pg_end) ||
-            (lower_bound != hobject_t() && lower_bound < pg_start))) {
-         // this should only happen with a buggy client.
-         dout(10) << "outside of PG bounds " << pg_start << " .. "
-                  << pg_end << dendl;
-         result = -EINVAL;
-         break;
-       }
-
-       hobject_t current = lower_bound;
-       osr->flush();
-       int r = pgbackend->objects_list_partial(
-         current,
-         list_size,
-         list_size,
-         &sentries,
-         &next);
-       if (r != 0) {
-         result = -EINVAL;
-         break;
-       }
-
-       map<hobject_t, pg_missing_item>::const_iterator missing_iter =
-         pg_log.get_missing().get_items().lower_bound(current);
-       vector<hobject_t>::iterator ls_iter = sentries.begin();
-       hobject_t _max = hobject_t::get_max();
-       while (1) {
-         const hobject_t &mcand =
-           missing_iter == pg_log.get_missing().get_items().end() ?
-           _max :
-           missing_iter->first;
-         const hobject_t &lcand =
-           ls_iter == sentries.end() ?
-           _max :
-           *ls_iter;
-
-         hobject_t candidate;
-         if (mcand == lcand) {
-           candidate = mcand;
-           if (!mcand.is_max()) {
-             ++ls_iter;
-             ++missing_iter;
-           }
-         } else if (mcand < lcand) {
-           candidate = mcand;
-           assert(!mcand.is_max());
-           ++missing_iter;
-         } else {
-           candidate = lcand;
-           assert(!lcand.is_max());
-           ++ls_iter;
-         }
-
-          dout(10) << " pgnls candidate 0x" << std::hex << candidate.get_hash()
-            << " vs lower bound 0x" << lower_bound.get_hash() << dendl;
-
-         if (candidate >= next) {
-           break;
-         }
-
-         if (response.entries.size() == list_size) {
-           next = candidate;
-           break;
-         }
-
-         // skip snapdir objects
-         if (candidate.snap == CEPH_SNAPDIR)
-           continue;
-
-         if (candidate.snap != CEPH_NOSNAP)
-           continue;
-
-         // skip internal namespace
-         if (candidate.get_namespace() == cct->_conf->osd_hit_set_namespace)
-           continue;
-
-         if (missing_loc.is_deleted(candidate))
-           continue;
-
-         // skip wrong namespace
-         if (m->get_hobj().nspace != librados::all_nspaces &&
-               candidate.get_namespace() != m->get_hobj().nspace)
-           continue;
-
-         if (filter && !pgls_filter(filter, candidate, filter_out))
-           continue;
-
-          dout(20) << "pgnls item 0x" << std::hex
-            << candidate.get_hash()
-            << ", rev 0x" << hobject_t::_reverse_bits(candidate.get_hash())
-            << std::dec << " "
-            << candidate.oid.name << dendl;
-
-         librados::ListObjectImpl item;
-         item.nspace = candidate.get_namespace();
-         item.oid = candidate.oid.name;
-         item.locator = candidate.get_key();
-         response.entries.push_back(item);
-       }
-
-       if (next.is_max() &&
-           missing_iter == pg_log.get_missing().get_items().end() &&
-           ls_iter == sentries.end()) {
-         result = 1;
-
-         // Set response.handle to the start of the next PG according
-         // to the object sort order.
-         response.handle = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num());
-       } else {
-          response.handle = next;
-        }
-        dout(10) << "pgnls handle=" << response.handle << dendl;
-       ::encode(response, osd_op.outdata);
-       if (filter)
-         ::encode(filter_out, osd_op.outdata);
-       dout(10) << " pgnls result=" << result << " outdata.length()="
-                << osd_op.outdata.length() << dendl;
-      }
-      break;
-
-    case CEPH_OSD_OP_PGLS_FILTER:
-      try {
-       ::decode(cname, bp);
-       ::decode(mname, bp);
-      }
-      catch (const buffer::error& e) {
-       dout(0) << "unable to decode PGLS_FILTER description in " << *m << dendl;
-       result = -EINVAL;
-       break;
-      }
-      if (filter) {
-       delete filter;
-       filter = NULL;
-      }
-      result = get_pgls_filter(bp, &filter);
-      if (result < 0)
-        break;
-
-      assert(filter);
-
-      // fall through
-
-    case CEPH_OSD_OP_PGLS:
-      if (snapid != CEPH_NOSNAP) {
-       result = -EINVAL;
-       break;
-      }
-      if (get_osdmap()->raw_pg_to_pg(m->get_pg()) != info.pgid.pgid) {
-        dout(10) << " pgls pg=" << m->get_pg()
-                << " " << get_osdmap()->raw_pg_to_pg(m->get_pg())
-                << " != " << info.pgid << dendl;
-       result = 0; // hmm?
-      } else {
-       unsigned list_size = MIN(cct->_conf->osd_max_pgls, p->op.pgls.count);
-
-        dout(10) << " pgls pg=" << m->get_pg() << " count " << list_size << dendl;
-       // read into a buffer
-        vector<hobject_t> sentries;
-        pg_ls_response_t response;
-       try {
-         ::decode(response.handle, bp);
-       }
-       catch (const buffer::error& e) {
-         dout(0) << "unable to decode PGLS handle in " << *m << dendl;
-         result = -EINVAL;
-         break;
-       }
-
-       hobject_t next;
-       hobject_t current = response.handle;
-       osr->flush();
-       int r = pgbackend->objects_list_partial(
-         current,
-         list_size,
-         list_size,
-         &sentries,
-         &next);
-       if (r != 0) {
-         result = -EINVAL;
-         break;
-       }
-
-       assert(snapid == CEPH_NOSNAP || pg_log.get_missing().get_items().empty());
-
-       map<hobject_t, pg_missing_item>::const_iterator missing_iter =
-         pg_log.get_missing().get_items().lower_bound(current);
-       vector<hobject_t>::iterator ls_iter = sentries.begin();
-       hobject_t _max = hobject_t::get_max();
-       while (1) {
-         const hobject_t &mcand =
-           missing_iter == pg_log.get_missing().get_items().end() ?
-           _max :
-           missing_iter->first;
-         const hobject_t &lcand =
-           ls_iter == sentries.end() ?
-           _max :
-           *ls_iter;
-
-         hobject_t candidate;
-         if (mcand == lcand) {
-           candidate = mcand;
-           if (!mcand.is_max()) {
-             ++ls_iter;
-             ++missing_iter;
-           }
-         } else if (mcand < lcand) {
-           candidate = mcand;
-           assert(!mcand.is_max());
-           ++missing_iter;
-         } else {
-           candidate = lcand;
-           assert(!lcand.is_max());
-           ++ls_iter;
-         }
-
-         if (candidate >= next) {
-           break;
-         }
-           
-         if (response.entries.size() == list_size) {
-           next = candidate;
-           break;
-         }
-
-         // skip snapdir objects
-         if (candidate.snap == CEPH_SNAPDIR)
-           continue;
-
-         if (candidate.snap != CEPH_NOSNAP)
-           continue;
-
-         // skip wrong namespace
-         if (candidate.get_namespace() != m->get_hobj().nspace)
-           continue;
-
-         if (missing_loc.is_deleted(candidate))
-           continue;
-
-         if (filter && !pgls_filter(filter, candidate, filter_out))
-           continue;
-
-         response.entries.push_back(make_pair(candidate.oid,
-                                              candidate.get_key()));
-       }
-       if (next.is_max() &&
-           missing_iter == pg_log.get_missing().get_items().end() &&
-           ls_iter == sentries.end()) {
-         result = 1;
-       }
-       response.handle = next;
-       ::encode(response, osd_op.outdata);
-       if (filter)
-         ::encode(filter_out, osd_op.outdata);
-       dout(10) << " pgls result=" << result << " outdata.length()="
-                << osd_op.outdata.length() << dendl;
-      }
-      break;
-
-    case CEPH_OSD_OP_PG_HITSET_LS:
-      {
-       list< pair<utime_t,utime_t> > ls;
-       for (list<pg_hit_set_info_t>::const_iterator p = info.hit_set.history.begin();
-            p != info.hit_set.history.end();
-            ++p)
-         ls.push_back(make_pair(p->begin, p->end));
-       if (hit_set)
-         ls.push_back(make_pair(hit_set_start_stamp, utime_t()));
-       ::encode(ls, osd_op.outdata);
-      }
-      break;
-
-    case CEPH_OSD_OP_PG_HITSET_GET:
-      {
-       utime_t stamp(osd_op.op.hit_set_get.stamp);
-       if (hit_set_start_stamp && stamp >= hit_set_start_stamp) {
-         // read the current in-memory HitSet, not the version we've
-         // checkpointed.
-         if (!hit_set) {
-           result= -ENOENT;
-           break;
-         }
-         ::encode(*hit_set, osd_op.outdata);
-         result = osd_op.outdata.length();
-       } else {
-         // read an archived HitSet.
-         hobject_t oid;
-         for (list<pg_hit_set_info_t>::const_iterator p = info.hit_set.history.begin();
-              p != info.hit_set.history.end();
-              ++p) {
-           if (stamp >= p->begin && stamp <= p->end) {
-             oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-             break;
-           }
-         }
-         if (oid == hobject_t()) {
-           result = -ENOENT;
-           break;
-         }
-         if (!pool.info.is_replicated()) {
-           // FIXME: EC not supported yet
-           result = -EOPNOTSUPP;
-           break;
-         }
-         if (is_unreadable_object(oid)) {
-           wait_for_unreadable_object(oid, op);
-            delete filter;
-           return;
-         }
-         result = osd->store->read(ch, ghobject_t(oid), 0, 0, osd_op.outdata);
-       }
-      }
-      break;
-
-   case CEPH_OSD_OP_SCRUBLS:
-      result = do_scrub_ls(m, &osd_op);
-      break;
-
-    default:
-      result = -EINVAL;
-      break;
-    }
-
-    if (result < 0)
-      break;
-  }
-
-  // reply
-  MOSDOpReply *reply = new MOSDOpReply(m, 0, get_osdmap()->get_epoch(),
-                                      CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK,
-                                      false);
-  reply->claim_op_out_data(ops);
-  reply->set_result(result);
-  reply->set_reply_versions(info.last_update, info.last_user_version);
-  osd->send_message_osd_client(reply, m->get_connection());
-  delete filter;
-}
-
-int PrimaryLogPG::do_scrub_ls(MOSDOp *m, OSDOp *osd_op)
-{
-  if (m->get_pg() != info.pgid.pgid) {
-    dout(10) << " scrubls pg=" << m->get_pg() << " != " << info.pgid << dendl;
-    return -EINVAL; // hmm?
-  }
-  auto bp = osd_op->indata.begin();
-  scrub_ls_arg_t arg;
-  try {
-    arg.decode(bp);
-  } catch (buffer::error&) {
-    dout(10) << " corrupted scrub_ls_arg_t" << dendl;
-    return -EINVAL;
-  }
-  int r = 0;
-  scrub_ls_result_t result = {.interval = info.history.same_interval_since};
-  if (arg.interval != 0 && arg.interval != info.history.same_interval_since) {
-    r = -EAGAIN;
-  } else if (!scrubber.store) {
-    r = -ENOENT;
-  } else if (arg.get_snapsets) {
-    result.vals = scrubber.store->get_snap_errors(osd->store,
-                                                 get_pgid().pool(),
-                                                 arg.start_after,
-                                                 arg.max_return);
-  } else {
-    result.vals = scrubber.store->get_object_errors(osd->store,
-                                                   get_pgid().pool(),
-                                                   arg.start_after,
-                                                   arg.max_return);
-  }
-  ::encode(result, osd_op->outdata);
-  return r;
-}
-
-void PrimaryLogPG::calc_trim_to()
-{
-  size_t target = cct->_conf->osd_min_pg_log_entries;
-  if (is_degraded() ||
-      state_test(PG_STATE_RECOVERING |
-                PG_STATE_RECOVERY_WAIT |
-                PG_STATE_BACKFILLING |
-                PG_STATE_BACKFILL_WAIT |
-                PG_STATE_BACKFILL_TOOFULL)) {
-    target = cct->_conf->osd_max_pg_log_entries;
-  }
-
-  eversion_t limit = MIN(
-    min_last_complete_ondisk,
-    pg_log.get_can_rollback_to());
-  if (limit != eversion_t() &&
-      limit != pg_trim_to &&
-      pg_log.get_log().approx_size() > target) {
-    size_t num_to_trim = pg_log.get_log().approx_size() - target;
-    if (num_to_trim < cct->_conf->osd_pg_log_trim_min) {
-      return;
-    }
-    list<pg_log_entry_t>::const_iterator it = pg_log.get_log().log.begin();
-    eversion_t new_trim_to;
-    for (size_t i = 0; i < num_to_trim; ++i) {
-      new_trim_to = it->version;
-      ++it;
-      if (new_trim_to > limit) {
-       new_trim_to = limit;
-       dout(10) << "calc_trim_to trimming to min_last_complete_ondisk" << dendl;
-       break;
-      }
-    }
-    dout(10) << "calc_trim_to " << pg_trim_to << " -> " << new_trim_to << dendl;
-    pg_trim_to = new_trim_to;
-    assert(pg_trim_to <= pg_log.get_head());
-    assert(pg_trim_to <= min_last_complete_ondisk);
-  }
-}
-
-PrimaryLogPG::PrimaryLogPG(OSDService *o, OSDMapRef curmap,
-                          const PGPool &_pool, spg_t p) :
-  PG(o, curmap, _pool, p),
-  pgbackend(
-    PGBackend::build_pg_backend(
-      _pool.info, curmap, this, coll_t(p), ch, o->store, cct)),
-  object_contexts(o->cct, o->cct->_conf->osd_pg_object_context_cache_count),
-  snapset_contexts_lock("PrimaryLogPG::snapset_contexts_lock"),
-  new_backfill(false),
-  temp_seq(0),
-  snap_trimmer_machine(this)
-{ 
-  missing_loc.set_backend_predicates(
-    pgbackend->get_is_readable_predicate(),
-    pgbackend->get_is_recoverable_predicate());
-  snap_trimmer_machine.initiate();
-}
-
-void PrimaryLogPG::get_src_oloc(const object_t& oid, const object_locator_t& oloc, object_locator_t& src_oloc)
-{
-  src_oloc = oloc;
-  if (oloc.key.empty())
-    src_oloc.key = oid.name;
-}
-
-void PrimaryLogPG::handle_backoff(OpRequestRef& op)
-{
-  const MOSDBackoff *m = static_cast<const MOSDBackoff*>(op->get_req());
-  SessionRef session = static_cast<Session*>(m->get_connection()->get_priv());
-  if (!session)
-    return;  // drop it.
-  session->put();  // get_priv takes a ref, and so does the SessionRef
-  hobject_t begin = info.pgid.pgid.get_hobj_start();
-  hobject_t end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num());
-  if (begin < m->begin) {
-    begin = m->begin;
-  }
-  if (end > m->end) {
-    end = m->end;
-  }
-  dout(10) << __func__ << " backoff ack id " << m->id
-          << " [" << begin << "," << end << ")" << dendl;
-  session->ack_backoff(cct, m->pgid, m->id, begin, end);
-}
-
-void PrimaryLogPG::do_request(
-  OpRequestRef& op,
-  ThreadPool::TPHandle &handle)
-{
-  if (op->osd_trace) {
-    op->pg_trace.init("pg op", &trace_endpoint, &op->osd_trace);
-    op->pg_trace.event("do request");
-  }
-  // make sure we have a new enough map
-  auto p = waiting_for_map.find(op->get_source());
-  if (p != waiting_for_map.end()) {
-    // preserve ordering
-    dout(20) << __func__ << " waiting_for_map "
-            << p->first << " not empty, queueing" << dendl;
-    p->second.push_back(op);
-    op->mark_delayed("waiting_for_map not empty");
-    return;
-  }
-  if (!have_same_or_newer_map(op->min_epoch)) {
-    dout(20) << __func__ << " min " << op->min_epoch
-            << ", queue on waiting_for_map " << op->get_source() << dendl;
-    waiting_for_map[op->get_source()].push_back(op);
-    op->mark_delayed("op must wait for map");
-    osd->request_osdmap_update(op->min_epoch);
-    return;
-  }
-
-  if (can_discard_request(op)) {
-    return;
-  }
-
-  // pg-wide backoffs
-  const Message *m = op->get_req();
-  if (m->get_connection()->has_feature(CEPH_FEATURE_RADOS_BACKOFF)) {
-    SessionRef session = static_cast<Session*>(m->get_connection()->get_priv());
-    if (!session)
-      return;  // drop it.
-    session->put();  // get_priv takes a ref, and so does the SessionRef
-
-    if (op->get_req()->get_type() == CEPH_MSG_OSD_OP) {
-      if (session->check_backoff(cct, info.pgid,
-                                info.pgid.pgid.get_hobj_start(), m)) {
-       return;
-      }
-
-      bool backoff =
-       is_down() ||
-       is_incomplete() ||
-       (!is_active() && is_peered());
-      if (g_conf->osd_backoff_on_peering && !backoff) {
-       if (is_peering()) {
-         backoff = true;
-       }
-      }
-      if (backoff) {
-       add_pg_backoff(session);
-       return;
-      }
-    }
-    // pg backoff acks at pg-level
-    if (op->get_req()->get_type() == CEPH_MSG_OSD_BACKOFF) {
-      const MOSDBackoff *ba = static_cast<const MOSDBackoff*>(m);
-      if (ba->begin != ba->end) {
-       handle_backoff(op);
-       return;
-      }
-    }
-  }
-
-  if (flushes_in_progress > 0) {
-    dout(20) << flushes_in_progress
-            << " flushes_in_progress pending "
-            << "waiting for active on " << op << dendl;
-    waiting_for_peered.push_back(op);
-    op->mark_delayed("waiting for peered");
-    return;
-  }
-
-  if (!is_peered()) {
-    // Delay unless PGBackend says it's ok
-    if (pgbackend->can_handle_while_inactive(op)) {
-      bool handled = pgbackend->handle_message(op);
-      assert(handled);
-      return;
-    } else {
-      waiting_for_peered.push_back(op);
-      op->mark_delayed("waiting for peered");
-      return;
-    }
-  }
-
-  assert(is_peered() && flushes_in_progress == 0);
-  if (pgbackend->handle_message(op))
-    return;
-
-  switch (op->get_req()->get_type()) {
-  case CEPH_MSG_OSD_OP:
-  case CEPH_MSG_OSD_BACKOFF:
-    if (!is_active()) {
-      dout(20) << " peered, not active, waiting for active on " << op << dendl;
-      waiting_for_active.push_back(op);
-      op->mark_delayed("waiting for active");
-      return;
-    }
-    switch (op->get_req()->get_type()) {
-    case CEPH_MSG_OSD_OP:
-      // verify client features
-      if ((pool.info.has_tiers() || pool.info.is_tier()) &&
-         !op->has_feature(CEPH_FEATURE_OSD_CACHEPOOL)) {
-       osd->reply_op_error(op, -EOPNOTSUPP);
-       return;
-      }
-      do_op(op);
-      break;
-    case CEPH_MSG_OSD_BACKOFF:
-      // object-level backoff acks handled in osdop context
-      handle_backoff(op);
-      break;
-    }
-    break;
-
-  case MSG_OSD_SUBOP:
-    do_sub_op(op);
-    break;
-
-  case MSG_OSD_SUBOPREPLY:
-    do_sub_op_reply(op);
-    break;
-
-  case MSG_OSD_PG_SCAN:
-    do_scan(op, handle);
-    break;
-
-  case MSG_OSD_PG_BACKFILL:
-    do_backfill(op);
-    break;
-
-  case MSG_OSD_PG_BACKFILL_REMOVE:
-    do_backfill_remove(op);
-    break;
-
-  case MSG_OSD_SCRUB_RESERVE:
-    {
-      const MOSDScrubReserve *m =
-       static_cast<const MOSDScrubReserve*>(op->get_req());
-      switch (m->type) {
-      case MOSDScrubReserve::REQUEST:
-       handle_scrub_reserve_request(op);
-       break;
-      case MOSDScrubReserve::GRANT:
-       handle_scrub_reserve_grant(op, m->from);
-       break;
-      case MOSDScrubReserve::REJECT:
-       handle_scrub_reserve_reject(op, m->from);
-       break;
-      case MOSDScrubReserve::RELEASE:
-       handle_scrub_reserve_release(op);
-       break;
-      }
-    }
-    break;
-
-  case MSG_OSD_REP_SCRUB:
-    replica_scrub(op, handle);
-    break;
-
-  case MSG_OSD_REP_SCRUBMAP:
-    do_replica_scrub_map(op);
-    break;
-
-  case MSG_OSD_PG_UPDATE_LOG_MISSING:
-    do_update_log_missing(op);
-    break;
-
-  case MSG_OSD_PG_UPDATE_LOG_MISSING_REPLY:
-    do_update_log_missing_reply(op);
-    break;
-
-  default:
-    assert(0 == "bad message type in do_request");
-  }
-}
-
-hobject_t PrimaryLogPG::earliest_backfill() const
-{
-  hobject_t e = hobject_t::get_max();
-  for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    pg_shard_t bt = *i;
-    map<pg_shard_t, pg_info_t>::const_iterator iter = peer_info.find(bt);
-    assert(iter != peer_info.end());
-    if (iter->second.last_backfill < e)
-      e = iter->second.last_backfill;
-  }
-  return e;
-}
-
-/** do_op - do an op
- * pg lock will be held (if multithreaded)
- * osd_lock NOT held.
- */
-void PrimaryLogPG::do_op(OpRequestRef& op)
-{
-  FUNCTRACE();
-  // NOTE: take a non-const pointer here; we must be careful not to
-  // change anything that will break other reads on m (operator<<).
-  MOSDOp *m = static_cast<MOSDOp*>(op->get_nonconst_req());
-  assert(m->get_type() == CEPH_MSG_OSD_OP);
-  if (m->finish_decode()) {
-    op->reset_desc();   // for TrackedOp
-    m->clear_payload();
-  }
-
-  dout(20) << __func__ << ": op " << *m << dendl;
-
-  hobject_t head = m->get_hobj();
-  head.snap = CEPH_NOSNAP;
-
-  if (!info.pgid.pgid.contains(
-       info.pgid.pgid.get_split_bits(pool.info.get_pg_num()), head)) {
-    derr << __func__ << " " << info.pgid.pgid << " does not contain "
-        << head << " pg_num " << pool.info.get_pg_num() << " hash "
-        << std::hex << head.get_hash() << std::dec << dendl;
-    osd->clog->warn() << info.pgid.pgid << " does not contain " << head
-                     << " op " << *m;
-    assert(!cct->_conf->osd_debug_misdirected_ops);
-    return;
-  }
-
-  bool can_backoff =
-    m->get_connection()->has_feature(CEPH_FEATURE_RADOS_BACKOFF);
-  SessionRef session;
-  if (can_backoff) {
-    session = static_cast<Session*>(m->get_connection()->get_priv());
-    if (!session.get()) {
-      dout(10) << __func__ << " no session" << dendl;
-      return;
-    }
-    session->put();  // get_priv() takes a ref, and so does the intrusive_ptr
-
-    if (session->check_backoff(cct, info.pgid, head, m)) {
-      return;
-    }
-  }
-
-  if (m->has_flag(CEPH_OSD_FLAG_PARALLELEXEC)) {
-    // not implemented.
-    dout(20) << __func__ << ": PARALLELEXEC not implemented " << *m << dendl;
-    osd->reply_op_error(op, -EINVAL);
-    return;
-  }
-
-  if (op->rmw_flags == 0) {
-    int r = osd->osd->init_op_flags(op);
-    if (r) {
-      osd->reply_op_error(op, r);
-      return;
-    }
-  }
-
-  if ((m->get_flags() & (CEPH_OSD_FLAG_BALANCE_READS |
-                        CEPH_OSD_FLAG_LOCALIZE_READS)) &&
-      op->may_read() &&
-      !(op->may_write() || op->may_cache())) {
-    // balanced reads; any replica will do
-    if (!(is_primary() || is_replica())) {
-      osd->handle_misdirected_op(this, op);
-      return;
-    }
-  } else {
-    // normal case; must be primary
-    if (!is_primary()) {
-      osd->handle_misdirected_op(this, op);
-      return;
-    }
-  }
-
-  if (!op_has_sufficient_caps(op)) {
-    osd->reply_op_error(op, -EPERM);
-    return;
-  }
-
-  if (op->includes_pg_op()) {
-    return do_pg_op(op);
-  }
-
-  // object name too long?
-  if (m->get_oid().name.size() > cct->_conf->osd_max_object_name_len) {
-    dout(4) << "do_op name is longer than "
-           << cct->_conf->osd_max_object_name_len
-           << " bytes" << dendl;
-    osd->reply_op_error(op, -ENAMETOOLONG);
-    return;
-  }
-  if (m->get_hobj().get_key().size() > cct->_conf->osd_max_object_name_len) {
-    dout(4) << "do_op locator is longer than "
-           << cct->_conf->osd_max_object_name_len
-           << " bytes" << dendl;
-    osd->reply_op_error(op, -ENAMETOOLONG);
-    return;
-  }
-  if (m->get_hobj().nspace.size() > cct->_conf->osd_max_object_namespace_len) {
-    dout(4) << "do_op namespace is longer than "
-           << cct->_conf->osd_max_object_namespace_len
-           << " bytes" << dendl;
-    osd->reply_op_error(op, -ENAMETOOLONG);
-    return;
-  }
-
-  if (int r = osd->store->validate_hobject_key(head)) {
-    dout(4) << "do_op object " << head << " invalid for backing store: "
-           << r << dendl;
-    osd->reply_op_error(op, r);
-    return;
-  }
-
-  // blacklisted?
-  if (get_osdmap()->is_blacklisted(m->get_source_addr())) {
-    dout(10) << "do_op " << m->get_source_addr() << " is blacklisted" << dendl;
-    osd->reply_op_error(op, -EBLACKLISTED);
-    return;
-  }
-
-  // order this op as a write?
-  bool write_ordered = op->rwordered();
-
-  // discard due to cluster full transition?  (we discard any op that
-  // originates before the cluster or pool is marked full; the client
-  // will resend after the full flag is removed or if they expect the
-  // op to succeed despite being full).  The except is FULL_FORCE and
-  // FULL_TRY ops, which there is no reason to discard because they
-  // bypass all full checks anyway.  If this op isn't write or
-  // read-ordered, we skip.
-  // FIXME: we exclude mds writes for now.
-  if (write_ordered && !(m->get_source().is_mds() ||
-                        m->has_flag(CEPH_OSD_FLAG_FULL_TRY) ||
-                        m->has_flag(CEPH_OSD_FLAG_FULL_FORCE)) &&
-      info.history.last_epoch_marked_full > m->get_map_epoch()) {
-    dout(10) << __func__ << " discarding op sent before full " << m << " "
-            << *m << dendl;
-    return;
-  }
-  // mds should have stopped writing before this point.
-  // We can't allow OSD to become non-startable even if mds
-  // could be writing as part of file removals.
-  ostringstream ss;
-  if (write_ordered && osd->check_failsafe_full(ss)) {
-    dout(10) << __func__ << " fail-safe full check failed, dropping request"
-             << ss.str()
-            << dendl;
-    return;
-  }
-  int64_t poolid = get_pgid().pool();
-  if (op->may_write()) {
-
-    const pg_pool_t *pi = get_osdmap()->get_pg_pool(poolid);
-    if (!pi) {
-      return;
-    }
-
-    // invalid?
-    if (m->get_snapid() != CEPH_NOSNAP) {
-      dout(20) << __func__ << ": write to clone not valid " << *m << dendl;
-      osd->reply_op_error(op, -EINVAL);
-      return;
-    }
-
-    // too big?
-    if (cct->_conf->osd_max_write_size &&
-        m->get_data_len() > cct->_conf->osd_max_write_size << 20) {
-      // journal can't hold commit!
-      derr << "do_op msg data len " << m->get_data_len()
-           << " > osd_max_write_size " << (cct->_conf->osd_max_write_size << 20)
-           << " on " << *m << dendl;
-      osd->reply_op_error(op, -OSD_WRITETOOBIG);
-      return;
-    }
-  }
-
-  dout(10) << "do_op " << *m
-          << (op->may_write() ? " may_write" : "")
-          << (op->may_read() ? " may_read" : "")
-          << (op->may_cache() ? " may_cache" : "")
-          << " -> " << (write_ordered ? "write-ordered" : "read-ordered")
-          << " flags " << ceph_osd_flag_string(m->get_flags())
-          << dendl;
-
-  // missing object?
-  if (is_unreadable_object(head)) {
-    if (!is_primary()) {
-      osd->reply_op_error(op, -EAGAIN);
-      return;
-    }
-    if (can_backoff &&
-       (g_conf->osd_backoff_on_degraded ||
-        (g_conf->osd_backoff_on_unfound && missing_loc.is_unfound(head)))) {
-      add_backoff(session, head, head);
-      maybe_kick_recovery(head);
-    } else {
-      wait_for_unreadable_object(head, op);
-    }
-    return;
-  }
-
-  // degraded object?
-  if (write_ordered && is_degraded_or_backfilling_object(head)) {
-    if (can_backoff && g_conf->osd_backoff_on_degraded) {
-      add_backoff(session, head, head);
-      maybe_kick_recovery(head);
-    } else {
-      wait_for_degraded_object(head, op);
-    }
-    return;
-  }
-
-  if (write_ordered &&
-      scrubber.write_blocked_by_scrub(head)) {
-    dout(20) << __func__ << ": waiting for scrub" << dendl;
-    waiting_for_scrub.push_back(op);
-    op->mark_delayed("waiting for scrub");
-    return;
-  }
-
-  // blocked on snap?
-  map<hobject_t, snapid_t>::iterator blocked_iter =
-    objects_blocked_on_degraded_snap.find(head);
-  if (write_ordered && blocked_iter != objects_blocked_on_degraded_snap.end()) {
-    hobject_t to_wait_on(head);
-    to_wait_on.snap = blocked_iter->second;
-    wait_for_degraded_object(to_wait_on, op);
-    return;
-  }
-  map<hobject_t, ObjectContextRef>::iterator blocked_snap_promote_iter =
-    objects_blocked_on_snap_promotion.find(head);
-  if (write_ordered && 
-      blocked_snap_promote_iter != objects_blocked_on_snap_promotion.end()) {
-    wait_for_blocked_object(
-      blocked_snap_promote_iter->second->obs.oi.soid,
-      op);
-    return;
-  }
-  if (write_ordered && objects_blocked_on_cache_full.count(head)) {
-    block_write_on_full_cache(head, op);
-    return;
-  }
-
-  // missing snapdir?
-  hobject_t snapdir = head.get_snapdir();
-
-  if (is_unreadable_object(snapdir)) {
-    wait_for_unreadable_object(snapdir, op);
-    return;
-  }
-
-  // degraded object?
-  if (write_ordered && is_degraded_or_backfilling_object(snapdir)) {
-    wait_for_degraded_object(snapdir, op);
-    return;
-  }
-
-  // dup/resent?
-  if (op->may_write() || op->may_cache()) {
-    // warning: we will get back *a* request for this reqid, but not
-    // necessarily the most recent.  this happens with flush and
-    // promote ops, but we can't possible have both in our log where
-    // the original request is still not stable on disk, so for our
-    // purposes here it doesn't matter which one we get.
-    eversion_t version;
-    version_t user_version;
-    int return_code = 0;
-    bool got = check_in_progress_op(
-      m->get_reqid(), &version, &user_version, &return_code);
-    if (got) {
-      dout(3) << __func__ << " dup " << m->get_reqid()
-             << " version " << version << dendl;
-      if (already_complete(version)) {
-       osd->reply_op_error(op, return_code, version, user_version);
-      } else {
-       dout(10) << " waiting for " << version << " to commit" << dendl;
-        // always queue ondisk waiters, so that we can requeue if needed
-       waiting_for_ondisk[version].push_back(make_pair(op, user_version));
-       op->mark_delayed("waiting for ondisk");
-      }
-      return;
-    }
-  }
-
-  ObjectContextRef obc;
-  bool can_create = op->may_write() || op->may_cache();
-  hobject_t missing_oid;
-  const hobject_t& oid = m->get_hobj();
-
-  // io blocked on obc?
-  if (!m->has_flag(CEPH_OSD_FLAG_FLUSH) &&
-      maybe_await_blocked_snapset(oid, op)) {
-    return;
-  }
-
-  int r = find_object_context(
-    oid, &obc, can_create,
-    m->has_flag(CEPH_OSD_FLAG_MAP_SNAP_CLONE),
-    &missing_oid);
-
-  if (r == -EAGAIN) {
-    // If we're not the primary of this OSD, we just return -EAGAIN. Otherwise,
-    // we have to wait for the object.
-    if (is_primary()) {
-      // missing the specific snap we need; requeue and wait.
-      assert(!op->may_write()); // only happens on a read/cache
-      wait_for_unreadable_object(missing_oid, op);
-      return;
-    }
-  } else if (r == 0) {
-    if (is_unreadable_object(obc->obs.oi.soid)) {
-      dout(10) << __func__ << ": clone " << obc->obs.oi.soid
-              << " is unreadable, waiting" << dendl;
-      wait_for_unreadable_object(obc->obs.oi.soid, op);
-      return;
-    }
-
-    // degraded object?  (the check above was for head; this could be a clone)
-    if (write_ordered &&
-       obc->obs.oi.soid.snap != CEPH_NOSNAP &&
-       is_degraded_or_backfilling_object(obc->obs.oi.soid)) {
-      dout(10) << __func__ << ": clone " << obc->obs.oi.soid
-              << " is degraded, waiting" << dendl;
-      wait_for_degraded_object(obc->obs.oi.soid, op);
-      return;
-    }
-  }
-
-  bool in_hit_set = false;
-  if (hit_set) {
-    if (obc.get()) {
-      if (obc->obs.oi.soid != hobject_t() && hit_set->contains(obc->obs.oi.soid))
-       in_hit_set = true;
-    } else {
-      if (missing_oid != hobject_t() && hit_set->contains(missing_oid))
-        in_hit_set = true;
-    }
-    if (!op->hitset_inserted) {
-      hit_set->insert(oid);
-      op->hitset_inserted = true;
-      if (hit_set->is_full() ||
-          hit_set_start_stamp + pool.info.hit_set_period <= m->get_recv_stamp()) {
-        hit_set_persist();
-      }
-    }
-  }
-
-  if (agent_state) {
-    if (agent_choose_mode(false, op))
-      return;
-  }
-
-  if (obc.get() && obc->obs.exists && obc->obs.oi.has_manifest()) {
-    if (maybe_handle_manifest(op,
-                              write_ordered,
-                              obc))
-    return;
-  }
-
-  if (maybe_handle_cache(op,
-                        write_ordered,
-                        obc,
-                        r,
-                        missing_oid,
-                        false,
-                        in_hit_set))
-    return;
-
-  if (r && (r != -ENOENT || !obc)) {
-    // copy the reqids for copy get on ENOENT
-    if (r == -ENOENT &&
-       (m->ops[0].op.op == CEPH_OSD_OP_COPY_GET)) {
-      fill_in_copy_get_noent(op, oid, m->ops[0]);
-      return;
-    }
-    dout(20) << __func__ << ": find_object_context got error " << r << dendl;
-    if (op->may_write() &&
-       get_osdmap()->require_osd_release >= CEPH_RELEASE_KRAKEN) {
-      record_write_error(op, oid, nullptr, r);
-    } else {
-      osd->reply_op_error(op, r);
-    }
-    return;
-  }
-
-  // make sure locator is consistent
-  object_locator_t oloc(obc->obs.oi.soid);
-  if (m->get_object_locator() != oloc) {
-    dout(10) << " provided locator " << m->get_object_locator() 
-            << " != object's " << obc->obs.oi.soid << dendl;
-    osd->clog->warn() << "bad locator " << m->get_object_locator() 
-                    << " on object " << oloc
-                     << " op " << *m;
-  }
-
-  // io blocked on obc?
-  if (obc->is_blocked() &&
-      !m->has_flag(CEPH_OSD_FLAG_FLUSH)) {
-    wait_for_blocked_object(obc->obs.oi.soid, op);
-    return;
-  }
-
-  dout(25) << __func__ << " oi " << obc->obs.oi << dendl;
-
-  for (vector<OSDOp>::iterator p = m->ops.begin(); p != m->ops.end(); ++p) {
-    OSDOp& osd_op = *p;
-
-    // make sure LIST_SNAPS is on CEPH_SNAPDIR and nothing else
-    if (osd_op.op.op == CEPH_OSD_OP_LIST_SNAPS &&
-       m->get_snapid() != CEPH_SNAPDIR) {
-      dout(10) << "LIST_SNAPS with incorrect context" << dendl;
-      osd->reply_op_error(op, -EINVAL);
-      return;
-    }
-  }
-
-  OpContext *ctx = new OpContext(op, m->get_reqid(), &m->ops, obc, this);
-
-  if (!obc->obs.exists)
-    ctx->snapset_obc = get_object_context(obc->obs.oi.soid.get_snapdir(), false);
-
-  /* Due to obc caching, we might have a cached non-existent snapset_obc
-   * for the snapdir.  If so, we can ignore it.  Subsequent parts of the
-   * do_op pipeline make decisions based on whether snapset_obc is
-   * populated.
-   */
-  if (ctx->snapset_obc && !ctx->snapset_obc->obs.exists)
-    ctx->snapset_obc = ObjectContextRef();
-
-  if (m->has_flag(CEPH_OSD_FLAG_SKIPRWLOCKS)) {
-    dout(20) << __func__ << ": skipping rw locks" << dendl;
-  } else if (m->get_flags() & CEPH_OSD_FLAG_FLUSH) {
-    dout(20) << __func__ << ": part of flush, will ignore write lock" << dendl;
-
-    // verify there is in fact a flush in progress
-    // FIXME: we could make this a stronger test.
-    map<hobject_t,FlushOpRef>::iterator p = flush_ops.find(obc->obs.oi.soid);
-    if (p == flush_ops.end()) {
-      dout(10) << __func__ << " no flush in progress, aborting" << dendl;
-      reply_ctx(ctx, -EINVAL);
-      return;
-    }
-  } else if (!get_rw_locks(write_ordered, ctx)) {
-    dout(20) << __func__ << " waiting for rw locks " << dendl;
-    op->mark_delayed("waiting for rw locks");
-    close_op_ctx(ctx);
-    return;
-  }
-  dout(20) << __func__ << " obc " << *obc << dendl;
-
-  if (r) {
-    dout(20) << __func__ << " returned an error: " << r << dendl;
-    close_op_ctx(ctx);
-    if (op->may_write() &&
-       get_osdmap()->require_osd_release >= CEPH_RELEASE_KRAKEN) {
-      record_write_error(op, oid, nullptr, r);
-    } else {
-      osd->reply_op_error(op, r);
-    }
-    return;
-  }
-
-  if (m->has_flag(CEPH_OSD_FLAG_IGNORE_CACHE)) {
-    ctx->ignore_cache = true;
-  }
-
-  if ((op->may_read()) && (obc->obs.oi.is_lost())) {
-    // This object is lost. Reading from it returns an error.
-    dout(20) << __func__ << ": object " << obc->obs.oi.soid
-            << " is lost" << dendl;
-    reply_ctx(ctx, -ENFILE);
-    return;
-  }
-  if (!op->may_write() &&
-      !op->may_cache() &&
-      (!obc->obs.exists ||
-       ((m->get_snapid() != CEPH_SNAPDIR) &&
-       obc->obs.oi.is_whiteout()))) {
-    // copy the reqids for copy get on ENOENT
-    if (m->ops[0].op.op == CEPH_OSD_OP_COPY_GET) {
-      fill_in_copy_get_noent(op, oid, m->ops[0]);
-      close_op_ctx(ctx);
-      return;
-    }
-    reply_ctx(ctx, -ENOENT);
-    return;
-  }
-
-  op->mark_started();
-
-  execute_ctx(ctx);
-  utime_t prepare_latency = ceph_clock_now();
-  prepare_latency -= op->get_dequeued_time();
-  osd->logger->tinc(l_osd_op_prepare_lat, prepare_latency);
-  if (op->may_read() && op->may_write()) {
-    osd->logger->tinc(l_osd_op_rw_prepare_lat, prepare_latency);
-  } else if (op->may_read()) {
-    osd->logger->tinc(l_osd_op_r_prepare_lat, prepare_latency);
-  } else if (op->may_write() || op->may_cache()) {
-    osd->logger->tinc(l_osd_op_w_prepare_lat, prepare_latency);
-  }
-
-  // force recovery of the oldest missing object if too many logs
-  maybe_force_recovery();
-}
-PrimaryLogPG::cache_result_t PrimaryLogPG::maybe_handle_manifest_detail(
-  OpRequestRef op,
-  bool write_ordered,
-  ObjectContextRef obc)
-{
-  if (static_cast<const MOSDOp *>(op->get_req())->get_flags() &
-      CEPH_OSD_FLAG_IGNORE_REDIRECT) {
-    dout(20) << __func__ << ": ignoring redirect due to flag" << dendl;
-    return cache_result_t::NOOP;
-  }
-
-  if (obc)
-    dout(10) << __func__ << " " << obc->obs.oi << " "
-       << (obc->obs.exists ? "exists" : "DNE")
-       << dendl;
-
-  // if it is write-ordered and blocked, stop now
-  if (obc.get() && obc->is_blocked() && write_ordered) {
-    // we're already doing something with this object
-    dout(20) << __func__ << " blocked on " << obc->obs.oi.soid << dendl;
-    return cache_result_t::NOOP;
-  }
-
-  vector<OSDOp> ops = static_cast<const MOSDOp*>(op->get_req())->ops;
-  for (vector<OSDOp>::iterator p = ops.begin(); p != ops.end(); ++p) {
-    OSDOp& osd_op = *p;
-    ceph_osd_op& op = osd_op.op;
-    if (op.op == CEPH_OSD_OP_SET_REDIRECT) {
-      return cache_result_t::NOOP;
-    }
-  }
-
-  switch (obc->obs.oi.manifest.type) {
-  case object_manifest_t::TYPE_REDIRECT:
-    if (op->may_write() || write_ordered) {
-      do_proxy_write(op, obc->obs.oi.soid, obc);
-    } else {
-      do_proxy_read(op, obc);
-    }
-    return cache_result_t::HANDLED_PROXY;
-  case object_manifest_t::TYPE_CHUNKED:
-  default:
-    assert(0 == "unrecognized manifest type");
-  }
-
-  return cache_result_t::NOOP;
-}
-
-void PrimaryLogPG::record_write_error(OpRequestRef op, const hobject_t &soid,
-                                     MOSDOpReply *orig_reply, int r)
-{
-  dout(20) << __func__ << " r=" << r << dendl;
-  assert(op->may_write());
-  const osd_reqid_t &reqid = static_cast<const MOSDOp*>(op->get_req())->get_reqid();
-  mempool::osd_pglog::list<pg_log_entry_t> entries;
-  entries.push_back(pg_log_entry_t(pg_log_entry_t::ERROR, soid,
-                                  get_next_version(), eversion_t(), 0,
-                                  reqid, utime_t(), r));
-
-  struct OnComplete {
-    PrimaryLogPG *pg;
-    OpRequestRef op;
-    boost::intrusive_ptr<MOSDOpReply> orig_reply;
-    int r;
-    OnComplete(
-      PrimaryLogPG *pg,
-      OpRequestRef op,
-      MOSDOpReply *orig_reply,
-      int r)
-      : pg(pg), op(op),
-       orig_reply(orig_reply, false /* take over ref */), r(r)
-      {}
-    void operator()() {
-      ldpp_dout(pg, 20) << "finished " << __func__ << " r=" << r << dendl;
-      const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-      int flags = m->get_flags() & (CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-      MOSDOpReply *reply = orig_reply.detach();
-      if (reply == nullptr) {
-       reply = new MOSDOpReply(m, r, pg->get_osdmap()->get_epoch(),
-                               flags, true);
-      }
-      ldpp_dout(pg, 10) << " sending commit on " << *m << " " << reply << dendl;
-      pg->osd->send_message_osd_client(reply, m->get_connection());
-    }
-  };
-
-  ObcLockManager lock_manager;
-  submit_log_entries(
-    entries,
-    std::move(lock_manager),
-    boost::optional<std::function<void(void)> >(
-      OnComplete(this, op, orig_reply, r)),
-    op,
-    r);
-}
-
-PrimaryLogPG::cache_result_t PrimaryLogPG::maybe_handle_cache_detail(
-  OpRequestRef op,
-  bool write_ordered,
-  ObjectContextRef obc,
-  int r, hobject_t missing_oid,
-  bool must_promote,
-  bool in_hit_set,
-  ObjectContextRef *promote_obc)
-{
-  if (op &&
-      op->get_req() &&
-      op->get_req()->get_type() == CEPH_MSG_OSD_OP &&
-      (static_cast<const MOSDOp *>(op->get_req())->get_flags() &
-       CEPH_OSD_FLAG_IGNORE_CACHE)) {
-    dout(20) << __func__ << ": ignoring cache due to flag" << dendl;
-    return cache_result_t::NOOP;
-  }
-  // return quickly if caching is not enabled
-  if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE)
-    return cache_result_t::NOOP;
-
-  must_promote = must_promote || op->need_promote();
-
-  if (obc)
-    dout(25) << __func__ << " " << obc->obs.oi << " "
-            << (obc->obs.exists ? "exists" : "DNE")
-            << " missing_oid " << missing_oid
-            << " must_promote " << (int)must_promote
-            << " in_hit_set " << (int)in_hit_set
-            << dendl;
-  else
-    dout(25) << __func__ << " (no obc)"
-            << " missing_oid " << missing_oid
-            << " must_promote " << (int)must_promote
-            << " in_hit_set " << (int)in_hit_set
-            << dendl;
-
-  // if it is write-ordered and blocked, stop now
-  if (obc.get() && obc->is_blocked() && write_ordered) {
-    // we're already doing something with this object
-    dout(20) << __func__ << " blocked on " << obc->obs.oi.soid << dendl;
-    return cache_result_t::NOOP;
-  }
-
-  if (r == -ENOENT && missing_oid == hobject_t()) {
-    // we know this object is logically absent (e.g., an undefined clone)
-    return cache_result_t::NOOP;
-  }
-
-  if (obc.get() && obc->obs.exists) {
-    osd->logger->inc(l_osd_op_cache_hit);
-    return cache_result_t::NOOP;
-  }
-
-  if (missing_oid == hobject_t() && obc.get()) {
-    missing_oid = obc->obs.oi.soid;
-  }
-
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  const object_locator_t oloc = m->get_object_locator();
-
-  if (op->need_skip_handle_cache()) {
-    return cache_result_t::NOOP;
-  }
-
-  // older versions do not proxy the feature bits.
-  bool can_proxy_write = get_osdmap()->get_up_osd_features() &
-    CEPH_FEATURE_OSD_PROXY_WRITE_FEATURES;
-  OpRequestRef promote_op;
-
-  switch (pool.info.cache_mode) {
-  case pg_pool_t::CACHEMODE_WRITEBACK:
-    if (agent_state &&
-       agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) {
-      if (!op->may_write() && !op->may_cache() &&
-         !write_ordered && !must_promote) {
-       dout(20) << __func__ << " cache pool full, proxying read" << dendl;
-       do_proxy_read(op);
-       return cache_result_t::HANDLED_PROXY;
-      }
-      dout(20) << __func__ << " cache pool full, waiting" << dendl;
-      block_write_on_full_cache(missing_oid, op);
-      return cache_result_t::BLOCKED_FULL;
-    }
-
-    if (must_promote || (!hit_set && !op->need_skip_promote())) {
-      promote_object(obc, missing_oid, oloc, op, promote_obc);
-      return cache_result_t::BLOCKED_PROMOTE;
-    }
-
-    if (op->may_write() || op->may_cache()) {
-      if (can_proxy_write) {
-        do_proxy_write(op, missing_oid);
-      } else {
-       // promote if can't proxy the write
-       promote_object(obc, missing_oid, oloc, op, promote_obc);
-       return cache_result_t::BLOCKED_PROMOTE;
-      }
-
-      // Promote too?
-      if (!op->need_skip_promote() && 
-          maybe_promote(obc, missing_oid, oloc, in_hit_set,
-                     pool.info.min_write_recency_for_promote,
-                     OpRequestRef(),
-                     promote_obc)) {
-       return cache_result_t::BLOCKED_PROMOTE;
-      }
-      return cache_result_t::HANDLED_PROXY;
-    } else {
-      do_proxy_read(op);
-
-      // Avoid duplicate promotion
-      if (obc.get() && obc->is_blocked()) {
-       if (promote_obc)
-         *promote_obc = obc;
-        return cache_result_t::BLOCKED_PROMOTE;
-      }
-
-      // Promote too?
-      if (!op->need_skip_promote()) {
-        (void)maybe_promote(obc, missing_oid, oloc, in_hit_set,
-                            pool.info.min_read_recency_for_promote,
-                            promote_op, promote_obc);
-      }
-
-      return cache_result_t::HANDLED_PROXY;
-    }
-    assert(0 == "unreachable");
-    return cache_result_t::NOOP;
-
-  case pg_pool_t::CACHEMODE_FORWARD:
-    // FIXME: this mode allows requests to be reordered.
-    do_cache_redirect(op);
-    return cache_result_t::HANDLED_REDIRECT;
-
-  case pg_pool_t::CACHEMODE_READONLY:
-    // TODO: clean this case up
-    if (!obc.get() && r == -ENOENT) {
-      // we don't have the object and op's a read
-      promote_object(obc, missing_oid, oloc, op, promote_obc);
-      return cache_result_t::BLOCKED_PROMOTE;
-    }
-    if (!r) { // it must be a write
-      do_cache_redirect(op);
-      return cache_result_t::HANDLED_REDIRECT;
-    }
-    // crap, there was a failure of some kind
-    return cache_result_t::NOOP;
-
-  case pg_pool_t::CACHEMODE_READFORWARD:
-    // Do writeback to the cache tier for writes
-    if (op->may_write() || write_ordered || must_promote) {
-      if (agent_state &&
-         agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) {
-       dout(20) << __func__ << " cache pool full, waiting" << dendl;
-       block_write_on_full_cache(missing_oid, op);
-       return cache_result_t::BLOCKED_FULL;
-      }
-      promote_object(obc, missing_oid, oloc, op, promote_obc);
-      return cache_result_t::BLOCKED_PROMOTE;
-    }
-
-    // If it is a read, we can read, we need to forward it
-    do_cache_redirect(op);
-    return cache_result_t::HANDLED_REDIRECT;
-
-  case pg_pool_t::CACHEMODE_PROXY:
-    if (!must_promote) {
-      if (op->may_write() || op->may_cache() || write_ordered) {
-       if (can_proxy_write) {
-         do_proxy_write(op, missing_oid);
-         return cache_result_t::HANDLED_PROXY;
-       }
-      } else {
-       do_proxy_read(op);
-       return cache_result_t::HANDLED_PROXY;
-      }
-    }
-    // ugh, we're forced to promote.
-    if (agent_state &&
-       agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) {
-      dout(20) << __func__ << " cache pool full, waiting" << dendl;
-      block_write_on_full_cache(missing_oid, op);
-      return cache_result_t::BLOCKED_FULL;
-    }
-    promote_object(obc, missing_oid, oloc, op, promote_obc);
-    return cache_result_t::BLOCKED_PROMOTE;
-
-  case pg_pool_t::CACHEMODE_READPROXY:
-    // Do writeback to the cache tier for writes
-    if (op->may_write() || write_ordered || must_promote) {
-      if (agent_state &&
-         agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) {
-       dout(20) << __func__ << " cache pool full, waiting" << dendl;
-       block_write_on_full_cache(missing_oid, op);
-       return cache_result_t::BLOCKED_FULL;
-      }
-      promote_object(obc, missing_oid, oloc, op, promote_obc);
-      return cache_result_t::BLOCKED_PROMOTE;
-    }
-
-    // If it is a read, we can read, we need to proxy it
-    do_proxy_read(op);
-    return cache_result_t::HANDLED_PROXY;
-
-  default:
-    assert(0 == "unrecognized cache_mode");
-  }
-  return cache_result_t::NOOP;
-}
-
-bool PrimaryLogPG::maybe_promote(ObjectContextRef obc,
-                                const hobject_t& missing_oid,
-                                const object_locator_t& oloc,
-                                bool in_hit_set,
-                                uint32_t recency,
-                                OpRequestRef promote_op,
-                                ObjectContextRef *promote_obc)
-{
-  dout(20) << __func__ << " missing_oid " << missing_oid
-          << "  in_hit_set " << in_hit_set << dendl;
-
-  switch (recency) {
-  case 0:
-    break;
-  case 1:
-    // Check if in the current hit set
-    if (in_hit_set) {
-      break;
-    } else {
-      // not promoting
-      return false;
-    }
-    break;
-  default:
-    {
-      unsigned count = (int)in_hit_set;
-      if (count) {
-       // Check if in other hit sets
-       const hobject_t& oid = obc.get() ? obc->obs.oi.soid : missing_oid;
-       for (map<time_t,HitSetRef>::reverse_iterator itor =
-              agent_state->hit_set_map.rbegin();
-            itor != agent_state->hit_set_map.rend();
-            ++itor) {
-         if (!itor->second->contains(oid)) {
-           break;
-         }
-         ++count;
-         if (count >= recency) {
-           break;
-         }
-       }
-      }
-      if (count >= recency) {
-       break;
-      }
-      return false;    // not promoting
-    }
-    break;
-  }
-
-  if (osd->promote_throttle()) {
-    dout(10) << __func__ << " promote throttled" << dendl;
-    return false;
-  }
-  promote_object(obc, missing_oid, oloc, promote_op, promote_obc);
-  return true;
-}
-
-void PrimaryLogPG::do_cache_redirect(OpRequestRef op)
-{
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  int flags = m->get_flags() & (CEPH_OSD_FLAG_ACK|CEPH_OSD_FLAG_ONDISK);
-  MOSDOpReply *reply = new MOSDOpReply(m, -ENOENT,
-                                      get_osdmap()->get_epoch(), flags, false);
-  request_redirect_t redir(m->get_object_locator(), pool.info.tier_of);
-  reply->set_redirect(redir);
-  dout(10) << "sending redirect to pool " << pool.info.tier_of << " for op "
-          << op << dendl;
-  m->get_connection()->send_message(reply);
-  return;
-}
-
-struct C_ProxyRead : public Context {
-  PrimaryLogPGRef pg;
-  hobject_t oid;
-  epoch_t last_peering_reset;
-  ceph_tid_t tid;
-  PrimaryLogPG::ProxyReadOpRef prdop;
-  utime_t start;
-  C_ProxyRead(PrimaryLogPG *p, hobject_t o, epoch_t lpr,
-            const PrimaryLogPG::ProxyReadOpRef& prd)
-    : pg(p), oid(o), last_peering_reset(lpr),
-      tid(0), prdop(prd), start(ceph_clock_now())
-  {}
-  void finish(int r) override {
-    if (prdop->canceled)
-      return;
-    pg->lock();
-    if (prdop->canceled) {
-      pg->unlock();
-      return;
-    }
-    if (last_peering_reset == pg->get_last_peering_reset()) {
-      pg->finish_proxy_read(oid, tid, r);
-      pg->osd->logger->tinc(l_osd_tier_r_lat, ceph_clock_now() - start);
-    }
-    pg->unlock();
-  }
-};
-
-void PrimaryLogPG::do_proxy_read(OpRequestRef op, ObjectContextRef obc)
-{
-  // NOTE: non-const here because the ProxyReadOp needs mutable refs to
-  // stash the result in the request's OSDOp vector
-  MOSDOp *m = static_cast<MOSDOp*>(op->get_nonconst_req());
-  object_locator_t oloc;
-  hobject_t soid;
-  /* extensible tier */
-  if (obc && obc->obs.exists && obc->obs.oi.has_manifest()) {
-    switch (obc->obs.oi.manifest.type) {
-      case object_manifest_t::TYPE_REDIRECT:
-         oloc = object_locator_t(obc->obs.oi.manifest.redirect_target);
-         soid = obc->obs.oi.manifest.redirect_target;  
-         break;
-      case object_manifest_t::TYPE_CHUNKED:
-      default:
-       assert(0 == "unrecognized manifest type");
-    }
-  } else {
-  /* proxy */
-    soid = m->get_hobj();
-    oloc = object_locator_t(m->get_object_locator());
-    oloc.pool = pool.info.tier_of;
-  }
-  unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY;
-
-  // pass through some original flags that make sense.
-  //  - leave out redirection and balancing flags since we are
-  //    already proxying through the primary
-  //  - leave off read/write/exec flags that are derived from the op
-  flags |= m->get_flags() & (CEPH_OSD_FLAG_RWORDERED |
-                            CEPH_OSD_FLAG_ORDERSNAP |
-                            CEPH_OSD_FLAG_ENFORCE_SNAPC |
-                            CEPH_OSD_FLAG_MAP_SNAP_CLONE);
-
-  dout(10) << __func__ << " Start proxy read for " << *m << dendl;
-
-  ProxyReadOpRef prdop(std::make_shared<ProxyReadOp>(op, soid, m->ops));
-
-  ObjectOperation obj_op;
-  obj_op.dup(prdop->ops);
-
-  if (pool.info.cache_mode == pg_pool_t::CACHEMODE_WRITEBACK &&
-      (agent_state && agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL)) {
-    for (unsigned i = 0; i < obj_op.ops.size(); i++) {
-      ceph_osd_op op = obj_op.ops[i].op;
-      switch (op.op) {
-       case CEPH_OSD_OP_READ:
-       case CEPH_OSD_OP_SYNC_READ:
-       case CEPH_OSD_OP_SPARSE_READ:
-       case CEPH_OSD_OP_CHECKSUM:
-       case CEPH_OSD_OP_CMPEXT:
-         op.flags = (op.flags | CEPH_OSD_OP_FLAG_FADVISE_SEQUENTIAL) &
-                      ~(CEPH_OSD_OP_FLAG_FADVISE_DONTNEED | CEPH_OSD_OP_FLAG_FADVISE_NOCACHE);
-      }
-    }
-  }
-
-  C_ProxyRead *fin = new C_ProxyRead(this, soid, get_last_peering_reset(),
-                                    prdop);
-  ceph_tid_t tid = osd->objecter->read(
-    soid.oid, oloc, obj_op,
-    m->get_snapid(), NULL,
-    flags, new C_OnFinisher(fin, &osd->objecter_finisher),
-    &prdop->user_version,
-    &prdop->data_offset,
-    m->get_features());
-  fin->tid = tid;
-  prdop->objecter_tid = tid;
-  proxyread_ops[tid] = prdop;
-  in_progress_proxy_ops[soid].push_back(op);
-}
-
-void PrimaryLogPG::finish_proxy_read(hobject_t oid, ceph_tid_t tid, int r)
-{
-  dout(10) << __func__ << " " << oid << " tid " << tid
-          << " " << cpp_strerror(r) << dendl;
-
-  map<ceph_tid_t, ProxyReadOpRef>::iterator p = proxyread_ops.find(tid);
-  if (p == proxyread_ops.end()) {
-    dout(10) << __func__ << " no proxyread_op found" << dendl;
-    return;
-  }
-  ProxyReadOpRef prdop = p->second;
-  if (tid != prdop->objecter_tid) {
-    dout(10) << __func__ << " tid " << tid << " != prdop " << prdop
-            << " tid " << prdop->objecter_tid << dendl;
-    return;
-  }
-  if (oid != prdop->soid) {
-    dout(10) << __func__ << " oid " << oid << " != prdop " << prdop
-            << " soid " << prdop->soid << dendl;
-    return;
-  }
-  proxyread_ops.erase(tid);
-
-  map<hobject_t, list<OpRequestRef>>::iterator q = in_progress_proxy_ops.find(oid);
-  if (q == in_progress_proxy_ops.end()) {
-    dout(10) << __func__ << " no in_progress_proxy_ops found" << dendl;
-    return;
-  }
-  assert(q->second.size());
-  list<OpRequestRef>::iterator it = std::find(q->second.begin(),
-                                              q->second.end(),
-                                             prdop->op);
-  assert(it != q->second.end());
-  OpRequestRef op = *it;
-  q->second.erase(it);
-  if (q->second.size() == 0) {
-    in_progress_proxy_ops.erase(oid);
-  }
-
-  osd->logger->inc(l_osd_tier_proxy_read);
-
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  OpContext *ctx = new OpContext(op, m->get_reqid(), &prdop->ops, this);
-  ctx->reply = new MOSDOpReply(m, 0, get_osdmap()->get_epoch(), 0, false);
-  ctx->user_at_version = prdop->user_version;
-  ctx->data_off = prdop->data_offset;
-  ctx->ignore_log_op_stats = true;
-  complete_read_ctx(r, ctx);
-}
-
-void PrimaryLogPG::kick_proxy_ops_blocked(hobject_t& soid)
-{
-  map<hobject_t, list<OpRequestRef>>::iterator p = in_progress_proxy_ops.find(soid);
-  if (p == in_progress_proxy_ops.end())
-    return;
-
-  list<OpRequestRef>& ls = p->second;
-  dout(10) << __func__ << " " << soid << " requeuing " << ls.size() << " requests" << dendl;
-  requeue_ops(ls);
-  in_progress_proxy_ops.erase(p);
-}
-
-void PrimaryLogPG::cancel_proxy_read(ProxyReadOpRef prdop)
-{
-  dout(10) << __func__ << " " << prdop->soid << dendl;
-  prdop->canceled = true;
-
-  // cancel objecter op, if we can
-  if (prdop->objecter_tid) {
-    osd->objecter->op_cancel(prdop->objecter_tid, -ECANCELED);
-    for (uint32_t i = 0; i < prdop->ops.size(); i++) {
-      prdop->ops[i].outdata.clear();
-    }
-    proxyread_ops.erase(prdop->objecter_tid);
-    prdop->objecter_tid = 0;
-  }
-}
-
-void PrimaryLogPG::cancel_proxy_ops(bool requeue)
-{
-  dout(10) << __func__ << dendl;
-
-  // cancel proxy reads
-  map<ceph_tid_t, ProxyReadOpRef>::iterator p = proxyread_ops.begin();
-  while (p != proxyread_ops.end()) {
-    cancel_proxy_read((p++)->second);
-  }
-
-  // cancel proxy writes
-  map<ceph_tid_t, ProxyWriteOpRef>::iterator q = proxywrite_ops.begin();
-  while (q != proxywrite_ops.end()) {
-    cancel_proxy_write((q++)->second);
-  }
-
-  if (requeue) {
-    map<hobject_t, list<OpRequestRef>>::iterator p =
-      in_progress_proxy_ops.begin();
-    while (p != in_progress_proxy_ops.end()) {
-      list<OpRequestRef>& ls = p->second;
-      dout(10) << __func__ << " " << p->first << " requeuing " << ls.size()
-              << " requests" << dendl;
-      requeue_ops(ls);
-      in_progress_proxy_ops.erase(p++);
-    }
-  } else {
-    in_progress_proxy_ops.clear();
-  }
-}
-
-struct C_ProxyWrite_Commit : public Context {
-  PrimaryLogPGRef pg;
-  hobject_t oid;
-  epoch_t last_peering_reset;
-  ceph_tid_t tid;
-  PrimaryLogPG::ProxyWriteOpRef pwop;
-  C_ProxyWrite_Commit(PrimaryLogPG *p, hobject_t o, epoch_t lpr,
-                     const PrimaryLogPG::ProxyWriteOpRef& pw)
-    : pg(p), oid(o), last_peering_reset(lpr),
-      tid(0), pwop(pw)
-  {}
-  void finish(int r) override {
-    if (pwop->canceled)
-      return;
-    pg->lock();
-    if (pwop->canceled) {
-      pg->unlock();
-      return;
-    }
-    if (last_peering_reset == pg->get_last_peering_reset()) {
-      pg->finish_proxy_write(oid, tid, r);
-    }
-    pg->unlock();
-  }
-};
-
-void PrimaryLogPG::do_proxy_write(OpRequestRef op, const hobject_t& missing_oid, ObjectContextRef obc)
-{
-  // NOTE: non-const because ProxyWriteOp takes a mutable ref
-  MOSDOp *m = static_cast<MOSDOp*>(op->get_nonconst_req());
-  object_locator_t oloc;
-  SnapContext snapc(m->get_snap_seq(), m->get_snaps());
-  hobject_t soid;
-  /* extensible tier */
-  if (obc && obc->obs.exists && obc->obs.oi.has_manifest()) {
-    switch (obc->obs.oi.manifest.type) {
-      case object_manifest_t::TYPE_REDIRECT:
-         oloc = object_locator_t(obc->obs.oi.manifest.redirect_target);
-         soid = obc->obs.oi.manifest.redirect_target;  
-         break;
-      case object_manifest_t::TYPE_CHUNKED:
-      default:
-       assert(0 == "unrecognized manifest type");
-    }
-  } else {
-  /* proxy */
-    soid = m->get_hobj();
-    oloc = object_locator_t(m->get_object_locator());
-    oloc.pool = pool.info.tier_of;
-  }
-
-  unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY;
-  if (!(op->may_write() || op->may_cache())) {
-    flags |= CEPH_OSD_FLAG_RWORDERED;
-  }
-  dout(10) << __func__ << " Start proxy write for " << *m << dendl;
-
-  ProxyWriteOpRef pwop(std::make_shared<ProxyWriteOp>(op, soid, m->ops, m->get_reqid()));
-  pwop->ctx = new OpContext(op, m->get_reqid(), &pwop->ops, this);
-  pwop->mtime = m->get_mtime();
-
-  ObjectOperation obj_op;
-  obj_op.dup(pwop->ops);
-
-  C_ProxyWrite_Commit *fin = new C_ProxyWrite_Commit(
-      this, soid, get_last_peering_reset(), pwop);
-  ceph_tid_t tid = osd->objecter->mutate(
-    soid.oid, oloc, obj_op, snapc,
-    ceph::real_clock::from_ceph_timespec(pwop->mtime),
-    flags, new C_OnFinisher(fin, &osd->objecter_finisher),
-    &pwop->user_version, pwop->reqid);
-  fin->tid = tid;
-  pwop->objecter_tid = tid;
-  proxywrite_ops[tid] = pwop;
-  in_progress_proxy_ops[soid].push_back(op);
-}
-
-void PrimaryLogPG::finish_proxy_write(hobject_t oid, ceph_tid_t tid, int r)
-{
-  dout(10) << __func__ << " " << oid << " tid " << tid
-          << " " << cpp_strerror(r) << dendl;
-
-  map<ceph_tid_t, ProxyWriteOpRef>::iterator p = proxywrite_ops.find(tid);
-  if (p == proxywrite_ops.end()) {
-    dout(10) << __func__ << " no proxywrite_op found" << dendl;
-    return;
-  }
-  ProxyWriteOpRef pwop = p->second;
-  assert(tid == pwop->objecter_tid);
-  assert(oid == pwop->soid);
-
-  proxywrite_ops.erase(tid);
-
-  map<hobject_t, list<OpRequestRef> >::iterator q = in_progress_proxy_ops.find(oid);
-  if (q == in_progress_proxy_ops.end()) {
-    dout(10) << __func__ << " no in_progress_proxy_ops found" << dendl;
-    delete pwop->ctx;
-    pwop->ctx = NULL;
-    return;
-  }
-  list<OpRequestRef>& in_progress_op = q->second;
-  assert(in_progress_op.size());
-  list<OpRequestRef>::iterator it = std::find(in_progress_op.begin(),
-                                              in_progress_op.end(),
-                                             pwop->op);
-  assert(it != in_progress_op.end());
-  in_progress_op.erase(it);
-  if (in_progress_op.size() == 0) {
-    in_progress_proxy_ops.erase(oid);
-  }
-
-  osd->logger->inc(l_osd_tier_proxy_write);
-
-  const MOSDOp *m = static_cast<const MOSDOp*>(pwop->op->get_req());
-  assert(m != NULL);
-
-  if (!pwop->sent_reply) {
-    // send commit.
-    MOSDOpReply *reply = pwop->ctx->reply;
-    if (reply)
-      pwop->ctx->reply = NULL;
-    else {
-      reply = new MOSDOpReply(m, r, get_osdmap()->get_epoch(), 0, true);
-      reply->set_reply_versions(eversion_t(), pwop->user_version);
-    }
-    reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-    dout(10) << " sending commit on " << pwop << " " << reply << dendl;
-    osd->send_message_osd_client(reply, m->get_connection());
-    pwop->sent_reply = true;
-    pwop->ctx->op->mark_commit_sent();
-  }
-
-  delete pwop->ctx;
-  pwop->ctx = NULL;
-}
-
-void PrimaryLogPG::cancel_proxy_write(ProxyWriteOpRef pwop)
-{
-  dout(10) << __func__ << " " << pwop->soid << dendl;
-  pwop->canceled = true;
-
-  // cancel objecter op, if we can
-  if (pwop->objecter_tid) {
-    osd->objecter->op_cancel(pwop->objecter_tid, -ECANCELED);
-    delete pwop->ctx;
-    pwop->ctx = NULL;
-    proxywrite_ops.erase(pwop->objecter_tid);
-    pwop->objecter_tid = 0;
-  }
-}
-
-class PromoteCallback: public PrimaryLogPG::CopyCallback {
-  ObjectContextRef obc;
-  PrimaryLogPG *pg;
-  utime_t start;
-public:
-  PromoteCallback(ObjectContextRef obc_, PrimaryLogPG *pg_)
-    : obc(obc_),
-      pg(pg_),
-      start(ceph_clock_now()) {}
-
-  void finish(PrimaryLogPG::CopyCallbackResults results) override {
-    PrimaryLogPG::CopyResults *results_data = results.get<1>();
-    int r = results.get<0>();
-    pg->finish_promote(r, results_data, obc);
-    pg->osd->logger->tinc(l_osd_tier_promote_lat, ceph_clock_now() - start);
-  }
-};
-
-void PrimaryLogPG::promote_object(ObjectContextRef obc,
-                                 const hobject_t& missing_oid,
-                                 const object_locator_t& oloc,
-                                 OpRequestRef op,
-                                 ObjectContextRef *promote_obc)
-{
-  hobject_t hoid = obc ? obc->obs.oi.soid : missing_oid;
-  assert(hoid != hobject_t());
-  if (scrubber.write_blocked_by_scrub(hoid)) {
-    dout(10) << __func__ << " " << hoid
-            << " blocked by scrub" << dendl;
-    if (op) {
-      waiting_for_scrub.push_back(op);
-      op->mark_delayed("waiting for scrub");
-      dout(10) << __func__ << " " << hoid
-              << " placing op in waiting_for_scrub" << dendl;
-    } else {
-      dout(10) << __func__ << " " << hoid
-              << " no op, dropping on the floor" << dendl;
-    }
-    return;
-  }
-  if (!obc) { // we need to create an ObjectContext
-    assert(missing_oid != hobject_t());
-    obc = get_object_context(missing_oid, true);
-  }
-  if (promote_obc)
-    *promote_obc = obc;
-
-  /*
-   * Before promote complete, if there are  proxy-reads for the object,
-   * for this case we don't use DONTNEED.
-   */
-  unsigned src_fadvise_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL;
-  map<hobject_t, list<OpRequestRef>>::iterator q = in_progress_proxy_ops.find(obc->obs.oi.soid);
-  if (q == in_progress_proxy_ops.end()) {
-    src_fadvise_flags |= LIBRADOS_OP_FLAG_FADVISE_DONTNEED;
-  }
-
-  PromoteCallback *cb = new PromoteCallback(obc, this);
-  object_locator_t my_oloc = oloc;
-  my_oloc.pool = pool.info.tier_of;
-
-  unsigned flags = CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY |
-                   CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE |
-                   CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE |
-                   CEPH_OSD_COPY_FROM_FLAG_RWORDERED;
-  start_copy(cb, obc, obc->obs.oi.soid, my_oloc, 0, flags,
-            obc->obs.oi.soid.snap == CEPH_NOSNAP,
-            src_fadvise_flags, 0);
-
-  assert(obc->is_blocked());
-
-  if (op)
-    wait_for_blocked_object(obc->obs.oi.soid, op);
-  info.stats.stats.sum.num_promote++;
-}
-
-void PrimaryLogPG::execute_ctx(OpContext *ctx)
-{
-  FUNCTRACE();
-  dout(10) << __func__ << " " << ctx << dendl;
-  ctx->reset_obs(ctx->obc);
-  ctx->update_log_only = false; // reset in case finish_copyfrom() is re-running execute_ctx
-  OpRequestRef op = ctx->op;
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-  ObjectContextRef obc = ctx->obc;
-  const hobject_t& soid = obc->obs.oi.soid;
-
-  // this method must be idempotent since we may call it several times
-  // before we finally apply the resulting transaction.
-  ctx->op_t.reset(new PGTransaction);
-
-  if (op->may_write() || op->may_cache()) {
-    // snap
-    if (!(m->has_flag(CEPH_OSD_FLAG_ENFORCE_SNAPC)) &&
-       pool.info.is_pool_snaps_mode()) {
-      // use pool's snapc
-      ctx->snapc = pool.snapc;
-    } else {
-      // client specified snapc
-      ctx->snapc.seq = m->get_snap_seq();
-      ctx->snapc.snaps = m->get_snaps();
-      filter_snapc(ctx->snapc.snaps);
-    }
-    if ((m->has_flag(CEPH_OSD_FLAG_ORDERSNAP)) &&
-       ctx->snapc.seq < obc->ssc->snapset.seq) {
-      dout(10) << " ORDERSNAP flag set and snapc seq " << ctx->snapc.seq
-              << " < snapset seq " << obc->ssc->snapset.seq
-              << " on " << obc->obs.oi.soid << dendl;
-      reply_ctx(ctx, -EOLDSNAPC);
-      return;
-    }
-
-    // version
-    ctx->at_version = get_next_version();
-    ctx->mtime = m->get_mtime();
-
-    dout(10) << __func__ << " " << soid << " " << *ctx->ops
-            << " ov " << obc->obs.oi.version << " av " << ctx->at_version 
-            << " snapc " << ctx->snapc
-            << " snapset " << obc->ssc->snapset
-            << dendl;  
-  } else {
-    dout(10) << __func__ << " " << soid << " " << *ctx->ops
-            << " ov " << obc->obs.oi.version
-            << dendl;  
-  }
-
-  if (!ctx->user_at_version)
-    ctx->user_at_version = obc->obs.oi.user_version;
-  dout(30) << __func__ << " user_at_version " << ctx->user_at_version << dendl;
-
-  if (op->may_read()) {
-    dout(10) << " taking ondisk_read_lock" << dendl;
-    obc->ondisk_read_lock();
-  }
-
-  {
-#ifdef WITH_LTTNG
-    osd_reqid_t reqid = ctx->op->get_reqid();
-#endif
-    tracepoint(osd, prepare_tx_enter, reqid.name._type,
-        reqid.name._num, reqid.tid, reqid.inc);
-  }
-
-  int result = prepare_transaction(ctx);
-
-  {
-#ifdef WITH_LTTNG
-    osd_reqid_t reqid = ctx->op->get_reqid();
-#endif
-    tracepoint(osd, prepare_tx_exit, reqid.name._type,
-        reqid.name._num, reqid.tid, reqid.inc);
-  }
-
-  if (op->may_read()) {
-    dout(10) << " dropping ondisk_read_lock" << dendl;
-    obc->ondisk_read_unlock();
-  }
-
-  bool pending_async_reads = !ctx->pending_async_reads.empty();
-  if (result == -EINPROGRESS || pending_async_reads) {
-    // come back later.
-    if (pending_async_reads) {
-      in_progress_async_reads.push_back(make_pair(op, ctx));
-      ctx->start_async_reads(this);
-    }
-    return;
-  }
-
-  if (result == -EAGAIN) {
-    // clean up after the ctx
-    close_op_ctx(ctx);
-    return;
-  }
-
-  bool successful_write = !ctx->op_t->empty() && op->may_write() && result >= 0;
-  // prepare the reply
-  ctx->reply = new MOSDOpReply(m, 0, get_osdmap()->get_epoch(), 0,
-                              successful_write);
-
-  // Write operations aren't allowed to return a data payload because
-  // we can't do so reliably. If the client has to resend the request
-  // and it has already been applied, we will return 0 with no
-  // payload.  Non-deterministic behavior is no good.  However, it is
-  // possible to construct an operation that does a read, does a guard
-  // check (e.g., CMPXATTR), and then a write.  Then we either succeed
-  // with the write, or return a CMPXATTR and the read value.
-  if (successful_write) {
-    // write.  normalize the result code.
-    dout(20) << " zeroing write result code " << result << dendl;
-    result = 0;
-  }
-  ctx->reply->set_result(result);
-
-  // read or error?
-  if ((ctx->op_t->empty() || result < 0) && !ctx->update_log_only) {
-    // finish side-effects
-    if (result >= 0)
-      do_osd_op_effects(ctx, m->get_connection());
-
-    complete_read_ctx(result, ctx);
-    return;
-  }
-
-  ctx->reply->set_reply_versions(ctx->at_version, ctx->user_at_version);
-
-  assert(op->may_write() || op->may_cache());
-
-  // trim log?
-  calc_trim_to();
-
-  // verify that we are doing this in order?
-  if (cct->_conf->osd_debug_op_order && m->get_source().is_client() &&
-      !pool.info.is_tier() && !pool.info.has_tiers()) {
-    map<client_t,ceph_tid_t>& cm = debug_op_order[obc->obs.oi.soid];
-    ceph_tid_t t = m->get_tid();
-    client_t n = m->get_source().num();
-    map<client_t,ceph_tid_t>::iterator p = cm.find(n);
-    if (p == cm.end()) {
-      dout(20) << " op order client." << n << " tid " << t << " (first)" << dendl;
-      cm[n] = t;
-    } else {
-      dout(20) << " op order client." << n << " tid " << t << " last was " << p->second << dendl;
-      if (p->second > t) {
-       derr << "bad op order, already applied " << p->second << " > this " << t << dendl;
-       assert(0 == "out of order op");
-      }
-      p->second = t;
-    }
-  }
-
-  if (ctx->update_log_only) {
-    if (result >= 0)
-      do_osd_op_effects(ctx, m->get_connection());
-
-    dout(20) << __func__ << " update_log_only -- result=" << result << dendl;
-    // save just what we need from ctx
-    MOSDOpReply *reply = ctx->reply;
-    ctx->reply = nullptr;
-    reply->claim_op_out_data(*ctx->ops);
-    reply->get_header().data_off = (ctx->data_off ? *ctx->data_off : 0);
-    close_op_ctx(ctx);
-
-    if (result == -ENOENT) {
-      reply->set_enoent_reply_versions(info.last_update,
-                                      info.last_user_version);
-    }
-    reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-    // append to pg log for dup detection - don't save buffers for now
-    record_write_error(op, soid, reply, result);
-    return;
-  }
-
-  // no need to capture PG ref, repop cancel will handle that
-  // Can capture the ctx by pointer, it's owned by the repop
-  ctx->register_on_commit(
-    [m, ctx, this](){
-      if (ctx->op)
-       log_op_stats(
-         ctx);
-
-      if (m && !ctx->sent_reply) {
-       MOSDOpReply *reply = ctx->reply;
-       if (reply)
-         ctx->reply = nullptr;
-       else {
-         reply = new MOSDOpReply(m, 0, get_osdmap()->get_epoch(), 0, true);
-         reply->set_reply_versions(ctx->at_version,
-                                   ctx->user_at_version);
-       }
-       reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-       dout(10) << " sending reply on " << *m << " " << reply << dendl;
-       osd->send_message_osd_client(reply, m->get_connection());
-       ctx->sent_reply = true;
-       ctx->op->mark_commit_sent();
-      }
-    });
-  ctx->register_on_success(
-    [ctx, this]() {
-      do_osd_op_effects(
-       ctx,
-       ctx->op ? ctx->op->get_req()->get_connection() :
-       ConnectionRef());
-    });
-  ctx->register_on_finish(
-    [ctx, this]() {
-      delete ctx;
-    });
-
-  // issue replica writes
-  ceph_tid_t rep_tid = osd->get_tid();
-
-  RepGather *repop = new_repop(ctx, obc, rep_tid);
-
-  issue_repop(repop, ctx);
-  eval_repop(repop);
-  repop->put();
-}
-
-void PrimaryLogPG::close_op_ctx(OpContext *ctx) {
-  release_object_locks(ctx->lock_manager);
-
-  ctx->op_t.reset();
-
-  for (auto p = ctx->on_finish.begin(); p != ctx->on_finish.end();
-       ctx->on_finish.erase(p++)) {
-    (*p)();
-  }
-  delete ctx;
-}
-
-void PrimaryLogPG::reply_ctx(OpContext *ctx, int r)
-{
-  if (ctx->op)
-    osd->reply_op_error(ctx->op, r);
-  close_op_ctx(ctx);
-}
-
-void PrimaryLogPG::reply_ctx(OpContext *ctx, int r, eversion_t v, version_t uv)
-{
-  if (ctx->op)
-    osd->reply_op_error(ctx->op, r, v, uv);
-  close_op_ctx(ctx);
-}
-
-void PrimaryLogPG::log_op_stats(OpContext *ctx)
-{
-  OpRequestRef op = ctx->op;
-  const MOSDOp *m = static_cast<const MOSDOp*>(op->get_req());
-
-  utime_t now = ceph_clock_now();
-  utime_t latency = now;
-  latency -= ctx->op->get_req()->get_recv_stamp();
-  utime_t process_latency = now;
-  process_latency -= ctx->op->get_dequeued_time();
-
-  uint64_t inb = ctx->bytes_written;
-  uint64_t outb = ctx->bytes_read;
-
-  osd->logger->inc(l_osd_op);
-
-  osd->logger->inc(l_osd_op_outb, outb);
-  osd->logger->inc(l_osd_op_inb, inb);
-  osd->logger->tinc(l_osd_op_lat, latency);
-  osd->logger->tinc(l_osd_op_process_lat, process_latency);
-
-  if (op->may_read() && op->may_write()) {
-    osd->logger->inc(l_osd_op_rw);
-    osd->logger->inc(l_osd_op_rw_inb, inb);
-    osd->logger->inc(l_osd_op_rw_outb, outb);
-    osd->logger->tinc(l_osd_op_rw_lat, latency);
-    osd->logger->hinc(l_osd_op_rw_lat_inb_hist, latency.to_nsec(), inb);
-    osd->logger->hinc(l_osd_op_rw_lat_outb_hist, latency.to_nsec(), outb);
-    osd->logger->tinc(l_osd_op_rw_process_lat, process_latency);
-  } else if (op->may_read()) {
-    osd->logger->inc(l_osd_op_r);
-    osd->logger->inc(l_osd_op_r_outb, outb);
-    osd->logger->tinc(l_osd_op_r_lat, latency);
-    osd->logger->hinc(l_osd_op_r_lat_outb_hist, latency.to_nsec(), outb);
-    osd->logger->tinc(l_osd_op_r_process_lat, process_latency);
-  } else if (op->may_write() || op->may_cache()) {
-    osd->logger->inc(l_osd_op_w);
-    osd->logger->inc(l_osd_op_w_inb, inb);
-    osd->logger->tinc(l_osd_op_w_lat, latency);
-    osd->logger->hinc(l_osd_op_w_lat_inb_hist, latency.to_nsec(), inb);
-    osd->logger->tinc(l_osd_op_w_process_lat, process_latency);
-  } else
-    ceph_abort();
-
-  dout(15) << "log_op_stats " << *m
-          << " inb " << inb
-          << " outb " << outb
-          << " lat " << latency << dendl;
-}
-
-void PrimaryLogPG::do_sub_op(OpRequestRef op)
-{
-  const MOSDSubOp *m = static_cast<const MOSDSubOp*>(op->get_req());
-  assert(have_same_or_newer_map(m->map_epoch));
-  assert(m->get_type() == MSG_OSD_SUBOP);
-  dout(15) << "do_sub_op " << *op->get_req() << dendl;
-
-  if (!is_peered()) {
-    waiting_for_peered.push_back(op);
-    op->mark_delayed("waiting for active");
-    return;
-  }
-
-  const OSDOp *first = NULL;
-  if (m->ops.size() >= 1) {
-    first = &m->ops[0];
-  }
-
-  if (first) {
-    switch (first->op.op) {
-    case CEPH_OSD_OP_DELETE:
-      sub_op_remove(op);
-      return;
-    case CEPH_OSD_OP_SCRUB_RESERVE:
-      handle_scrub_reserve_request(op);
-      return;
-    case CEPH_OSD_OP_SCRUB_UNRESERVE:
-      handle_scrub_reserve_release(op);
-      return;
-    case CEPH_OSD_OP_SCRUB_MAP:
-      sub_op_scrub_map(op);
-      return;
-    }
-  }
-}
-
-void PrimaryLogPG::do_sub_op_reply(OpRequestRef op)
-{
-  const MOSDSubOpReply *r = static_cast<const MOSDSubOpReply *>(op->get_req());
-  assert(r->get_type() == MSG_OSD_SUBOPREPLY);
-  if (r->ops.size() >= 1) {
-    const OSDOp& first = r->ops[0];
-    switch (first.op.op) {
-    case CEPH_OSD_OP_SCRUB_RESERVE:
-      {
-       pg_shard_t from = r->from;
-       bufferlist::iterator p = const_cast<bufferlist&>(r->get_data()).begin();
-       bool reserved;
-       ::decode(reserved, p);
-       if (reserved) {
-         handle_scrub_reserve_grant(op, from);
-       } else {
-         handle_scrub_reserve_reject(op, from);
-       }
-      }
-      return;
-    }
-  }
-}
-
-void PrimaryLogPG::do_scan(
-  OpRequestRef op,
-  ThreadPool::TPHandle &handle)
-{
-  const MOSDPGScan *m = static_cast<const MOSDPGScan*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_SCAN);
-  dout(10) << "do_scan " << *m << dendl;
-
-  op->mark_started();
-
-  switch (m->op) {
-  case MOSDPGScan::OP_SCAN_GET_DIGEST:
-    {
-      ostringstream ss;
-      if (osd->check_backfill_full(ss)) {
-       dout(1) << __func__ << ": Canceling backfill, " << ss.str() << dendl;
-       queue_peering_event(
-         CephPeeringEvtRef(
-           std::make_shared<CephPeeringEvt>(
-             get_osdmap()->get_epoch(),
-             get_osdmap()->get_epoch(),
-             BackfillTooFull())));
-       return;
-      }
-
-      BackfillInterval bi;
-      bi.begin = m->begin;
-      // No need to flush, there won't be any in progress writes occuring
-      // past m->begin
-      scan_range(
-       cct->_conf->osd_backfill_scan_min,
-       cct->_conf->osd_backfill_scan_max,
-       &bi,
-       handle);
-      MOSDPGScan *reply = new MOSDPGScan(
-       MOSDPGScan::OP_SCAN_DIGEST,
-       pg_whoami,
-       get_osdmap()->get_epoch(), m->query_epoch,
-       spg_t(info.pgid.pgid, get_primary().shard), bi.begin, bi.end);
-      ::encode(bi.objects, reply->get_data());
-      osd->send_message_osd_cluster(reply, m->get_connection());
-    }
-    break;
-
-  case MOSDPGScan::OP_SCAN_DIGEST:
-    {
-      pg_shard_t from = m->from;
-
-      // Check that from is in backfill_targets vector
-      assert(is_backfill_targets(from));
-
-      BackfillInterval& bi = peer_backfill_info[from];
-      bi.begin = m->begin;
-      bi.end = m->end;
-      bufferlist::iterator p = const_cast<bufferlist&>(m->get_data()).begin();
-
-      // take care to preserve ordering!
-      bi.clear_objects();
-      ::decode_noclear(bi.objects, p);
-
-      if (waiting_on_backfill.erase(from)) {
-       if (waiting_on_backfill.empty()) {
-         assert(peer_backfill_info.size() == backfill_targets.size());
-         finish_recovery_op(hobject_t::get_max());
-       }
-      } else {
-       // we canceled backfill for a while due to a too full, and this
-       // is an extra response from a non-too-full peer
-      }
-    }
-    break;
-  }
-}
-
-void PrimaryLogPG::do_backfill(OpRequestRef op)
-{
-  const MOSDPGBackfill *m = static_cast<const MOSDPGBackfill*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_BACKFILL);
-  dout(10) << "do_backfill " << *m << dendl;
-
-  op->mark_started();
-
-  switch (m->op) {
-  case MOSDPGBackfill::OP_BACKFILL_FINISH:
-    {
-      assert(cct->_conf->osd_kill_backfill_at != 1);
-
-      MOSDPGBackfill *reply = new MOSDPGBackfill(
-       MOSDPGBackfill::OP_BACKFILL_FINISH_ACK,
-       get_osdmap()->get_epoch(),
-       m->query_epoch,
-       spg_t(info.pgid.pgid, get_primary().shard));
-      reply->set_priority(get_recovery_op_priority());
-      osd->send_message_osd_cluster(reply, m->get_connection());
-      queue_peering_event(
-       CephPeeringEvtRef(
-         std::make_shared<CephPeeringEvt>(
-           get_osdmap()->get_epoch(),
-           get_osdmap()->get_epoch(),
-           RecoveryDone())));
-    }
-    // fall-thru
-
-  case MOSDPGBackfill::OP_BACKFILL_PROGRESS:
-    {
-      assert(cct->_conf->osd_kill_backfill_at != 2);
-
-      info.set_last_backfill(m->last_backfill);
-      info.stats = m->stats;
-
-      ObjectStore::Transaction t;
-      dirty_info = true;
-      write_if_dirty(t);
-      int tr = osd->store->queue_transaction(osr.get(), std::move(t), NULL);
-      assert(tr == 0);
-    }
-    break;
-
-  case MOSDPGBackfill::OP_BACKFILL_FINISH_ACK:
-    {
-      assert(is_primary());
-      assert(cct->_conf->osd_kill_backfill_at != 3);
-      finish_recovery_op(hobject_t::get_max());
-    }
-    break;
-  }
-}
-
-void PrimaryLogPG::do_backfill_remove(OpRequestRef op)
-{
-  const MOSDPGBackfillRemove *m = static_cast<const MOSDPGBackfillRemove*>(
-    op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_BACKFILL_REMOVE);
-  dout(7) << __func__ << " " << m->ls << dendl;
-
-  op->mark_started();
-
-  ObjectStore::Transaction t;
-  for (auto& p : m->ls) {
-    remove_snap_mapped_object(t, p.first);
-  }
-  int r = osd->store->queue_transaction(osr.get(), std::move(t), NULL);
-  assert(r == 0);
-}
-
-int PrimaryLogPG::trim_object(
-  bool first, const hobject_t &coid, PrimaryLogPG::OpContextUPtr *ctxp)
-{
-  *ctxp = NULL;
-  // load clone info
-  bufferlist bl;
-  ObjectContextRef obc = get_object_context(coid, false, NULL);
-  if (!obc || !obc->ssc || !obc->ssc->exists) {
-    osd->clog->error() << __func__ << ": Can not trim " << coid
-      << " repair needed " << (obc ? "(no obc->ssc or !exists)" : "(no obc)");
-    return -ENOENT;
-  }
-
-  hobject_t snapoid(
-    coid.oid, coid.get_key(),
-    obc->ssc->snapset.head_exists ? CEPH_NOSNAP:CEPH_SNAPDIR, coid.get_hash(),
-    info.pgid.pool(), coid.get_namespace());
-  ObjectContextRef snapset_obc = get_object_context(snapoid, false);
-  if (!snapset_obc) {
-    osd->clog->error() << __func__ << ": Can not trim " << coid
-      << " repair needed, no snapset obc for " << snapoid;
-    return -ENOENT;
-  }
-
-  SnapSet& snapset = obc->ssc->snapset;
-
-  bool legacy = snapset.is_legacy() ||
-    get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS;
-
-  object_info_t &coi = obc->obs.oi;
-  set<snapid_t> old_snaps;
-  if (legacy) {
-    old_snaps.insert(coi.legacy_snaps.begin(), coi.legacy_snaps.end());
-  } else {
-    auto p = snapset.clone_snaps.find(coid.snap);
-    if (p == snapset.clone_snaps.end()) {
-      osd->clog->error() << "No clone_snaps in snapset " << snapset
-                        << " for object " << coid << "\n";
-      return -ENOENT;
-    }
-    old_snaps.insert(snapset.clone_snaps[coid.snap].begin(),
-                    snapset.clone_snaps[coid.snap].end());
-  }
-  if (old_snaps.empty()) {
-    osd->clog->error() << "No object info snaps for object " << coid;
-    return -ENOENT;
-  }
-
-  dout(10) << coid << " old_snaps " << old_snaps
-          << " old snapset " << snapset << dendl;
-  if (snapset.seq == 0) {
-    osd->clog->error() << "No snapset.seq for object " << coid;
-    return -ENOENT;
-  }
-
-  set<snapid_t> new_snaps;
-  for (set<snapid_t>::iterator i = old_snaps.begin();
-       i != old_snaps.end();
-       ++i) {
-    if (!pool.info.is_removed_snap(*i))
-      new_snaps.insert(*i);
-  }
-
-  vector<snapid_t>::iterator p = snapset.clones.end();
-
-  if (new_snaps.empty()) {
-    p = std::find(snapset.clones.begin(), snapset.clones.end(), coid.snap);
-    if (p == snapset.clones.end()) {
-      osd->clog->error() << "Snap " << coid.snap << " not in clones";
-      return -ENOENT;
-    }
-  }
-
-  OpContextUPtr ctx = simple_opc_create(obc);
-  ctx->snapset_obc = snapset_obc;
-
-  if (!ctx->lock_manager.get_snaptrimmer_write(
-       coid,
-       obc,
-       first)) {
-    close_op_ctx(ctx.release());
-    dout(10) << __func__ << ": Unable to get a wlock on " << coid << dendl;
-    return -ENOLCK;
-  }
-
-  if (!ctx->lock_manager.get_snaptrimmer_write(
-       snapoid,
-       snapset_obc,
-       first)) {
-    close_op_ctx(ctx.release());
-    dout(10) << __func__ << ": Unable to get a wlock on " << snapoid << dendl;
-    return -ENOLCK;
-  }
-
-  ctx->at_version = get_next_version();
-
-  PGTransaction *t = ctx->op_t.get();
-  if (new_snaps.empty()) {
-    // remove clone
-    dout(10) << coid << " snaps " << old_snaps << " -> "
-            << new_snaps << " ... deleting" << dendl;
-
-    // ...from snapset
-    assert(p != snapset.clones.end());
-  
-    snapid_t last = coid.snap;
-    ctx->delta_stats.num_bytes -= snapset.get_clone_bytes(last);
-
-    if (p != snapset.clones.begin()) {
-      // not the oldest... merge overlap into next older clone
-      vector<snapid_t>::iterator n = p - 1;
-      hobject_t prev_coid = coid;
-      prev_coid.snap = *n;
-      bool adjust_prev_bytes = is_present_clone(prev_coid);
-
-      if (adjust_prev_bytes)
-       ctx->delta_stats.num_bytes -= snapset.get_clone_bytes(*n);
-
-      snapset.clone_overlap[*n].intersection_of(
-       snapset.clone_overlap[*p]);
-
-      if (adjust_prev_bytes)
-       ctx->delta_stats.num_bytes += snapset.get_clone_bytes(*n);
-    }
-    ctx->delta_stats.num_objects--;
-    if (coi.is_dirty())
-      ctx->delta_stats.num_objects_dirty--;
-    if (coi.is_omap())
-      ctx->delta_stats.num_objects_omap--;
-    if (coi.is_whiteout()) {
-      dout(20) << __func__ << " trimming whiteout on " << coid << dendl;
-      ctx->delta_stats.num_whiteouts--;
-    }
-    ctx->delta_stats.num_object_clones--;
-    if (coi.is_cache_pinned())
-      ctx->delta_stats.num_objects_pinned--;
-    obc->obs.exists = false;
-
-    snapset.clones.erase(p);
-    snapset.clone_overlap.erase(last);
-    snapset.clone_size.erase(last);
-    snapset.clone_snaps.erase(last);
-       
-    ctx->log.push_back(
-      pg_log_entry_t(
-       pg_log_entry_t::DELETE,
-       coid,
-       ctx->at_version,
-       ctx->obs->oi.version,
-       0,
-       osd_reqid_t(),
-       ctx->mtime,
-       0)
-      );
-    t->remove(coid);
-    t->update_snaps(
-      coid,
-      old_snaps,
-      new_snaps);
-
-    coi = object_info_t(coid);
-
-    ctx->at_version.version++;
-  } else {
-    // save adjusted snaps for this object
-    dout(10) << coid << " snaps " << old_snaps << " -> " << new_snaps << dendl;
-    if (legacy) {
-      coi.legacy_snaps = vector<snapid_t>(new_snaps.rbegin(), new_snaps.rend());
-    } else {
-      snapset.clone_snaps[coid.snap] = vector<snapid_t>(new_snaps.rbegin(),
-                                                       new_snaps.rend());
-      // we still do a 'modify' event on this object just to trigger a
-      // snapmapper.update ... :(
-    }
-
-    coi.prior_version = coi.version;
-    coi.version = ctx->at_version;
-    bl.clear();
-    ::encode(coi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-    t->setattr(coid, OI_ATTR, bl);
-
-    ctx->log.push_back(
-      pg_log_entry_t(
-       pg_log_entry_t::MODIFY,
-       coid,
-       coi.version,
-       coi.prior_version,
-       0,
-       osd_reqid_t(),
-       ctx->mtime,
-       0)
-      );
-    ctx->at_version.version++;
-
-    t->update_snaps(
-      coid,
-      old_snaps,
-      new_snaps);
-  }
-
-  // save head snapset
-  dout(10) << coid << " new snapset " << snapset << " on "
-          << snapset_obc->obs.oi << dendl;
-  if (snapset.clones.empty() &&
-      (!snapset.head_exists ||
-       (snapset_obc->obs.oi.is_whiteout() &&
-       !(snapset_obc->obs.oi.is_dirty() && pool.info.is_tier()) &&
-       !snapset_obc->obs.oi.is_cache_pinned()))) {
-    // NOTE: this arguably constitutes minor interference with the
-    // tiering agent if this is a cache tier since a snap trim event
-    // is effectively evicting a whiteout we might otherwise want to
-    // keep around.
-    dout(10) << coid << " removing " << snapoid << dendl;
-    ctx->log.push_back(
-      pg_log_entry_t(
-       pg_log_entry_t::DELETE,
-       snapoid,
-       ctx->at_version,
-       ctx->snapset_obc->obs.oi.version,
-       0,
-       osd_reqid_t(),
-       ctx->mtime,
-       0)
-      );
-    if (snapoid.is_head()) {
-      derr << "removing snap head" << dendl;
-      object_info_t& oi = ctx->snapset_obc->obs.oi;
-      ctx->delta_stats.num_objects--;
-      if (oi.is_dirty()) {
-       ctx->delta_stats.num_objects_dirty--;
-      }
-      if (oi.is_omap())
-       ctx->delta_stats.num_objects_omap--;
-      if (oi.is_whiteout()) {
-       dout(20) << __func__ << " trimming whiteout on " << oi.soid << dendl;
-       ctx->delta_stats.num_whiteouts--;
-      }
-      if (oi.is_cache_pinned()) {
-       ctx->delta_stats.num_objects_pinned--;
-      }
-    }
-    ctx->snapset_obc->obs.exists = false;
-    ctx->snapset_obc->obs.oi = object_info_t(snapoid);
-    t->remove(snapoid);
-  } else {
-    dout(10) << coid << " filtering snapset on " << snapoid << dendl;
-    snapset.filter(pool.info);
-    dout(10) << coid << " writing updated snapset on " << snapoid
-            << ", snapset is " << snapset << dendl;
-    ctx->log.push_back(
-      pg_log_entry_t(
-       pg_log_entry_t::MODIFY,
-       snapoid,
-       ctx->at_version,
-       ctx->snapset_obc->obs.oi.version,
-       0,
-       osd_reqid_t(),
-       ctx->mtime,
-       0)
-      );
-
-    ctx->snapset_obc->obs.oi.prior_version =
-      ctx->snapset_obc->obs.oi.version;
-    ctx->snapset_obc->obs.oi.version = ctx->at_version;
-
-    map <string, bufferlist> attrs;
-    bl.clear();
-    ::encode(snapset, bl);
-    attrs[SS_ATTR].claim(bl);
-
-    bl.clear();
-    ::encode(ctx->snapset_obc->obs.oi, bl,
-            get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-    attrs[OI_ATTR].claim(bl);
-    t->setattrs(snapoid, attrs);
-  }
-
-  *ctxp = std::move(ctx);
-  return 0;
-}
-
-void PrimaryLogPG::kick_snap_trim()
-{
-  assert(is_active());
-  assert(is_primary());
-  if (is_clean() && !snap_trimq.empty()) {
-    dout(10) << __func__ << ": clean and snaps to trim, kicking" << dendl;
-    snap_trimmer_machine.process_event(KickTrim());
-  }
-}
-
-void PrimaryLogPG::snap_trimmer_scrub_complete()
-{
-  if (is_primary() && is_active() && is_clean()) {
-    assert(!snap_trimq.empty());
-    snap_trimmer_machine.process_event(ScrubComplete());
-  }
-}
-
-void PrimaryLogPG::snap_trimmer(epoch_t queued)
-{
-  if (deleting || pg_has_reset_since(queued)) {
-    return;
-  }
-
-  assert(is_primary());
-
-  dout(10) << "snap_trimmer posting" << dendl;
-  snap_trimmer_machine.process_event(DoSnapWork());
-  dout(10) << "snap_trimmer complete" << dendl;
-  return;
-}
-
-int PrimaryLogPG::do_xattr_cmp_u64(int op, __u64 v1, bufferlist& xattr)
-{
-  __u64 v2;
-
-  string v2s(xattr.c_str(), xattr.length());
-  if (v2s.length())
-    v2 = strtoull(v2s.c_str(), NULL, 10);
-  else
-    v2 = 0;
-
-  dout(20) << "do_xattr_cmp_u64 '" << v1 << "' vs '" << v2 << "' op " << op << dendl;
-
-  switch (op) {
-  case CEPH_OSD_CMPXATTR_OP_EQ:
-    return (v1 == v2);
-  case CEPH_OSD_CMPXATTR_OP_NE:
-    return (v1 != v2);
-  case CEPH_OSD_CMPXATTR_OP_GT:
-    return (v1 > v2);
-  case CEPH_OSD_CMPXATTR_OP_GTE:
-    return (v1 >= v2);
-  case CEPH_OSD_CMPXATTR_OP_LT:
-    return (v1 < v2);
-  case CEPH_OSD_CMPXATTR_OP_LTE:
-    return (v1 <= v2);
-  default:
-    return -EINVAL;
-  }
-}
-
-int PrimaryLogPG::do_xattr_cmp_str(int op, string& v1s, bufferlist& xattr)
-{
-  string v2s(xattr.c_str(), xattr.length());
-
-  dout(20) << "do_xattr_cmp_str '" << v1s << "' vs '" << v2s << "' op " << op << dendl;
-
-  switch (op) {
-  case CEPH_OSD_CMPXATTR_OP_EQ:
-    return (v1s.compare(v2s) == 0);
-  case CEPH_OSD_CMPXATTR_OP_NE:
-    return (v1s.compare(v2s) != 0);
-  case CEPH_OSD_CMPXATTR_OP_GT:
-    return (v1s.compare(v2s) > 0);
-  case CEPH_OSD_CMPXATTR_OP_GTE:
-    return (v1s.compare(v2s) >= 0);
-  case CEPH_OSD_CMPXATTR_OP_LT:
-    return (v1s.compare(v2s) < 0);
-  case CEPH_OSD_CMPXATTR_OP_LTE:
-    return (v1s.compare(v2s) <= 0);
-  default:
-    return -EINVAL;
-  }
-}
-
-int PrimaryLogPG::do_writesame(OpContext *ctx, OSDOp& osd_op)
-{
-  ceph_osd_op& op = osd_op.op;
-  vector<OSDOp> write_ops(1);
-  OSDOp& write_op = write_ops[0];
-  uint64_t write_length = op.writesame.length;
-  int result = 0;
-
-  if (!write_length)
-    return 0;
-
-  if (!op.writesame.data_length || write_length % op.writesame.data_length)
-    return -EINVAL;
-
-  if (op.writesame.data_length != osd_op.indata.length()) {
-    derr << "invalid length ws data length " << op.writesame.data_length << " actual len " << osd_op.indata.length() << dendl;
-    return -EINVAL;
-  }
-
-  while (write_length) {
-    write_op.indata.append(osd_op.indata);
-    write_length -= op.writesame.data_length;
-  }
-
-  write_op.op.op = CEPH_OSD_OP_WRITE;
-  write_op.op.extent.offset = op.writesame.offset;
-  write_op.op.extent.length = op.writesame.length;
-  result = do_osd_ops(ctx, write_ops);
-  if (result < 0)
-    derr << "do_writesame do_osd_ops failed " << result << dendl;
-
-  return result;
-}
-
-// ========================================================================
-// low level osd ops
-
-int PrimaryLogPG::do_tmap2omap(OpContext *ctx, unsigned flags)
-{
-  dout(20) << " convert tmap to omap for " << ctx->new_obs.oi.soid << dendl;
-  bufferlist header, vals;
-  int r = _get_tmap(ctx, &header, &vals);
-  if (r < 0) {
-    if (r == -ENODATA && (flags & CEPH_OSD_TMAP2OMAP_NULLOK))
-      r = 0;
-    return r;
-  }
-
-  vector<OSDOp> ops(3);
-
-  ops[0].op.op = CEPH_OSD_OP_TRUNCATE;
-  ops[0].op.extent.offset = 0;
-  ops[0].op.extent.length = 0;
-
-  ops[1].op.op = CEPH_OSD_OP_OMAPSETHEADER;
-  ops[1].indata.claim(header);
-
-  ops[2].op.op = CEPH_OSD_OP_OMAPSETVALS;
-  ops[2].indata.claim(vals);
-
-  return do_osd_ops(ctx, ops);
-}
-
-int PrimaryLogPG::do_tmapup_slow(OpContext *ctx, bufferlist::iterator& bp, OSDOp& osd_op,
-                                   bufferlist& bl)
-{
-  // decode
-  bufferlist header;
-  map<string, bufferlist> m;
-  if (bl.length()) {
-    bufferlist::iterator p = bl.begin();
-    ::decode(header, p);
-    ::decode(m, p);
-    assert(p.end());
-  }
-
-  // do the update(s)
-  while (!bp.end()) {
-    __u8 op;
-    string key;
-    ::decode(op, bp);
-
-    switch (op) {
-    case CEPH_OSD_TMAP_SET: // insert key
-      {
-       ::decode(key, bp);
-       bufferlist data;
-       ::decode(data, bp);
-       m[key] = data;
-      }
-      break;
-    case CEPH_OSD_TMAP_RM: // remove key
-      ::decode(key, bp);
-      if (!m.count(key)) {
-       return -ENOENT;
-      }
-      m.erase(key);
-      break;
-    case CEPH_OSD_TMAP_RMSLOPPY: // remove key
-      ::decode(key, bp);
-      m.erase(key);
-      break;
-    case CEPH_OSD_TMAP_HDR: // update header
-      {
-       ::decode(header, bp);
-      }
-      break;
-    default:
-      return -EINVAL;
-    }
-  }
-
-  // reencode
-  bufferlist obl;
-  ::encode(header, obl);
-  ::encode(m, obl);
-
-  // write it out
-  vector<OSDOp> nops(1);
-  OSDOp& newop = nops[0];
-  newop.op.op = CEPH_OSD_OP_WRITEFULL;
-  newop.op.extent.offset = 0;
-  newop.op.extent.length = obl.length();
-  newop.indata = obl;
-  do_osd_ops(ctx, nops);
-  osd_op.outdata.claim(newop.outdata);
-  return 0;
-}
-
-int PrimaryLogPG::do_tmapup(OpContext *ctx, bufferlist::iterator& bp, OSDOp& osd_op)
-{
-  bufferlist::iterator orig_bp = bp;
-  int result = 0;
-  if (bp.end()) {
-    dout(10) << "tmapup is a no-op" << dendl;
-  } else {
-    // read the whole object
-    vector<OSDOp> nops(1);
-    OSDOp& newop = nops[0];
-    newop.op.op = CEPH_OSD_OP_READ;
-    newop.op.extent.offset = 0;
-    newop.op.extent.length = 0;
-    result = do_osd_ops(ctx, nops);
-
-    dout(10) << "tmapup read " << newop.outdata.length() << dendl;
-
-    dout(30) << " starting is \n";
-    newop.outdata.hexdump(*_dout);
-    *_dout << dendl;
-
-    bufferlist::iterator ip = newop.outdata.begin();
-    bufferlist obl;
-
-    dout(30) << "the update command is: \n";
-    osd_op.indata.hexdump(*_dout);
-    *_dout << dendl;
-
-    // header
-    bufferlist header;
-    __u32 nkeys = 0;
-    if (newop.outdata.length()) {
-      ::decode(header, ip);
-      ::decode(nkeys, ip);
-    }
-    dout(10) << "tmapup header " << header.length() << dendl;
-
-    if (!bp.end() && *bp == CEPH_OSD_TMAP_HDR) {
-      ++bp;
-      ::decode(header, bp);
-      dout(10) << "tmapup new header " << header.length() << dendl;
-    }
-
-    ::encode(header, obl);
-
-    dout(20) << "tmapup initial nkeys " << nkeys << dendl;
-
-    // update keys
-    bufferlist newkeydata;
-    string nextkey, last_in_key;
-    bufferlist nextval;
-    bool have_next = false;
-    if (!ip.end()) {
-      have_next = true;
-      ::decode(nextkey, ip);
-      ::decode(nextval, ip);
-    }
-    while (!bp.end() && !result) {
-      __u8 op;
-      string key;
-      try {
-       ::decode(op, bp);
-       ::decode(key, bp);
-      }
-      catch (buffer::error& e) {
-       return -EINVAL;
-      }
-      if (key < last_in_key) {
-       dout(5) << "tmapup warning: key '" << key << "' < previous key '" << last_in_key
-               << "', falling back to an inefficient (unsorted) update" << dendl;
-       bp = orig_bp;
-       return do_tmapup_slow(ctx, bp, osd_op, newop.outdata);
-      }
-      last_in_key = key;
-
-      dout(10) << "tmapup op " << (int)op << " key " << key << dendl;
-         
-      // skip existing intervening keys
-      bool key_exists = false;
-      while (have_next && !key_exists) {
-       dout(20) << "  (have_next=" << have_next << " nextkey=" << nextkey << ")" << dendl;
-       if (nextkey > key)
-         break;
-       if (nextkey < key) {
-         // copy untouched.
-         ::encode(nextkey, newkeydata);
-         ::encode(nextval, newkeydata);
-         dout(20) << "  keep " << nextkey << " " << nextval.length() << dendl;
-       } else {
-         // don't copy; discard old value.  and stop.
-         dout(20) << "  drop " << nextkey << " " << nextval.length() << dendl;
-         key_exists = true;
-         nkeys--;
-       }
-       if (!ip.end()) {
-         ::decode(nextkey, ip);
-         ::decode(nextval, ip);
-       } else {
-         have_next = false;
-       }
-      }
-
-      if (op == CEPH_OSD_TMAP_SET) {
-       bufferlist val;
-       try {
-         ::decode(val, bp);
-       }
-       catch (buffer::error& e) {
-         return -EINVAL;
-       }
-       ::encode(key, newkeydata);
-       ::encode(val, newkeydata);
-       dout(20) << "   set " << key << " " << val.length() << dendl;
-       nkeys++;
-      } else if (op == CEPH_OSD_TMAP_CREATE) {
-       if (key_exists) {
-         return -EEXIST;
-       }
-       bufferlist val;
-       try {
-         ::decode(val, bp);
-       }
-       catch (buffer::error& e) {
-         return -EINVAL;
-       }
-       ::encode(key, newkeydata);
-       ::encode(val, newkeydata);
-       dout(20) << "   create " << key << " " << val.length() << dendl;
-       nkeys++;
-      } else if (op == CEPH_OSD_TMAP_RM) {
-       // do nothing.
-       if (!key_exists) {
-         return -ENOENT;
-       }
-      } else if (op == CEPH_OSD_TMAP_RMSLOPPY) {
-       // do nothing
-      } else {
-       dout(10) << "  invalid tmap op " << (int)op << dendl;
-       return -EINVAL;
-      }
-    }
-
-    // copy remaining
-    if (have_next) {
-      ::encode(nextkey, newkeydata);
-      ::encode(nextval, newkeydata);
-      dout(20) << "  keep " << nextkey << " " << nextval.length() << dendl;
-    }
-    if (!ip.end()) {
-      bufferlist rest;
-      rest.substr_of(newop.outdata, ip.get_off(), newop.outdata.length() - ip.get_off());
-      dout(20) << "  keep trailing " << rest.length()
-              << " at " << newkeydata.length() << dendl;
-      newkeydata.claim_append(rest);
-    }
-
-    // encode final key count + key data
-    dout(20) << "tmapup final nkeys " << nkeys << dendl;
-    ::encode(nkeys, obl);
-    obl.claim_append(newkeydata);
-
-    if (0) {
-      dout(30) << " final is \n";
-      obl.hexdump(*_dout);
-      *_dout << dendl;
-
-      // sanity check
-      bufferlist::iterator tp = obl.begin();
-      bufferlist h;
-      ::decode(h, tp);
-      map<string,bufferlist> d;
-      ::decode(d, tp);
-      assert(tp.end());
-      dout(0) << " **** debug sanity check, looks ok ****" << dendl;
-    }
-
-    // write it out
-    if (!result) {
-      dout(20) << "tmapput write " << obl.length() << dendl;
-      newop.op.op = CEPH_OSD_OP_WRITEFULL;
-      newop.op.extent.offset = 0;
-      newop.op.extent.length = obl.length();
-      newop.indata = obl;
-      do_osd_ops(ctx, nops);
-      osd_op.outdata.claim(newop.outdata);
-    }
-  }
-  return result;
-}
-
-static int check_offset_and_length(uint64_t offset, uint64_t length, uint64_t max)
-{
-  if (offset >= max ||
-      length > max ||
-      offset + length > max)
-    return -EFBIG;
-
-  return 0;
-}
-
-struct FillInVerifyExtent : public Context {
-  ceph_le64 *r;
-  int32_t *rval;
-  bufferlist *outdatap;
-  boost::optional<uint32_t> maybe_crc;
-  uint64_t size;
-  OSDService *osd;
-  hobject_t soid;
-  __le32 flags;
-  FillInVerifyExtent(ceph_le64 *r, int32_t *rv, bufferlist *blp,
-                    boost::optional<uint32_t> mc, uint64_t size,
-                    OSDService *osd, hobject_t soid, __le32 flags) :
-    r(r), rval(rv), outdatap(blp), maybe_crc(mc),
-    size(size), osd(osd), soid(soid), flags(flags) {}
-  void finish(int len) override {
-    *r = len;
-    if (len < 0) {
-      *rval = len;
-      return;
-    }
-    *rval = 0;
-
-    // whole object?  can we verify the checksum?
-    if (maybe_crc && *r == size) {
-      uint32_t crc = outdatap->crc32c(-1);
-      if (maybe_crc != crc) {
-        osd->clog->error() << std::hex << " full-object read crc 0x" << crc
-                          << " != expected 0x" << *maybe_crc
-                          << std::dec << " on " << soid;
-        if (!(flags & CEPH_OSD_OP_FLAG_FAILOK)) {
-         *rval = -EIO;
-         *r = 0;
-       }
-      }
-    }
-  }
-};
-
-struct ToSparseReadResult : public Context {
-  int* result;
-  bufferlist* data_bl;
-  uint64_t data_offset;
-  ceph_le64* len;
-  ToSparseReadResult(int* result, bufferlist* bl, uint64_t offset,
-                    ceph_le64* len)
-    : result(result), data_bl(bl), data_offset(offset),len(len) {}
-  void finish(int r) override {
-    if (r < 0) {
-      *result = r;
-      return;
-    }
-    *result = 0;
-    *len = r;
-    bufferlist outdata;
-    map<uint64_t, uint64_t> extents = {{data_offset, r}};
-    ::encode(extents, outdata);
-    ::encode_destructively(*data_bl, outdata);
-    data_bl->swap(outdata);
-  }
-};
-
-template<typename V>
-static string list_keys(const map<string, V>& m) {
-  string s;
-  for (typename map<string, V>::const_iterator itr = m.begin(); itr != m.end(); ++itr) {
-    if (!s.empty()) {
-      s.push_back(',');
-    }
-    s.append(itr->first);
-  }
-  return s;
-}
-
-template<typename T>
-static string list_entries(const T& m) {
-  string s;
-  for (typename T::const_iterator itr = m.begin(); itr != m.end(); ++itr) {
-    if (!s.empty()) {
-      s.push_back(',');
-    }
-    s.append(*itr);
-  }
-  return s;
-}
-
-void PrimaryLogPG::maybe_create_new_object(
-  OpContext *ctx,
-  bool ignore_transaction)
-{
-  ObjectState& obs = ctx->new_obs;
-  if (!obs.exists) {
-    ctx->delta_stats.num_objects++;
-    obs.exists = true;
-    assert(!obs.oi.is_whiteout());
-    obs.oi.new_object();
-    if (!ignore_transaction)
-      ctx->op_t->create(obs.oi.soid);
-  } else if (obs.oi.is_whiteout()) {
-    dout(10) << __func__ << " clearing whiteout on " << obs.oi.soid << dendl;
-    ctx->new_obs.oi.clear_flag(object_info_t::FLAG_WHITEOUT);
-    --ctx->delta_stats.num_whiteouts;
-  }
-}
-
-struct ReadFinisher : public PrimaryLogPG::OpFinisher {
-  OSDOp& osd_op;
-
-  ReadFinisher(OSDOp& osd_op) : osd_op(osd_op) {
-  }
-
-  int execute() override {
-    return osd_op.rval;
-  }
-};
-
-struct C_ChecksumRead : public Context {
-  PrimaryLogPG *primary_log_pg;
-  OSDOp &osd_op;
-  Checksummer::CSumType csum_type;
-  bufferlist init_value_bl;
-  ceph_le64 read_length;
-  bufferlist read_bl;
-  Context *fill_extent_ctx;
-
-  C_ChecksumRead(PrimaryLogPG *primary_log_pg, OSDOp &osd_op,
-                Checksummer::CSumType csum_type, bufferlist &&init_value_bl,
-                boost::optional<uint32_t> maybe_crc, uint64_t size,
-                OSDService *osd, hobject_t soid, __le32 flags)
-    : primary_log_pg(primary_log_pg), osd_op(osd_op),
-      csum_type(csum_type), init_value_bl(std::move(init_value_bl)),
-      fill_extent_ctx(new FillInVerifyExtent(&read_length, &osd_op.rval,
-                                            &read_bl, maybe_crc, size,
-                                            osd, soid, flags)) {
-  }
-  ~C_ChecksumRead() override {
-    delete fill_extent_ctx;
-  }
-
-  void finish(int r) override {
-    fill_extent_ctx->complete(r);
-    fill_extent_ctx = nullptr;
-
-    if (osd_op.rval >= 0) {
-      bufferlist::iterator init_value_bl_it = init_value_bl.begin();
-      osd_op.rval = primary_log_pg->finish_checksum(osd_op, csum_type,
-                                                   &init_value_bl_it, read_bl);
-    }
-  }
-};
-
-int PrimaryLogPG::do_checksum(OpContext *ctx, OSDOp& osd_op,
-                             bufferlist::iterator *bl_it)
-{
-  dout(20) << __func__ << dendl;
-
-  auto& op = osd_op.op;
-  if (op.checksum.chunk_size > 0) {
-    if (op.checksum.length == 0) {
-      dout(10) << __func__ << ": length required when chunk size provided"
-              << dendl;
-      return -EINVAL;
-    }
-    if (op.checksum.length % op.checksum.chunk_size != 0) {
-      dout(10) << __func__ << ": length not aligned to chunk size" << dendl;
-      return -EINVAL;
-    }
-  }
-
-  auto& oi = ctx->new_obs.oi;
-  if (op.checksum.offset == 0 && op.checksum.length == 0) {
-    // zeroed offset+length implies checksum whole object
-    op.checksum.length = oi.size;
-  } else if (op.checksum.offset + op.checksum.length > oi.size) {
-    return -EOVERFLOW;
-  }
-
-  Checksummer::CSumType csum_type;
-  switch (op.checksum.type) {
-  case CEPH_OSD_CHECKSUM_OP_TYPE_XXHASH32:
-    csum_type = Checksummer::CSUM_XXHASH32;
-    break;
-  case CEPH_OSD_CHECKSUM_OP_TYPE_XXHASH64:
-    csum_type = Checksummer::CSUM_XXHASH64;
-    break;
-  case CEPH_OSD_CHECKSUM_OP_TYPE_CRC32C:
-    csum_type = Checksummer::CSUM_CRC32C;
-    break;
-  default:
-    dout(10) << __func__ << ": unknown crc type ("
-            << static_cast<uint32_t>(op.checksum.type) << ")" << dendl;
-    return -EINVAL;
-  }
-
-  size_t csum_init_value_size = Checksummer::get_csum_init_value_size(csum_type);
-  if (bl_it->get_remaining() < csum_init_value_size) {
-    dout(10) << __func__ << ": init value not provided" << dendl;
-    return -EINVAL;
-  }
-
-  bufferlist init_value_bl;
-  init_value_bl.substr_of(bl_it->get_bl(), bl_it->get_off(),
-                         csum_init_value_size);
-  bl_it->advance(csum_init_value_size);
-
-  if (pool.info.require_rollback() && op.checksum.length > 0) {
-    // If there is a data digest and it is possible we are reading
-    // entire object, pass the digest.
-    boost::optional<uint32_t> maybe_crc;
-    if (oi.is_data_digest() && op.checksum.offset == 0 &&
-        op.checksum.length >= oi.size) {
-      maybe_crc = oi.data_digest;
-    }
-
-    // async read
-    auto& soid = oi.soid;
-    auto checksum_ctx = new C_ChecksumRead(this, osd_op, csum_type,
-                                          std::move(init_value_bl), maybe_crc,
-                                          oi.size, osd, soid, op.flags);
-
-    ctx->pending_async_reads.push_back({
-      {op.checksum.offset, op.checksum.length, op.flags},
-      {&checksum_ctx->read_bl, checksum_ctx}});
-
-    dout(10) << __func__ << ": async_read noted for " << soid << dendl;
-    ctx->op_finishers[ctx->current_osd_subop_num].reset(
-      new ReadFinisher(osd_op));
-    return -EINPROGRESS;
-  }
-
-  // sync read
-  std::vector<OSDOp> read_ops(1);
-  auto& read_op = read_ops[0];
-  if (op.checksum.length > 0) {
-    read_op.op.op = CEPH_OSD_OP_READ;
-    read_op.op.flags = op.flags;
-    read_op.op.extent.offset = op.checksum.offset;
-    read_op.op.extent.length = op.checksum.length;
-    read_op.op.extent.truncate_size = 0;
-    read_op.op.extent.truncate_seq = 0;
-
-    int r = do_osd_ops(ctx, read_ops);
-    if (r < 0) {
-      derr << __func__ << ": do_osd_ops failed: " << cpp_strerror(r) << dendl;
-      return r;
-    }
-  }
-
-  bufferlist::iterator init_value_bl_it = init_value_bl.begin();
-  return finish_checksum(osd_op, csum_type, &init_value_bl_it,
-                        read_op.outdata);
-}
-
-int PrimaryLogPG::finish_checksum(OSDOp& osd_op,
-                                 Checksummer::CSumType csum_type,
-                                 bufferlist::iterator *init_value_bl_it,
-                                 const bufferlist &read_bl) {
-  dout(20) << __func__ << dendl;
-
-  auto& op = osd_op.op;
-
-  if (op.checksum.length > 0 && read_bl.length() != op.checksum.length) {
-    derr << __func__ << ": bytes read " << read_bl.length() << " != "
-        << op.checksum.length << dendl;
-    return -EINVAL;
-  }
-
-  size_t csum_chunk_size = (op.checksum.chunk_size != 0 ?
-                             op.checksum.chunk_size : read_bl.length());
-  uint32_t csum_count = (csum_chunk_size > 0 ?
-                          read_bl.length() / csum_chunk_size : 0);
-
-  bufferlist csum;
-  bufferptr csum_data;
-  if (csum_count > 0) {
-    size_t csum_value_size = Checksummer::get_csum_value_size(csum_type);
-    csum_data = buffer::create(csum_value_size * csum_count);
-    csum_data.zero();
-    csum.append(csum_data);
-
-    switch (csum_type) {
-    case Checksummer::CSUM_XXHASH32:
-      {
-        Checksummer::xxhash32::init_value_t init_value;
-        ::decode(init_value, *init_value_bl_it);
-        Checksummer::calculate<Checksummer::xxhash32>(
-         init_value, csum_chunk_size, 0, read_bl.length(), read_bl,
-         &csum_data);
-      }
-      break;
-    case Checksummer::CSUM_XXHASH64:
-      {
-        Checksummer::xxhash64::init_value_t init_value;
-        ::decode(init_value, *init_value_bl_it);
-        Checksummer::calculate<Checksummer::xxhash64>(
-         init_value, csum_chunk_size, 0, read_bl.length(), read_bl,
-         &csum_data);
-      }
-      break;
-    case Checksummer::CSUM_CRC32C:
-      {
-        Checksummer::crc32c::init_value_t init_value;
-        ::decode(init_value, *init_value_bl_it);
-        Checksummer::calculate<Checksummer::crc32c>(
-         init_value, csum_chunk_size, 0, read_bl.length(), read_bl,
-         &csum_data);
-      }
-      break;
-    default:
-      break;
-    }
-  }
-
-  ::encode(csum_count, osd_op.outdata);
-  osd_op.outdata.claim_append(csum);
-  return 0;
-}
-
-struct C_ExtentCmpRead : public Context {
-  PrimaryLogPG *primary_log_pg;
-  OSDOp &osd_op;
-  ceph_le64 read_length;
-  bufferlist read_bl;
-  Context *fill_extent_ctx;
-
-  C_ExtentCmpRead(PrimaryLogPG *primary_log_pg, OSDOp &osd_op,
-                 boost::optional<uint32_t> maybe_crc, uint64_t size,
-                 OSDService *osd, hobject_t soid, __le32 flags)
-    : primary_log_pg(primary_log_pg), osd_op(osd_op),
-      fill_extent_ctx(new FillInVerifyExtent(&read_length, &osd_op.rval,
-                                            &read_bl, maybe_crc, size,
-                                            osd, soid, flags)) {
-  }
-  ~C_ExtentCmpRead() override {
-    delete fill_extent_ctx;
-  }
-
-  void finish(int r) override {
-    if (r == -ENOENT) {
-      osd_op.rval = 0;
-      read_bl.clear();
-      delete fill_extent_ctx;
-    } else {
-      fill_extent_ctx->complete(r);
-    }
-    fill_extent_ctx = nullptr;
-
-    if (osd_op.rval >= 0) {
-      osd_op.rval = primary_log_pg->finish_extent_cmp(osd_op, read_bl);
-    }
-  }
-};
-
-int PrimaryLogPG::do_extent_cmp(OpContext *ctx, OSDOp& osd_op)
-{
-  dout(20) << __func__ << dendl;
-  ceph_osd_op& op = osd_op.op;
-
-  auto& oi = ctx->new_obs.oi;
-  uint64_t size = oi.size;
-  if ((oi.truncate_seq < op.extent.truncate_seq) &&
-      (op.extent.offset + op.extent.length > op.extent.truncate_size)) {
-    size = op.extent.truncate_size;
-  }
-
-  if (op.extent.offset >= size) {
-    op.extent.length = 0;
-  } else if (op.extent.offset + op.extent.length > size) {
-    op.extent.length = size - op.extent.offset;
-  }
-
-  if (op.extent.length == 0) {
-    dout(20) << __func__ << " zero length extent" << dendl;
-    return finish_extent_cmp(osd_op, bufferlist{});
-  } else if (!ctx->obs->exists || ctx->obs->oi.is_whiteout()) {
-    dout(20) << __func__ << " object DNE" << dendl;
-    return finish_extent_cmp(osd_op, {});
-  } else if (pool.info.require_rollback()) {
-    // If there is a data digest and it is possible we are reading
-    // entire object, pass the digest.
-    boost::optional<uint32_t> maybe_crc;
-    if (oi.is_data_digest() && op.checksum.offset == 0 &&
-        op.checksum.length >= oi.size) {
-      maybe_crc = oi.data_digest;
-    }
-
-    // async read
-    auto& soid = oi.soid;
-    auto extent_cmp_ctx = new C_ExtentCmpRead(this, osd_op, maybe_crc, oi.size,
-                                             osd, soid, op.flags);
-    ctx->pending_async_reads.push_back({
-      {op.extent.offset, op.extent.length, op.flags},
-      {&extent_cmp_ctx->read_bl, extent_cmp_ctx}});
-
-    dout(10) << __func__ << ": async_read noted for " << soid << dendl;
-
-    ctx->op_finishers[ctx->current_osd_subop_num].reset(
-      new ReadFinisher(osd_op));
-    return -EINPROGRESS;
-  }
-
-  // sync read
-  vector<OSDOp> read_ops(1);
-  OSDOp& read_op = read_ops[0];
-
-  read_op.op.op = CEPH_OSD_OP_SYNC_READ;
-  read_op.op.extent.offset = op.extent.offset;
-  read_op.op.extent.length = op.extent.length;
-  read_op.op.extent.truncate_seq = op.extent.truncate_seq;
-  read_op.op.extent.truncate_size = op.extent.truncate_size;
-
-  int result = do_osd_ops(ctx, read_ops);
-  if (result < 0) {
-    derr << __func__ << " failed " << result << dendl;
-    return result;
-  }
-  return finish_extent_cmp(osd_op, read_op.outdata);
-}
-
-int PrimaryLogPG::finish_extent_cmp(OSDOp& osd_op, const bufferlist &read_bl)
-{
-  for (uint64_t idx = 0; idx < osd_op.indata.length(); ++idx) {
-    char read_byte = (idx < read_bl.length() ? read_bl[idx] : 0);
-    if (osd_op.indata[idx] != read_byte) {
-        return (-MAX_ERRNO - idx);
-    }
-  }
-
-  return 0;
-}
-
-int PrimaryLogPG::do_read(OpContext *ctx, OSDOp& osd_op) {
-  dout(20) << __func__ << dendl;
-  auto& op = osd_op.op;
-  auto& oi = ctx->new_obs.oi;
-  auto& soid = oi.soid;
-  __u32 seq = oi.truncate_seq;
-  uint64_t size = oi.size;
-  bool trimmed_read = false;
-
-  // are we beyond truncate_size?
-  if ( (seq < op.extent.truncate_seq) &&
-       (op.extent.offset + op.extent.length > op.extent.truncate_size) )
-    size = op.extent.truncate_size;
-
-  if (op.extent.length == 0) //length is zero mean read the whole object
-    op.extent.length = size;
-
-  if (op.extent.offset >= size) {
-    op.extent.length = 0;
-    trimmed_read = true;
-  } else if (op.extent.offset + op.extent.length > size) {
-    op.extent.length = size - op.extent.offset;
-    trimmed_read = true;
-  }
-
-  // read into a buffer
-  int result = 0;
-  if (trimmed_read && op.extent.length == 0) {
-    // read size was trimmed to zero and it is expected to do nothing
-    // a read operation of 0 bytes does *not* do nothing, this is why
-    // the trimmed_read boolean is needed
-  } else if (pool.info.require_rollback()) {
-    boost::optional<uint32_t> maybe_crc;
-    // If there is a data digest and it is possible we are reading
-    // entire object, pass the digest.  FillInVerifyExtent will
-    // will check the oi.size again.
-    if (oi.is_data_digest() && op.extent.offset == 0 &&
-        op.extent.length >= oi.size)
-      maybe_crc = oi.data_digest;
-    ctx->pending_async_reads.push_back(
-      make_pair(
-        boost::make_tuple(op.extent.offset, op.extent.length, op.flags),
-        make_pair(&osd_op.outdata,
-                 new FillInVerifyExtent(&op.extent.length, &osd_op.rval,
-                                        &osd_op.outdata, maybe_crc, oi.size,
-                                        osd, soid, op.flags))));
-    dout(10) << " async_read noted for " << soid << dendl;
-
-    ctx->op_finishers[ctx->current_osd_subop_num].reset(
-      new ReadFinisher(osd_op));
-  } else {
-    int r = pgbackend->objects_read_sync(
-      soid, op.extent.offset, op.extent.length, op.flags, &osd_op.outdata);
-    if (r == -EIO) {
-      r = rep_repair_primary_object(soid, ctx->op);
-    }
-    if (r >= 0)
-      op.extent.length = r;
-    else {
-      result = r;
-      op.extent.length = 0;
-    }
-    dout(10) << " read got " << r << " / " << op.extent.length
-            << " bytes from obj " << soid << dendl;
-
-    // whole object?  can we verify the checksum?
-    if (op.extent.length == oi.size && oi.is_data_digest()) {
-      uint32_t crc = osd_op.outdata.crc32c(-1);
-      if (oi.data_digest != crc) {
-        osd->clog->error() << info.pgid << std::hex
-                          << " full-object read crc 0x" << crc
-                          << " != expected 0x" << oi.data_digest
-                          << std::dec << " on " << soid;
-        // FIXME fall back to replica or something?
-        result = -EIO;
-      }
-    }
-  }
-
-  // XXX the op.extent.length is the requested length for async read
-  // On error this length is changed to 0 after the error comes back.
-  ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(op.extent.length, 10);
-  ctx->delta_stats.num_rd++;
-  return result;
-}
-
-int PrimaryLogPG::do_sparse_read(OpContext *ctx, OSDOp& osd_op) {
-  dout(20) << __func__ << dendl;
-  auto& op = osd_op.op;
-  auto& oi = ctx->new_obs.oi;
-  auto& soid = oi.soid;
-
-  if (op.extent.truncate_seq) {
-    dout(0) << "sparse_read does not support truncation sequence " << dendl;
-    return -EINVAL;
-  }
-
-  ++ctx->num_read;
-  if (pool.info.ec_pool()) {
-    // translate sparse read to a normal one if not supported
-    uint64_t offset = op.extent.offset;
-    uint64_t length = op.extent.length;
-    if (offset > oi.size) {
-      length = 0;
-    } else if (offset + length > oi.size) {
-      length = oi.size - offset;
-    }
-
-    if (length > 0) {
-      ctx->pending_async_reads.push_back(
-        make_pair(
-          boost::make_tuple(offset, length, op.flags),
-          make_pair(
-           &osd_op.outdata,
-           new ToSparseReadResult(&osd_op.rval, &osd_op.outdata, offset,
-                                  &op.extent.length))));
-      dout(10) << " async_read (was sparse_read) noted for " << soid << dendl;
-
-      ctx->op_finishers[ctx->current_osd_subop_num].reset(
-        new ReadFinisher(osd_op));
-    } else {
-      dout(10) << " sparse read ended up empty for " << soid << dendl;
-      map<uint64_t, uint64_t> extents;
-      ::encode(extents, osd_op.outdata);
-    }
-  } else {
-    // read into a buffer
-    map<uint64_t, uint64_t> m;
-    uint32_t total_read = 0;
-    int r = osd->store->fiemap(ch, ghobject_t(soid, ghobject_t::NO_GEN,
-                                             info.pgid.shard),
-                              op.extent.offset, op.extent.length, m);
-    if (r < 0)  {
-      return r;
-    }
-
-    map<uint64_t, uint64_t>::iterator miter;
-    bufferlist data_bl;
-    uint64_t last = op.extent.offset;
-    for (miter = m.begin(); miter != m.end(); ++miter) {
-      // verify hole?
-      if (cct->_conf->osd_verify_sparse_read_holes &&
-          last < miter->first) {
-        bufferlist t;
-        uint64_t len = miter->first - last;
-        r = pgbackend->objects_read_sync(soid, last, len, op.flags, &t);
-        if (r < 0) {
-          osd->clog->error() << coll << " " << soid
-                            << " sparse-read failed to read: "
-                            << r;
-        } else if (!t.is_zero()) {
-          osd->clog->error() << coll << " " << soid
-                            << " sparse-read found data in hole "
-                            << last << "~" << len;
-        }
-      }
-
-      bufferlist tmpbl;
-      r = pgbackend->objects_read_sync(soid, miter->first, miter->second,
-                                      op.flags, &tmpbl);
-      if (r == -EIO) {
-        r = rep_repair_primary_object(soid, ctx->op);
-      }
-      if (r < 0) {
-       return r;
-      }
-
-      // this is usually happen when we get extent that exceeds the actual file
-      // size
-      if (r < (int)miter->second)
-        miter->second = r;
-      total_read += r;
-      dout(10) << "sparse-read " << miter->first << "@" << miter->second
-              << dendl;
-      data_bl.claim_append(tmpbl);
-      last = miter->first + r;
-    }
-
-    if (r < 0) {
-      return r;
-    }
-
-    // verify trailing hole?
-    if (cct->_conf->osd_verify_sparse_read_holes) {
-      uint64_t end = MIN(op.extent.offset + op.extent.length, oi.size);
-      if (last < end) {
-        bufferlist t;
-        uint64_t len = end - last;
-        r = pgbackend->objects_read_sync(soid, last, len, op.flags, &t);
-        if (r < 0) {
-          osd->clog->error() << coll << " " << soid
-                            << " sparse-read failed to read: " << r;
-        } else if (!t.is_zero()) {
-          osd->clog->error() << coll << " " << soid
-                            << " sparse-read found data in hole "
-                            << last << "~" << len;
-        }
-      }
-    }
-
-    // Why SPARSE_READ need checksum? In fact, librbd always use sparse-read.
-    // Maybe at first, there is no much whole objects. With continued use, more
-    // and more whole object exist. So from this point, for spare-read add
-    // checksum make sense.
-    if (total_read == oi.size && oi.is_data_digest()) {
-      uint32_t crc = data_bl.crc32c(-1);
-      if (oi.data_digest != crc) {
-        osd->clog->error() << info.pgid << std::hex
-          << " full-object read crc 0x" << crc
-          << " != expected 0x" << oi.data_digest
-          << std::dec << " on " << soid;
-        // FIXME fall back to replica or something?
-        return -EIO;
-      }
-    }
-
-    op.extent.length = total_read;
-
-    ::encode(m, osd_op.outdata); // re-encode since it might be modified
-    ::encode_destructively(data_bl, osd_op.outdata);
-
-    dout(10) << " sparse_read got " << total_read << " bytes from object "
-            << soid << dendl;
-  }
-
-  ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(op.extent.length, 10);
-  ctx->delta_stats.num_rd++;
-  return 0;
-}
-
-int PrimaryLogPG::do_osd_ops(OpContext *ctx, vector<OSDOp>& ops)
-{
-  int result = 0;
-  SnapSetContext *ssc = ctx->obc->ssc;
-  ObjectState& obs = ctx->new_obs;
-  object_info_t& oi = obs.oi;
-  const hobject_t& soid = oi.soid;
-
-  PGTransaction* t = ctx->op_t.get();
-
-  dout(10) << "do_osd_op " << soid << " " << ops << dendl;
-
-  ctx->current_osd_subop_num = 0;
-  for (vector<OSDOp>::iterator p = ops.begin(); p != ops.end(); ++p, ctx->current_osd_subop_num++) {
-    OSDOp& osd_op = *p;
-    ceph_osd_op& op = osd_op.op;
-
-    OpFinisher* op_finisher = nullptr;
-    {
-      auto op_finisher_it = ctx->op_finishers.find(ctx->current_osd_subop_num);
-      if (op_finisher_it != ctx->op_finishers.end()) {
-        op_finisher = op_finisher_it->second.get();
-      }
-    }
-
-    // TODO: check endianness (__le32 vs uint32_t, etc.)
-    // The fields in ceph_osd_op are little-endian (according to the definition in rados.h),
-    // but the code in this function seems to treat them as native-endian.  What should the
-    // tracepoints do?
-    tracepoint(osd, do_osd_op_pre, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op), op.flags);
-
-    dout(10) << "do_osd_op  " << osd_op << dendl;
-
-    bufferlist::iterator bp = osd_op.indata.begin();
-
-    // user-visible modifcation?
-    switch (op.op) {
-      // non user-visible modifications
-    case CEPH_OSD_OP_WATCH:
-    case CEPH_OSD_OP_CACHE_EVICT:
-    case CEPH_OSD_OP_CACHE_FLUSH:
-    case CEPH_OSD_OP_CACHE_TRY_FLUSH:
-    case CEPH_OSD_OP_UNDIRTY:
-    case CEPH_OSD_OP_COPY_FROM:  // we handle user_version update explicitly
-    case CEPH_OSD_OP_CACHE_PIN:
-    case CEPH_OSD_OP_CACHE_UNPIN:
-    case CEPH_OSD_OP_SET_REDIRECT:
-      break;
-    default:
-      if (op.op & CEPH_OSD_OP_MODE_WR)
-       ctx->user_modify = true;
-    }
-
-    // munge -1 truncate to 0 truncate
-    if (ceph_osd_op_uses_extent(op.op) &&
-        op.extent.truncate_seq == 1 &&
-        op.extent.truncate_size == (-1ULL)) {
-      op.extent.truncate_size = 0;
-      op.extent.truncate_seq = 0;
-    }
-
-    // munge ZERO -> TRUNCATE?  (don't munge to DELETE or we risk hosing attributes)
-    if (op.op == CEPH_OSD_OP_ZERO &&
-       obs.exists &&
-       op.extent.offset < cct->_conf->osd_max_object_size &&
-       op.extent.length >= 1 &&
-       op.extent.length <= cct->_conf->osd_max_object_size &&
-       op.extent.offset + op.extent.length >= oi.size) {
-      if (op.extent.offset >= oi.size) {
-        // no-op
-       goto fail;
-      }
-      dout(10) << " munging ZERO " << op.extent.offset << "~" << op.extent.length
-              << " -> TRUNCATE " << op.extent.offset << " (old size is " << oi.size << ")" << dendl;
-      op.op = CEPH_OSD_OP_TRUNCATE;
-    }
-
-    switch (op.op) {
-      
-      // --- READS ---
-
-    case CEPH_OSD_OP_CMPEXT:
-      ++ctx->num_read;
-      tracepoint(osd, do_osd_op_pre_extent_cmp, soid.oid.name.c_str(),
-                soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset,
-                op.extent.length, op.extent.truncate_size,
-                op.extent.truncate_seq);
-
-      if (op_finisher == nullptr) {
-       result = do_extent_cmp(ctx, osd_op);
-      } else {
-       result = op_finisher->execute();
-      }
-      break;
-
-    case CEPH_OSD_OP_SYNC_READ:
-      if (pool.info.require_rollback()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      // fall through
-    case CEPH_OSD_OP_READ:
-      ++ctx->num_read;
-      tracepoint(osd, do_osd_op_pre_read, soid.oid.name.c_str(),
-                soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset,
-                op.extent.length, op.extent.truncate_size,
-                op.extent.truncate_seq);
-      if (op_finisher == nullptr) {
-       if (!ctx->data_off) {
-         ctx->data_off = op.extent.offset;
-       }
-       result = do_read(ctx, osd_op);
-      } else {
-       result = op_finisher->execute();
-      }
-      break;
-
-    case CEPH_OSD_OP_CHECKSUM:
-      ++ctx->num_read;
-      {
-       tracepoint(osd, do_osd_op_pre_checksum, soid.oid.name.c_str(),
-                  soid.snap.val, oi.size, oi.truncate_seq, op.checksum.type,
-                  op.checksum.offset, op.checksum.length,
-                  op.checksum.chunk_size);
-
-       if (op_finisher == nullptr) {
-         result = do_checksum(ctx, osd_op, &bp);
-       } else {
-         result = op_finisher->execute();
-       }
-      }
-      break;
-
-    /* map extents */
-    case CEPH_OSD_OP_MAPEXT:
-      tracepoint(osd, do_osd_op_pre_mapext, soid.oid.name.c_str(), soid.snap.val, op.extent.offset, op.extent.length);
-      if (pool.info.require_rollback()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_read;
-      {
-       // read into a buffer
-       bufferlist bl;
-       int r = osd->store->fiemap(ch, ghobject_t(soid, ghobject_t::NO_GEN,
-                                                 info.pgid.shard),
-                                  op.extent.offset, op.extent.length, bl);
-       osd_op.outdata.claim(bl);
-       if (r < 0)
-         result = r;
-       else
-         ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(bl.length(), 10);
-       ctx->delta_stats.num_rd++;
-       dout(10) << " map_extents done on object " << soid << dendl;
-      }
-      break;
-
-    /* map extents */
-    case CEPH_OSD_OP_SPARSE_READ:
-      tracepoint(osd, do_osd_op_pre_sparse_read, soid.oid.name.c_str(),
-                soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset,
-                op.extent.length, op.extent.truncate_size,
-                op.extent.truncate_seq);
-      if (op_finisher == nullptr) {
-       result = do_sparse_read(ctx, osd_op);
-      } else {
-       result = op_finisher->execute();
-      }
-      break;
-
-    case CEPH_OSD_OP_CALL:
-      {
-       string cname, mname;
-       bufferlist indata;
-       try {
-         bp.copy(op.cls.class_len, cname);
-         bp.copy(op.cls.method_len, mname);
-         bp.copy(op.cls.indata_len, indata);
-       } catch (buffer::error& e) {
-         dout(10) << "call unable to decode class + method + indata" << dendl;
-         dout(30) << "in dump: ";
-         osd_op.indata.hexdump(*_dout);
-         *_dout << dendl;
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_call, soid.oid.name.c_str(), soid.snap.val, "???", "???");
-         break;
-       }
-       tracepoint(osd, do_osd_op_pre_call, soid.oid.name.c_str(), soid.snap.val, cname.c_str(), mname.c_str());
-
-       ClassHandler::ClassData *cls;
-       result = osd->class_handler->open_class(cname, &cls);
-       assert(result == 0);   // init_op_flags() already verified this works.
-
-       ClassHandler::ClassMethod *method = cls->get_method(mname.c_str());
-       if (!method) {
-         dout(10) << "call method " << cname << "." << mname << " does not exist" << dendl;
-         result = -EOPNOTSUPP;
-         break;
-       }
-
-       int flags = method->get_flags();
-       if (flags & CLS_METHOD_WR)
-         ctx->user_modify = true;
-
-       bufferlist outdata;
-       dout(10) << "call method " << cname << "." << mname << dendl;
-       int prev_rd = ctx->num_read;
-       int prev_wr = ctx->num_write;
-       result = method->exec((cls_method_context_t)&ctx, indata, outdata);
-
-       if (ctx->num_read > prev_rd && !(flags & CLS_METHOD_RD)) {
-         derr << "method " << cname << "." << mname << " tried to read object but is not marked RD" << dendl;
-         result = -EIO;
-         break;
-       }
-       if (ctx->num_write > prev_wr && !(flags & CLS_METHOD_WR)) {
-         derr << "method " << cname << "." << mname << " tried to update object but is not marked WR" << dendl;
-         result = -EIO;
-         break;
-       }
-
-       dout(10) << "method called response length=" << outdata.length() << dendl;
-       op.extent.length = outdata.length();
-       osd_op.outdata.claim_append(outdata);
-       dout(30) << "out dump: ";
-       osd_op.outdata.hexdump(*_dout);
-       *_dout << dendl;
-      }
-      break;
-
-    case CEPH_OSD_OP_STAT:
-      // note: stat does not require RD
-      {
-       tracepoint(osd, do_osd_op_pre_stat, soid.oid.name.c_str(), soid.snap.val);
-
-       if (obs.exists && !oi.is_whiteout()) {
-         ::encode(oi.size, osd_op.outdata);
-         ::encode(oi.mtime, osd_op.outdata);
-         dout(10) << "stat oi has " << oi.size << " " << oi.mtime << dendl;
-       } else {
-         result = -ENOENT;
-         dout(10) << "stat oi object does not exist" << dendl;
-       }
-
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-    case CEPH_OSD_OP_ISDIRTY:
-      ++ctx->num_read;
-      {
-       tracepoint(osd, do_osd_op_pre_isdirty, soid.oid.name.c_str(), soid.snap.val);
-       bool is_dirty = obs.oi.is_dirty();
-       ::encode(is_dirty, osd_op.outdata);
-       ctx->delta_stats.num_rd++;
-       result = 0;
-      }
-      break;
-
-    case CEPH_OSD_OP_UNDIRTY:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_undirty, soid.oid.name.c_str(), soid.snap.val);
-       if (oi.is_dirty()) {
-         ctx->undirty = true;  // see make_writeable()
-         ctx->modify = true;
-         ctx->delta_stats.num_wr++;
-       }
-       result = 0;
-      }
-      break;
-
-    case CEPH_OSD_OP_CACHE_TRY_FLUSH:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_try_flush, soid.oid.name.c_str(), soid.snap.val);
-       if (ctx->lock_type != ObjectContext::RWState::RWNONE) {
-         dout(10) << "cache-try-flush without SKIPRWLOCKS flag set" << dendl;
-         result = -EINVAL;
-         break;
-       }
-       if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE) {
-         result = -EINVAL;
-         break;
-       }
-       if (!obs.exists) {
-         result = 0;
-         break;
-       }
-       if (oi.is_cache_pinned()) {
-         dout(10) << "cache-try-flush on a pinned object, consider unpin this object first" << dendl;
-         result = -EPERM;
-         break;
-       }
-       if (oi.is_dirty()) {
-         result = start_flush(ctx->op, ctx->obc, false, NULL, boost::none);
-         if (result == -EINPROGRESS)
-           result = -EAGAIN;
-       } else {
-         result = 0;
-       }
-      }
-      break;
-
-    case CEPH_OSD_OP_CACHE_FLUSH:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_cache_flush, soid.oid.name.c_str(), soid.snap.val);
-       if (ctx->lock_type == ObjectContext::RWState::RWNONE) {
-         dout(10) << "cache-flush with SKIPRWLOCKS flag set" << dendl;
-         result = -EINVAL;
-         break;
-       }
-       if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE) {
-         result = -EINVAL;
-         break;
-       }
-       if (!obs.exists) {
-         result = 0;
-         break;
-       }
-       if (oi.is_cache_pinned()) {
-         dout(10) << "cache-flush on a pinned object, consider unpin this object first" << dendl;
-         result = -EPERM;
-         break;
-       }
-       hobject_t missing;
-       if (oi.is_dirty()) {
-         result = start_flush(ctx->op, ctx->obc, true, &missing, boost::none);
-         if (result == -EINPROGRESS)
-           result = -EAGAIN;
-       } else {
-         result = 0;
-       }
-       // Check special return value which has set missing_return
-        if (result == -ENOENT) {
-          dout(10) << __func__ << " CEPH_OSD_OP_CACHE_FLUSH got ENOENT" << dendl;
-         assert(!missing.is_min());
-         wait_for_unreadable_object(missing, ctx->op);
-         // Error code which is used elsewhere when wait_for_unreadable_object() is used
-         result = -EAGAIN;
-       }
-      }
-      break;
-
-    case CEPH_OSD_OP_CACHE_EVICT:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_cache_evict, soid.oid.name.c_str(), soid.snap.val);
-       if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE) {
-         result = -EINVAL;
-         break;
-       }
-       if (!obs.exists) {
-         result = 0;
-         break;
-       }
-       if (oi.is_cache_pinned()) {
-         dout(10) << "cache-evict on a pinned object, consider unpin this object first" << dendl;
-         result = -EPERM;
-         break;
-       }
-       if (oi.is_dirty()) {
-         result = -EBUSY;
-         break;
-       }
-       if (!oi.watchers.empty()) {
-         result = -EBUSY;
-         break;
-       }
-       if (soid.snap == CEPH_NOSNAP) {
-         result = _verify_no_head_clones(soid, ssc->snapset);
-         if (result < 0)
-           break;
-       }
-       result = _delete_oid(ctx, true, false);
-       if (result >= 0) {
-         // mark that this is a cache eviction to avoid triggering normal
-         // make_writeable() clone or snapdir object creation in finish_ctx()
-         ctx->cache_evict = true;
-       }
-       osd->logger->inc(l_osd_tier_evict);
-      }
-      break;
-
-    case CEPH_OSD_OP_GETXATTR:
-      ++ctx->num_read;
-      {
-       string aname;
-       bp.copy(op.xattr.name_len, aname);
-       tracepoint(osd, do_osd_op_pre_getxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str());
-       string name = "_" + aname;
-       int r = getattr_maybe_cache(
-         ctx->obc,
-         name,
-         &(osd_op.outdata));
-       if (r >= 0) {
-         op.xattr.value_len = osd_op.outdata.length();
-         result = 0;
-         ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(osd_op.outdata.length(), 10);
-       } else
-         result = r;
-
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-   case CEPH_OSD_OP_GETXATTRS:
-      ++ctx->num_read;
-      {
-       tracepoint(osd, do_osd_op_pre_getxattrs, soid.oid.name.c_str(), soid.snap.val);
-       map<string, bufferlist> out;
-       result = getattrs_maybe_cache(
-         ctx->obc,
-         &out,
-         true);
-        
-        bufferlist bl;
-        ::encode(out, bl);
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(bl.length(), 10);
-        ctx->delta_stats.num_rd++;
-        osd_op.outdata.claim_append(bl);
-      }
-      break;
-      
-    case CEPH_OSD_OP_CMPXATTR:
-      ++ctx->num_read;
-      {
-       string aname;
-       bp.copy(op.xattr.name_len, aname);
-       tracepoint(osd, do_osd_op_pre_cmpxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str());
-       string name = "_" + aname;
-       name[op.xattr.name_len + 1] = 0;
-       
-       bufferlist xattr;
-       result = getattr_maybe_cache(
-         ctx->obc,
-         name,
-         &xattr);
-       if (result < 0 && result != -EEXIST && result != -ENODATA)
-         break;
-       
-       ctx->delta_stats.num_rd++;
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(xattr.length(), 10);
-
-       switch (op.xattr.cmp_mode) {
-       case CEPH_OSD_CMPXATTR_MODE_STRING:
-         {
-           string val;
-           bp.copy(op.xattr.value_len, val);
-           val[op.xattr.value_len] = 0;
-           dout(10) << "CEPH_OSD_OP_CMPXATTR name=" << name << " val=" << val
-                    << " op=" << (int)op.xattr.cmp_op << " mode=" << (int)op.xattr.cmp_mode << dendl;
-           result = do_xattr_cmp_str(op.xattr.cmp_op, val, xattr);
-         }
-         break;
-
-        case CEPH_OSD_CMPXATTR_MODE_U64:
-         {
-           uint64_t u64val;
-           try {
-             ::decode(u64val, bp);
-           }
-           catch (buffer::error& e) {
-             result = -EINVAL;
-             goto fail;
-           }
-           dout(10) << "CEPH_OSD_OP_CMPXATTR name=" << name << " val=" << u64val
-                    << " op=" << (int)op.xattr.cmp_op << " mode=" << (int)op.xattr.cmp_mode << dendl;
-           result = do_xattr_cmp_u64(op.xattr.cmp_op, u64val, xattr);
-         }
-         break;
-
-       default:
-         dout(10) << "bad cmp mode " << (int)op.xattr.cmp_mode << dendl;
-         result = -EINVAL;
-       }
-
-       if (!result) {
-         dout(10) << "comparison returned false" << dendl;
-         result = -ECANCELED;
-         break;
-       }
-       if (result < 0) {
-         dout(10) << "comparison returned " << result << " " << cpp_strerror(-result) << dendl;
-         break;
-       }
-
-       dout(10) << "comparison returned true" << dendl;
-      }
-      break;
-
-    case CEPH_OSD_OP_ASSERT_VER:
-      ++ctx->num_read;
-      {
-       uint64_t ver = op.assert_ver.ver;
-       tracepoint(osd, do_osd_op_pre_assert_ver, soid.oid.name.c_str(), soid.snap.val, ver);
-       if (!ver)
-         result = -EINVAL;
-        else if (ver < oi.user_version)
-         result = -ERANGE;
-       else if (ver > oi.user_version)
-         result = -EOVERFLOW;
-      }
-      break;
-
-    case CEPH_OSD_OP_LIST_WATCHERS:
-      ++ctx->num_read;
-      {
-       tracepoint(osd, do_osd_op_pre_list_watchers, soid.oid.name.c_str(), soid.snap.val);
-        obj_list_watch_response_t resp;
-
-        map<pair<uint64_t, entity_name_t>, watch_info_t>::const_iterator oi_iter;
-        for (oi_iter = oi.watchers.begin(); oi_iter != oi.watchers.end();
-                                       ++oi_iter) {
-          dout(20) << "key cookie=" << oi_iter->first.first
-               << " entity=" << oi_iter->first.second << " "
-               << oi_iter->second << dendl;
-          assert(oi_iter->first.first == oi_iter->second.cookie);
-          assert(oi_iter->first.second.is_client());
-
-          watch_item_t wi(oi_iter->first.second, oi_iter->second.cookie,
-                oi_iter->second.timeout_seconds, oi_iter->second.addr);
-          resp.entries.push_back(wi);
-        }
-
-        resp.encode(osd_op.outdata, ctx->get_features());
-        result = 0;
-
-        ctx->delta_stats.num_rd++;
-        break;
-      }
-
-    case CEPH_OSD_OP_LIST_SNAPS:
-      ++ctx->num_read;
-      {
-       tracepoint(osd, do_osd_op_pre_list_snaps, soid.oid.name.c_str(), soid.snap.val);
-        obj_list_snap_response_t resp;
-
-        if (!ssc) {
-         ssc = ctx->obc->ssc = get_snapset_context(soid, false);
-        }
-        assert(ssc);
-
-        int clonecount = ssc->snapset.clones.size();
-        if (ssc->snapset.head_exists)
-          clonecount++;
-        resp.clones.reserve(clonecount);
-        for (auto clone_iter = ssc->snapset.clones.begin();
-            clone_iter != ssc->snapset.clones.end(); ++clone_iter) {
-          clone_info ci;
-          ci.cloneid = *clone_iter;
-
-         hobject_t clone_oid = soid;
-         clone_oid.snap = *clone_iter;
-
-         if (!ssc->snapset.is_legacy()) {
-           auto p = ssc->snapset.clone_snaps.find(*clone_iter);
-           if (p == ssc->snapset.clone_snaps.end()) {
-             osd->clog->error() << "osd." << osd->whoami
-                                << ": inconsistent clone_snaps found for oid "
-                                << soid << " clone " << *clone_iter
-                                << " snapset " << ssc->snapset;
-             result = -EINVAL;
-             break;
-           }
-           for (auto q = p->second.rbegin(); q != p->second.rend(); ++q) {
-             ci.snaps.push_back(*q);
-           }
-         } else {
-           /* No need to take a lock here.  We are only inspecting state cached on
-            * in the ObjectContext, so we aren't performing an actual read unless
-            * the clone obc is not already loaded (in which case, it cannot have
-            * an in progress write).  We also do not risk exposing uncommitted
-            * state since we do have a read lock on the head object or snapdir,
-            * which we would have to write lock in order to make user visible
-            * modifications to the snapshot state (snap trim related mutations
-            * are not user visible).
-            */
-           if (is_missing_object(clone_oid)) {
-             dout(20) << "LIST_SNAPS " << clone_oid << " missing" << dendl;
-             wait_for_unreadable_object(clone_oid, ctx->op);
-             result = -EAGAIN;
-             break;
-           }
-
-           ObjectContextRef clone_obc = get_object_context(clone_oid, false);
-           if (!clone_obc) {
-             if (maybe_handle_cache(
-                   ctx->op, true, clone_obc, -ENOENT, clone_oid, true)) {
-               // promoting the clone
-               result = -EAGAIN;
-             } else {
-               osd->clog->error() << "osd." << osd->whoami
-                                  << ": missing clone " << clone_oid
-                                  << " for oid "
-                                  << soid;
-               // should not happen
-               result = -ENOENT;
-             }
-             break;
-           }
-           for (vector<snapid_t>::reverse_iterator p =
-                  clone_obc->obs.oi.legacy_snaps.rbegin();
-                p != clone_obc->obs.oi.legacy_snaps.rend();
-                ++p) {
-             ci.snaps.push_back(*p);
-           }
-         }
-
-          dout(20) << " clone " << *clone_iter << " snaps " << ci.snaps << dendl;
-
-          map<snapid_t, interval_set<uint64_t> >::const_iterator coi;
-          coi = ssc->snapset.clone_overlap.find(ci.cloneid);
-          if (coi == ssc->snapset.clone_overlap.end()) {
-            osd->clog->error() << "osd." << osd->whoami
-                              << ": inconsistent clone_overlap found for oid "
-                             << soid << " clone " << *clone_iter;
-            result = -EINVAL;
-            break;
-          }
-          const interval_set<uint64_t> &o = coi->second;
-          ci.overlap.reserve(o.num_intervals());
-          for (interval_set<uint64_t>::const_iterator r = o.begin();
-               r != o.end(); ++r) {
-            ci.overlap.push_back(pair<uint64_t,uint64_t>(r.get_start(),
-                                                        r.get_len()));
-          }
-
-          map<snapid_t, uint64_t>::const_iterator si;
-          si = ssc->snapset.clone_size.find(ci.cloneid);
-          if (si == ssc->snapset.clone_size.end()) {
-            osd->clog->error() << "osd." << osd->whoami
-                              << ": inconsistent clone_size found for oid "
-                              << soid << " clone " << *clone_iter;
-            result = -EINVAL;
-            break;
-          }
-          ci.size = si->second;
-
-          resp.clones.push_back(ci);
-        }
-       if (result < 0) {
-         break;
-       }         
-        if (ssc->snapset.head_exists &&
-           !ctx->obc->obs.oi.is_whiteout()) {
-          assert(obs.exists);
-          clone_info ci;
-          ci.cloneid = CEPH_NOSNAP;
-
-          //Size for HEAD is oi.size
-          ci.size = oi.size;
-
-          resp.clones.push_back(ci);
-        }
-       resp.seq = ssc->snapset.seq;
-
-        resp.encode(osd_op.outdata);
-        result = 0;
-
-        ctx->delta_stats.num_rd++;
-        break;
-      }
-
-   case CEPH_OSD_OP_NOTIFY:
-      ++ctx->num_read;
-      {
-       uint32_t timeout;
-        bufferlist bl;
-
-       try {
-         uint32_t ver; // obsolete
-          ::decode(ver, bp);
-         ::decode(timeout, bp);
-          ::decode(bl, bp);
-       } catch (const buffer::error &e) {
-         timeout = 0;
-       }
-       tracepoint(osd, do_osd_op_pre_notify, soid.oid.name.c_str(), soid.snap.val, timeout);
-       if (!timeout)
-         timeout = cct->_conf->osd_default_notify_timeout;
-
-       notify_info_t n;
-       n.timeout = timeout;
-       n.notify_id = osd->get_next_id(get_osdmap()->get_epoch());
-       n.cookie = op.watch.cookie;
-        n.bl = bl;
-       ctx->notifies.push_back(n);
-
-       // return our unique notify id to the client
-       ::encode(n.notify_id, osd_op.outdata);
-      }
-      break;
-
-    case CEPH_OSD_OP_NOTIFY_ACK:
-      ++ctx->num_read;
-      {
-       try {
-         uint64_t notify_id = 0;
-         uint64_t watch_cookie = 0;
-         ::decode(notify_id, bp);
-         ::decode(watch_cookie, bp);
-         bufferlist reply_bl;
-         if (!bp.end()) {
-           ::decode(reply_bl, bp);
-         }
-         tracepoint(osd, do_osd_op_pre_notify_ack, soid.oid.name.c_str(), soid.snap.val, notify_id, watch_cookie, "Y");
-         OpContext::NotifyAck ack(notify_id, watch_cookie, reply_bl);
-         ctx->notify_acks.push_back(ack);
-       } catch (const buffer::error &e) {
-         tracepoint(osd, do_osd_op_pre_notify_ack, soid.oid.name.c_str(), soid.snap.val, op.watch.cookie, 0, "N");
-         OpContext::NotifyAck ack(
-           // op.watch.cookie is actually the notify_id for historical reasons
-           op.watch.cookie
-           );
-         ctx->notify_acks.push_back(ack);
-       }
-      }
-      break;
-
-    case CEPH_OSD_OP_SETALLOCHINT:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_setallochint, soid.oid.name.c_str(), soid.snap.val, op.alloc_hint.expected_object_size, op.alloc_hint.expected_write_size);
-       maybe_create_new_object(ctx);
-       oi.expected_object_size = op.alloc_hint.expected_object_size;
-       oi.expected_write_size = op.alloc_hint.expected_write_size;
-       oi.alloc_hint_flags = op.alloc_hint.flags;
-        t->set_alloc_hint(soid, op.alloc_hint.expected_object_size,
-                          op.alloc_hint.expected_write_size,
-                         op.alloc_hint.flags);
-        ctx->delta_stats.num_wr++;
-        result = 0;
-      }
-      break;
-
-
-      // --- WRITES ---
-
-      // -- object data --
-
-    case CEPH_OSD_OP_WRITE:
-      ++ctx->num_write;
-      { // write
-        __u32 seq = oi.truncate_seq;
-       tracepoint(osd, do_osd_op_pre_write, soid.oid.name.c_str(), soid.snap.val, oi.size, seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq);
-       if (op.extent.length != osd_op.indata.length()) {
-         result = -EINVAL;
-         break;
-       }
-
-       if (pool.info.has_flag(pg_pool_t::FLAG_WRITE_FADVISE_DONTNEED))
-         op.flags = op.flags | CEPH_OSD_OP_FLAG_FADVISE_DONTNEED;
-
-       if (pool.info.requires_aligned_append() &&
-           (op.extent.offset % pool.info.required_alignment() != 0)) {
-         result = -EOPNOTSUPP;
-         break;
-       }
-
-       if (!obs.exists) {
-         if (pool.info.requires_aligned_append() && op.extent.offset) {
-           result = -EOPNOTSUPP;
-           break;
-         }
-       } else if (op.extent.offset != oi.size &&
-                  pool.info.requires_aligned_append()) {
-         result = -EOPNOTSUPP;
-         break;
-       }
-
-        if (seq && (seq > op.extent.truncate_seq) &&
-            (op.extent.offset + op.extent.length > oi.size)) {
-         // old write, arrived after trimtrunc
-         op.extent.length = (op.extent.offset > oi.size ? 0 : oi.size - op.extent.offset);
-         dout(10) << " old truncate_seq " << op.extent.truncate_seq << " < current " << seq
-                  << ", adjusting write length to " << op.extent.length << dendl;
-         bufferlist t;
-         t.substr_of(osd_op.indata, 0, op.extent.length);
-         osd_op.indata.swap(t);
-        }
-       if (op.extent.truncate_seq > seq) {
-         // write arrives before trimtrunc
-         if (obs.exists && !oi.is_whiteout()) {
-           dout(10) << " truncate_seq " << op.extent.truncate_seq << " > current " << seq
-                    << ", truncating to " << op.extent.truncate_size << dendl;
-           t->truncate(soid, op.extent.truncate_size);
-           oi.truncate_seq = op.extent.truncate_seq;
-           oi.truncate_size = op.extent.truncate_size;
-           if (op.extent.truncate_size != oi.size) {
-             ctx->delta_stats.num_bytes -= oi.size;
-             ctx->delta_stats.num_bytes += op.extent.truncate_size;
-             oi.size = op.extent.truncate_size;
-           }
-         } else {
-           dout(10) << " truncate_seq " << op.extent.truncate_seq << " > current " << seq
-                    << ", but object is new" << dendl;
-           oi.truncate_seq = op.extent.truncate_seq;
-           oi.truncate_size = op.extent.truncate_size;
-         }
-       }
-       result = check_offset_and_length(op.extent.offset, op.extent.length, cct->_conf->osd_max_object_size);
-       if (result < 0)
-         break;
-
-       maybe_create_new_object(ctx);
-
-       if (op.extent.length == 0) {
-         if (op.extent.offset > oi.size) {
-           t->truncate(
-             soid, op.extent.offset);
-         } else {
-           t->nop(soid);
-         }
-       } else {
-         t->write(
-           soid, op.extent.offset, op.extent.length, osd_op.indata, op.flags);
-       }
-
-       if (op.extent.offset == 0 && op.extent.length >= oi.size)
-         obs.oi.set_data_digest(osd_op.indata.crc32c(-1));
-       else if (op.extent.offset == oi.size && obs.oi.is_data_digest())
-         obs.oi.set_data_digest(osd_op.indata.crc32c(obs.oi.data_digest));
-       else
-         obs.oi.clear_data_digest();
-       write_update_size_and_usage(ctx->delta_stats, oi, ctx->modified_ranges,
-                                   op.extent.offset, op.extent.length);
-
-      }
-      break;
-      
-    case CEPH_OSD_OP_WRITEFULL:
-      ++ctx->num_write;
-      { // write full object
-       tracepoint(osd, do_osd_op_pre_writefull, soid.oid.name.c_str(), soid.snap.val, oi.size, 0, op.extent.length);
-
-       if (op.extent.length != osd_op.indata.length()) {
-         result = -EINVAL;
-         break;
-       }
-       result = check_offset_and_length(0, op.extent.length, cct->_conf->osd_max_object_size);
-       if (result < 0)
-         break;
-
-       if (pool.info.has_flag(pg_pool_t::FLAG_WRITE_FADVISE_DONTNEED))
-         op.flags = op.flags | CEPH_OSD_OP_FLAG_FADVISE_DONTNEED;
-
-       maybe_create_new_object(ctx);
-       if (pool.info.require_rollback()) {
-         t->truncate(soid, 0);
-       } else if (obs.exists && op.extent.length < oi.size) {
-         t->truncate(soid, op.extent.length);
-       }
-       if (op.extent.length) {
-         t->write(soid, 0, op.extent.length, osd_op.indata, op.flags);
-       }
-       obs.oi.set_data_digest(osd_op.indata.crc32c(-1));
-
-       write_update_size_and_usage(ctx->delta_stats, oi, ctx->modified_ranges,
-           0, op.extent.length, true);
-      }
-      break;
-
-    case CEPH_OSD_OP_WRITESAME:
-      ++ctx->num_write;
-      tracepoint(osd, do_osd_op_pre_writesame, soid.oid.name.c_str(), soid.snap.val, oi.size, op.writesame.offset, op.writesame.length, op.writesame.data_length);
-      result = do_writesame(ctx, osd_op);
-      break;
-
-    case CEPH_OSD_OP_ROLLBACK :
-      ++ctx->num_write;
-      tracepoint(osd, do_osd_op_pre_rollback, soid.oid.name.c_str(), soid.snap.val);
-      result = _rollback_to(ctx, op);
-      break;
-
-    case CEPH_OSD_OP_ZERO:
-      tracepoint(osd, do_osd_op_pre_zero, soid.oid.name.c_str(), soid.snap.val, op.extent.offset, op.extent.length);
-      if (pool.info.requires_aligned_append()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_write;
-      { // zero
-       result = check_offset_and_length(op.extent.offset, op.extent.length, cct->_conf->osd_max_object_size);
-       if (result < 0)
-         break;
-       assert(op.extent.length);
-       if (obs.exists && !oi.is_whiteout()) {
-         t->zero(soid, op.extent.offset, op.extent.length);
-         interval_set<uint64_t> ch;
-         ch.insert(op.extent.offset, op.extent.length);
-         ctx->modified_ranges.union_of(ch);
-         ctx->delta_stats.num_wr++;
-         oi.clear_data_digest();
-       } else {
-         // no-op
-       }
-      }
-      break;
-    case CEPH_OSD_OP_CREATE:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_create, soid.oid.name.c_str(), soid.snap.val);
-        int flags = le32_to_cpu(op.flags);
-       if (obs.exists && !oi.is_whiteout() &&
-           (flags & CEPH_OSD_OP_FLAG_EXCL)) {
-          result = -EEXIST; /* this is an exclusive create */
-       } else {
-         if (osd_op.indata.length()) {
-           bufferlist::iterator p = osd_op.indata.begin();
-           string category;
-           try {
-             ::decode(category, p);
-           }
-           catch (buffer::error& e) {
-             result = -EINVAL;
-             goto fail;
-           }
-           // category is no longer implemented.
-         }
-          if (result >= 0) {
-           maybe_create_new_object(ctx);
-           t->nop(soid);
-          }
-       }
-      }
-      break;
-
-    case CEPH_OSD_OP_TRIMTRUNC:
-      op.extent.offset = op.extent.truncate_size;
-      // falling through
-
-    case CEPH_OSD_OP_TRUNCATE:
-      tracepoint(osd, do_osd_op_pre_truncate, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq);
-      if (pool.info.requires_aligned_append()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_write;
-      {
-       // truncate
-       if (!obs.exists || oi.is_whiteout()) {
-         dout(10) << " object dne, truncate is a no-op" << dendl;
-         break;
-       }
-
-       if (op.extent.offset > cct->_conf->osd_max_object_size) {
-         result = -EFBIG;
-         break;
-       }
-
-       if (op.extent.truncate_seq) {
-         assert(op.extent.offset == op.extent.truncate_size);
-         if (op.extent.truncate_seq <= oi.truncate_seq) {
-           dout(10) << " truncate seq " << op.extent.truncate_seq << " <= current " << oi.truncate_seq
-                    << ", no-op" << dendl;
-           break; // old
-         }
-         dout(10) << " truncate seq " << op.extent.truncate_seq << " > current " << oi.truncate_seq
-                  << ", truncating" << dendl;
-         oi.truncate_seq = op.extent.truncate_seq;
-         oi.truncate_size = op.extent.truncate_size;
-       }
-
-       maybe_create_new_object(ctx);
-       t->truncate(soid, op.extent.offset);
-       if (oi.size > op.extent.offset) {
-         interval_set<uint64_t> trim;
-         trim.insert(op.extent.offset, oi.size-op.extent.offset);
-         ctx->modified_ranges.union_of(trim);
-       }
-       if (op.extent.offset != oi.size) {
-         ctx->delta_stats.num_bytes -= oi.size;
-         ctx->delta_stats.num_bytes += op.extent.offset;
-         oi.size = op.extent.offset;
-       }
-       ctx->delta_stats.num_wr++;
-       // do no set exists, or we will break above DELETE -> TRUNCATE munging.
-
-       oi.clear_data_digest();
-      }
-      break;
-    
-    case CEPH_OSD_OP_DELETE:
-      ++ctx->num_write;
-      tracepoint(osd, do_osd_op_pre_delete, soid.oid.name.c_str(), soid.snap.val);
-      {
-       result = _delete_oid(ctx, false, ctx->ignore_cache);
-      }
-      break;
-
-    case CEPH_OSD_OP_WATCH:
-      ++ctx->num_write;
-      {
-       tracepoint(osd, do_osd_op_pre_watch, soid.oid.name.c_str(), soid.snap.val,
-                  op.watch.cookie, op.watch.op);
-       if (!obs.exists) {
-         result = -ENOENT;
-         break;
-       }
-        uint64_t cookie = op.watch.cookie;
-        entity_name_t entity = ctx->reqid.name;
-       ObjectContextRef obc = ctx->obc;
-
-       dout(10) << "watch " << ceph_osd_watch_op_name(op.watch.op)
-                << ": ctx->obc=" << (void *)obc.get() << " cookie=" << cookie
-                << " oi.version=" << oi.version.version << " ctx->at_version=" << ctx->at_version << dendl;
-       dout(10) << "watch: oi.user_version=" << oi.user_version<< dendl;
-       dout(10) << "watch: peer_addr="
-         << ctx->op->get_req()->get_connection()->get_peer_addr() << dendl;
-
-       uint32_t timeout = cct->_conf->osd_client_watch_timeout;
-       if (op.watch.timeout != 0) {
-         timeout = op.watch.timeout;
-       }
-
-       watch_info_t w(cookie, timeout,
-         ctx->op->get_req()->get_connection()->get_peer_addr());
-       if (op.watch.op == CEPH_OSD_WATCH_OP_WATCH ||
-           op.watch.op == CEPH_OSD_WATCH_OP_LEGACY_WATCH) {
-         if (oi.watchers.count(make_pair(cookie, entity))) {
-           dout(10) << " found existing watch " << w << " by " << entity << dendl;
-         } else {
-           dout(10) << " registered new watch " << w << " by " << entity << dendl;
-           oi.watchers[make_pair(cookie, entity)] = w;
-           t->nop(soid);  // make sure update the object_info on disk!
-         }
-         bool will_ping = (op.watch.op == CEPH_OSD_WATCH_OP_WATCH);
-         ctx->watch_connects.push_back(make_pair(w, will_ping));
-        } else if (op.watch.op == CEPH_OSD_WATCH_OP_RECONNECT) {
-         if (!oi.watchers.count(make_pair(cookie, entity))) {
-           result = -ENOTCONN;
-           break;
-         }
-         dout(10) << " found existing watch " << w << " by " << entity << dendl;
-         ctx->watch_connects.push_back(make_pair(w, true));
-        } else if (op.watch.op == CEPH_OSD_WATCH_OP_PING) {
-         /* Note: WATCH with PING doesn't cause may_write() to return true,
-          * so if there is nothing else in the transaction, this is going
-          * to run do_osd_op_effects, but not write out a log entry */
-         if (!oi.watchers.count(make_pair(cookie, entity))) {
-           result = -ENOTCONN;
-           break;
-         }
-         map<pair<uint64_t,entity_name_t>,WatchRef>::iterator p =
-           obc->watchers.find(make_pair(cookie, entity));
-         if (p == obc->watchers.end() ||
-             !p->second->is_connected()) {
-           // client needs to reconnect
-           result = -ETIMEDOUT;
-           break;
-         }
-         dout(10) << " found existing watch " << w << " by " << entity << dendl;
-         p->second->got_ping(ceph_clock_now());
-         result = 0;
-        } else if (op.watch.op == CEPH_OSD_WATCH_OP_UNWATCH) {
-         map<pair<uint64_t, entity_name_t>, watch_info_t>::iterator oi_iter =
-           oi.watchers.find(make_pair(cookie, entity));
-         if (oi_iter != oi.watchers.end()) {
-           dout(10) << " removed watch " << oi_iter->second << " by "
-                    << entity << dendl;
-            oi.watchers.erase(oi_iter);
-           t->nop(soid);  // update oi on disk
-           ctx->watch_disconnects.push_back(
-             watch_disconnect_t(cookie, entity, false));
-         } else {
-           dout(10) << " can't remove: no watch by " << entity << dendl;
-         }
-        }
-      }
-      break;
-
-    case CEPH_OSD_OP_CACHE_PIN:
-      tracepoint(osd, do_osd_op_pre_cache_pin, soid.oid.name.c_str(), soid.snap.val);
-      if ((!pool.info.is_tier() ||
-         pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE)) {
-        result = -EINVAL;
-        dout(10) << " pin object is only allowed on the cache tier " << dendl;
-        break;
-      }
-      ++ctx->num_write;
-      {
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         break;
-       }
-
-       if (!oi.is_cache_pinned()) {
-         oi.set_flag(object_info_t::FLAG_CACHE_PIN);
-         ctx->modify = true;
-         ctx->delta_stats.num_objects_pinned++;
-         ctx->delta_stats.num_wr++;
-       }
-       result = 0;
-      }
-      break;
-
-    case CEPH_OSD_OP_CACHE_UNPIN:
-      tracepoint(osd, do_osd_op_pre_cache_unpin, soid.oid.name.c_str(), soid.snap.val);
-      if ((!pool.info.is_tier() ||
-         pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE)) {
-        result = -EINVAL;
-        dout(10) << " pin object is only allowed on the cache tier " << dendl;
-        break;
-      }
-      ++ctx->num_write;
-      {
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         break;
-       }
-
-       if (oi.is_cache_pinned()) {
-         oi.clear_flag(object_info_t::FLAG_CACHE_PIN);
-         ctx->modify = true;
-         ctx->delta_stats.num_objects_pinned--;
-         ctx->delta_stats.num_wr++;
-       }
-       result = 0;
-      }
-      break;
-
-    case CEPH_OSD_OP_SET_REDIRECT:
-      ++ctx->num_write;
-      {
-       if (pool.info.is_tier()) {
-         result = -EINVAL;
-         break;
-       }
-       if (!obs.exists) {
-         result = -ENOENT;
-         break;
-       }
-       if (get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS) {
-         result = -EOPNOTSUPP;
-         break;
-       }
-
-       object_t target_name;
-       object_locator_t target_oloc;
-       snapid_t target_snapid = (uint64_t)op.copy_from.snapid;
-       version_t target_version = op.copy_from.src_version;
-       try {
-         ::decode(target_name, bp);
-         ::decode(target_oloc, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         goto fail;
-       }
-       pg_t raw_pg;
-       get_osdmap()->object_locator_to_pg(target_name, target_oloc, raw_pg);
-       hobject_t target(target_name, target_oloc.key, target_snapid,
-               raw_pg.ps(), raw_pg.pool(),
-               target_oloc.nspace);
-       if (target == soid) {
-         dout(20) << " set-redirect self is invalid" << dendl;
-         result = -EINVAL;
-         break;
-       }
-        oi.set_flag(object_info_t::FLAG_MANIFEST);
-       oi.manifest.redirect_target = target;
-       oi.manifest.type = object_manifest_t::TYPE_REDIRECT;
-       t->truncate(soid, 0);
-       if (oi.is_omap() && pool.info.supports_omap()) {
-         t->omap_clear(soid);
-         obs.oi.clear_omap_digest();
-         obs.oi.clear_flag(object_info_t::FLAG_OMAP);
-       }
-       ctx->delta_stats.num_bytes -= oi.size;
-       oi.size = 0;
-       oi.new_object();
-       oi.user_version = target_version;
-       ctx->user_at_version = target_version;
-       /* rm_attrs */
-       map<string,bufferlist> rmattrs;
-       result = getattrs_maybe_cache(ctx->obc,
-                   &rmattrs,
-                   true);
-       if (result < 0) {
-         return result;
-       }
-       map<string, bufferlist>::iterator iter;
-       for (iter = rmattrs.begin(); iter != rmattrs.end(); ++iter) {
-         const string& name = iter->first;
-         t->rmattr(soid, name);
-       }
-       dout(10) << "set-redirect oid:" << oi.soid << " user_version: " << oi.user_version << dendl;
-      }
-
-      break;
-
-      // -- object attrs --
-      
-    case CEPH_OSD_OP_SETXATTR:
-      ++ctx->num_write;
-      {
-       if (cct->_conf->osd_max_attr_size > 0 &&
-           op.xattr.value_len > cct->_conf->osd_max_attr_size) {
-         tracepoint(osd, do_osd_op_pre_setxattr, soid.oid.name.c_str(), soid.snap.val, "???");
-         result = -EFBIG;
-         break;
-       }
-       unsigned max_name_len = MIN(osd->store->get_max_attr_name_length(),
-                                   cct->_conf->osd_max_attr_name_len);
-       if (op.xattr.name_len > max_name_len) {
-         result = -ENAMETOOLONG;
-         break;
-       }
-       maybe_create_new_object(ctx);
-       string aname;
-       bp.copy(op.xattr.name_len, aname);
-       tracepoint(osd, do_osd_op_pre_setxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str());
-       string name = "_" + aname;
-       bufferlist bl;
-       bp.copy(op.xattr.value_len, bl);
-       t->setattr(soid, name, bl);
-       ctx->delta_stats.num_wr++;
-      }
-      break;
-
-    case CEPH_OSD_OP_RMXATTR:
-      ++ctx->num_write;
-      {
-       string aname;
-       bp.copy(op.xattr.name_len, aname);
-       tracepoint(osd, do_osd_op_pre_rmxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str());
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         break;
-       }
-       string name = "_" + aname;
-       t->rmattr(soid, name);
-       ctx->delta_stats.num_wr++;
-      }
-      break;
-    
-
-      // -- fancy writers --
-    case CEPH_OSD_OP_APPEND:
-      {
-       tracepoint(osd, do_osd_op_pre_append, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq);
-       // just do it inline; this works because we are happy to execute
-       // fancy op on replicas as well.
-       vector<OSDOp> nops(1);
-       OSDOp& newop = nops[0];
-       newop.op.op = CEPH_OSD_OP_WRITE;
-       newop.op.extent.offset = oi.size;
-       newop.op.extent.length = op.extent.length;
-       newop.op.extent.truncate_seq = oi.truncate_seq;
-        newop.indata = osd_op.indata;
-       result = do_osd_ops(ctx, nops);
-       osd_op.outdata.claim(newop.outdata);
-      }
-      break;
-
-    case CEPH_OSD_OP_STARTSYNC:
-      tracepoint(osd, do_osd_op_pre_startsync, soid.oid.name.c_str(), soid.snap.val);
-      t->nop(soid);
-      break;
-
-
-      // -- trivial map --
-    case CEPH_OSD_OP_TMAPGET:
-      tracepoint(osd, do_osd_op_pre_tmapget, soid.oid.name.c_str(), soid.snap.val);
-      if (pool.info.require_rollback()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      {
-       vector<OSDOp> nops(1);
-       OSDOp& newop = nops[0];
-       newop.op.op = CEPH_OSD_OP_SYNC_READ;
-       newop.op.extent.offset = 0;
-       newop.op.extent.length = 0;
-       do_osd_ops(ctx, nops);
-       osd_op.outdata.claim(newop.outdata);
-      }
-      break;
-
-    case CEPH_OSD_OP_TMAPPUT:
-      tracepoint(osd, do_osd_op_pre_tmapput, soid.oid.name.c_str(), soid.snap.val);
-      if (pool.info.require_rollback()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      {
-       //_dout_lock.Lock();
-       //osd_op.data.hexdump(*_dout);
-       //_dout_lock.Unlock();
-
-       // verify sort order
-       bool unsorted = false;
-       if (true) {
-         bufferlist header;
-         ::decode(header, bp);
-         uint32_t n;
-         ::decode(n, bp);
-         string last_key;
-         while (n--) {
-           string key;
-           ::decode(key, bp);
-           dout(10) << "tmapput key " << key << dendl;
-           bufferlist val;
-           ::decode(val, bp);
-           if (key < last_key) {
-             dout(10) << "TMAPPUT is unordered; resorting" << dendl;
-             unsorted = true;
-             break;
-           }
-           last_key = key;
-         }
-       }
-
-       // write it
-       vector<OSDOp> nops(1);
-       OSDOp& newop = nops[0];
-       newop.op.op = CEPH_OSD_OP_WRITEFULL;
-       newop.op.extent.offset = 0;
-       newop.op.extent.length = osd_op.indata.length();
-       newop.indata = osd_op.indata;
-
-       if (unsorted) {
-         bp = osd_op.indata.begin();
-         bufferlist header;
-         map<string, bufferlist> m;
-         ::decode(header, bp);
-         ::decode(m, bp);
-         assert(bp.end());
-         bufferlist newbl;
-         ::encode(header, newbl);
-         ::encode(m, newbl);
-         newop.indata = newbl;
-       }
-       result = do_osd_ops(ctx, nops);
-       assert(result == 0);
-      }
-      break;
-
-    case CEPH_OSD_OP_TMAPUP:
-      tracepoint(osd, do_osd_op_pre_tmapup, soid.oid.name.c_str(), soid.snap.val);
-      if (pool.info.require_rollback()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_write;
-      result = do_tmapup(ctx, bp, osd_op);
-      break;
-
-    case CEPH_OSD_OP_TMAP2OMAP:
-      ++ctx->num_write;
-      tracepoint(osd, do_osd_op_pre_tmap2omap, soid.oid.name.c_str(), soid.snap.val);
-      result = do_tmap2omap(ctx, op.tmap2omap.flags);
-      break;
-
-      // OMAP Read ops
-    case CEPH_OSD_OP_OMAPGETKEYS:
-      ++ctx->num_read;
-      {
-       string start_after;
-       uint64_t max_return;
-       try {
-         ::decode(start_after, bp);
-         ::decode(max_return, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omapgetkeys, soid.oid.name.c_str(), soid.snap.val, "???", 0);
-         goto fail;
-       }
-       if (max_return > cct->_conf->osd_max_omap_entries_per_request) {
-         max_return = cct->_conf->osd_max_omap_entries_per_request;
-       }
-       tracepoint(osd, do_osd_op_pre_omapgetkeys, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return);
-
-       bufferlist bl;
-       uint32_t num = 0;
-       bool truncated = false;
-       if (oi.is_omap()) {
-         ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator(
-           coll, ghobject_t(soid)
-           );
-         assert(iter);
-         iter->upper_bound(start_after);
-         for (num = 0; iter->valid(); ++num, iter->next(false)) {
-           if (num >= max_return ||
-               bl.length() >= cct->_conf->osd_max_omap_bytes_per_request) {
-             truncated = true;
-             break;
-           }
-           ::encode(iter->key(), bl);
-         }
-       } // else return empty out_set
-       ::encode(num, osd_op.outdata);
-       osd_op.outdata.claim_append(bl);
-       ::encode(truncated, osd_op.outdata);
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(osd_op.outdata.length(), 10);
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-    case CEPH_OSD_OP_OMAPGETVALS:
-      ++ctx->num_read;
-      {
-       string start_after;
-       uint64_t max_return;
-       string filter_prefix;
-       try {
-         ::decode(start_after, bp);
-         ::decode(max_return, bp);
-         ::decode(filter_prefix, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omapgetvals, soid.oid.name.c_str(), soid.snap.val, "???", 0, "???");
-         goto fail;
-       }
-       if (max_return > cct->_conf->osd_max_omap_entries_per_request) {
-         max_return = cct->_conf->osd_max_omap_entries_per_request;
-       }
-       tracepoint(osd, do_osd_op_pre_omapgetvals, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return, filter_prefix.c_str());
-
-       uint32_t num = 0;
-       bool truncated = false;
-       bufferlist bl;
-       if (oi.is_omap()) {
-         ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator(
-           coll, ghobject_t(soid)
-           );
-          if (!iter) {
-            result = -ENOENT;
-            goto fail;
-          }
-         iter->upper_bound(start_after);
-         if (filter_prefix > start_after) iter->lower_bound(filter_prefix);
-         for (num = 0;
-              iter->valid() &&
-                iter->key().substr(0, filter_prefix.size()) == filter_prefix;
-              ++num, iter->next(false)) {
-           dout(20) << "Found key " << iter->key() << dendl;
-           if (num >= max_return ||
-               bl.length() >= cct->_conf->osd_max_omap_bytes_per_request) {
-             truncated = true;
-             break;
-           }
-           ::encode(iter->key(), bl);
-           ::encode(iter->value(), bl);
-         }
-       } // else return empty out_set
-       ::encode(num, osd_op.outdata);
-       osd_op.outdata.claim_append(bl);
-       ::encode(truncated, osd_op.outdata);
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(osd_op.outdata.length(), 10);
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-    case CEPH_OSD_OP_OMAPGETHEADER:
-      tracepoint(osd, do_osd_op_pre_omapgetheader, soid.oid.name.c_str(), soid.snap.val);
-      if (!oi.is_omap()) {
-       // return empty header
-       break;
-      }
-      ++ctx->num_read;
-      {
-       osd->store->omap_get_header(ch, ghobject_t(soid), &osd_op.outdata);
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(osd_op.outdata.length(), 10);
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-    case CEPH_OSD_OP_OMAPGETVALSBYKEYS:
-      ++ctx->num_read;
-      {
-       set<string> keys_to_get;
-       try {
-         ::decode(keys_to_get, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omapgetvalsbykeys, soid.oid.name.c_str(), soid.snap.val, "???");
-         goto fail;
-       }
-       tracepoint(osd, do_osd_op_pre_omapgetvalsbykeys, soid.oid.name.c_str(), soid.snap.val, list_entries(keys_to_get).c_str());
-       map<string, bufferlist> out;
-       if (oi.is_omap()) {
-         osd->store->omap_get_values(ch, ghobject_t(soid), keys_to_get, &out);
-       } // else return empty omap entries
-       ::encode(out, osd_op.outdata);
-       ctx->delta_stats.num_rd_kb += SHIFT_ROUND_UP(osd_op.outdata.length(), 10);
-       ctx->delta_stats.num_rd++;
-      }
-      break;
-
-    case CEPH_OSD_OP_OMAP_CMP:
-      ++ctx->num_read;
-      {
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, "???");
-         break;
-       }
-       map<string, pair<bufferlist, int> > assertions;
-       try {
-         ::decode(assertions, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, "???");
-         goto fail;
-       }
-       tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, list_keys(assertions).c_str());
-       
-       map<string, bufferlist> out;
-
-       if (oi.is_omap()) {
-         set<string> to_get;
-         for (map<string, pair<bufferlist, int> >::iterator i = assertions.begin();
-              i != assertions.end();
-              ++i)
-           to_get.insert(i->first);
-         int r = osd->store->omap_get_values(ch, ghobject_t(soid),
-                                             to_get, &out);
-         if (r < 0) {
-           result = r;
-           break;
-         }
-       } // else leave out empty
-
-       //Should set num_rd_kb based on encode length of map
-       ctx->delta_stats.num_rd++;
-
-       int r = 0;
-       bufferlist empty;
-       for (map<string, pair<bufferlist, int> >::iterator i = assertions.begin();
-            i != assertions.end();
-            ++i) {
-         auto out_entry = out.find(i->first);
-         bufferlist &bl = (out_entry != out.end()) ?
-           out_entry->second : empty;
-         switch (i->second.second) {
-         case CEPH_OSD_CMPXATTR_OP_EQ:
-           if (!(bl == i->second.first)) {
-             r = -ECANCELED;
-           }
-           break;
-         case CEPH_OSD_CMPXATTR_OP_LT:
-           if (!(bl < i->second.first)) {
-             r = -ECANCELED;
-           }
-           break;
-         case CEPH_OSD_CMPXATTR_OP_GT:
-           if (!(bl > i->second.first)) {
-             r = -ECANCELED;
-           }
-           break;
-         default:
-           r = -EINVAL;
-           break;
-         }
-         if (r < 0)
-           break;
-       }
-       if (r < 0) {
-         result = r;
-       }
-      }
-      break;
-
-      // OMAP Write ops
-    case CEPH_OSD_OP_OMAPSETVALS:
-      if (!pool.info.supports_omap()) {
-       result = -EOPNOTSUPP;
-       tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val);
-       break;
-      }
-      ++ctx->num_write;
-      {
-       maybe_create_new_object(ctx);
-       bufferlist to_set_bl;
-       try {
-         decode_str_str_map_to_bl(bp, &to_set_bl);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val);
-         goto fail;
-       }
-       tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val);
-       if (cct->_conf->subsys.should_gather(dout_subsys, 20)) {
-         dout(20) << "setting vals: " << dendl;
-         map<string,bufferlist> to_set;
-         bufferlist::iterator pt = to_set_bl.begin();
-         ::decode(to_set, pt);
-         for (map<string, bufferlist>::iterator i = to_set.begin();
-              i != to_set.end();
-              ++i) {
-           dout(20) << "\t" << i->first << dendl;
-         }
-       }
-       t->omap_setkeys(soid, to_set_bl);
-       ctx->delta_stats.num_wr++;
-      }
-      obs.oi.set_flag(object_info_t::FLAG_OMAP);
-      obs.oi.clear_omap_digest();
-      break;
-
-    case CEPH_OSD_OP_OMAPSETHEADER:
-      tracepoint(osd, do_osd_op_pre_omapsetheader, soid.oid.name.c_str(), soid.snap.val);
-      if (!pool.info.supports_omap()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_write;
-      {
-       maybe_create_new_object(ctx);
-       t->omap_setheader(soid, osd_op.indata);
-       ctx->delta_stats.num_wr++;
-      }
-      obs.oi.set_flag(object_info_t::FLAG_OMAP);
-      obs.oi.clear_omap_digest();
-      break;
-
-    case CEPH_OSD_OP_OMAPCLEAR:
-      tracepoint(osd, do_osd_op_pre_omapclear, soid.oid.name.c_str(), soid.snap.val);
-      if (!pool.info.supports_omap()) {
-       result = -EOPNOTSUPP;
-       break;
-      }
-      ++ctx->num_write;
-      {
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         break;
-       }
-       if (oi.is_omap()) {
-         t->omap_clear(soid);
-         ctx->delta_stats.num_wr++;
-         obs.oi.clear_omap_digest();
-         obs.oi.clear_flag(object_info_t::FLAG_OMAP);
-       }
-      }
-      break;
-
-    case CEPH_OSD_OP_OMAPRMKEYS:
-      if (!pool.info.supports_omap()) {
-       result = -EOPNOTSUPP;
-       tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val);
-       break;
-      }
-      ++ctx->num_write;
-      {
-       if (!obs.exists || oi.is_whiteout()) {
-         result = -ENOENT;
-         tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val);
-         break;
-       }
-       bufferlist to_rm_bl;
-       try {
-         decode_str_set_to_bl(bp, &to_rm_bl);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val);
-         goto fail;
-       }
-       tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val);
-       t->omap_rmkeys(soid, to_rm_bl);
-       ctx->delta_stats.num_wr++;
-      }
-      obs.oi.clear_omap_digest();
-      break;
-
-    case CEPH_OSD_OP_COPY_GET:
-      ++ctx->num_read;
-      tracepoint(osd, do_osd_op_pre_copy_get, soid.oid.name.c_str(),
-                soid.snap.val);
-      if (op_finisher == nullptr) {
-       result = do_copy_get(ctx, bp, osd_op, ctx->obc);
-      } else {
-       result = op_finisher->execute();
-      }
-      break;
-
-    case CEPH_OSD_OP_COPY_FROM:
-      ++ctx->num_write;
-      {
-       object_t src_name;
-       object_locator_t src_oloc;
-       snapid_t src_snapid = (uint64_t)op.copy_from.snapid;
-       version_t src_version = op.copy_from.src_version;
-       try {
-         ::decode(src_name, bp);
-         ::decode(src_oloc, bp);
-       }
-       catch (buffer::error& e) {
-         result = -EINVAL;
-         tracepoint(osd,
-                    do_osd_op_pre_copy_from,
-                    soid.oid.name.c_str(),
-                    soid.snap.val,
-                    "???",
-                    0,
-                    "???",
-                    "???",
-                    0,
-                    src_snapid,
-                    src_version);
-         goto fail;
-       }
-       tracepoint(osd,
-                  do_osd_op_pre_copy_from,
-                  soid.oid.name.c_str(),
-                  soid.snap.val,
-                  src_name.name.c_str(),
-                  src_oloc.pool,
-                  src_oloc.key.c_str(),
-                  src_oloc.nspace.c_str(),
-                  src_oloc.hash,
-                  src_snapid,
-                  src_version);
-       if (op_finisher == nullptr) {
-         // start
-         pg_t raw_pg;
-         get_osdmap()->object_locator_to_pg(src_name, src_oloc, raw_pg);
-         hobject_t src(src_name, src_oloc.key, src_snapid,
-                       raw_pg.ps(), raw_pg.pool(),
-                       src_oloc.nspace);
-         if (src == soid) {
-           dout(20) << " copy from self is invalid" << dendl;
-           result = -EINVAL;
-           break;
-         }
-         CopyFromCallback *cb = new CopyFromCallback(ctx, osd_op);
-          ctx->op_finishers[ctx->current_osd_subop_num].reset(
-            new CopyFromFinisher(cb));
-         start_copy(cb, ctx->obc, src, src_oloc, src_version,
-                    op.copy_from.flags,
-                    false,
-                    op.copy_from.src_fadvise_flags,
-                    op.flags);
-         result = -EINPROGRESS;
-       } else {
-         // finish
-         result = op_finisher->execute();
-         assert(result == 0);
-
-          // COPY_FROM cannot be executed multiple times -- it must restart
-          ctx->op_finishers.erase(ctx->current_osd_subop_num);
-       }
-      }
-      break;
-
-    default:
-      tracepoint(osd, do_osd_op_pre_unknown, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op));
-      dout(1) << "unrecognized osd op " << op.op
-             << " " << ceph_osd_op_name(op.op)
-             << dendl;
-      result = -EOPNOTSUPP;
-    }
-
-  fail:
-    osd_op.rval = result;
-    tracepoint(osd, do_osd_op_post, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op), op.flags, result);
-    if (result < 0 && (op.flags & CEPH_OSD_OP_FLAG_FAILOK))
-      result = 0;
-
-    if (result < 0)
-      break;
-  }
-  return result;
-}
-
-int PrimaryLogPG::_get_tmap(OpContext *ctx, bufferlist *header, bufferlist *vals)
-{
-  if (ctx->new_obs.oi.size == 0) {
-    dout(20) << "unable to get tmap for zero sized " << ctx->new_obs.oi.soid << dendl;
-    return -ENODATA;
-  }
-  vector<OSDOp> nops(1);
-  OSDOp &newop = nops[0];
-  newop.op.op = CEPH_OSD_OP_TMAPGET;
-  do_osd_ops(ctx, nops);
-  try {
-    bufferlist::iterator i = newop.outdata.begin();
-    ::decode(*header, i);
-    (*vals).substr_of(newop.outdata, i.get_off(), i.get_remaining());
-  } catch (...) {
-    dout(20) << "unsuccessful at decoding tmap for " << ctx->new_obs.oi.soid
-            << dendl;
-    return -EINVAL;
-  }
-  dout(20) << "successful at decoding tmap for " << ctx->new_obs.oi.soid
-          << dendl;
-  return 0;
-}
-
-int PrimaryLogPG::_verify_no_head_clones(const hobject_t& soid,
-                                       const SnapSet& ss)
-{
-  // verify that all clones have been evicted
-  dout(20) << __func__ << " verifying clones are absent "
-          << ss << dendl;
-  for (vector<snapid_t>::const_iterator p = ss.clones.begin();
-       p != ss.clones.end();
-       ++p) {
-    hobject_t clone_oid = soid;
-    clone_oid.snap = *p;
-    if (is_missing_object(clone_oid))
-      return -EBUSY;
-    ObjectContextRef clone_obc = get_object_context(clone_oid, false);
-    if (clone_obc && clone_obc->obs.exists) {
-      dout(10) << __func__ << " cannot evict head before clone "
-              << clone_oid << dendl;
-      return -EBUSY;
-    }
-    if (copy_ops.count(clone_oid)) {
-      dout(10) << __func__ << " cannot evict head, pending promote on clone "
-              << clone_oid << dendl;
-      return -EBUSY;
-    }
-  }
-  return 0;
-}
-
-inline int PrimaryLogPG::_delete_oid(
-  OpContext *ctx,
-  bool no_whiteout,     // no whiteouts, no matter what.
-  bool try_no_whiteout) // try not to whiteout
-{
-  SnapSet& snapset = ctx->new_snapset;
-  ObjectState& obs = ctx->new_obs;
-  object_info_t& oi = obs.oi;
-  const hobject_t& soid = oi.soid;
-  PGTransaction* t = ctx->op_t.get();
-
-  // cache: cache: set whiteout on delete?
-  bool whiteout = false;
-  if (pool.info.cache_mode != pg_pool_t::CACHEMODE_NONE
-      && !no_whiteout
-      && !try_no_whiteout) {
-    whiteout = true;
-  }
-  bool legacy;
-  if (get_osdmap()->require_osd_release >= CEPH_RELEASE_LUMINOUS) {
-    legacy = false;
-    // in luminous or later, we can't delete the head if there are
-    // clones. we trust the caller passing no_whiteout has already
-    // verified they don't exist.
-    if (!snapset.clones.empty() ||
-       (!ctx->snapc.snaps.empty() && ctx->snapc.snaps[0] > snapset.seq)) {
-      if (no_whiteout) {
-       dout(20) << __func__ << " has or will have clones but no_whiteout=1"
-                << dendl;
-      } else {
-       dout(20) << __func__ << " has or will have clones; will whiteout"
-                << dendl;
-       whiteout = true;
-      }
-    }
-  } else {
-    legacy = true;
-  }
-  dout(20) << __func__ << " " << soid << " whiteout=" << (int)whiteout
-          << " no_whiteout=" << (int)no_whiteout
-          << " try_no_whiteout=" << (int)try_no_whiteout
-          << dendl;
-  if (!obs.exists || (obs.oi.is_whiteout() && whiteout))
-    return -ENOENT;
-
-  t->remove(soid);
-
-  if (oi.size > 0) {
-    interval_set<uint64_t> ch;
-    ch.insert(0, oi.size);
-    ctx->modified_ranges.union_of(ch);
-  }
-
-  ctx->delta_stats.num_wr++;
-  if (soid.is_snap()) {
-    assert(ctx->obc->ssc->snapset.clone_overlap.count(soid.snap));
-    ctx->delta_stats.num_bytes -= ctx->obc->ssc->snapset.get_clone_bytes(soid.snap);
-  } else {
-    ctx->delta_stats.num_bytes -= oi.size;
-  }
-  oi.size = 0;
-  oi.new_object();
-
-  // disconnect all watchers
-  for (map<pair<uint64_t, entity_name_t>, watch_info_t>::iterator p =
-        oi.watchers.begin();
-       p != oi.watchers.end();
-       ++p) {
-    dout(20) << __func__ << " will disconnect watcher " << p->first << dendl;
-    ctx->watch_disconnects.push_back(
-      watch_disconnect_t(p->first.first, p->first.second, true));
-  }
-  oi.watchers.clear();
-
-  if (whiteout) {
-    dout(20) << __func__ << " setting whiteout on " << soid << dendl;
-    oi.set_flag(object_info_t::FLAG_WHITEOUT);
-    ctx->delta_stats.num_whiteouts++;
-    t->create(soid);
-    osd->logger->inc(l_osd_tier_whiteout);
-    return 0;
-  }
-
-  // delete the head
-  ctx->delta_stats.num_objects--;
-  if (soid.is_snap())
-    ctx->delta_stats.num_object_clones--;
-  if (oi.is_whiteout()) {
-    dout(20) << __func__ << " deleting whiteout on " << soid << dendl;
-    ctx->delta_stats.num_whiteouts--;
-    oi.clear_flag(object_info_t::FLAG_WHITEOUT);
-  }
-  if (oi.is_cache_pinned()) {
-    ctx->delta_stats.num_objects_pinned--;
-  }
-  if ((legacy || snapset.is_legacy()) && soid.is_head()) {
-    snapset.head_exists = false;
-  }
-  obs.exists = false;
-  return 0;
-}
-
-int PrimaryLogPG::_rollback_to(OpContext *ctx, ceph_osd_op& op)
-{
-  SnapSet& snapset = ctx->new_snapset;
-  ObjectState& obs = ctx->new_obs;
-  object_info_t& oi = obs.oi;
-  const hobject_t& soid = oi.soid;
-  PGTransaction* t = ctx->op_t.get();
-  snapid_t snapid = (uint64_t)op.snap.snapid;
-  hobject_t missing_oid;
-
-  dout(10) << "_rollback_to " << soid << " snapid " << snapid << dendl;
-
-  ObjectContextRef rollback_to;
-  int ret = find_object_context(
-    hobject_t(soid.oid, soid.get_key(), snapid, soid.get_hash(), info.pgid.pool(),
-             soid.get_namespace()),
-    &rollback_to, false, false, &missing_oid);
-  if (ret == -EAGAIN) {
-    /* clone must be missing */
-    assert(is_degraded_or_backfilling_object(missing_oid));
-    dout(20) << "_rollback_to attempted to roll back to a missing or backfilling clone "
-            << missing_oid << " (requested snapid: ) " << snapid << dendl;
-    block_write_on_degraded_snap(missing_oid, ctx->op);
-    return ret;
-  }
-  {
-    ObjectContextRef promote_obc;
-    cache_result_t tier_mode_result;
-    if (obs.exists && obs.oi.has_manifest()) { 
-      tier_mode_result = 
-       maybe_handle_manifest_detail(
-         ctx->op,
-         true,
-         rollback_to);
-    } else {
-      tier_mode_result = 
-       maybe_handle_cache_detail(
-         ctx->op,
-         true,
-         rollback_to,
-         ret,
-         missing_oid,
-         true,
-         false,
-         &promote_obc);
-    }
-    switch (tier_mode_result) {
-    case cache_result_t::NOOP:
-      break;
-    case cache_result_t::BLOCKED_PROMOTE:
-      assert(promote_obc);
-      block_write_on_snap_rollback(soid, promote_obc, ctx->op);
-      return -EAGAIN;
-    case cache_result_t::BLOCKED_FULL:
-      block_write_on_full_cache(soid, ctx->op);
-      return -EAGAIN;
-    default:
-      assert(0 == "must promote was set, other values are not valid");
-      return -EAGAIN;
-    }
-  }
-
-  if (ret == -ENOENT || (rollback_to && rollback_to->obs.oi.is_whiteout())) {
-    // there's no snapshot here, or there's no object.
-    // if there's no snapshot, we delete the object; otherwise, do nothing.
-    dout(20) << "_rollback_to deleting head on " << soid.oid
-            << " because got ENOENT|whiteout on find_object_context" << dendl;
-    if (ctx->obc->obs.oi.watchers.size()) {
-      // Cannot delete an object with watchers
-      ret = -EBUSY;
-    } else {
-      _delete_oid(ctx, false, false);
-      ret = 0;
-    }
-  } else if (ret) {
-    // ummm....huh? It *can't* return anything else at time of writing.
-    assert(0 == "unexpected error code in _rollback_to");
-  } else { //we got our context, let's use it to do the rollback!
-    hobject_t& rollback_to_sobject = rollback_to->obs.oi.soid;
-    if (is_degraded_or_backfilling_object(rollback_to_sobject)) {
-      dout(20) << "_rollback_to attempted to roll back to a degraded object "
-              << rollback_to_sobject << " (requested snapid: ) " << snapid << dendl;
-      block_write_on_degraded_snap(rollback_to_sobject, ctx->op);
-      ret = -EAGAIN;
-    } else if (rollback_to->obs.oi.soid.snap == CEPH_NOSNAP) {
-      // rolling back to the head; we just need to clone it.
-      ctx->modify = true;
-    } else {
-      /* 1) Delete current head
-       * 2) Clone correct snapshot into head
-       * 3) Calculate clone_overlaps by following overlaps
-       *    forward from rollback snapshot */
-      dout(10) << "_rollback_to deleting " << soid.oid
-              << " and rolling back to old snap" << dendl;
-
-      if (obs.exists) {
-       t->remove(soid);
-      }
-      t->clone(soid, rollback_to_sobject);
-      snapset.head_exists = true;
-      t->add_obc(rollback_to);
-
-      map<snapid_t, interval_set<uint64_t> >::iterator iter =
-       snapset.clone_overlap.lower_bound(snapid);
-      interval_set<uint64_t> overlaps = iter->second;
-      assert(iter != snapset.clone_overlap.end());
-      for ( ;
-           iter != snapset.clone_overlap.end();
-           ++iter)
-       overlaps.intersection_of(iter->second);
-
-      if (obs.oi.size > 0) {
-       interval_set<uint64_t> modified;
-       modified.insert(0, obs.oi.size);
-       overlaps.intersection_of(modified);
-       modified.subtract(overlaps);
-       ctx->modified_ranges.union_of(modified);
-      }
-
-      // Adjust the cached objectcontext
-      maybe_create_new_object(ctx, true);
-      ctx->delta_stats.num_bytes -= obs.oi.size;
-      ctx->delta_stats.num_bytes += rollback_to->obs.oi.size;
-      obs.oi.size = rollback_to->obs.oi.size;
-      if (rollback_to->obs.oi.is_data_digest())
-       obs.oi.set_data_digest(rollback_to->obs.oi.data_digest);
-      else
-       obs.oi.clear_data_digest();
-      if (rollback_to->obs.oi.is_omap_digest())
-       obs.oi.set_omap_digest(rollback_to->obs.oi.omap_digest);
-      else
-       obs.oi.clear_omap_digest();
-
-      if (rollback_to->obs.oi.is_omap()) {
-       dout(10) << __func__ << " setting omap flag on " << obs.oi.soid << dendl;
-       obs.oi.set_flag(object_info_t::FLAG_OMAP);
-      } else {
-       dout(10) << __func__ << " clearing omap flag on " << obs.oi.soid << dendl;
-       obs.oi.clear_flag(object_info_t::FLAG_OMAP);
-      }
-
-      snapset.head_exists = true;
-    }
-  }
-  return ret;
-}
-
-void PrimaryLogPG::_make_clone(
-  OpContext *ctx,
-  PGTransaction* t,
-  ObjectContextRef obc,
-  const hobject_t& head, const hobject_t& coid,
-  object_info_t *poi)
-{
-  bufferlist bv;
-  ::encode(*poi, bv, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-
-  t->clone(coid, head);
-  setattr_maybe_cache(obc, ctx, t, OI_ATTR, bv);
-  rmattr_maybe_cache(obc, ctx, t, SS_ATTR);
-}
-
-void PrimaryLogPG::make_writeable(OpContext *ctx)
-{
-  const hobject_t& soid = ctx->obs->oi.soid;
-  SnapContext& snapc = ctx->snapc;
-
-  // clone?
-  assert(soid.snap == CEPH_NOSNAP);
-  dout(20) << "make_writeable " << soid << " snapset=" << ctx->new_snapset
-          << "  snapc=" << snapc << dendl;
-  
-  bool was_dirty = ctx->obc->obs.oi.is_dirty();
-  if (ctx->new_obs.exists) {
-    // we will mark the object dirty
-    if (ctx->undirty && was_dirty) {
-      dout(20) << " clearing DIRTY flag" << dendl;
-      assert(ctx->new_obs.oi.is_dirty());
-      ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY);
-      --ctx->delta_stats.num_objects_dirty;
-      osd->logger->inc(l_osd_tier_clean);
-    } else if (!was_dirty && !ctx->undirty) {
-      dout(20) << " setting DIRTY flag" << dendl;
-      ctx->new_obs.oi.set_flag(object_info_t::FLAG_DIRTY);
-      ++ctx->delta_stats.num_objects_dirty;
-      osd->logger->inc(l_osd_tier_dirty);
-    }
-  } else {
-    if (was_dirty) {
-      dout(20) << " deletion, decrementing num_dirty and clearing flag" << dendl;
-      ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY);
-      --ctx->delta_stats.num_objects_dirty;
-    }
-  }
-
-  if ((ctx->new_obs.exists &&
-       ctx->new_obs.oi.is_omap()) &&
-      (!ctx->obc->obs.exists ||
-       !ctx->obc->obs.oi.is_omap())) {
-    ++ctx->delta_stats.num_objects_omap;
-  }
-  if ((!ctx->new_obs.exists ||
-       !ctx->new_obs.oi.is_omap()) &&
-      (ctx->obc->obs.exists &&
-       ctx->obc->obs.oi.is_omap())) {
-    --ctx->delta_stats.num_objects_omap;
-  }
-
-  // use newer snapc?
-  if (ctx->new_snapset.seq > snapc.seq) {
-    snapc.seq = ctx->new_snapset.seq;
-    snapc.snaps = ctx->new_snapset.snaps;
-    filter_snapc(snapc.snaps);
-    dout(10) << " using newer snapc " << snapc << dendl;
-  }
-
-  if ((ctx->obs->exists && !ctx->obs->oi.is_whiteout()) && // head exist(ed)
-      snapc.snaps.size() &&                 // there are snaps
-      !ctx->cache_evict &&
-      snapc.snaps[0] > ctx->new_snapset.seq) {  // existing object is old
-    // clone
-    hobject_t coid = soid;
-    coid.snap = snapc.seq;
-    
-    unsigned l;
-    for (l=1; l<snapc.snaps.size() && snapc.snaps[l] > ctx->new_snapset.seq; l++) ;
-    
-    vector<snapid_t> snaps(l);
-    for (unsigned i=0; i<l; i++)
-      snaps[i] = snapc.snaps[i];
-    
-    // prepare clone
-    object_info_t static_snap_oi(coid);
-    object_info_t *snap_oi;
-    if (is_primary()) {
-      ctx->clone_obc = object_contexts.lookup_or_create(static_snap_oi.soid);
-      ctx->clone_obc->destructor_callback = new C_PG_ObjectContext(this, ctx->clone_obc.get());
-      ctx->clone_obc->obs.oi = static_snap_oi;
-      ctx->clone_obc->obs.exists = true;
-      ctx->clone_obc->ssc = ctx->obc->ssc;
-      ctx->clone_obc->ssc->ref++;
-      if (pool.info.require_rollback())
-       ctx->clone_obc->attr_cache = ctx->obc->attr_cache;
-      snap_oi = &ctx->clone_obc->obs.oi;
-      bool got = ctx->lock_manager.get_write_greedy(
-       coid,
-       ctx->clone_obc,
-       ctx->op);
-      assert(got);
-      dout(20) << " got greedy write on clone_obc " << *ctx->clone_obc << dendl;
-    } else {
-      snap_oi = &static_snap_oi;
-    }
-    snap_oi->version = ctx->at_version;
-    snap_oi->prior_version = ctx->obs->oi.version;
-    snap_oi->copy_user_bits(ctx->obs->oi);
-
-    bool legacy = ctx->new_snapset.is_legacy() ||
-      get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS;
-    if (legacy) {
-      snap_oi->legacy_snaps = snaps;
-    }
-
-    _make_clone(ctx, ctx->op_t.get(), ctx->clone_obc, soid, coid, snap_oi);
-    
-    ctx->delta_stats.num_objects++;
-    if (snap_oi->is_dirty()) {
-      ctx->delta_stats.num_objects_dirty++;
-      osd->logger->inc(l_osd_tier_dirty);
-    }
-    if (snap_oi->is_omap())
-      ctx->delta_stats.num_objects_omap++;
-    if (snap_oi->is_cache_pinned())
-      ctx->delta_stats.num_objects_pinned++;
-    ctx->delta_stats.num_object_clones++;
-    ctx->new_snapset.clones.push_back(coid.snap);
-    ctx->new_snapset.clone_size[coid.snap] = ctx->obs->oi.size;
-    if (!legacy) {
-      ctx->new_snapset.clone_snaps[coid.snap] = snaps;
-    }
-
-    // clone_overlap should contain an entry for each clone 
-    // (an empty interval_set if there is no overlap)
-    ctx->new_snapset.clone_overlap[coid.snap];
-    if (ctx->obs->oi.size)
-      ctx->new_snapset.clone_overlap[coid.snap].insert(0, ctx->obs->oi.size);
-    
-    // log clone
-    dout(10) << " cloning v " << ctx->obs->oi.version
-            << " to " << coid << " v " << ctx->at_version
-            << " snaps=" << snaps
-            << " snapset=" << ctx->new_snapset << dendl;
-    ctx->log.push_back(pg_log_entry_t(pg_log_entry_t::CLONE, coid, ctx->at_version,
-                                     ctx->obs->oi.version,
-                                     ctx->obs->oi.user_version,
-                                     osd_reqid_t(), ctx->new_obs.oi.mtime, 0));
-    ::encode(snaps, ctx->log.back().snaps);
-
-    ctx->at_version.version++;
-  }
-
-  // update most recent clone_overlap and usage stats
-  if (ctx->new_snapset.clones.size() > 0) {
-    /* we need to check whether the most recent clone exists, if it's been evicted,
-     * it's not included in the stats */
-    hobject_t last_clone_oid = soid;
-    last_clone_oid.snap = ctx->new_snapset.clone_overlap.rbegin()->first;
-    if (is_present_clone(last_clone_oid)) {
-      interval_set<uint64_t> &newest_overlap = ctx->new_snapset.clone_overlap.rbegin()->second;
-      ctx->modified_ranges.intersection_of(newest_overlap);
-      // modified_ranges is still in use by the clone
-      add_interval_usage(ctx->modified_ranges, ctx->delta_stats);
-      newest_overlap.subtract(ctx->modified_ranges);
-    }
-  }
-  
-  // update snapset with latest snap context
-  ctx->new_snapset.seq = snapc.seq;
-  ctx->new_snapset.snaps = snapc.snaps;
-  if (get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS) {
-    // pessimistic assumption that this is a net-new legacy SnapSet
-    ctx->delta_stats.num_legacy_snapsets++;
-    ctx->new_snapset.head_exists = ctx->new_obs.exists;
-  } else if (ctx->new_snapset.is_legacy()) {
-    ctx->new_snapset.head_exists = ctx->new_obs.exists;
-  }
-  dout(20) << "make_writeable " << soid
-          << " done, snapset=" << ctx->new_snapset << dendl;
-}
-
-
-void PrimaryLogPG::write_update_size_and_usage(object_stat_sum_t& delta_stats, object_info_t& oi,
-                                              interval_set<uint64_t>& modified, uint64_t offset,
-                                              uint64_t length, bool write_full)
-{
-  interval_set<uint64_t> ch;
-  if (write_full) {
-    if (oi.size)
-      ch.insert(0, oi.size);
-  } else if (length)
-    ch.insert(offset, length);
-  modified.union_of(ch);
-  if (write_full || offset + length > oi.size) {
-    uint64_t new_size = offset + length;
-    delta_stats.num_bytes -= oi.size;
-    delta_stats.num_bytes += new_size;
-    oi.size = new_size;
-  }
-  delta_stats.num_wr++;
-  delta_stats.num_wr_kb += SHIFT_ROUND_UP(length, 10);
-}
-
-void PrimaryLogPG::add_interval_usage(interval_set<uint64_t>& s, object_stat_sum_t& delta_stats)
-{
-  for (interval_set<uint64_t>::const_iterator p = s.begin(); p != s.end(); ++p) {
-    delta_stats.num_bytes += p.get_len();
-  }
-}
-
-void PrimaryLogPG::complete_disconnect_watches(
-  ObjectContextRef obc,
-  const list<watch_disconnect_t> &to_disconnect)
-{
-  for (list<watch_disconnect_t>::const_iterator i =
-        to_disconnect.begin();
-       i != to_disconnect.end();
-       ++i) {
-    pair<uint64_t, entity_name_t> watcher(i->cookie, i->name);
-    auto watchers_entry = obc->watchers.find(watcher);
-    if (watchers_entry != obc->watchers.end()) {
-      WatchRef watch = watchers_entry->second;
-      dout(10) << "do_osd_op_effects disconnect watcher " << watcher << dendl;
-      obc->watchers.erase(watcher);
-      watch->remove(i->send_disconnect);
-    } else {
-      dout(10) << "do_osd_op_effects disconnect failed to find watcher "
-              << watcher << dendl;
-    }
-  }
-}
-
-void PrimaryLogPG::do_osd_op_effects(OpContext *ctx, const ConnectionRef& conn)
-{
-  entity_name_t entity = ctx->reqid.name;
-  dout(15) << "do_osd_op_effects " << entity << " con " << conn.get() << dendl;
-
-  // disconnects first
-  complete_disconnect_watches(ctx->obc, ctx->watch_disconnects);
-
-  assert(conn);
-
-  boost::intrusive_ptr<Session> session((Session *)conn->get_priv());
-  if (!session.get())
-    return;
-  session->put();  // get_priv() takes a ref, and so does the intrusive_ptr
-
-  for (list<pair<watch_info_t,bool> >::iterator i = ctx->watch_connects.begin();
-       i != ctx->watch_connects.end();
-       ++i) {
-    pair<uint64_t, entity_name_t> watcher(i->first.cookie, entity);
-    dout(15) << "do_osd_op_effects applying watch connect on session "
-            << session.get() << " watcher " << watcher << dendl;
-    WatchRef watch;
-    if (ctx->obc->watchers.count(watcher)) {
-      dout(15) << "do_osd_op_effects found existing watch watcher " << watcher
-              << dendl;
-      watch = ctx->obc->watchers[watcher];
-    } else {
-      dout(15) << "do_osd_op_effects new watcher " << watcher
-              << dendl;
-      watch = Watch::makeWatchRef(
-       this, osd, ctx->obc, i->first.timeout_seconds,
-       i->first.cookie, entity, conn->get_peer_addr());
-      ctx->obc->watchers.insert(
-       make_pair(
-         watcher,
-         watch));
-    }
-    watch->connect(conn, i->second);
-  }
-
-  for (list<notify_info_t>::iterator p = ctx->notifies.begin();
-       p != ctx->notifies.end();
-       ++p) {
-    dout(10) << "do_osd_op_effects, notify " << *p << dendl;
-    ConnectionRef conn(ctx->op->get_req()->get_connection());
-    NotifyRef notif(
-      Notify::makeNotifyRef(
-       conn,
-       ctx->reqid.name.num(),
-       p->bl,
-       p->timeout,
-       p->cookie,
-       p->notify_id,
-       ctx->obc->obs.oi.user_version,
-       osd));
-    for (map<pair<uint64_t, entity_name_t>, WatchRef>::iterator i =
-          ctx->obc->watchers.begin();
-        i != ctx->obc->watchers.end();
-        ++i) {
-      dout(10) << "starting notify on watch " << i->first << dendl;
-      i->second->start_notify(notif);
-    }
-    notif->init();
-  }
-
-  for (list<OpContext::NotifyAck>::iterator p = ctx->notify_acks.begin();
-       p != ctx->notify_acks.end();
-       ++p) {
-    if (p->watch_cookie)
-      dout(10) << "notify_ack " << make_pair(p->watch_cookie.get(), p->notify_id) << dendl;
-    else
-      dout(10) << "notify_ack " << make_pair("NULL", p->notify_id) << dendl;
-    for (map<pair<uint64_t, entity_name_t>, WatchRef>::iterator i =
-          ctx->obc->watchers.begin();
-        i != ctx->obc->watchers.end();
-        ++i) {
-      if (i->first.second != entity) continue;
-      if (p->watch_cookie &&
-         p->watch_cookie.get() != i->first.first) continue;
-      dout(10) << "acking notify on watch " << i->first << dendl;
-      i->second->notify_ack(p->notify_id, p->reply_bl);
-    }
-  }
-}
-
-hobject_t PrimaryLogPG::generate_temp_object(const hobject_t& target)
-{
-  ostringstream ss;
-  ss << "temp_" << info.pgid << "_" << get_role()
-     << "_" << osd->monc->get_global_id() << "_" << (++temp_seq);
-  hobject_t hoid = target.make_temp_hobject(ss.str());
-  dout(20) << __func__ << " " << hoid << dendl;
-  return hoid;
-}
-
-hobject_t PrimaryLogPG::get_temp_recovery_object(
-  const hobject_t& target,
-  eversion_t version)
-{
-  ostringstream ss;
-  ss << "temp_recovering_" << info.pgid  // (note this includes the shardid)
-     << "_" << version
-     << "_" << info.history.same_interval_since
-     << "_" << target.snap;
-  // pgid + version + interval + snapid is unique, and short
-  hobject_t hoid = target.make_temp_hobject(ss.str());
-  dout(20) << __func__ << " " << hoid << dendl;
-  return hoid;
-}
-
-int PrimaryLogPG::prepare_transaction(OpContext *ctx)
-{
-  assert(!ctx->ops->empty());
-
-  const hobject_t& soid = ctx->obs->oi.soid;
-
-  // valid snap context?
-  if (!ctx->snapc.is_valid()) {
-    dout(10) << " invalid snapc " << ctx->snapc << dendl;
-    return -EINVAL;
-  }
-
-  // prepare the actual mutation
-  int result = do_osd_ops(ctx, *ctx->ops);
-  if (result < 0) {
-    if (ctx->op->may_write() &&
-       get_osdmap()->require_osd_release >= CEPH_RELEASE_KRAKEN) {
-      // need to save the error code in the pg log, to detect dup ops,
-      // but do nothing else
-      ctx->update_log_only = true;
-    }
-    return result;
-  }
-
-  // read-op?  write-op noop? done?
-  if (ctx->op_t->empty() && !ctx->modify) {
-    unstable_stats.add(ctx->delta_stats);
-    if (ctx->op->may_write() &&
-       get_osdmap()->require_osd_release >= CEPH_RELEASE_KRAKEN) {
-      ctx->update_log_only = true;
-    }
-    return result;
-  }
-
-  // check for full
-  if ((ctx->delta_stats.num_bytes > 0 ||
-       ctx->delta_stats.num_objects > 0) &&  // FIXME: keys?
-      (pool.info.has_flag(pg_pool_t::FLAG_FULL) ||
-       get_osdmap()->test_flag(CEPH_OSDMAP_FULL))) {
-    const MOSDOp *m = static_cast<const MOSDOp*>(ctx->op->get_req());
-    if (ctx->reqid.name.is_mds() ||   // FIXME: ignore MDS for now
-       m->has_flag(CEPH_OSD_FLAG_FULL_FORCE)) {
-      dout(20) << __func__ << " full, but proceeding due to FULL_FORCE or MDS"
-              << dendl;
-    } else if (m->has_flag(CEPH_OSD_FLAG_FULL_TRY)) {
-      // they tried, they failed.
-      dout(20) << __func__ << " full, replying to FULL_TRY op" << dendl;
-      return pool.info.has_flag(pg_pool_t::FLAG_FULL) ? -EDQUOT : -ENOSPC;
-    } else {
-      // drop request
-      dout(20) << __func__ << " full, dropping request (bad client)" << dendl;
-      return -EAGAIN;
-    }
-  }
-
-  // clone, if necessary
-  if (soid.snap == CEPH_NOSNAP)
-    make_writeable(ctx);
-
-  finish_ctx(ctx,
-            ctx->new_obs.exists ? pg_log_entry_t::MODIFY :
-            pg_log_entry_t::DELETE);
-
-  return result;
-}
-
-void PrimaryLogPG::finish_ctx(OpContext *ctx, int log_op_type, bool maintain_ssc)
-{
-  const hobject_t& soid = ctx->obs->oi.soid;
-  dout(20) << __func__ << " " << soid << " " << ctx
-          << " op " << pg_log_entry_t::get_op_name(log_op_type)
-          << dendl;
-  utime_t now = ceph_clock_now();
-
-  // snapset
-  bufferlist bss;
-
-  if (soid.snap == CEPH_NOSNAP && maintain_ssc) {
-    ::encode(ctx->new_snapset, bss);
-    assert(ctx->new_obs.exists == ctx->new_snapset.head_exists ||
-          !ctx->new_snapset.is_legacy());
-
-    if (ctx->new_obs.exists) {
-      if (!ctx->obs->exists) {
-       if (ctx->snapset_obc && ctx->snapset_obc->obs.exists) {
-         hobject_t snapoid = soid.get_snapdir();
-         dout(10) << " removing unneeded snapdir " << snapoid << dendl;
-         ctx->log.push_back(pg_log_entry_t(pg_log_entry_t::DELETE, snapoid,
-             ctx->at_version,
-             ctx->snapset_obc->obs.oi.version,
-             0, osd_reqid_t(), ctx->mtime, 0));
-         ctx->op_t->remove(snapoid);
-
-         ctx->at_version.version++;
-
-         ctx->snapset_obc->obs.exists = false;
-       }
-      }
-    } else if (!ctx->new_snapset.clones.empty() &&
-              !ctx->cache_evict &&
-              !ctx->new_snapset.head_exists &&
-              (!ctx->snapset_obc || !ctx->snapset_obc->obs.exists)) {
-      // save snapset on _snap
-      hobject_t snapoid(soid.oid, soid.get_key(), CEPH_SNAPDIR, soid.get_hash(),
-                       info.pgid.pool(), soid.get_namespace());
-      dout(10) << " final snapset " << ctx->new_snapset
-              << " in " << snapoid << dendl;
-      assert(get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS);
-      ctx->log.push_back(pg_log_entry_t(pg_log_entry_t::MODIFY, snapoid,
-                                       ctx->at_version,
-                                       eversion_t(),
-                                       0, osd_reqid_t(), ctx->mtime, 0));
-
-      if (!ctx->snapset_obc)
-       ctx->snapset_obc = get_object_context(snapoid, true);
-      bool got = false;
-      if (ctx->lock_type == ObjectContext::RWState::RWWRITE) {
-       got = ctx->lock_manager.get_write_greedy(
-         snapoid,
-         ctx->snapset_obc,
-         ctx->op);
-      } else {
-       assert(ctx->lock_type == ObjectContext::RWState::RWEXCL);
-       got = ctx->lock_manager.get_lock_type(
-         ObjectContext::RWState::RWEXCL,
-         snapoid,
-         ctx->snapset_obc,
-         ctx->op);
-      }
-      assert(got);
-      dout(20) << " got greedy write on snapset_obc " << *ctx->snapset_obc << dendl;
-      ctx->snapset_obc->obs.exists = true;
-      ctx->snapset_obc->obs.oi.version = ctx->at_version;
-      ctx->snapset_obc->obs.oi.last_reqid = ctx->reqid;
-      ctx->snapset_obc->obs.oi.mtime = ctx->mtime;
-      ctx->snapset_obc->obs.oi.local_mtime = now;
-
-      map<string, bufferlist> attrs;
-      bufferlist bv(sizeof(ctx->new_obs.oi));
-      ::encode(ctx->snapset_obc->obs.oi, bv,
-              get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-      ctx->op_t->create(snapoid);
-      attrs[OI_ATTR].claim(bv);
-      attrs[SS_ATTR].claim(bss);
-      setattrs_maybe_cache(ctx->snapset_obc, ctx, ctx->op_t.get(), attrs);
-      ctx->at_version.version++;
-    }
-  }
-
-  // finish and log the op.
-  if (ctx->user_modify) {
-    // update the user_version for any modify ops, except for the watch op
-    ctx->user_at_version = MAX(info.last_user_version, ctx->new_obs.oi.user_version) + 1;
-    /* In order for new clients and old clients to interoperate properly
-     * when exchanging versions, we need to lower bound the user_version
-     * (which our new clients pay proper attention to)
-     * by the at_version (which is all the old clients can ever see). */
-    if (ctx->at_version.version > ctx->user_at_version)
-      ctx->user_at_version = ctx->at_version.version;
-    ctx->new_obs.oi.user_version = ctx->user_at_version;
-  }
-  ctx->bytes_written = ctx->op_t->get_bytes_written();
-  if (ctx->new_obs.exists) {
-    // on the head object
-    ctx->new_obs.oi.version = ctx->at_version;
-    ctx->new_obs.oi.prior_version = ctx->obs->oi.version;
-    ctx->new_obs.oi.last_reqid = ctx->reqid;
-    if (ctx->mtime != utime_t()) {
-      ctx->new_obs.oi.mtime = ctx->mtime;
-      dout(10) << " set mtime to " << ctx->new_obs.oi.mtime << dendl;
-      ctx->new_obs.oi.local_mtime = now;
-    } else {
-      dout(10) << " mtime unchanged at " << ctx->new_obs.oi.mtime << dendl;
-    }
-
-    map <string, bufferlist> attrs;
-    bufferlist bv(sizeof(ctx->new_obs.oi));
-    ::encode(ctx->new_obs.oi, bv,
-            get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-    attrs[OI_ATTR].claim(bv);
-
-    if (soid.snap == CEPH_NOSNAP) {
-      dout(10) << " final snapset " << ctx->new_snapset
-              << " in " << soid << dendl;
-      attrs[SS_ATTR].claim(bss);
-    } else {
-      dout(10) << " no snapset (this is a clone)" << dendl;
-    }
-    ctx->op_t->setattrs(soid, attrs);
-  } else {
-    ctx->new_obs.oi = object_info_t(ctx->obc->obs.oi.soid);
-  }
-
-  bool legacy_snapset = ctx->new_snapset.is_legacy() ||
-    get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS;
-
-  // append to log
-  ctx->log.push_back(pg_log_entry_t(log_op_type, soid, ctx->at_version,
-                                   ctx->obs->oi.version,
-                                   ctx->user_at_version, ctx->reqid,
-                                   ctx->mtime, 0));
-  if (soid.snap < CEPH_NOSNAP) {
-    switch (log_op_type) {
-    case pg_log_entry_t::MODIFY:
-    case pg_log_entry_t::PROMOTE:
-    case pg_log_entry_t::CLEAN:
-      if (legacy_snapset) {
-       dout(20) << __func__ << " encoding legacy_snaps "
-                << ctx->new_obs.oi.legacy_snaps
-                << dendl;
-       ::encode(ctx->new_obs.oi.legacy_snaps, ctx->log.back().snaps);
-      } else {
-       dout(20) << __func__ << " encoding snaps from " << ctx->new_snapset
-                << dendl;
-       ::encode(ctx->new_snapset.clone_snaps[soid.snap], ctx->log.back().snaps);
-      }
-      break;
-    default:
-      break;
-    }
-  }
-
-  if (!ctx->extra_reqids.empty()) {
-    dout(20) << __func__ << "  extra_reqids " << ctx->extra_reqids << dendl;
-    ctx->log.back().extra_reqids.swap(ctx->extra_reqids);
-  }
-
-  // apply new object state.
-  ctx->obc->obs = ctx->new_obs;
-
-  if (soid.is_head() && !ctx->obc->obs.exists &&
-      (!maintain_ssc || ctx->cache_evict)) {
-    ctx->obc->ssc->exists = false;
-    ctx->obc->ssc->snapset = SnapSet();
-  } else {
-    ctx->obc->ssc->exists = true;
-    ctx->obc->ssc->snapset = ctx->new_snapset;
-  }
-}
-
-void PrimaryLogPG::apply_stats(
-  const hobject_t &soid,
-  const object_stat_sum_t &delta_stats) {
-
-  info.stats.stats.add(delta_stats);
-
-  for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    pg_shard_t bt = *i;
-    pg_info_t& pinfo = peer_info[bt];
-    if (soid <= pinfo.last_backfill)
-      pinfo.stats.stats.add(delta_stats);
-    else if (soid <= last_backfill_started)
-      pending_backfill_updates[soid].stats.add(delta_stats);
-  }
-
-  if (is_primary() && scrubber.active) {
-    if (soid < scrubber.start) {
-      dout(20) << __func__ << " " << soid << " < [" << scrubber.start
-              << "," << scrubber.end << ")" << dendl;
-      scrub_cstat.add(delta_stats);
-    } else {
-      dout(20) << __func__ << " " << soid << " >= [" << scrubber.start
-              << "," << scrubber.end << ")" << dendl;
-    }
-  }
-}
-
-void PrimaryLogPG::complete_read_ctx(int result, OpContext *ctx)
-{
-  const MOSDOp *m = static_cast<const MOSDOp*>(ctx->op->get_req());
-  assert(ctx->async_reads_complete());
-
-  for (vector<OSDOp>::iterator p = ctx->ops->begin();
-    p != ctx->ops->end() && result >= 0; ++p) {
-    if (p->rval < 0 && !(p->op.flags & CEPH_OSD_OP_FLAG_FAILOK)) {
-      result = p->rval;
-      break;
-    }
-    ctx->bytes_read += p->outdata.length();
-  }
-  ctx->reply->claim_op_out_data(*ctx->ops);
-  ctx->reply->get_header().data_off = (ctx->data_off ? *ctx->data_off : 0);
-
-  MOSDOpReply *reply = ctx->reply;
-  ctx->reply = nullptr;
-
-  if (result >= 0) {
-    if (!ctx->ignore_log_op_stats) {
-      log_op_stats(ctx);
-      publish_stats_to_osd();
-    }
-
-    // on read, return the current object version
-    if (ctx->obs) {
-      reply->set_reply_versions(eversion_t(), ctx->obs->oi.user_version);
-    } else {
-      reply->set_reply_versions(eversion_t(), ctx->user_at_version);
-    }
-  } else if (result == -ENOENT) {
-    // on ENOENT, set a floor for what the next user version will be.
-    reply->set_enoent_reply_versions(info.last_update, info.last_user_version);
-  }
-
-  reply->set_result(result);
-  reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-  osd->send_message_osd_client(reply, m->get_connection());
-  close_op_ctx(ctx);
-}
-
-// ========================================================================
-// copyfrom
-
-struct C_Copyfrom : public Context {
-  PrimaryLogPGRef pg;
-  hobject_t oid;
-  epoch_t last_peering_reset;
-  ceph_tid_t tid;
-  PrimaryLogPG::CopyOpRef cop;
-  C_Copyfrom(PrimaryLogPG *p, hobject_t o, epoch_t lpr,
-            const PrimaryLogPG::CopyOpRef& c)
-    : pg(p), oid(o), last_peering_reset(lpr),
-      tid(0), cop(c)
-  {}
-  void finish(int r) override {
-    if (r == -ECANCELED)
-      return;
-    pg->lock();
-    if (last_peering_reset == pg->get_last_peering_reset()) {
-      pg->process_copy_chunk(oid, tid, r);
-    }
-    pg->unlock();
-  }
-};
-
-struct C_CopyFrom_AsyncReadCb : public Context {
-  OSDOp *osd_op;
-  object_copy_data_t reply_obj;
-  uint64_t features;
-  size_t len;
-  C_CopyFrom_AsyncReadCb(OSDOp *osd_op, uint64_t features) :
-    osd_op(osd_op), features(features), len(0) {}
-  void finish(int r) override {
-    osd_op->rval = r;
-    if (r < 0) {
-      return;
-    }
-
-    assert(len > 0);
-    assert(len <= reply_obj.data.length());
-    bufferlist bl;
-    bl.substr_of(reply_obj.data, 0, len);
-    reply_obj.data.swap(bl);
-    ::encode(reply_obj, osd_op->outdata, features);
-  }
-};
-
-int PrimaryLogPG::do_copy_get(OpContext *ctx, bufferlist::iterator& bp,
-                             OSDOp& osd_op, ObjectContextRef &obc)
-{
-  object_info_t& oi = obc->obs.oi;
-  hobject_t& soid = oi.soid;
-  int result = 0;
-  object_copy_cursor_t cursor;
-  uint64_t out_max;
-  try {
-    ::decode(cursor, bp);
-    ::decode(out_max, bp);
-  }
-  catch (buffer::error& e) {
-    result = -EINVAL;
-    return result;
-  }
-
-  const MOSDOp *op = reinterpret_cast<const MOSDOp*>(ctx->op->get_req());
-  uint64_t features = op->get_features();
-
-  bool async_read_started = false;
-  object_copy_data_t _reply_obj;
-  C_CopyFrom_AsyncReadCb *cb = NULL;
-  if (pool.info.require_rollback()) {
-    cb = new C_CopyFrom_AsyncReadCb(&osd_op, features);
-  }
-  object_copy_data_t &reply_obj = cb ? cb->reply_obj : _reply_obj;
-  // size, mtime
-  reply_obj.size = oi.size;
-  reply_obj.mtime = oi.mtime;
-  assert(obc->ssc);
-  if (soid.snap < CEPH_NOSNAP) {
-    if (obc->ssc->snapset.is_legacy()) {
-      reply_obj.snaps = oi.legacy_snaps;
-    } else {
-      auto p = obc->ssc->snapset.clone_snaps.find(soid.snap);
-      assert(p != obc->ssc->snapset.clone_snaps.end()); // warn?
-      reply_obj.snaps = p->second;
-    }
-  } else {
-    reply_obj.snap_seq = obc->ssc->snapset.seq;
-  }
-  if (oi.is_data_digest()) {
-    reply_obj.flags |= object_copy_data_t::FLAG_DATA_DIGEST;
-    reply_obj.data_digest = oi.data_digest;
-  }
-  if (oi.is_omap_digest()) {
-    reply_obj.flags |= object_copy_data_t::FLAG_OMAP_DIGEST;
-    reply_obj.omap_digest = oi.omap_digest;
-  }
-  reply_obj.truncate_seq = oi.truncate_seq;
-  reply_obj.truncate_size = oi.truncate_size;
-
-  // attrs
-  map<string,bufferlist>& out_attrs = reply_obj.attrs;
-  if (!cursor.attr_complete) {
-    result = getattrs_maybe_cache(
-      ctx->obc,
-      &out_attrs,
-      true);
-    if (result < 0) {
-      if (cb) {
-        delete cb;
-      }
-      return result;
-    }
-    cursor.attr_complete = true;
-    dout(20) << " got attrs" << dendl;
-  }
-
-  int64_t left = out_max - osd_op.outdata.length();
-
-  // data
-  bufferlist& bl = reply_obj.data;
-  if (left > 0 && !cursor.data_complete) {
-    if (cursor.data_offset < oi.size) {
-      uint64_t max_read = MIN(oi.size - cursor.data_offset, (uint64_t)left);
-      if (cb) {
-       async_read_started = true;
-       ctx->pending_async_reads.push_back(
-         make_pair(
-           boost::make_tuple(cursor.data_offset, max_read, osd_op.op.flags),
-           make_pair(&bl, cb)));
-       cb->len = max_read;
-
-        ctx->op_finishers[ctx->current_osd_subop_num].reset(
-          new ReadFinisher(osd_op));
-       result = -EINPROGRESS;
-
-       dout(10) << __func__ << ": async_read noted for " << soid << dendl;
-      } else {
-       result = pgbackend->objects_read_sync(
-         oi.soid, cursor.data_offset, max_read, osd_op.op.flags, &bl);
-       if (result < 0)
-         return result;
-      }
-      left -= max_read;
-      cursor.data_offset += max_read;
-    }
-    if (cursor.data_offset == oi.size) {
-      cursor.data_complete = true;
-      dout(20) << " got data" << dendl;
-    }
-    assert(cursor.data_offset <= oi.size);
-  }
-
-  // omap
-  uint32_t omap_keys = 0;
-  if (!pool.info.supports_omap() || !oi.is_omap()) {
-    cursor.omap_complete = true;
-  } else {
-    if (left > 0 && !cursor.omap_complete) {
-      assert(cursor.data_complete);
-      if (cursor.omap_offset.empty()) {
-       osd->store->omap_get_header(ch, ghobject_t(oi.soid),
-                                   &reply_obj.omap_header);
-      }
-      bufferlist omap_data;
-      ObjectMap::ObjectMapIterator iter =
-       osd->store->get_omap_iterator(coll, ghobject_t(oi.soid));
-      assert(iter);
-      iter->upper_bound(cursor.omap_offset);
-      for (; iter->valid(); iter->next(false)) {
-       ++omap_keys;
-       ::encode(iter->key(), omap_data);
-       ::encode(iter->value(), omap_data);
-       left -= iter->key().length() + 4 + iter->value().length() + 4;
-       if (left <= 0)
-         break;
-      }
-      if (omap_keys) {
-       ::encode(omap_keys, reply_obj.omap_data);
-       reply_obj.omap_data.claim_append(omap_data);
-      }
-      if (iter->valid()) {
-       cursor.omap_offset = iter->key();
-      } else {
-       cursor.omap_complete = true;
-       dout(20) << " got omap" << dendl;
-      }
-    }
-  }
-
-  if (cursor.is_complete()) {
-    // include reqids only in the final step.  this is a bit fragile
-    // but it works...
-    pg_log.get_log().get_object_reqids(ctx->obc->obs.oi.soid, 10, &reply_obj.reqids);
-    dout(20) << " got reqids" << dendl;
-  }
-
-  dout(20) << " cursor.is_complete=" << cursor.is_complete()
-          << " " << out_attrs.size() << " attrs"
-          << " " << bl.length() << " bytes"
-          << " " << reply_obj.omap_header.length() << " omap header bytes"
-          << " " << reply_obj.omap_data.length() << " omap data bytes in "
-          << omap_keys << " keys"
-          << " " << reply_obj.reqids.size() << " reqids"
-          << dendl;
-  reply_obj.cursor = cursor;
-  if (!async_read_started) {
-    ::encode(reply_obj, osd_op.outdata, features);
-  }
-  if (cb && !async_read_started) {
-    delete cb;
-  }
-
-  if (result > 0) {
-    result = 0;
-  }
-  return result;
-}
-
-void PrimaryLogPG::fill_in_copy_get_noent(OpRequestRef& op, hobject_t oid,
-                                          OSDOp& osd_op)
-{
-  // NOTE: we take non-const ref here for claim_op_out_data below; we must
-  // be careful not to modify anything else that will upset a racing
-  // operator<<
-  MOSDOp *m = static_cast<MOSDOp*>(op->get_nonconst_req());
-  uint64_t features = m->get_features();
-  object_copy_data_t reply_obj;
-
-  pg_log.get_log().get_object_reqids(oid, 10, &reply_obj.reqids);
-  dout(20) << __func__ << " got reqids " << reply_obj.reqids << dendl;
-  ::encode(reply_obj, osd_op.outdata, features);
-  osd_op.rval = -ENOENT;
-  MOSDOpReply *reply = new MOSDOpReply(m, 0, get_osdmap()->get_epoch(), 0, false);
-  reply->claim_op_out_data(m->ops);
-  reply->set_result(-ENOENT);
-  reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK);
-  osd->send_message_osd_client(reply, m->get_connection());
-}
-
-void PrimaryLogPG::start_copy(CopyCallback *cb, ObjectContextRef obc,
-                             hobject_t src, object_locator_t oloc,
-                             version_t version, unsigned flags,
-                             bool mirror_snapset,
-                             unsigned src_obj_fadvise_flags,
-                             unsigned dest_obj_fadvise_flags)
-{
-  const hobject_t& dest = obc->obs.oi.soid;
-  dout(10) << __func__ << " " << dest
-          << " from " << src << " " << oloc << " v" << version
-          << " flags " << flags
-          << (mirror_snapset ? " mirror_snapset" : "")
-          << dendl;
-
-  assert(!mirror_snapset || (src.snap == CEPH_NOSNAP ||
-                            src.snap == CEPH_SNAPDIR));
-
-  // cancel a previous in-progress copy?
-  if (copy_ops.count(dest)) {
-    // FIXME: if the src etc match, we could avoid restarting from the
-    // beginning.
-    CopyOpRef cop = copy_ops[dest];
-    cancel_copy(cop, false);
-  }
-
-  CopyOpRef cop(std::make_shared<CopyOp>(cb, obc, src, oloc, version, flags,
-                          mirror_snapset, src_obj_fadvise_flags,
-                          dest_obj_fadvise_flags));
-  copy_ops[dest] = cop;
-  obc->start_block();
-
-  _copy_some(obc, cop);
-}
-
-void PrimaryLogPG::_copy_some(ObjectContextRef obc, CopyOpRef cop)
-{
-  dout(10) << __func__ << " " << obc << " " << cop << dendl;
-
-  unsigned flags = 0;
-  if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_FLUSH)
-    flags |= CEPH_OSD_FLAG_FLUSH;
-  if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE)
-    flags |= CEPH_OSD_FLAG_IGNORE_CACHE;
-  if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY)
-    flags |= CEPH_OSD_FLAG_IGNORE_OVERLAY;
-  if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE)
-    flags |= CEPH_OSD_FLAG_MAP_SNAP_CLONE;
-  if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_RWORDERED)
-    flags |= CEPH_OSD_FLAG_RWORDERED;
-
-  C_GatherBuilder gather(cct);
-
-  if (cop->cursor.is_initial() && cop->mirror_snapset) {
-    // list snaps too.
-    assert(cop->src.snap == CEPH_NOSNAP);
-    ObjectOperation op;
-    op.list_snaps(&cop->results.snapset, NULL);
-    ceph_tid_t tid = osd->objecter->read(cop->src.oid, cop->oloc, op,
-                                   CEPH_SNAPDIR, NULL,
-                                   flags, gather.new_sub(), NULL);
-    cop->objecter_tid2 = tid;
-  }
-
-  ObjectOperation op;
-  if (cop->results.user_version) {
-    op.assert_version(cop->results.user_version);
-  } else {
-    // we should learn the version after the first chunk, if we didn't know
-    // it already!
-    assert(cop->cursor.is_initial());
-  }
-  op.copy_get(&cop->cursor, get_copy_chunk_size(),
-             &cop->results.object_size, &cop->results.mtime,
-             &cop->attrs, &cop->data, &cop->omap_header, &cop->omap_data,
-             &cop->results.snaps, &cop->results.snap_seq,
-             &cop->results.flags,
-             &cop->results.source_data_digest,
-             &cop->results.source_omap_digest,
-             &cop->results.reqids,
-             &cop->results.truncate_seq,
-             &cop->results.truncate_size,
-             &cop->rval);
-  op.set_last_op_flags(cop->src_obj_fadvise_flags);
-
-  C_Copyfrom *fin = new C_Copyfrom(this, obc->obs.oi.soid,
-                                  get_last_peering_reset(), cop);
-  gather.set_finisher(new C_OnFinisher(fin,
-                                      &osd->objecter_finisher));
-
-  ceph_tid_t tid = osd->objecter->read(cop->src.oid, cop->oloc, op,
-                                 cop->src.snap, NULL,
-                                 flags,
-                                 gather.new_sub(),
-                                 // discover the object version if we don't know it yet
-                                 cop->results.user_version ? NULL : &cop->results.user_version);
-  fin->tid = tid;
-  cop->objecter_tid = tid;
-  gather.activate();
-}
-
-void PrimaryLogPG::process_copy_chunk(hobject_t oid, ceph_tid_t tid, int r)
-{
-  dout(10) << __func__ << " " << oid << " tid " << tid
-          << " " << cpp_strerror(r) << dendl;
-  map<hobject_t,CopyOpRef>::iterator p = copy_ops.find(oid);
-  if (p == copy_ops.end()) {
-    dout(10) << __func__ << " no copy_op found" << dendl;
-    return;
-  }
-  CopyOpRef cop = p->second;
-  if (tid != cop->objecter_tid) {
-    dout(10) << __func__ << " tid " << tid << " != cop " << cop
-            << " tid " << cop->objecter_tid << dendl;
-    return;
-  }
-
-  if (cop->omap_data.length() || cop->omap_header.length())
-    cop->results.has_omap = true;
-
-  if (r >= 0 && !pool.info.supports_omap() &&
-      (cop->omap_data.length() || cop->omap_header.length())) {
-    r = -EOPNOTSUPP;
-  }
-  cop->objecter_tid = 0;
-  cop->objecter_tid2 = 0;  // assume this ordered before us (if it happened)
-  ObjectContextRef& cobc = cop->obc;
-
-  if (r < 0)
-    goto out;
-
-  assert(cop->rval >= 0);
-
-  if (oid.snap < CEPH_NOSNAP && !cop->results.snaps.empty()) {
-    // verify snap hasn't been deleted
-    vector<snapid_t>::iterator p = cop->results.snaps.begin();
-    while (p != cop->results.snaps.end()) {
-      if (pool.info.is_removed_snap(*p)) {
-       dout(10) << __func__ << " clone snap " << *p << " has been deleted"
-                << dendl;
-       for (vector<snapid_t>::iterator q = p + 1;
-            q != cop->results.snaps.end();
-            ++q)
-         *(q - 1) = *q;
-       cop->results.snaps.resize(cop->results.snaps.size() - 1);
-      } else {
-       ++p;
-      }
-    }
-    if (cop->results.snaps.empty()) {
-      dout(10) << __func__ << " no more snaps for " << oid << dendl;
-      r = -ENOENT;
-      goto out;
-    }
-  }
-
-  assert(cop->rval >= 0);
-
-  if (!cop->temp_cursor.data_complete) {
-    cop->results.data_digest = cop->data.crc32c(cop->results.data_digest);
-  }
-  if (pool.info.supports_omap() && !cop->temp_cursor.omap_complete) {
-    if (cop->omap_header.length()) {
-      cop->results.omap_digest =
-       cop->omap_header.crc32c(cop->results.omap_digest);
-    }
-    if (cop->omap_data.length()) {
-      bufferlist keys;
-      keys.substr_of(cop->omap_data, 4, cop->omap_data.length() - 4);
-      cop->results.omap_digest = keys.crc32c(cop->results.omap_digest);
-    }
-  }
-
-  if (!cop->temp_cursor.attr_complete) {
-    for (map<string,bufferlist>::iterator p = cop->attrs.begin();
-        p != cop->attrs.end();
-        ++p) {
-      cop->results.attrs[string("_") + p->first] = p->second;
-    }
-    cop->attrs.clear();
-  }
-
-  if (!cop->cursor.is_complete()) {
-    // write out what we have so far
-    if (cop->temp_cursor.is_initial()) {
-      assert(!cop->results.started_temp_obj);
-      cop->results.started_temp_obj = true;
-      cop->results.temp_oid = generate_temp_object(oid);
-      dout(20) << __func__ << " using temp " << cop->results.temp_oid << dendl;
-    }
-    ObjectContextRef tempobc = get_object_context(cop->results.temp_oid, true);
-    OpContextUPtr ctx = simple_opc_create(tempobc);
-    if (cop->temp_cursor.is_initial()) {
-      ctx->new_temp_oid = cop->results.temp_oid;
-    }
-    _write_copy_chunk(cop, ctx->op_t.get());
-    simple_opc_submit(std::move(ctx));
-    dout(10) << __func__ << " fetching more" << dendl;
-    _copy_some(cobc, cop);
-    return;
-  }
-
-  // verify digests?
-  if (cop->results.is_data_digest() || cop->results.is_omap_digest()) {
-    dout(20) << __func__ << std::hex
-      << " got digest: rx data 0x" << cop->results.data_digest
-      << " omap 0x" << cop->results.omap_digest
-      << ", source: data 0x" << cop->results.source_data_digest
-      << " omap 0x" <<  cop->results.source_omap_digest
-      << std::dec
-      << " flags " << cop->results.flags
-      << dendl;
-  }
-  if (cop->results.is_data_digest() &&
-      cop->results.data_digest != cop->results.source_data_digest) {
-    derr << __func__ << std::hex << " data digest 0x" << cop->results.data_digest
-        << " != source 0x" << cop->results.source_data_digest << std::dec
-        << dendl;
-    osd->clog->error() << info.pgid << " copy from " << cop->src
-                      << " to " << cop->obc->obs.oi.soid << std::hex
-                      << " data digest 0x" << cop->results.data_digest
-                      << " != source 0x" << cop->results.source_data_digest
-                      << std::dec;
-    r = -EIO;
-    goto out;
-  }
-  if (cop->results.is_omap_digest() &&
-      cop->results.omap_digest != cop->results.source_omap_digest) {
-    derr << __func__ << std::hex
-        << " omap digest 0x" << cop->results.omap_digest
-        << " != source 0x" << cop->results.source_omap_digest
-        << std::dec << dendl;
-    osd->clog->error() << info.pgid << " copy from " << cop->src
-                      << " to " << cop->obc->obs.oi.soid << std::hex
-                      << " omap digest 0x" << cop->results.omap_digest
-                      << " != source 0x" << cop->results.source_omap_digest
-                      << std::dec;
-    r = -EIO;
-    goto out;
-  }
-  if (cct->_conf->osd_debug_inject_copyfrom_error) {
-    derr << __func__ << " injecting copyfrom failure" << dendl;
-    r = -EIO;
-    goto out;
-  }
-
-  cop->results.fill_in_final_tx = std::function<void(PGTransaction*)>(
-    [this, &cop /* avoid ref cycle */](PGTransaction *t) {
-      ObjectState& obs = cop->obc->obs;
-      if (cop->temp_cursor.is_initial()) {
-       dout(20) << "fill_in_final_tx: writing "
-                << "directly to final object" << dendl;
-       // write directly to final object
-       cop->results.temp_oid = obs.oi.soid;
-       _write_copy_chunk(cop, t);
-      } else {
-       // finish writing to temp object, then move into place
-       dout(20) << "fill_in_final_tx: writing to temp object" << dendl;
-       _write_copy_chunk(cop, t);
-       t->rename(obs.oi.soid, cop->results.temp_oid);
-      }
-      t->setattrs(obs.oi.soid, cop->results.attrs);
-    });
-
-  dout(20) << __func__ << " success; committing" << dendl;
-
- out:
-  dout(20) << __func__ << " complete r = " << cpp_strerror(r) << dendl;
-  CopyCallbackResults results(r, &cop->results);
-  cop->cb->complete(results);
-
-  copy_ops.erase(cobc->obs.oi.soid);
-  cobc->stop_block();
-
-  if (r < 0 && cop->results.started_temp_obj) {
-    dout(10) << __func__ << " deleting partial temp object "
-            << cop->results.temp_oid << dendl;
-    ObjectContextRef tempobc = get_object_context(cop->results.temp_oid, true);
-    OpContextUPtr ctx = simple_opc_create(tempobc);
-    ctx->op_t->remove(cop->results.temp_oid);
-    ctx->discard_temp_oid = cop->results.temp_oid;
-    simple_opc_submit(std::move(ctx));
-  }
-
-  // cancel and requeue proxy ops on this object
-  if (!r) {
-    for (map<ceph_tid_t, ProxyReadOpRef>::iterator it = proxyread_ops.begin();
-       it != proxyread_ops.end();) {
-      if (it->second->soid == cobc->obs.oi.soid) {
-       cancel_proxy_read((it++)->second);
-      } else {
-       ++it;
-      }
-    }
-    for (map<ceph_tid_t, ProxyWriteOpRef>::iterator it = proxywrite_ops.begin();
-        it != proxywrite_ops.end();) {
-      if (it->second->soid == cobc->obs.oi.soid) {
-       cancel_proxy_write((it++)->second);
-      } else {
-       ++it;
-      }
-    }
-    kick_proxy_ops_blocked(cobc->obs.oi.soid);
-  }
-
-  kick_object_context_blocked(cobc);
-}
-
-void PrimaryLogPG::_write_copy_chunk(CopyOpRef cop, PGTransaction *t)
-{
-  dout(20) << __func__ << " " << cop
-          << " " << cop->attrs.size() << " attrs"
-          << " " << cop->data.length() << " bytes"
-          << " " << cop->omap_header.length() << " omap header bytes"
-          << " " << cop->omap_data.length() << " omap data bytes"
-          << dendl;
-  if (!cop->temp_cursor.attr_complete) {
-    t->create(cop->results.temp_oid);
-  }
-  if (!cop->temp_cursor.data_complete) {
-    assert(cop->data.length() + cop->temp_cursor.data_offset ==
-          cop->cursor.data_offset);
-    if (pool.info.requires_aligned_append() &&
-       !cop->cursor.data_complete) {
-      /**
-       * Trim off the unaligned bit at the end, we'll adjust cursor.data_offset
-       * to pick it up on the next pass.
-       */
-      assert(cop->temp_cursor.data_offset %
-            pool.info.required_alignment() == 0);
-      if (cop->data.length() % pool.info.required_alignment() != 0) {
-       uint64_t to_trim =
-         cop->data.length() % pool.info.required_alignment();
-       bufferlist bl;
-       bl.substr_of(cop->data, 0, cop->data.length() - to_trim);
-       cop->data.swap(bl);
-       cop->cursor.data_offset -= to_trim;
-       assert(cop->data.length() + cop->temp_cursor.data_offset ==
-              cop->cursor.data_offset);
-      }
-    }
-    if (cop->data.length()) {
-      t->write(
-       cop->results.temp_oid,
-       cop->temp_cursor.data_offset,
-       cop->data.length(),
-       cop->data,
-       cop->dest_obj_fadvise_flags);
-    }
-    cop->data.clear();
-  }
-  if (pool.info.supports_omap()) {
-    if (!cop->temp_cursor.omap_complete) {
-      if (cop->omap_header.length()) {
-       t->omap_setheader(
-         cop->results.temp_oid,
-         cop->omap_header);
-       cop->omap_header.clear();
-      }
-      if (cop->omap_data.length()) {
-       map<string,bufferlist> omap;
-       bufferlist::iterator p = cop->omap_data.begin();
-       ::decode(omap, p);
-       t->omap_setkeys(cop->results.temp_oid, omap);
-       cop->omap_data.clear();
-      }
-    }
-  } else {
-    assert(cop->omap_header.length() == 0);
-    assert(cop->omap_data.length() == 0);
-  }
-  cop->temp_cursor = cop->cursor;
-}
-
-void PrimaryLogPG::finish_copyfrom(CopyFromCallback *cb)
-{
-  OpContext *ctx = cb->ctx;
-  dout(20) << "finish_copyfrom on " << ctx->obs->oi.soid << dendl;
-
-  ObjectState& obs = ctx->new_obs;
-  if (obs.exists) {
-    dout(20) << __func__ << ": exists, removing" << dendl;
-    ctx->op_t->remove(obs.oi.soid);
-  } else {
-    ctx->delta_stats.num_objects++;
-    obs.exists = true;
-  }
-  if (cb->is_temp_obj_used()) {
-    ctx->discard_temp_oid = cb->results->temp_oid;
-  }
-  cb->results->fill_in_final_tx(ctx->op_t.get());
-
-  // CopyFromCallback fills this in for us
-  obs.oi.user_version = ctx->user_at_version;
-
-  obs.oi.set_data_digest(cb->results->data_digest);
-  obs.oi.set_omap_digest(cb->results->omap_digest);
-
-  obs.oi.truncate_seq = cb->results->truncate_seq;
-  obs.oi.truncate_size = cb->results->truncate_size;
-
-  ctx->extra_reqids = cb->results->reqids;
-
-  // cache: clear whiteout?
-  if (obs.oi.is_whiteout()) {
-    dout(10) << __func__ << " clearing whiteout on " << obs.oi.soid << dendl;
-    obs.oi.clear_flag(object_info_t::FLAG_WHITEOUT);
-    --ctx->delta_stats.num_whiteouts;
-  }
-
-  if (cb->results->has_omap) {
-    dout(10) << __func__ << " setting omap flag on " << obs.oi.soid << dendl;
-    obs.oi.set_flag(object_info_t::FLAG_OMAP);
-  } else {
-    dout(10) << __func__ << " clearing omap flag on " << obs.oi.soid << dendl;
-    obs.oi.clear_flag(object_info_t::FLAG_OMAP);
-  }
-
-  interval_set<uint64_t> ch;
-  if (obs.oi.size > 0)
-    ch.insert(0, obs.oi.size);
-  ctx->modified_ranges.union_of(ch);
-
-  if (cb->get_data_size() != obs.oi.size) {
-    ctx->delta_stats.num_bytes -= obs.oi.size;
-    obs.oi.size = cb->get_data_size();
-    ctx->delta_stats.num_bytes += obs.oi.size;
-  }
-  ctx->delta_stats.num_wr++;
-  ctx->delta_stats.num_wr_kb += SHIFT_ROUND_UP(obs.oi.size, 10);
-
-  osd->logger->inc(l_osd_copyfrom);
-}
-
-void PrimaryLogPG::finish_promote(int r, CopyResults *results,
-                                 ObjectContextRef obc)
-{
-  const hobject_t& soid = obc->obs.oi.soid;
-  dout(10) << __func__ << " " << soid << " r=" << r
-          << " uv" << results->user_version << dendl;
-
-  if (r == -ECANCELED) {
-    return;
-  }
-
-  if (r != -ENOENT && soid.is_snap()) {
-    if (results->snaps.empty()) {
-      // we must have read "snap" content from the head object in
-      // the base pool.  use snap_seq to construct what snaps should
-      // be for this clone (what is was before we evicted the clean
-      // clone from this pool, and what it will be when we flush and
-      // the clone eventually happens in the base pool).
-      SnapSet& snapset = obc->ssc->snapset;
-      vector<snapid_t>::iterator p = snapset.snaps.begin();
-      while (p != snapset.snaps.end() && *p > soid.snap)
-       ++p;
-      while (p != snapset.snaps.end() && *p > results->snap_seq) {
-       results->snaps.push_back(*p);
-       ++p;
-      }
-    }
-
-    dout(20) << __func__ << " snaps " << results->snaps << dendl;
-    filter_snapc(results->snaps);
-
-    dout(20) << __func__ << " filtered snaps " << results->snaps << dendl;
-    if (results->snaps.empty()) {
-      dout(20) << __func__
-              << " snaps are empty, clone is invalid,"
-              << " setting r to ENOENT" << dendl;
-      r = -ENOENT;
-    }
-  }
-
-  if (r < 0 && results->started_temp_obj) {
-    dout(10) << __func__ << " abort; will clean up partial work" << dendl;
-    ObjectContextRef tempobc = get_object_context(results->temp_oid, false);
-    assert(tempobc);
-    OpContextUPtr ctx = simple_opc_create(tempobc);
-    ctx->op_t->remove(results->temp_oid);
-    simple_opc_submit(std::move(ctx));
-    results->started_temp_obj = false;
-  }
-
-  if (r == -ENOENT && soid.is_snap()) {
-    dout(10) << __func__
-            << ": enoent while trying to promote clone, " << soid
-            << " must have been trimmed, removing from snapset"
-            << dendl;
-    hobject_t head(soid.get_head());
-    ObjectContextRef obc = get_object_context(head, false);
-    assert(obc);
-
-    OpContextUPtr tctx = simple_opc_create(obc);
-    tctx->at_version = get_next_version();
-    filter_snapc(tctx->new_snapset.snaps);
-    vector<snapid_t> new_clones;
-    map<snapid_t, vector<snapid_t>> new_clone_snaps;
-    for (vector<snapid_t>::iterator i = tctx->new_snapset.clones.begin();
-        i != tctx->new_snapset.clones.end();
-        ++i) {
-      if (*i != soid.snap) {
-       new_clones.push_back(*i);
-       auto p = tctx->new_snapset.clone_snaps.find(*i);
-       if (p != tctx->new_snapset.clone_snaps.end()) {
-         new_clone_snaps[*i] = p->second;
-       }
-      }
-    }
-    tctx->new_snapset.clones.swap(new_clones);
-    tctx->new_snapset.clone_overlap.erase(soid.snap);
-    tctx->new_snapset.clone_size.erase(soid.snap);
-    tctx->new_snapset.clone_snaps.swap(new_clone_snaps);
-
-    // take RWWRITE lock for duration of our local write.  ignore starvation.
-    if (!tctx->lock_manager.take_write_lock(
-         head,
-         obc)) {
-      assert(0 == "problem!");
-    }
-    dout(20) << __func__ << " took lock on obc, " << obc->rwstate << dendl;
-
-    finish_ctx(tctx.get(), pg_log_entry_t::PROMOTE);
-
-    simple_opc_submit(std::move(tctx));
-    return;
-  }
-
-  bool whiteout = false;
-  if (r == -ENOENT) {
-    assert(soid.snap == CEPH_NOSNAP); // snap case is above
-    dout(10) << __func__ << " whiteout " << soid << dendl;
-    whiteout = true;
-  }
-
-  if (r < 0 && !whiteout) {
-    derr << __func__ << " unexpected promote error " << cpp_strerror(r) << dendl;
-    // pass error to everyone blocked on this object
-    // FIXME: this is pretty sloppy, but at this point we got
-    // something unexpected and don't have many other options.
-    map<hobject_t,list<OpRequestRef>>::iterator blocked_iter =
-      waiting_for_blocked_object.find(soid);
-    if (blocked_iter != waiting_for_blocked_object.end()) {
-      while (!blocked_iter->second.empty()) {
-       osd->reply_op_error(blocked_iter->second.front(), r);
-       blocked_iter->second.pop_front();
-      }
-      waiting_for_blocked_object.erase(blocked_iter);
-    }
-    return;
-  }
-
-  osd->promote_finish(results->object_size);
-
-  OpContextUPtr tctx =  simple_opc_create(obc);
-  tctx->at_version = get_next_version();
-
-  ++tctx->delta_stats.num_objects;
-  if (soid.snap < CEPH_NOSNAP)
-    ++tctx->delta_stats.num_object_clones;
-  tctx->new_obs.exists = true;
-
-  tctx->extra_reqids = results->reqids;
-
-  bool legacy_snapset = tctx->new_snapset.is_legacy() ||
-    get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS;
-
-  if (whiteout) {
-    // create a whiteout
-    tctx->op_t->create(soid);
-    tctx->new_obs.oi.set_flag(object_info_t::FLAG_WHITEOUT);
-    ++tctx->delta_stats.num_whiteouts;
-    dout(20) << __func__ << " creating whiteout on " << soid << dendl;
-    osd->logger->inc(l_osd_tier_whiteout);
-  } else {
-    if (results->has_omap) {
-      dout(10) << __func__ << " setting omap flag on " << soid << dendl;
-      tctx->new_obs.oi.set_flag(object_info_t::FLAG_OMAP);
-      ++tctx->delta_stats.num_objects_omap;
-    }
-
-    results->fill_in_final_tx(tctx->op_t.get());
-    if (results->started_temp_obj) {
-      tctx->discard_temp_oid = results->temp_oid;
-    }
-    tctx->new_obs.oi.size = results->object_size;
-    tctx->new_obs.oi.user_version = results->user_version;
-    // Don't care src object whether have data or omap digest
-    if (results->object_size)
-      tctx->new_obs.oi.set_data_digest(results->data_digest);
-    if (results->has_omap)
-      tctx->new_obs.oi.set_omap_digest(results->omap_digest);
-    tctx->new_obs.oi.truncate_seq = results->truncate_seq;
-    tctx->new_obs.oi.truncate_size = results->truncate_size;
-
-    if (soid.snap != CEPH_NOSNAP) {
-      if (legacy_snapset) {
-       tctx->new_obs.oi.legacy_snaps = results->snaps;
-       assert(!tctx->new_obs.oi.legacy_snaps.empty());
-      } else {
-       // it's already in the snapset
-       assert(obc->ssc->snapset.clone_snaps.count(soid.snap));
-      }
-      assert(obc->ssc->snapset.clone_size.count(soid.snap));
-      assert(obc->ssc->snapset.clone_size[soid.snap] ==
-            results->object_size);
-      assert(obc->ssc->snapset.clone_overlap.count(soid.snap));
-
-      tctx->delta_stats.num_bytes += obc->ssc->snapset.get_clone_bytes(soid.snap);
-    } else {
-      tctx->delta_stats.num_bytes += results->object_size;
-    }
-  }
-
-  if (results->mirror_snapset) {
-    assert(tctx->new_obs.oi.soid.snap == CEPH_NOSNAP);
-    tctx->new_snapset.from_snap_set(
-      results->snapset,
-      get_osdmap()->require_osd_release < CEPH_RELEASE_LUMINOUS);
-  }
-  tctx->new_snapset.head_exists = true;
-  dout(20) << __func__ << " new_snapset " << tctx->new_snapset << dendl;
-
-  // take RWWRITE lock for duration of our local write.  ignore starvation.
-  if (!tctx->lock_manager.take_write_lock(
-       obc->obs.oi.soid,
-       obc)) {
-    assert(0 == "problem!");
-  }
-  dout(20) << __func__ << " took lock on obc, " << obc->rwstate << dendl;
-
-  finish_ctx(tctx.get(), pg_log_entry_t::PROMOTE);
-
-  simple_opc_submit(std::move(tctx));
-
-  osd->logger->inc(l_osd_tier_promote);
-
-  if (agent_state &&
-      agent_state->is_idle())
-    agent_choose_mode();
-}
-
-void PrimaryLogPG::cancel_copy(CopyOpRef cop, bool requeue)
-{
-  dout(10) << __func__ << " " << cop->obc->obs.oi.soid
-          << " from " << cop->src << " " << cop->oloc
-          << " v" << cop->results.user_version << dendl;
-
-  // cancel objecter op, if we can
-  if (cop->objecter_tid) {
-    osd->objecter->op_cancel(cop->objecter_tid, -ECANCELED);
-    cop->objecter_tid = 0;
-    if (cop->objecter_tid2) {
-      osd->objecter->op_cancel(cop->objecter_tid2, -ECANCELED);
-      cop->objecter_tid2 = 0;
-    }
-  }
-
-  copy_ops.erase(cop->obc->obs.oi.soid);
-  cop->obc->stop_block();
-
-  kick_object_context_blocked(cop->obc);
-  cop->results.should_requeue = requeue;
-  CopyCallbackResults result(-ECANCELED, &cop->results);
-  cop->cb->complete(result);
-
-  // There may still be an objecter callback referencing this copy op.
-  // That callback will not need the obc since it's been canceled, and
-  // we need the obc reference to go away prior to flush.
-  cop->obc = ObjectContextRef();
-}
-
-void PrimaryLogPG::cancel_copy_ops(bool requeue)
-{
-  dout(10) << __func__ << dendl;
-  map<hobject_t,CopyOpRef>::iterator p = copy_ops.begin();
-  while (p != copy_ops.end()) {
-    // requeue this op? can I queue up all of them?
-    cancel_copy((p++)->second, requeue);
-  }
-}
-
-
-// ========================================================================
-// flush
-//
-// Flush a dirty object in the cache tier by writing it back to the
-// base tier.  The sequence looks like:
-//
-//  * send a copy-from operation to the base tier to copy the current
-//    version of the object
-//  * base tier will pull the object via (perhaps multiple) copy-get(s)
-//  * on completion, we check if the object has been modified.  if so,
-//    just reply with -EAGAIN.
-//  * try to take a write lock so we can clear the dirty flag.  if this
-//    fails, wait and retry
-//  * start a repop that clears the bit.
-//
-// If we have to wait, we will retry by coming back through the
-// start_flush method.  We check if a flush is already in progress
-// and, if so, try to finish it by rechecking the version and trying
-// to clear the dirty bit.
-//
-// In order for the cache-flush (a write op) to not block the copy-get
-// from reading the object, the client *must* set the SKIPRWLOCKS
-// flag.
-//
-// NOTE: normally writes are strictly ordered for the client, but
-// flushes are special in that they can be reordered with respect to
-// other writes.  In particular, we can't have a flush request block
-// an update to the cache pool object!
-
-struct C_Flush : public Context {
-  PrimaryLogPGRef pg;
-  hobject_t oid;
-  epoch_t last_peering_reset;
-  ceph_tid_t tid;
-  utime_t start;
-  C_Flush(PrimaryLogPG *p, hobject_t o, epoch_t lpr)
-    : pg(p), oid(o), last_peering_reset(lpr),
-      tid(0), start(ceph_clock_now())
-  {}
-  void finish(int r) override {
-    if (r == -ECANCELED)
-      return;
-    pg->lock();
-    if (last_peering_reset == pg->get_last_peering_reset()) {
-      pg->finish_flush(oid, tid, r);
-      pg->osd->logger->tinc(l_osd_tier_flush_lat, ceph_clock_now() - start);
-    }
-    pg->unlock();
-  }
-};
-
-int PrimaryLogPG::start_flush(
-  OpRequestRef op, ObjectContextRef obc,
-  bool blocking, hobject_t *pmissing,
-  boost::optional<std::function<void()>> &&on_flush)
-{
-  const object_info_t& oi = obc->obs.oi;
-  const hobject_t& soid = oi.soid;
-  dout(10) << __func__ << " " << soid
-          << " v" << oi.version
-          << " uv" << oi.user_version
-          << " " << (blocking ? "blocking" : "non-blocking/best-effort")
-          << dendl;
-
-  // get a filtered snapset, need to remove removed snaps
-  SnapSet snapset = obc->ssc->snapset.get_filtered(pool.info);
-
-  // verify there are no (older) check for dirty clones
-  {
-    dout(20) << " snapset " << snapset << dendl;
-    vector<snapid_t>::reverse_iterator p = snapset.clones.rbegin();
-    while (p != snapset.clones.rend() && *p >= soid.snap)
-      ++p;
-    if (p != snapset.clones.rend()) {
-      hobject_t next = soid;
-      next.snap = *p;
-      assert(next.snap < soid.snap);
-      if (pg_log.get_missing().is_missing(next)) {
-       dout(10) << __func__ << " missing clone is " << next << dendl;
-       if (pmissing)
-         *pmissing = next;
-       return -ENOENT;
-      }
-      ObjectContextRef older_obc = get_object_context(next, false);
-      if (older_obc) {
-       dout(20) << __func__ << " next oldest clone is " << older_obc->obs.oi
-                << dendl;
-       if (older_obc->obs.oi.is_dirty()) {
-         dout(10) << __func__ << " next oldest clone is dirty: "
-                  << older_obc->obs.oi << dendl;
-         return -EBUSY;
-       }
-      } else {
-       dout(20) << __func__ << " next oldest clone " << next
-                << " is not present; implicitly clean" << dendl;
-      }
-    } else {
-      dout(20) << __func__ << " no older clones" << dendl;
-    }
-  }
-
-  if (blocking)
-    obc->start_block();
-
-  map<hobject_t,FlushOpRef>::iterator p = flush_ops.find(soid);
-  if (p != flush_ops.end()) {
-    FlushOpRef fop = p->second;
-    if (fop->op == op) {
-      // we couldn't take the write lock on a cache-try-flush before;
-      // now we are trying again for the lock.
-      return try_flush_mark_clean(fop);
-    }
-    if (fop->flushed_version == obc->obs.oi.user_version &&
-       (fop->blocking || !blocking)) {
-      // nonblocking can join anything
-      // blocking can only join a blocking flush
-      dout(20) << __func__ << " piggybacking on existing flush " << dendl;
-      if (op)
-       fop->dup_ops.push_back(op);
-      return -EAGAIN;   // clean up this ctx; op will retry later
-    }
-
-    // cancel current flush since it will fail anyway, or because we
-    // are blocking and the existing flush is nonblocking.
-    dout(20) << __func__ << " canceling previous flush; it will fail" << dendl;
-    if (fop->op)
-      osd->reply_op_error(fop->op, -EBUSY);
-    while (!fop->dup_ops.empty()) {
-      osd->reply_op_error(fop->dup_ops.front(), -EBUSY);
-      fop->dup_ops.pop_front();
-    }
-    cancel_flush(fop, false);
-  }
-
-  /**
-   * In general, we need to send a delete and a copyfrom.
-   * Consider snapc 10:[10, 9, 8, 4, 3, 2]:[10(10, 9), 4(4,3,2)]
-   * where 4 is marked as clean.  To flush 10, we have to:
-   * 1) delete 4:[4,3,2] -- Logically, the object does not exist after 4
-   * 2) copyfrom 8:[8,4,3,2] -- flush object after snap 8
-   *
-   * There is a complicating case.  Supposed there had been a clone 7
-   * for snaps [7, 6] which has been trimmed since they no longer exist.
-   * In the base pool, we'd have 5:[4,3,2]:[4(4,3,2)]+head.  When we submit
-   * the delete, the snap will be promoted to 5, and the head will become
-   * a snapdir.  When the copy-from goes through, we'll end up with
-   * 8:[8,4,3,2]:[4(4,3,2)]+head.
-   *
-   * Another complication is the case where there is an interval change
-   * after doing the delete and the flush but before marking the object
-   * clean.  We'll happily delete head and then recreate it at the same
-   * sequence number, which works out ok.
-   */
-
-  SnapContext snapc, dsnapc;
-  if (snapset.seq != 0) {
-    if (soid.snap == CEPH_NOSNAP) {
-      snapc.seq = snapset.seq;
-      snapc.snaps = snapset.snaps;
-    } else {
-      snapid_t min_included_snap;
-      if (snapset.is_legacy()) {
-       min_included_snap = oi.legacy_snaps.back();
-      } else {
-       auto p = snapset.clone_snaps.find(soid.snap);
-       assert(p != snapset.clone_snaps.end());
-       min_included_snap = p->second.back();
-      }
-      snapc = snapset.get_ssc_as_of(min_included_snap - 1);
-    }
-
-    snapid_t prev_snapc = 0;
-    for (vector<snapid_t>::reverse_iterator citer = snapset.clones.rbegin();
-        citer != snapset.clones.rend();
-        ++citer) {
-      if (*citer < soid.snap) {
-       prev_snapc = *citer;
-       break;
-      }
-    }
-
-    dsnapc = snapset.get_ssc_as_of(prev_snapc);
-  }
-
-  object_locator_t base_oloc(soid);
-  base_oloc.pool = pool.info.tier_of;
-
-  if (dsnapc.seq < snapc.seq) {
-    ObjectOperation o;
-    o.remove();
-    osd->objecter->mutate(
-      soid.oid,
-      base_oloc,
-      o,
-      dsnapc,
-      ceph::real_clock::from_ceph_timespec(oi.mtime),
-      (CEPH_OSD_FLAG_IGNORE_OVERLAY |
-       CEPH_OSD_FLAG_ENFORCE_SNAPC),
-      NULL /* no callback, we'll rely on the ordering w.r.t the next op */);
-  }
-
-  FlushOpRef fop(std::make_shared<FlushOp>());
-  fop->obc = obc;
-  fop->flushed_version = oi.user_version;
-  fop->blocking = blocking;
-  fop->on_flush = std::move(on_flush);
-  fop->op = op;
-
-  ObjectOperation o;
-  if (oi.is_whiteout()) {
-    fop->removal = true;
-    o.remove();
-  } else {
-    object_locator_t oloc(soid);
-    o.copy_from(soid.oid.name, soid.snap, oloc, oi.user_version,
-               CEPH_OSD_COPY_FROM_FLAG_FLUSH |
-               CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY |
-               CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE |
-               CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE,
-               LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL|LIBRADOS_OP_FLAG_FADVISE_NOCACHE);
-
-    //mean the base tier don't cache data after this
-    if (agent_state && agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL)
-      o.set_last_op_flags(LIBRADOS_OP_FLAG_FADVISE_DONTNEED);
-  }
-  C_Flush *fin = new C_Flush(this, soid, get_last_peering_reset());
-
-  ceph_tid_t tid = osd->objecter->mutate(
-    soid.oid, base_oloc, o, snapc,
-    ceph::real_clock::from_ceph_timespec(oi.mtime),
-    CEPH_OSD_FLAG_IGNORE_OVERLAY | CEPH_OSD_FLAG_ENFORCE_SNAPC,
-    new C_OnFinisher(fin,
-                    &osd->objecter_finisher));
-  /* we're under the pg lock and fin->finish() is grabbing that */
-  fin->tid = tid;
-  fop->objecter_tid = tid;
-
-  flush_ops[soid] = fop;
-  info.stats.stats.sum.num_flush++;
-  info.stats.stats.sum.num_flush_kb += SHIFT_ROUND_UP(oi.size, 10);
-  return -EINPROGRESS;
-}
-
-void PrimaryLogPG::finish_flush(hobject_t oid, ceph_tid_t tid, int r)
-{
-  dout(10) << __func__ << " " << oid << " tid " << tid
-          << " " << cpp_strerror(r) << dendl;
-  map<hobject_t,FlushOpRef>::iterator p = flush_ops.find(oid);
-  if (p == flush_ops.end()) {
-    dout(10) << __func__ << " no flush_op found" << dendl;
-    return;
-  }
-  FlushOpRef fop = p->second;
-  if (tid != fop->objecter_tid) {
-    dout(10) << __func__ << " tid " << tid << " != fop " << fop
-            << " tid " << fop->objecter_tid << dendl;
-    return;
-  }
-  ObjectContextRef obc = fop->obc;
-  fop->objecter_tid = 0;
-
-  if (r < 0 && !(r == -ENOENT && fop->removal)) {
-    if (fop->op)
-      osd->reply_op_error(fop->op, -EBUSY);
-    if (fop->blocking) {
-      obc->stop_block();
-      kick_object_context_blocked(obc);
-    }
-
-    if (!fop->dup_ops.empty()) {
-      dout(20) << __func__ << " requeueing dups" << dendl;
-      requeue_ops(fop->dup_ops);
-    }
-    if (fop->on_flush) {
-      (*(fop->on_flush))();
-      fop->on_flush = boost::none;
-    }
-    flush_ops.erase(oid);
-    return;
-  }
-
-  r = try_flush_mark_clean(fop);
-  if (r == -EBUSY && fop->op) {
-    osd->reply_op_error(fop->op, r);
-  }
-}
-
-int PrimaryLogPG::try_flush_mark_clean(FlushOpRef fop)
-{
-  ObjectContextRef obc = fop->obc;
-  const hobject_t& oid = obc->obs.oi.soid;
-
-  if (fop->blocking) {
-    obc->stop_block();
-    kick_object_context_blocked(obc);
-  }
-
-  if (fop->flushed_version != obc->obs.oi.user_version ||
-      !obc->obs.exists) {
-    if (obc->obs.exists)
-      dout(10) << __func__ << " flushed_version " << fop->flushed_version
-              << " != current " << obc->obs.oi.user_version
-              << dendl;
-    else
-      dout(10) << __func__ << " object no longer exists" << dendl;
-
-    if (!fop->dup_ops.empty()) {
-      dout(20) << __func__ << " requeueing dups" << dendl;
-      requeue_ops(fop->dup_ops);
-    }
-    if (fop->on_flush) {
-      (*(fop->on_flush))();
-      fop->on_flush = boost::none;
-    }
-    flush_ops.erase(oid);
-    if (fop->blocking)
-      osd->logger->inc(l_osd_tier_flush_fail);
-    else
-      osd->logger->inc(l_osd_tier_try_flush_fail);
-    return -EBUSY;
-  }
-
-  if (!fop->blocking &&
-      scrubber.write_blocked_by_scrub(oid)) {
-    if (fop->op) {
-      dout(10) << __func__ << " blocked by scrub" << dendl;
-      requeue_op(fop->op);
-      requeue_ops(fop->dup_ops);
-      return -EAGAIN;    // will retry
-    } else {
-      osd->logger->inc(l_osd_tier_try_flush_fail);
-      cancel_flush(fop, false);
-      return -ECANCELED;
-    }
-  }
-
-  // successfully flushed, can we evict this object?
-  if (!fop->op && agent_state->evict_mode != TierAgentState::EVICT_MODE_IDLE &&
-      agent_maybe_evict(obc, true)) {
-    osd->logger->inc(l_osd_tier_clean);
-    if (fop->on_flush) {
-      (*(fop->on_flush))();
-      fop->on_flush = boost::none;
-    }
-    flush_ops.erase(oid);
-    return 0;
-  }
-
-  dout(10) << __func__ << " clearing DIRTY flag for " << oid << dendl;
-  OpContextUPtr ctx = simple_opc_create(fop->obc);
-
-  // successfully flushed; can we clear the dirty bit?
-  // try to take the lock manually, since we don't
-  // have a ctx yet.
-  if (ctx->lock_manager.get_lock_type(
-       ObjectContext::RWState::RWWRITE,
-       oid,
-       obc,
-       fop->op)) {
-    dout(20) << __func__ << " took write lock" << dendl;
-  } else if (fop->op) {
-    dout(10) << __func__ << " waiting on write lock" << dendl;
-    close_op_ctx(ctx.release());
-    requeue_op(fop->op);
-    requeue_ops(fop->dup_ops);
-    return -EAGAIN;    // will retry
-  } else {
-    dout(10) << __func__ << " failed write lock, no op; failing" << dendl;
-    close_op_ctx(ctx.release());
-    osd->logger->inc(l_osd_tier_try_flush_fail);
-    cancel_flush(fop, false);
-    return -ECANCELED;
-  }
-
-  if (fop->on_flush) {
-    ctx->register_on_finish(*(fop->on_flush));
-    fop->on_flush = boost::none;
-  }
-
-  ctx->at_version = get_next_version();
-
-  ctx->new_obs = obc->obs;
-  ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY);
-  --ctx->delta_stats.num_objects_dirty;
-
-  finish_ctx(ctx.get(), pg_log_entry_t::CLEAN);
-
-  osd->logger->inc(l_osd_tier_clean);
-
-  if (!fop->dup_ops.empty() || fop->op) {
-    dout(20) << __func__ << " requeueing for " << ctx->at_version << dendl;
-    list<OpRequestRef> ls;
-    if (fop->op)
-      ls.push_back(fop->op);
-    ls.splice(ls.end(), fop->dup_ops);
-    requeue_ops(ls);
-  }
-
-  simple_opc_submit(std::move(ctx));
-
-  flush_ops.erase(oid);
-
-  if (fop->blocking)
-    osd->logger->inc(l_osd_tier_flush);
-  else
-    osd->logger->inc(l_osd_tier_try_flush);
-
-  return -EINPROGRESS;
-}
-
-void PrimaryLogPG::cancel_flush(FlushOpRef fop, bool requeue)
-{
-  dout(10) << __func__ << " " << fop->obc->obs.oi.soid << " tid "
-          << fop->objecter_tid << dendl;
-  if (fop->objecter_tid) {
-    osd->objecter->op_cancel(fop->objecter_tid, -ECANCELED);
-    fop->objecter_tid = 0;
-  }
-  if (fop->blocking) {
-    fop->obc->stop_block();
-    kick_object_context_blocked(fop->obc);
-  }
-  if (requeue) {
-    if (fop->op)
-      requeue_op(fop->op);
-    requeue_ops(fop->dup_ops);
-  }
-  if (fop->on_flush) {
-    (*(fop->on_flush))();
-    fop->on_flush = boost::none;
-  }
-  flush_ops.erase(fop->obc->obs.oi.soid);
-}
-
-void PrimaryLogPG::cancel_flush_ops(bool requeue)
-{
-  dout(10) << __func__ << dendl;
-  map<hobject_t,FlushOpRef>::iterator p = flush_ops.begin();
-  while (p != flush_ops.end()) {
-    cancel_flush((p++)->second, requeue);
-  }
-}
-
-bool PrimaryLogPG::is_present_clone(hobject_t coid)
-{
-  if (!pool.info.allow_incomplete_clones())
-    return true;
-  if (is_missing_object(coid))
-    return true;
-  ObjectContextRef obc = get_object_context(coid, false);
-  return obc && obc->obs.exists;
-}
-
-// ========================================================================
-// rep op gather
-
-class C_OSD_RepopApplied : public Context {
-  PrimaryLogPGRef pg;
-  boost::intrusive_ptr<PrimaryLogPG::RepGather> repop;
-public:
-  C_OSD_RepopApplied(PrimaryLogPG *pg, PrimaryLogPG::RepGather *repop)
-  : pg(pg), repop(repop) {}
-  void finish(int) override {
-    pg->repop_all_applied(repop.get());
-  }
-};
-
-
-void PrimaryLogPG::repop_all_applied(RepGather *repop)
-{
-  dout(10) << __func__ << ": repop tid " << repop->rep_tid << " all applied "
-          << dendl;
-  assert(!repop->applies_with_commit);
-  repop->all_applied = true;
-  if (!repop->rep_aborted) {
-    eval_repop(repop);
-  }
-}
-
-class C_OSD_RepopCommit : public Context {
-  PrimaryLogPGRef pg;
-  boost::intrusive_ptr<PrimaryLogPG::RepGather> repop;
-public:
-  C_OSD_RepopCommit(PrimaryLogPG *pg, PrimaryLogPG::RepGather *repop)
-    : pg(pg), repop(repop) {}
-  void finish(int) override {
-    pg->repop_all_committed(repop.get());
-  }
-};
-
-void PrimaryLogPG::repop_all_committed(RepGather *repop)
-{
-  dout(10) << __func__ << ": repop tid " << repop->rep_tid << " all committed "
-          << dendl;
-  repop->all_committed = true;
-  if (repop->applies_with_commit) {
-    assert(!repop->all_applied);
-    repop->all_applied = true;
-  }
-
-  if (!repop->rep_aborted) {
-    if (repop->v != eversion_t()) {
-      last_update_ondisk = repop->v;
-      last_complete_ondisk = repop->pg_local_last_complete;
-    }
-    eval_repop(repop);
-  }
-}
-
-void PrimaryLogPG::op_applied(const eversion_t &applied_version)
-{
-  dout(10) << "op_applied version " << applied_version << dendl;
-  if (applied_version == eversion_t())
-    return;
-  assert(applied_version > last_update_applied);
-  assert(applied_version <= info.last_update);
-  last_update_applied = applied_version;
-  if (is_primary()) {
-    if (scrubber.active) {
-      if (last_update_applied >= scrubber.subset_last_update) {
-        if (ops_blocked_by_scrub()) {
-          requeue_scrub(true);
-        } else {
-          requeue_scrub(false);
-        }
-
-      }
-    } else {
-      assert(scrubber.start == scrubber.end);
-    }
-  } else {
-    if (scrubber.active_rep_scrub) {
-      if (last_update_applied >= static_cast<const MOSDRepScrub*>(
-           scrubber.active_rep_scrub->get_req())->scrub_to) {
-       osd->enqueue_back(
-         info.pgid,
-         PGQueueable(scrubber.active_rep_scrub, get_osdmap()->get_epoch()));
-       scrubber.active_rep_scrub = OpRequestRef();
-      }
-    }
-  }
-}
-
-void PrimaryLogPG::eval_repop(RepGather *repop)
-{
-  const MOSDOp *m = NULL;
-  if (repop->op)
-    m = static_cast<const MOSDOp *>(repop->op->get_req());
-
-  if (m)
-    dout(10) << "eval_repop " << *repop
-            << (repop->rep_done ? " DONE" : "")
-            << dendl;
-  else
-    dout(10) << "eval_repop " << *repop << " (no op)"
-            << (repop->rep_done ? " DONE" : "")
-            << dendl;
-
-  if (repop->rep_done)
-    return;
-
-  // ondisk?
-  if (repop->all_committed) {
-    dout(10) << " commit: " << *repop << dendl;
-    for (auto p = repop->on_committed.begin();
-        p != repop->on_committed.end();
-        repop->on_committed.erase(p++)) {
-      (*p)();
-    }
-    // send dup commits, in order
-    if (waiting_for_ondisk.count(repop->v)) {
-      assert(waiting_for_ondisk.begin()->first == repop->v);
-      for (list<pair<OpRequestRef, version_t> >::iterator i =
-            waiting_for_ondisk[repop->v].begin();
-          i != waiting_for_ondisk[repop->v].end();
-          ++i) {
-       osd->reply_op_error(i->first, repop->r, repop->v,
-                           i->second);
-      }
-      waiting_for_ondisk.erase(repop->v);
-    }
-  }
-
-  // applied?
-  if (repop->all_applied) {
-    if (repop->applies_with_commit) {
-      assert(repop->on_applied.empty());
-    }
-    dout(10) << " applied: " << *repop << " " << dendl;
-    for (auto p = repop->on_applied.begin();
-        p != repop->on_applied.end();
-        repop->on_applied.erase(p++)) {
-      (*p)();
-    }
-  }
-
-  // done.
-  if (repop->all_applied && repop->all_committed) {
-    repop->rep_done = true;
-
-    publish_stats_to_osd();
-    calc_min_last_complete_ondisk();
-
-    dout(10) << " removing " << *repop << dendl;
-    assert(!repop_queue.empty());
-    dout(20) << "   q front is " << *repop_queue.front() << dendl; 
-    if (repop_queue.front() != repop) {
-      if (!repop->applies_with_commit) {
-       dout(0) << " removing " << *repop << dendl;
-       dout(0) << "   q front is " << *repop_queue.front() << dendl;
-       assert(repop_queue.front() == repop);
-      }
-    } else {
-      RepGather *to_remove = nullptr;
-      while (!repop_queue.empty() &&
-            (to_remove = repop_queue.front())->rep_done) {
-       repop_queue.pop_front();
-       for (auto p = to_remove->on_success.begin();
-            p != to_remove->on_success.end();
-            to_remove->on_success.erase(p++)) {
-         (*p)();
-       }
-       remove_repop(to_remove);
-      }
-    }
-  }
-}
-
-void PrimaryLogPG::issue_repop(RepGather *repop, OpContext *ctx)
-{
-  FUNCTRACE();
-  const hobject_t& soid = ctx->obs->oi.soid;
-  dout(7) << "issue_repop rep_tid " << repop->rep_tid
-          << " o " << soid
-          << dendl;
-
-  repop->v = ctx->at_version;
-  if (ctx->at_version > eversion_t()) {
-    for (set<pg_shard_t>::iterator i = actingbackfill.begin();
-        i != actingbackfill.end();
-        ++i) {
-      if (*i == get_primary()) continue;
-      pg_info_t &pinfo = peer_info[*i];
-      // keep peer_info up to date
-      if (pinfo.last_complete == pinfo.last_update)
-       pinfo.last_complete = ctx->at_version;
-      pinfo.last_update = ctx->at_version;
-    }
-  }
-
-  ctx->obc->ondisk_write_lock();
-
-  bool unlock_snapset_obc = false;
-  ctx->op_t->add_obc(ctx->obc);
-  if (ctx->clone_obc) {
-    ctx->clone_obc->ondisk_write_lock();
-    ctx->op_t->add_obc(ctx->clone_obc);
-  }
-  if (ctx->snapset_obc && ctx->snapset_obc->obs.oi.soid !=
-      ctx->obc->obs.oi.soid) {
-    ctx->snapset_obc->ondisk_write_lock();
-    unlock_snapset_obc = true;
-    ctx->op_t->add_obc(ctx->snapset_obc);
-  }
-
-  Context *on_all_commit = new C_OSD_RepopCommit(this, repop);
-  Context *on_all_applied = new C_OSD_RepopApplied(this, repop);
-  Context *onapplied_sync = new C_OSD_OndiskWriteUnlock(
-    ctx->obc,
-    ctx->clone_obc,
-    unlock_snapset_obc ? ctx->snapset_obc : ObjectContextRef());
-  if (!(ctx->log.empty())) {
-    assert(ctx->at_version >= projected_last_update);
-    projected_last_update = ctx->at_version;
-  }
-  for (auto &&entry: ctx->log) {
-    projected_log.add(entry);
-  }
-  pgbackend->submit_transaction(
-    soid,
-    ctx->delta_stats,
-    ctx->at_version,
-    std::move(ctx->op_t),
-    pg_trim_to,
-    min_last_complete_ondisk,
-    ctx->log,
-    ctx->updated_hset_history,
-    onapplied_sync,
-    on_all_applied,
-    on_all_commit,
-    repop->rep_tid,
-    ctx->reqid,
-    ctx->op);
-}
-
-PrimaryLogPG::RepGather *PrimaryLogPG::new_repop(
-  OpContext *ctx, ObjectContextRef obc,
-  ceph_tid_t rep_tid)
-{
-  if (ctx->op)
-    dout(10) << "new_repop rep_tid " << rep_tid << " on " << *ctx->op->get_req() << dendl;
-  else
-    dout(10) << "new_repop rep_tid " << rep_tid << " (no op)" << dendl;
-
-  RepGather *repop = new RepGather(
-    ctx, rep_tid, info.last_complete, false);
-
-  repop->start = ceph_clock_now();
-
-  repop_queue.push_back(&repop->queue_item);
-  repop->get();
-
-  osd->logger->inc(l_osd_op_wip);
-
-  dout(10) << __func__ << ": " << *repop << dendl;
-  return repop;
-}
-
-boost::intrusive_ptr<PrimaryLogPG::RepGather> PrimaryLogPG::new_repop(
-  eversion_t version,
-  int r,
-  ObcLockManager &&manager,
-  OpRequestRef &&op,
-  boost::optional<std::function<void(void)> > &&on_complete)
-{
-  RepGather *repop = new RepGather(
-    std::move(manager),
-    std::move(op),
-    std::move(on_complete),
-    osd->get_tid(),
-    info.last_complete,
-    true,
-    r);
-  repop->v = version;
-
-  repop->start = ceph_clock_now();
-
-  repop_queue.push_back(&repop->queue_item);
-
-  osd->logger->inc(l_osd_op_wip);
-
-  dout(10) << __func__ << ": " << *repop << dendl;
-  return boost::intrusive_ptr<RepGather>(repop);
-}
-void PrimaryLogPG::remove_repop(RepGather *repop)
-{
-  dout(20) << __func__ << " " << *repop << dendl;
-
-  for (auto p = repop->on_finish.begin();
-       p != repop->on_finish.end();
-       repop->on_finish.erase(p++)) {
-    (*p)();
-  }
-
-  release_object_locks(
-    repop->lock_manager);
-  repop->put();
-
-  osd->logger->dec(l_osd_op_wip);
-}
-
-PrimaryLogPG::OpContextUPtr PrimaryLogPG::simple_opc_create(ObjectContextRef obc)
-{
-  dout(20) << __func__ << " " << obc->obs.oi.soid << dendl;
-  ceph_tid_t rep_tid = osd->get_tid();
-  osd_reqid_t reqid(osd->get_cluster_msgr_name(), 0, rep_tid);
-  OpContextUPtr ctx(new OpContext(OpRequestRef(), reqid, nullptr, obc, this));
-  ctx->op_t.reset(new PGTransaction());
-  ctx->mtime = ceph_clock_now();
-  return ctx;
-}
-
-void PrimaryLogPG::simple_opc_submit(OpContextUPtr ctx)
-{
-  RepGather *repop = new_repop(ctx.get(), ctx->obc, ctx->reqid.tid);
-  dout(20) << __func__ << " " << repop << dendl;
-  issue_repop(repop, ctx.get());
-  eval_repop(repop);
-  calc_trim_to();
-  repop->put();
-}
-
-
-void PrimaryLogPG::submit_log_entries(
-  const mempool::osd_pglog::list<pg_log_entry_t> &entries,
-  ObcLockManager &&manager,
-  boost::optional<std::function<void(void)> > &&_on_complete,
-  OpRequestRef op,
-  int r)
-{
-  dout(10) << __func__ << " " << entries << dendl;
-  assert(is_primary());
-
-  eversion_t version;
-  if (!entries.empty()) {
-    assert(entries.rbegin()->version >= projected_last_update);
-    version = projected_last_update = entries.rbegin()->version;
-  }
-
-  boost::intrusive_ptr<RepGather> repop;
-  boost::optional<std::function<void(void)> > on_complete;
-  if (get_osdmap()->require_osd_release >= CEPH_RELEASE_JEWEL) {
-    repop = new_repop(
-      version,
-      r,
-      std::move(manager),
-      std::move(op),
-      std::move(_on_complete));
-  } else {
-    on_complete = std::move(_on_complete);
-  }
-
-  pgbackend->call_write_ordered(
-    [this, entries, repop, on_complete]() {
-      ObjectStore::Transaction t;
-      eversion_t old_last_update = info.last_update;
-      merge_new_log_entries(entries, t);
-
-
-      set<pg_shard_t> waiting_on;
-      for (set<pg_shard_t>::const_iterator i = actingbackfill.begin();
-          i != actingbackfill.end();
-          ++i) {
-       pg_shard_t peer(*i);
-       if (peer == pg_whoami) continue;
-       assert(peer_missing.count(peer));
-       assert(peer_info.count(peer));
-       if (get_osdmap()->require_osd_release >= CEPH_RELEASE_JEWEL) {
-         assert(repop);
-         MOSDPGUpdateLogMissing *m = new MOSDPGUpdateLogMissing(
-           entries,
-           spg_t(info.pgid.pgid, i->shard),
-           pg_whoami.shard,
-           get_osdmap()->get_epoch(),
-           last_peering_reset,
-           repop->rep_tid);
-         osd->send_message_osd_cluster(
-           peer.osd, m, get_osdmap()->get_epoch());
-         waiting_on.insert(peer);
-       } else {
-         MOSDPGLog *m = new MOSDPGLog(
-           peer.shard, pg_whoami.shard,
-           info.last_update.epoch,
-           info);
-         m->log.log = entries;
-         m->log.tail = old_last_update;
-         m->log.head = info.last_update;
-         osd->send_message_osd_cluster(
-           peer.osd, m, get_osdmap()->get_epoch());
-       }
-      }
-      if (get_osdmap()->require_osd_release >= CEPH_RELEASE_JEWEL) {
-       ceph_tid_t rep_tid = repop->rep_tid;
-       waiting_on.insert(pg_whoami);
-       log_entry_update_waiting_on.insert(
-         make_pair(
-           rep_tid,
-           LogUpdateCtx{std::move(repop), std::move(waiting_on)}
-           ));
-       struct OnComplete : public Context {
-         PrimaryLogPGRef pg;
-         ceph_tid_t rep_tid;
-         epoch_t epoch;
-         OnComplete(
-           PrimaryLogPGRef pg,
-           ceph_tid_t rep_tid,
-           epoch_t epoch)
-           : pg(pg), rep_tid(rep_tid), epoch(epoch) {}
-         void finish(int) override {
-           pg->lock();
-           if (!pg->pg_has_reset_since(epoch)) {
-             auto it = pg->log_entry_update_waiting_on.find(rep_tid);
-             assert(it != pg->log_entry_update_waiting_on.end());
-             auto it2 = it->second.waiting_on.find(pg->pg_whoami);
-             assert(it2 != it->second.waiting_on.end());
-             it->second.waiting_on.erase(it2);
-             if (it->second.waiting_on.empty()) {
-               pg->repop_all_committed(it->second.repop.get());
-               pg->log_entry_update_waiting_on.erase(it);
-             }
-           }
-           pg->unlock();
-         }
-       };
-       t.register_on_commit(
-         new OnComplete{this, rep_tid, get_osdmap()->get_epoch()});
-      } else {
-       if (on_complete) {
-         struct OnComplete : public Context {
-           PrimaryLogPGRef pg;
-           std::function<void(void)> on_complete;
-           epoch_t epoch;
-           OnComplete(
-             PrimaryLogPGRef pg,
-             const std::function<void(void)> &on_complete,
-             epoch_t epoch)
-             : pg(pg),
-               on_complete(std::move(on_complete)),
-               epoch(epoch) {}
-           void finish(int) override {
-             pg->lock();
-             if (!pg->pg_has_reset_since(epoch))
-               on_complete();
-             pg->unlock();
-           }
-         };
-         t.register_on_complete(
-           new OnComplete{
-             this, *on_complete, get_osdmap()->get_epoch()
-               });
-       }
-      }
-      t.register_on_applied(
-       new C_OSD_OnApplied{this, get_osdmap()->get_epoch(), info.last_update});
-      int r = osd->store->queue_transaction(osr.get(), std::move(t), NULL);
-      assert(r == 0);
-    });
-}
-
-void PrimaryLogPG::cancel_log_updates()
-{
-  // get rid of all the LogUpdateCtx so their references to repops are
-  // dropped
-  log_entry_update_waiting_on.clear();
-}
-
-// -------------------------------------------------------
-
-void PrimaryLogPG::get_watchers(list<obj_watch_item_t> &pg_watchers)
-{
-  pair<hobject_t, ObjectContextRef> i;
-  while (object_contexts.get_next(i.first, &i)) {
-    ObjectContextRef obc(i.second);
-    get_obc_watchers(obc, pg_watchers);
-  }
-}
-
-void PrimaryLogPG::get_obc_watchers(ObjectContextRef obc, list<obj_watch_item_t> &pg_watchers)
-{
-  for (map<pair<uint64_t, entity_name_t>, WatchRef>::iterator j =
-        obc->watchers.begin();
-       j != obc->watchers.end();
-       ++j) {
-    obj_watch_item_t owi;
-
-    owi.obj = obc->obs.oi.soid;
-    owi.wi.addr = j->second->get_peer_addr();
-    owi.wi.name = j->second->get_entity();
-    owi.wi.cookie = j->second->get_cookie();
-    owi.wi.timeout_seconds = j->second->get_timeout();
-
-    dout(30) << "watch: Found oid=" << owi.obj << " addr=" << owi.wi.addr
-      << " name=" << owi.wi.name << " cookie=" << owi.wi.cookie << dendl;
-
-    pg_watchers.push_back(owi);
-  }
-}
-
-void PrimaryLogPG::check_blacklisted_watchers()
-{
-  dout(20) << "PrimaryLogPG::check_blacklisted_watchers for pg " << get_pgid() << dendl;
-  pair<hobject_t, ObjectContextRef> i;
-  while (object_contexts.get_next(i.first, &i))
-    check_blacklisted_obc_watchers(i.second);
-}
-
-void PrimaryLogPG::check_blacklisted_obc_watchers(ObjectContextRef obc)
-{
-  dout(20) << "PrimaryLogPG::check_blacklisted_obc_watchers for obc " << obc->obs.oi.soid << dendl;
-  for (map<pair<uint64_t, entity_name_t>, WatchRef>::iterator k =
-        obc->watchers.begin();
-       k != obc->watchers.end();
-       ) {
-    //Advance iterator now so handle_watch_timeout() can erase element
-    map<pair<uint64_t, entity_name_t>, WatchRef>::iterator j = k++;
-    dout(30) << "watch: Found " << j->second->get_entity() << " cookie " << j->second->get_cookie() << dendl;
-    entity_addr_t ea = j->second->get_peer_addr();
-    dout(30) << "watch: Check entity_addr_t " << ea << dendl;
-    if (get_osdmap()->is_blacklisted(ea)) {
-      dout(10) << "watch: Found blacklisted watcher for " << ea << dendl;
-      assert(j->second->get_pg() == this);
-      j->second->unregister_cb();
-      handle_watch_timeout(j->second);
-    }
-  }
-}
-
-void PrimaryLogPG::populate_obc_watchers(ObjectContextRef obc)
-{
-  assert(is_active());
-  assert((recovering.count(obc->obs.oi.soid) ||
-         !is_missing_object(obc->obs.oi.soid)) ||
-        (pg_log.get_log().objects.count(obc->obs.oi.soid) && // or this is a revert... see recover_primary()
-         pg_log.get_log().objects.find(obc->obs.oi.soid)->second->op ==
-           pg_log_entry_t::LOST_REVERT &&
-         pg_log.get_log().objects.find(obc->obs.oi.soid)->second->reverting_to ==
-           obc->obs.oi.version));
-
-  dout(10) << "populate_obc_watchers " << obc->obs.oi.soid << dendl;
-  assert(obc->watchers.empty());
-  // populate unconnected_watchers
-  for (map<pair<uint64_t, entity_name_t>, watch_info_t>::iterator p =
-       obc->obs.oi.watchers.begin();
-       p != obc->obs.oi.watchers.end();
-       ++p) {
-    utime_t expire = info.stats.last_became_active;
-    expire += p->second.timeout_seconds;
-    dout(10) << "  unconnected watcher " << p->first << " will expire " << expire << dendl;
-    WatchRef watch(
-      Watch::makeWatchRef(
-       this, osd, obc, p->second.timeout_seconds, p->first.first,
-       p->first.second, p->second.addr));
-    watch->disconnect();
-    obc->watchers.insert(
-      make_pair(
-       make_pair(p->first.first, p->first.second),
-       watch));
-  }
-  // Look for watchers from blacklisted clients and drop
-  check_blacklisted_obc_watchers(obc);
-}
-
-void PrimaryLogPG::handle_watch_timeout(WatchRef watch)
-{
-  ObjectContextRef obc = watch->get_obc(); // handle_watch_timeout owns this ref
-  dout(10) << "handle_watch_timeout obc " << obc << dendl;
-
-  if (!is_active()) {
-    dout(10) << "handle_watch_timeout not active, no-op" << dendl;
-    return;
-  }
-  if (is_degraded_or_backfilling_object(obc->obs.oi.soid)) {
-    callbacks_for_degraded_object[obc->obs.oi.soid].push_back(
-      watch->get_delayed_cb()
-      );
-    dout(10) << "handle_watch_timeout waiting for degraded on obj "
-            << obc->obs.oi.soid
-            << dendl;
-    return;
-  }
-
-  if (scrubber.write_blocked_by_scrub(obc->obs.oi.soid)) {
-    dout(10) << "handle_watch_timeout waiting for scrub on obj "
-            << obc->obs.oi.soid
-            << dendl;
-    scrubber.add_callback(
-      watch->get_delayed_cb() // This callback!
-      );
-    return;
-  }
-
-  OpContextUPtr ctx = simple_opc_create(obc);
-  ctx->at_version = get_next_version();
-
-  object_info_t& oi = ctx->new_obs.oi;
-  oi.watchers.erase(make_pair(watch->get_cookie(),
-                             watch->get_entity()));
-
-  list<watch_disconnect_t> watch_disconnects = {
-    watch_disconnect_t(watch->get_cookie(), watch->get_entity(), true)
-  };
-  ctx->register_on_success(
-    [this, obc, watch_disconnects]() {
-      complete_disconnect_watches(obc, watch_disconnects);
-    });
-
-
-  PGTransaction *t = ctx->op_t.get();
-  ctx->log.push_back(pg_log_entry_t(pg_log_entry_t::MODIFY, obc->obs.oi.soid,
-                                   ctx->at_version,
-                                   oi.version,
-                                   0,
-                                   osd_reqid_t(), ctx->mtime, 0));
-
-  oi.prior_version = obc->obs.oi.version;
-  oi.version = ctx->at_version;
-  bufferlist bl;
-  ::encode(oi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-  t->setattr(obc->obs.oi.soid, OI_ATTR, bl);
-
-  // apply new object state.
-  ctx->obc->obs = ctx->new_obs;
-
-  // no ctx->delta_stats
-  simple_opc_submit(std::move(ctx));
-}
-
-ObjectContextRef PrimaryLogPG::create_object_context(const object_info_t& oi,
-                                                    SnapSetContext *ssc)
-{
-  ObjectContextRef obc(object_contexts.lookup_or_create(oi.soid));
-  assert(obc->destructor_callback == NULL);
-  obc->destructor_callback = new C_PG_ObjectContext(this, obc.get());  
-  obc->obs.oi = oi;
-  obc->obs.exists = false;
-  obc->ssc = ssc;
-  if (ssc)
-    register_snapset_context(ssc);
-  dout(10) << "create_object_context " << (void*)obc.get() << " " << oi.soid << " " << dendl;
-  if (is_active())
-    populate_obc_watchers(obc);
-  return obc;
-}
-
-ObjectContextRef PrimaryLogPG::get_object_context(
-  const hobject_t& soid,
-  bool can_create,
-  const map<string, bufferlist> *attrs)
-{
-  assert(
-    attrs || !pg_log.get_missing().is_missing(soid) ||
-    // or this is a revert... see recover_primary()
-    (pg_log.get_log().objects.count(soid) &&
-      pg_log.get_log().objects.find(soid)->second->op ==
-      pg_log_entry_t::LOST_REVERT));
-  ObjectContextRef obc = object_contexts.lookup(soid);
-  osd->logger->inc(l_osd_object_ctx_cache_total);
-  if (obc) {
-    osd->logger->inc(l_osd_object_ctx_cache_hit);
-    dout(10) << __func__ << ": found obc in cache: " << obc
-            << dendl;
-  } else {
-    dout(10) << __func__ << ": obc NOT found in cache: " << soid << dendl;
-    // check disk
-    bufferlist bv;
-    if (attrs) {
-      assert(attrs->count(OI_ATTR));
-      bv = attrs->find(OI_ATTR)->second;
-    } else {
-      int r = pgbackend->objects_get_attr(soid, OI_ATTR, &bv);
-      if (r < 0) {
-       if (!can_create) {
-         dout(10) << __func__ << ": no obc for soid "
-                  << soid << " and !can_create"
-                  << dendl;
-         return ObjectContextRef();   // -ENOENT!
-       }
-
-       dout(10) << __func__ << ": no obc for soid "
-                << soid << " but can_create"
-                << dendl;
-       // new object.
-       object_info_t oi(soid);
-       SnapSetContext *ssc = get_snapset_context(
-         soid, true, 0, false);
-        assert(ssc);
-       obc = create_object_context(oi, ssc);
-       dout(10) << __func__ << ": " << obc << " " << soid
-                << " " << obc->rwstate
-                << " oi: " << obc->obs.oi
-                << " ssc: " << obc->ssc
-                << " snapset: " << obc->ssc->snapset << dendl;
-       return obc;
-      }
-    }
-
-    object_info_t oi;
-    try {
-      bufferlist::iterator bliter = bv.begin();
-      ::decode(oi, bliter);
-    } catch (...) {
-      dout(0) << __func__ << ": obc corrupt: " << soid << dendl;
-      return ObjectContextRef();   // -ENOENT!
-    }
-
-    assert(oi.soid.pool == (int64_t)info.pgid.pool());
-
-    obc = object_contexts.lookup_or_create(oi.soid);
-    obc->destructor_callback = new C_PG_ObjectContext(this, obc.get());
-    obc->obs.oi = oi;
-    obc->obs.exists = true;
-
-    obc->ssc = get_snapset_context(
-      soid, true,
-      soid.has_snapset() ? attrs : 0);
-
-    if (is_active())
-      populate_obc_watchers(obc);
-
-    if (pool.info.require_rollback()) {
-      if (attrs) {
-       obc->attr_cache = *attrs;
-      } else {
-       int r = pgbackend->objects_get_attrs(
-         soid,
-         &obc->attr_cache);
-       assert(r == 0);
-      }
-    }
-
-    dout(10) << __func__ << ": creating obc from disk: " << obc
-            << dendl;
-  }
-
-  // XXX: Caller doesn't expect this
-  if (obc->ssc == NULL) {
-    derr << __func__ << ": obc->ssc not available, not returning context" << dendl;
-    return ObjectContextRef();   // -ENOENT!
-  }
-
-  dout(10) << __func__ << ": " << obc << " " << soid
-          << " " << obc->rwstate
-          << " oi: " << obc->obs.oi
-          << " exists: " << (int)obc->obs.exists
-          << " ssc: " << obc->ssc
-          << " snapset: " << obc->ssc->snapset << dendl;
-  return obc;
-}
-
-void PrimaryLogPG::context_registry_on_change()
-{
-  pair<hobject_t, ObjectContextRef> i;
-  while (object_contexts.get_next(i.first, &i)) {
-    ObjectContextRef obc(i.second);
-    if (obc) {
-      for (map<pair<uint64_t, entity_name_t>, WatchRef>::iterator j =
-            obc->watchers.begin();
-          j != obc->watchers.end();
-          obc->watchers.erase(j++)) {
-       j->second->discard();
-      }
-    }
-  }
-}
-
-
-/*
- * If we return an error, and set *pmissing, then promoting that
- * object may help.
- *
- * If we return -EAGAIN, we will always set *pmissing to the missing
- * object to wait for.
- *
- * If we return an error but do not set *pmissing, then we know the
- * object does not exist.
- */
-int PrimaryLogPG::find_object_context(const hobject_t& oid,
-                                     ObjectContextRef *pobc,
-                                     bool can_create,
-                                     bool map_snapid_to_clone,
-                                     hobject_t *pmissing)
-{
-  FUNCTRACE();
-  assert(oid.pool == static_cast<int64_t>(info.pgid.pool()));
-  // want the head?
-  if (oid.snap == CEPH_NOSNAP) {
-    ObjectContextRef obc = get_object_context(oid, can_create);
-    if (!obc) {
-      if (pmissing)
-        *pmissing = oid;
-      return -ENOENT;
-    }
-    dout(10) << "find_object_context " << oid
-       << " @" << oid.snap
-       << " oi=" << obc->obs.oi
-       << dendl;
-    *pobc = obc;
-
-    return 0;
-  }
-
-  hobject_t head = oid.get_head();
-
-  // want the snapdir?
-  if (oid.snap == CEPH_SNAPDIR) {
-    // return head or snapdir, whichever exists.
-    ObjectContextRef headobc = get_object_context(head, can_create);
-    ObjectContextRef obc = headobc;
-    if (!obc || !obc->obs.exists)
-      obc = get_object_context(oid, can_create);
-    if (!obc || !obc->obs.exists) {
-      // if we have neither, we would want to promote the head.
-      if (pmissing)
-       *pmissing = head;
-      if (pobc)
-       *pobc = headobc; // may be null
-      return -ENOENT;
-    }
-    dout(10) << "find_object_context " << oid
-            << " @" << oid.snap
-            << " oi=" << obc->obs.oi
-            << dendl;
-    *pobc = obc;
-
-    // always populate ssc for SNAPDIR...
-    if (!obc->ssc)
-      obc->ssc = get_snapset_context(
-       oid, true);
-    return 0;
-  }
-
-  // we want a snap
-  if (!map_snapid_to_clone && pool.info.is_removed_snap(oid.snap)) {
-    dout(10) << __func__ << " snap " << oid.snap << " is removed" << dendl;
-    return -ENOENT;
-  }
-
-  SnapSetContext *ssc = get_snapset_context(oid, can_create);
-  if (!ssc || !(ssc->exists || can_create)) {
-    dout(20) << __func__ << " " << oid << " no snapset" << dendl;
-    if (pmissing)
-      *pmissing = head;  // start by getting the head
-    if (ssc)
-      put_snapset_context(ssc);
-    return -ENOENT;
-  }
-
-  if (map_snapid_to_clone) {
-    dout(10) << "find_object_context " << oid << " @" << oid.snap
-            << " snapset " << ssc->snapset
-            << " map_snapid_to_clone=true" << dendl;
-    if (oid.snap > ssc->snapset.seq) {
-      // already must be readable
-      ObjectContextRef obc = get_object_context(head, false);
-      dout(10) << "find_object_context " << oid << " @" << oid.snap
-              << " snapset " << ssc->snapset
-              << " maps to head" << dendl;
-      *pobc = obc;
-      put_snapset_context(ssc);
-      return (obc && obc->obs.exists) ? 0 : -ENOENT;
-    } else {
-      vector<snapid_t>::const_iterator citer = std::find(
-       ssc->snapset.clones.begin(),
-       ssc->snapset.clones.end(),
-       oid.snap);
-      if (citer == ssc->snapset.clones.end()) {
-       dout(10) << "find_object_context " << oid << " @" << oid.snap
-                << " snapset " << ssc->snapset
-                << " maps to nothing" << dendl;
-       put_snapset_context(ssc);
-       return -ENOENT;
-      }
-
-      dout(10) << "find_object_context " << oid << " @" << oid.snap
-              << " snapset " << ssc->snapset
-              << " maps to " << oid << dendl;
-
-      if (pg_log.get_missing().is_missing(oid)) {
-       dout(10) << "find_object_context " << oid << " @" << oid.snap
-                << " snapset " << ssc->snapset
-                << " " << oid << " is missing" << dendl;
-       if (pmissing)
-         *pmissing = oid;
-       put_snapset_context(ssc);
-       return -EAGAIN;
-      }
-
-      ObjectContextRef obc = get_object_context(oid, false);
-      if (!obc || !obc->obs.exists) {
-       dout(10) << "find_object_context " << oid << " @" << oid.snap
-                << " snapset " << ssc->snapset
-                << " " << oid << " is not present" << dendl;
-       if (pmissing)
-         *pmissing = oid;
-       put_snapset_context(ssc);
-       return -ENOENT;
-      }
-      dout(10) << "find_object_context " << oid << " @" << oid.snap
-              << " snapset " << ssc->snapset
-              << " " << oid << " HIT" << dendl;
-      *pobc = obc;
-      put_snapset_context(ssc);
-      return 0;
-    }
-    ceph_abort(); //unreachable
-  }
-
-  dout(10) << "find_object_context " << oid << " @" << oid.snap
-          << " snapset " << ssc->snapset << dendl;
-  // head?
-  if (oid.snap > ssc->snapset.seq) {
-    if (ssc->snapset.head_exists) {
-      ObjectContextRef obc = get_object_context(head, false);
-      dout(10) << "find_object_context  " << head
-              << " want " << oid.snap << " > snapset seq " << ssc->snapset.seq
-              << " -- HIT " << obc->obs
-              << dendl;
-      if (!obc->ssc)
-       obc->ssc = ssc;
-      else {
-       assert(ssc == obc->ssc);
-       put_snapset_context(ssc);
-      }
-      *pobc = obc;
-      return 0;
-    }
-    dout(10) << "find_object_context  " << head
-            << " want " << oid.snap << " > snapset seq " << ssc->snapset.seq
-            << " but head dne -- DNE"
-            << dendl;
-    put_snapset_context(ssc);
-    return -ENOENT;
-  }
-
-  // which clone would it be?
-  unsigned k = 0;
-  while (k < ssc->snapset.clones.size() &&
-        ssc->snapset.clones[k] < oid.snap)
-    k++;
-  if (k == ssc->snapset.clones.size()) {
-    dout(10) << "find_object_context  no clones with last >= oid.snap "
-            << oid.snap << " -- DNE" << dendl;
-    put_snapset_context(ssc);
-    return -ENOENT;
-  }
-  hobject_t soid(oid.oid, oid.get_key(), ssc->snapset.clones[k], oid.get_hash(),
-                info.pgid.pool(), oid.get_namespace());
-
-  if (pg_log.get_missing().is_missing(soid)) {
-    dout(20) << "find_object_context  " << soid << " missing, try again later"
-            << dendl;
-    if (pmissing)
-      *pmissing = soid;
-    put_snapset_context(ssc);
-    return -EAGAIN;
-  }
-
-  ObjectContextRef obc = get_object_context(soid, false);
-  if (!obc || !obc->obs.exists) {
-    if (pmissing)
-      *pmissing = soid;
-    put_snapset_context(ssc);
-    if (is_degraded_or_backfilling_object(soid)) {
-      dout(20) << __func__ << " clone is degraded or backfilling " << soid << dendl;
-      return -EAGAIN;
-    } else {
-      dout(20) << __func__ << " missing clone " << soid << dendl;
-      return -ENOENT;
-    }
-  }
-
-  if (!obc->ssc) {
-    obc->ssc = ssc;
-  } else {
-    assert(obc->ssc == ssc);
-    put_snapset_context(ssc);
-  }
-  ssc = 0;
-
-  // clone
-  dout(20) << "find_object_context  " << soid
-          << " snapset " << obc->ssc->snapset
-          << " legacy_snaps " << obc->obs.oi.legacy_snaps
-          << dendl;
-  snapid_t first, last;
-  if (obc->ssc->snapset.is_legacy()) {
-    first = obc->obs.oi.legacy_snaps.back();
-    last = obc->obs.oi.legacy_snaps.front();
-  } else {
-    auto p = obc->ssc->snapset.clone_snaps.find(soid.snap);
-    assert(p != obc->ssc->snapset.clone_snaps.end());
-    first = p->second.back();
-    last = p->second.front();
-  }
-  if (first <= oid.snap) {
-    dout(20) << "find_object_context  " << soid << " [" << first << "," << last
-            << "] contains " << oid.snap << " -- HIT " << obc->obs << dendl;
-    *pobc = obc;
-    return 0;
-  } else {
-    dout(20) << "find_object_context  " << soid << " [" << first << "," << last
-            << "] does not contain " << oid.snap << " -- DNE" << dendl;
-    return -ENOENT;
-  }
-}
-
-void PrimaryLogPG::object_context_destructor_callback(ObjectContext *obc)
-{
-  if (obc->ssc)
-    put_snapset_context(obc->ssc);
-}
-
-void PrimaryLogPG::add_object_context_to_pg_stat(ObjectContextRef obc, pg_stat_t *pgstat)
-{
-  object_info_t& oi = obc->obs.oi;
-
-  dout(10) << "add_object_context_to_pg_stat " << oi.soid << dendl;
-  object_stat_sum_t stat;
-
-  stat.num_bytes += oi.size;
-
-  if (oi.soid.snap != CEPH_SNAPDIR)
-    stat.num_objects++;
-  if (oi.is_dirty())
-    stat.num_objects_dirty++;
-  if (oi.is_whiteout())
-    stat.num_whiteouts++;
-  if (oi.is_omap())
-    stat.num_objects_omap++;
-  if (oi.is_cache_pinned())
-    stat.num_objects_pinned++;
-
-  if (oi.soid.snap && oi.soid.snap != CEPH_NOSNAP && oi.soid.snap != CEPH_SNAPDIR) {
-    stat.num_object_clones++;
-
-    if (!obc->ssc)
-      obc->ssc = get_snapset_context(oi.soid, false);
-    assert(obc->ssc);
-
-    // subtract off clone overlap
-    if (obc->ssc->snapset.clone_overlap.count(oi.soid.snap)) {
-      interval_set<uint64_t>& o = obc->ssc->snapset.clone_overlap[oi.soid.snap];
-      for (interval_set<uint64_t>::const_iterator r = o.begin();
-          r != o.end();
-          ++r) {
-       stat.num_bytes -= r.get_len();
-      }          
-    }
-  }
-
-  // add it in
-  pgstat->stats.sum.add(stat);
-}
-
-void PrimaryLogPG::kick_object_context_blocked(ObjectContextRef obc)
-{
-  const hobject_t& soid = obc->obs.oi.soid;
-  if (obc->is_blocked()) {
-    dout(10) << __func__ << " " << soid << " still blocked" << dendl;
-    return;
-  }
-
-  map<hobject_t, list<OpRequestRef>>::iterator p = waiting_for_blocked_object.find(soid);
-  if (p != waiting_for_blocked_object.end()) {
-    list<OpRequestRef>& ls = p->second;
-    dout(10) << __func__ << " " << soid << " requeuing " << ls.size() << " requests" << dendl;
-    requeue_ops(ls);
-    waiting_for_blocked_object.erase(p);
-  }
-
-  map<hobject_t, ObjectContextRef>::iterator i =
-    objects_blocked_on_snap_promotion.find(obc->obs.oi.soid.get_head());
-  if (i != objects_blocked_on_snap_promotion.end()) {
-    assert(i->second == obc);
-    objects_blocked_on_snap_promotion.erase(i);
-  }
-
-  if (obc->requeue_scrub_on_unblock) {
-    obc->requeue_scrub_on_unblock = false;
-    requeue_scrub();
-  }
-}
-
-SnapSetContext *PrimaryLogPG::get_snapset_context(
-  const hobject_t& oid,
-  bool can_create,
-  const map<string, bufferlist> *attrs,
-  bool oid_existed)
-{
-  Mutex::Locker l(snapset_contexts_lock);
-  SnapSetContext *ssc;
-  map<hobject_t, SnapSetContext*>::iterator p = snapset_contexts.find(
-    oid.get_snapdir());
-  if (p != snapset_contexts.end()) {
-    if (can_create || p->second->exists) {
-      ssc = p->second;
-    } else {
-      return NULL;
-    }
-  } else {
-    bufferlist bv;
-    if (!attrs) {
-      int r = -ENOENT;
-      if (!(oid.is_head() && !oid_existed))
-       r = pgbackend->objects_get_attr(oid.get_head(), SS_ATTR, &bv);
-      if (r < 0) {
-       // try _snapset
-       if (!(oid.is_snapdir() && !oid_existed))
-         r = pgbackend->objects_get_attr(oid.get_snapdir(), SS_ATTR, &bv);
-       if (r < 0 && !can_create)
-         return NULL;
-      }
-    } else {
-      assert(attrs->count(SS_ATTR));
-      bv = attrs->find(SS_ATTR)->second;
-    }
-    ssc = new SnapSetContext(oid.get_snapdir());
-    _register_snapset_context(ssc);
-    if (bv.length()) {
-      bufferlist::iterator bvp = bv.begin();
-      try {
-       ssc->snapset.decode(bvp);
-      } catch (buffer::error& e) {
-        dout(0) << __func__ << " Can't decode snapset: " << e << dendl;
-       return NULL;
-      }
-      ssc->exists = true;
-    } else {
-      ssc->exists = false;
-    }
-  }
-  assert(ssc);
-  ssc->ref++;
-  return ssc;
-}
-
-void PrimaryLogPG::put_snapset_context(SnapSetContext *ssc)
-{
-  Mutex::Locker l(snapset_contexts_lock);
-  --ssc->ref;
-  if (ssc->ref == 0) {
-    if (ssc->registered)
-      snapset_contexts.erase(ssc->oid);
-    delete ssc;
-  }
-}
-
-/** pull - request object from a peer
- */
-
-/*
- * Return values:
- *  NONE  - didn't pull anything
- *  YES   - pulled what the caller wanted
- *  OTHER - needed to pull something else first (_head or _snapdir)
- */
-enum { PULL_NONE, PULL_OTHER, PULL_YES };
-
-int PrimaryLogPG::recover_missing(
-  const hobject_t &soid, eversion_t v,
-  int priority,
-  PGBackend::RecoveryHandle *h)
-{
-  if (missing_loc.is_unfound(soid)) {
-    dout(7) << "pull " << soid
-           << " v " << v 
-           << " but it is unfound" << dendl;
-    return PULL_NONE;
-  }
-
-  if (missing_loc.is_deleted(soid)) {
-    start_recovery_op(soid);
-    assert(!recovering.count(soid));
-    recovering.insert(make_pair(soid, ObjectContextRef()));
-    epoch_t cur_epoch = get_osdmap()->get_epoch();
-    remove_missing_object(soid, v, new FunctionContext(
-     [=](int) {
-       lock();
-       if (!pg_has_reset_since(cur_epoch)) {
-        bool object_missing = false;
-        for (const auto& shard : actingbackfill) {
-          if (shard == pg_whoami)
-            continue;
-          if (peer_missing[shard].is_missing(soid)) {
-            dout(20) << __func__ << ": soid " << soid << " needs to be deleted from replica " << shard << dendl;
-            object_missing = true;
-            break;
-          }
-        }
-        if (!object_missing) {
-          object_stat_sum_t stat_diff;
-          stat_diff.num_objects_recovered = 1;
-          on_global_recover(soid, stat_diff, true);
-        } else {
-          auto recovery_handle = pgbackend->open_recovery_op();
-          pgbackend->recover_delete_object(soid, v, recovery_handle);
-          pgbackend->run_recovery_op(recovery_handle, priority);
-        }
-       }
-       unlock();
-     }));
-    return PULL_YES;
-  }
-
-  // is this a snapped object?  if so, consult the snapset.. we may not need the entire object!
-  ObjectContextRef obc;
-  ObjectContextRef head_obc;
-  if (soid.snap && soid.snap < CEPH_NOSNAP) {
-    // do we have the head and/or snapdir?
-    hobject_t head = soid.get_head();
-    if (pg_log.get_missing().is_missing(head)) {
-      if (recovering.count(head)) {
-       dout(10) << " missing but already recovering head " << head << dendl;
-       return PULL_NONE;
-      } else {
-       int r = recover_missing(
-         head, pg_log.get_missing().get_items().find(head)->second.need, priority,
-         h);
-       if (r != PULL_NONE)
-         return PULL_OTHER;
-       return PULL_NONE;
-      }
-    }
-    head = soid.get_snapdir();
-    if (pg_log.get_missing().is_missing(head)) {
-      if (recovering.count(head)) {
-       dout(10) << " missing but already recovering snapdir " << head << dendl;
-       return PULL_NONE;
-      } else {
-       int r = recover_missing(
-         head, pg_log.get_missing().get_items().find(head)->second.need, priority,
-         h);
-       if (r != PULL_NONE)
-         return PULL_OTHER;
-       return PULL_NONE;
-      }
-    }
-
-    // we must have one or the other
-    head_obc = get_object_context(
-      soid.get_head(),
-      false,
-      0);
-    if (!head_obc)
-      head_obc = get_object_context(
-       soid.get_snapdir(),
-       false,
-       0);
-    assert(head_obc);
-  }
-  start_recovery_op(soid);
-  assert(!recovering.count(soid));
-  recovering.insert(make_pair(soid, obc));
-  int r = pgbackend->recover_object(
-    soid,
-    v,
-    head_obc,
-    obc,
-    h);
-  // This is only a pull which shouldn't return an error
-  assert(r >= 0);
-  return PULL_YES;
-}
-
-void PrimaryLogPG::send_remove_op(
-  const hobject_t& oid, eversion_t v, pg_shard_t peer)
-{
-  ceph_tid_t tid = osd->get_tid();
-  osd_reqid_t rid(osd->get_cluster_msgr_name(), 0, tid);
-
-  dout(10) << "send_remove_op " << oid << " from osd." << peer
-          << " tid " << tid << dendl;
-
-  MOSDSubOp *subop = new MOSDSubOp(
-    rid, pg_whoami, spg_t(info.pgid.pgid, peer.shard),
-    oid, CEPH_OSD_FLAG_ACK,
-    get_osdmap()->get_epoch(), tid, v);
-  subop->ops = vector<OSDOp>(1);
-  subop->ops[0].op.op = CEPH_OSD_OP_DELETE;
-
-  osd->send_message_osd_cluster(peer.osd, subop, get_osdmap()->get_epoch());
-}
-
-void PrimaryLogPG::remove_missing_object(const hobject_t &soid,
-                                        eversion_t v, Context *on_complete)
-{
-  dout(20) << __func__ << " " << soid << " " << v << dendl;
-  assert(on_complete != nullptr);
-  // delete locally
-  ObjectStore::Transaction t;
-  remove_snap_mapped_object(t, soid);
-
-  ObjectRecoveryInfo recovery_info;
-  recovery_info.soid = soid;
-  recovery_info.version = v;
-
-  epoch_t cur_epoch = get_osdmap()->get_epoch();
-  t.register_on_complete(new FunctionContext(
-     [=](int) {
-       lock();
-       if (!pg_has_reset_since(cur_epoch)) {
-        ObjectStore::Transaction t2;
-        on_local_recover(soid, recovery_info, ObjectContextRef(), true, &t2);
-        t2.register_on_complete(on_complete);
-        int r = osd->store->queue_transaction(osr.get(), std::move(t2), nullptr);
-        assert(r == 0);
-        unlock();
-       } else {
-        unlock();
-        on_complete->complete(-EAGAIN);
-       }
-     }));
-  int r = osd->store->queue_transaction(osr.get(), std::move(t), nullptr);
-  assert(r == 0);
-}
-
-void PrimaryLogPG::finish_degraded_object(const hobject_t& oid)
-{
-  dout(10) << "finish_degraded_object " << oid << dendl;
-  if (callbacks_for_degraded_object.count(oid)) {
-    list<Context*> contexts;
-    contexts.swap(callbacks_for_degraded_object[oid]);
-    callbacks_for_degraded_object.erase(oid);
-    for (list<Context*>::iterator i = contexts.begin();
-        i != contexts.end();
-        ++i) {
-      (*i)->complete(0);
-    }
-  }
-  map<hobject_t, snapid_t>::iterator i = objects_blocked_on_degraded_snap.find(
-    oid.get_head());
-  if (i != objects_blocked_on_degraded_snap.end() &&
-      i->second == oid.snap)
-    objects_blocked_on_degraded_snap.erase(i);
-}
-
-void PrimaryLogPG::_committed_pushed_object(
-  epoch_t epoch, eversion_t last_complete)
-{
-  lock();
-  if (!pg_has_reset_since(epoch)) {
-    dout(10) << "_committed_pushed_object last_complete " << last_complete << " now ondisk" << dendl;
-    last_complete_ondisk = last_complete;
-
-    if (last_complete_ondisk == info.last_update) {
-      if (!is_primary()) {
-        // Either we are a replica or backfill target.
-       // we are fully up to date.  tell the primary!
-       osd->send_message_osd_cluster(
-         get_primary().osd,
-         new MOSDPGTrim(
-           get_osdmap()->get_epoch(),
-           spg_t(info.pgid.pgid, get_primary().shard),
-           last_complete_ondisk),
-         get_osdmap()->get_epoch());
-      } else {
-       calc_min_last_complete_ondisk();
-      }
-    }
-
-  } else {
-    dout(10) << "_committed_pushed_object pg has changed, not touching last_complete_ondisk" << dendl;
-  }
-
-  unlock();
-}
-
-void PrimaryLogPG::_applied_recovered_object(ObjectContextRef obc)
-{
-  lock();
-  dout(20) << __func__ << dendl;
-  if (obc) {
-    dout(20) << "obc = " << *obc << dendl;
-  }
-  assert(active_pushes >= 1);
-  --active_pushes;
-
-  // requeue an active chunky scrub waiting on recovery ops
-  if (!deleting && active_pushes == 0
-      && scrubber.is_chunky_scrub_active()) {
-    if (ops_blocked_by_scrub()) {
-      requeue_scrub(true);
-    } else {
-      requeue_scrub(false);
-    }
-  }
-  unlock();
-}
-
-void PrimaryLogPG::_applied_recovered_object_replica()
-{
-  lock();
-  dout(20) << __func__ << dendl;
-  assert(active_pushes >= 1);
-  --active_pushes;
-
-  // requeue an active chunky scrub waiting on recovery ops
-  if (!deleting && active_pushes == 0 &&
-      scrubber.active_rep_scrub && static_cast<const MOSDRepScrub*>(
-       scrubber.active_rep_scrub->get_req())->chunky) {
-    osd->enqueue_back(
-      info.pgid,
-      PGQueueable(scrubber.active_rep_scrub, get_osdmap()->get_epoch()));
-    scrubber.active_rep_scrub = OpRequestRef();
-  }
-  unlock();
-}
-
-void PrimaryLogPG::recover_got(hobject_t oid, eversion_t v)
-{
-  dout(10) << "got missing " << oid << " v " << v << dendl;
-  pg_log.recover_got(oid, v, info);
-  if (pg_log.get_log().complete_to != pg_log.get_log().log.end()) {
-    dout(10) << "last_complete now " << info.last_complete
-            << " log.complete_to " << pg_log.get_log().complete_to->version
-            << dendl;
-  } else {
-    dout(10) << "last_complete now " << info.last_complete
-            << " log.complete_to at end" << dendl;
-    //below is not true in the repair case.
-    //assert(missing.num_missing() == 0);  // otherwise, complete_to was wrong.
-    assert(info.last_complete == info.last_update);
-  }
-}
-
-void PrimaryLogPG::primary_failed(const hobject_t &soid)
-{
-  list<pg_shard_t> fl = { pg_whoami };
-  failed_push(fl, soid);
-}
-
-void PrimaryLogPG::failed_push(const list<pg_shard_t> &from, const hobject_t &soid)
-{
-  dout(20) << __func__ << ": " << soid << dendl;
-  assert(recovering.count(soid));
-  auto obc = recovering[soid];
-  if (obc) {
-    list<OpRequestRef> blocked_ops;
-    obc->drop_recovery_read(&blocked_ops);
-    requeue_ops(blocked_ops);
-  }
-  recovering.erase(soid);
-  for (auto&& i : from)
-    missing_loc.remove_location(soid, i);
-  dout(0) << __func__ << " " << soid << " from shard " << from
-         << ", reps on " << missing_loc.get_locations(soid)
-         << " unfound? " << missing_loc.is_unfound(soid) << dendl;
-  finish_recovery_op(soid);  // close out this attempt,
-}
-
-void PrimaryLogPG::sub_op_remove(OpRequestRef op)
-{
-  const MOSDSubOp *m = static_cast<const MOSDSubOp*>(op->get_req());
-  assert(m->get_type() == MSG_OSD_SUBOP);
-  dout(7) << "sub_op_remove " << m->poid << dendl;
-
-  op->mark_started();
-
-  ObjectStore::Transaction t;
-  remove_snap_mapped_object(t, m->poid);
-  int r = osd->store->queue_transaction(osr.get(), std::move(t), NULL);
-  assert(r == 0);
-}
-
-eversion_t PrimaryLogPG::pick_newest_available(const hobject_t& oid)
-{
-  eversion_t v;
-  pg_missing_item pmi;
-  bool is_missing = pg_log.get_missing().is_missing(oid, &pmi);
-  assert(is_missing);
-  v = pmi.have;
-  dout(10) << "pick_newest_available " << oid << " " << v << " on osd." << osd->whoami << " (local)" << dendl;
-
-  assert(!actingbackfill.empty());
-  for (set<pg_shard_t>::iterator i = actingbackfill.begin();
-       i != actingbackfill.end();
-       ++i) {
-    if (*i == get_primary()) continue;
-    pg_shard_t peer = *i;
-    if (!peer_missing[peer].is_missing(oid)) {
-      continue;
-    }
-    eversion_t h = peer_missing[peer].get_items().at(oid).have;
-    dout(10) << "pick_newest_available " << oid << " " << h << " on osd." << peer << dendl;
-    if (h > v)
-      v = h;
-  }
-
-  dout(10) << "pick_newest_available " << oid << " " << v << " (newest)" << dendl;
-  return v;
-}
-
-void PrimaryLogPG::do_update_log_missing(OpRequestRef &op)
-{
-  const MOSDPGUpdateLogMissing *m = static_cast<const MOSDPGUpdateLogMissing*>(
-    op->get_req());
-  assert(m->get_type() == MSG_OSD_PG_UPDATE_LOG_MISSING);
-  ObjectStore::Transaction t;
-  append_log_entries_update_missing(m->entries, t);
-
-  Context *complete = new FunctionContext(
-    [=](int) {
-      const MOSDPGUpdateLogMissing *msg = static_cast<const MOSDPGUpdateLogMissing*>(
-       op->get_req());
-      lock();
-      if (!pg_has_reset_since(msg->get_epoch())) {
-       MOSDPGUpdateLogMissingReply *reply =
-         new MOSDPGUpdateLogMissingReply(
-           spg_t(info.pgid.pgid, primary_shard().shard),
-           pg_whoami.shard,
-           msg->get_epoch(),
-           msg->min_epoch,
-           msg->get_tid());
-       reply->set_priority(CEPH_MSG_PRIO_HIGH);
-       msg->get_connection()->send_message(reply);
-      }
-      unlock();
-    });
-
-  if (get_osdmap()->require_osd_release >= CEPH_RELEASE_KRAKEN) {
-    t.register_on_commit(complete);
-  } else {
-    /* Hack to work around the fact that ReplicatedBackend sends
-     * ack+commit if commit happens first
-     *
-     * This behavior is no longer necessary, but we preserve it so old
-     * primaries can keep their repops in order */
-    if (pool.info.ec_pool()) {
-      t.register_on_complete(complete);
-    } else {
-      t.register_on_commit(complete);
-    }
-  }
-  t.register_on_applied(
-    new C_OSD_OnApplied{this, get_osdmap()->get_epoch(), info.last_update});
-  int tr = osd->store->queue_transaction(
-    osr.get(),
-    std::move(t),
-    nullptr);
-  assert(tr == 0);
-}
-
-void PrimaryLogPG::do_update_log_missing_reply(OpRequestRef &op)
-{
-  const MOSDPGUpdateLogMissingReply *m =
-    static_cast<const MOSDPGUpdateLogMissingReply*>(
-    op->get_req());
-  dout(20) << __func__ << " got reply from "
-          << m->get_from() << dendl;
-
-  auto it = log_entry_update_waiting_on.find(m->get_tid());
-  if (it != log_entry_update_waiting_on.end()) {
-    if (it->second.waiting_on.count(m->get_from())) {
-      it->second.waiting_on.erase(m->get_from());
-    } else {
-      osd->clog->error()
-       << info.pgid << " got reply "
-       << *m << " from shard we are not waiting for "
-       << m->get_from();
-    }
-
-    if (it->second.waiting_on.empty()) {
-      repop_all_committed(it->second.repop.get());
-      log_entry_update_waiting_on.erase(it);
-    }
-  } else {
-    osd->clog->error()
-      << info.pgid << " got reply "
-      << *m << " on unknown tid " << m->get_tid();
-  }
-}
-
-/* Mark all unfound objects as lost.
- */
-void PrimaryLogPG::mark_all_unfound_lost(
-  int what,
-  ConnectionRef con,
-  ceph_tid_t tid)
-{
-  dout(3) << __func__ << " " << pg_log_entry_t::get_op_name(what) << dendl;
-  list<hobject_t> oids;
-
-  dout(30) << __func__ << ": log before:\n";
-  pg_log.get_log().print(*_dout);
-  *_dout << dendl;
-
-  mempool::osd_pglog::list<pg_log_entry_t> log_entries;
-
-  utime_t mtime = ceph_clock_now();
-  map<hobject_t, pg_missing_item>::const_iterator m =
-    missing_loc.get_needs_recovery().begin();
-  map<hobject_t, pg_missing_item>::const_iterator mend =
-    missing_loc.get_needs_recovery().end();
-
-  ObcLockManager manager;
-  eversion_t v = get_next_version();
-  v.epoch = get_osdmap()->get_epoch();
-  uint64_t num_unfound = missing_loc.num_unfound();
-  while (m != mend) {
-    const hobject_t &oid(m->first);
-    if (!missing_loc.is_unfound(oid)) {
-      // We only care about unfound objects
-      ++m;
-      continue;
-    }
-
-    ObjectContextRef obc;
-    eversion_t prev;
-
-    switch (what) {
-    case pg_log_entry_t::LOST_MARK:
-      assert(0 == "actually, not implemented yet!");
-      break;
-
-    case pg_log_entry_t::LOST_REVERT:
-      prev = pick_newest_available(oid);
-      if (prev > eversion_t()) {
-       // log it
-       pg_log_entry_t e(
-         pg_log_entry_t::LOST_REVERT, oid, v,
-         m->second.need, 0, osd_reqid_t(), mtime, 0);
-       e.reverting_to = prev;
-       e.mark_unrollbackable();
-       log_entries.push_back(e);
-       dout(10) << e << dendl;
-
-       // we are now missing the new version; recovery code will sort it out.
-       ++v.version;
-       ++m;
-       break;
-      }
-
-    case pg_log_entry_t::LOST_DELETE:
-      {
-       pg_log_entry_t e(pg_log_entry_t::LOST_DELETE, oid, v, m->second.need,
-                        0, osd_reqid_t(), mtime, 0);
-       if (get_osdmap()->require_osd_release >= CEPH_RELEASE_JEWEL) {
-         if (pool.info.require_rollback()) {
-           e.mod_desc.try_rmobject(v.version);
-         } else {
-           e.mark_unrollbackable();
-         }
-       } // otherwise, just do what we used to do
-       dout(10) << e << dendl;
-       log_entries.push_back(e);
-        oids.push_back(oid);
-
-       ++v.version;
-       ++m;
-      }
-      break;
-
-    default:
-      ceph_abort();
-    }
-  }
-
-  info.stats.stats_invalid = true;
-
-  submit_log_entries(
-    log_entries,
-    std::move(manager),
-    boost::optional<std::function<void(void)> >(
-      [this, oids, con, num_unfound, tid]() {
-       if (perform_deletes_during_peering()) {
-         for (auto oid : oids) {
-           // clear old locations - merge_new_log_entries will have
-           // handled rebuilding missing_loc for each of these
-           // objects if we have the RECOVERY_DELETES flag
-           missing_loc.recovered(oid);
-         }
-       }
-
-       for (auto& p : waiting_for_unreadable_object) {
-         release_backoffs(p.first);
-       }
-       requeue_object_waiters(waiting_for_unreadable_object);
-       queue_recovery();
-
-       stringstream ss;
-       ss << "pg has " << num_unfound
-          << " objects unfound and apparently lost marking";
-       string rs = ss.str();
-       dout(0) << "do_command r=" << 0 << " " << rs << dendl;
-       osd->clog->info() << rs;
-       if (con) {
-         MCommandReply *reply = new MCommandReply(0, rs);
-         reply->set_tid(tid);
-         con->send_message(reply);
-       }
-      }),
-    OpRequestRef());
-}
-
-void PrimaryLogPG::_split_into(pg_t child_pgid, PG *child, unsigned split_bits)
-{
-  assert(repop_queue.empty());
-}
-
-/*
- * pg status change notification
- */
-
-void PrimaryLogPG::apply_and_flush_repops(bool requeue)
-{
-  list<OpRequestRef> rq;
-
-  // apply all repops
-  while (!repop_queue.empty()) {
-    RepGather *repop = repop_queue.front();
-    repop_queue.pop_front();
-    dout(10) << " canceling repop tid " << repop->rep_tid << dendl;
-    repop->rep_aborted = true;
-    repop->on_applied.clear();
-    repop->on_committed.clear();
-    repop->on_success.clear();
-
-    if (requeue) {
-      if (repop->op) {
-       dout(10) << " requeuing " << *repop->op->get_req() << dendl;
-       rq.push_back(repop->op);
-       repop->op = OpRequestRef();
-      }
-
-      // also requeue any dups, interleaved into position
-      map<eversion_t, list<pair<OpRequestRef, version_t> > >::iterator p =
-       waiting_for_ondisk.find(repop->v);
-      if (p != waiting_for_ondisk.end()) {
-       dout(10) << " also requeuing ondisk waiters " << p->second << dendl;
-       for (list<pair<OpRequestRef, version_t> >::iterator i =
-              p->second.begin();
-            i != p->second.end();
-            ++i) {
-         rq.push_back(i->first);
-       }
-       waiting_for_ondisk.erase(p);
-      }
-    }
-
-    remove_repop(repop);
-  }
-
-  assert(repop_queue.empty());
-
-  if (requeue) {
-    requeue_ops(rq);
-    if (!waiting_for_ondisk.empty()) {
-      for (map<eversion_t, list<pair<OpRequestRef, version_t> > >::iterator i =
-            waiting_for_ondisk.begin();
-          i != waiting_for_ondisk.end();
-          ++i) {
-       for (list<pair<OpRequestRef, version_t> >::iterator j =
-              i->second.begin();
-            j != i->second.end();
-            ++j) {
-         derr << __func__ << ": op " << *(j->first->get_req()) << " waiting on "
-              << i->first << dendl;
-       }
-      }
-      assert(waiting_for_ondisk.empty());
-    }
-  }
-
-  waiting_for_ondisk.clear();
-}
-
-void PrimaryLogPG::on_flushed()
-{
-  assert(flushes_in_progress > 0);
-  flushes_in_progress--;
-  if (flushes_in_progress == 0) {
-    requeue_ops(waiting_for_peered);
-  }
-  if (!is_peered() || !is_primary()) {
-    pair<hobject_t, ObjectContextRef> i;
-    while (object_contexts.get_next(i.first, &i)) {
-      derr << "on_flushed: object " << i.first << " obc still alive" << dendl;
-    }
-    assert(object_contexts.empty());
-  }
-  pgbackend->on_flushed();
-}
-
-void PrimaryLogPG::on_removal(ObjectStore::Transaction *t)
-{
-  dout(10) << "on_removal" << dendl;
-
-  // adjust info to backfill
-  info.set_last_backfill(hobject_t());
-  pg_log.reset_backfill();
-  dirty_info = true;
-
-
-  // clear log
-  PGLogEntryHandler rollbacker{this, t};
-  pg_log.roll_forward(&rollbacker);
-
-  write_if_dirty(*t);
-
-  if (!deleting)
-    on_shutdown();
-}
-
-void PrimaryLogPG::clear_async_reads()
-{
-  dout(10) << __func__ << dendl;
-  for(auto& i : in_progress_async_reads) {
-    dout(10) << "clear ctx: "
-             << "OpRequestRef " << i.first
-             << " OpContext " << i.second
-             << dendl;
-    close_op_ctx(i.second);
-  }
-}
-
-void PrimaryLogPG::on_shutdown()
-{
-  dout(10) << "on_shutdown" << dendl;
-
-  // remove from queues
-  osd->pg_stat_queue_dequeue(this);
-  osd->peering_wq.dequeue(this);
-
-  // handles queue races
-  deleting = true;
-
-  if (recovery_queued) {
-    recovery_queued = false;
-    osd->clear_queued_recovery(this);
-  }
-
-  clear_scrub_reserved();
-  scrub_clear_state();
-
-  unreg_next_scrub();
-  cancel_copy_ops(false);
-  cancel_flush_ops(false);
-  cancel_proxy_ops(false);
-  apply_and_flush_repops(false);
-  cancel_log_updates();
-  // we must remove PGRefs, so do this this prior to release_backoffs() callers
-  clear_backoffs(); 
-  // clean up snap trim references
-  snap_trimmer_machine.process_event(Reset());
-
-  pgbackend->on_change();
-
-  context_registry_on_change();
-  object_contexts.clear();
-
-  clear_async_reads();
-
-  osd->remote_reserver.cancel_reservation(info.pgid);
-  osd->local_reserver.cancel_reservation(info.pgid);
-
-  clear_primary_state();
-  cancel_recovery();
-}
-
-void PrimaryLogPG::on_activate()
-{
-  // all clean?
-  if (needs_recovery()) {
-    dout(10) << "activate not all replicas are up-to-date, queueing recovery" << dendl;
-    queue_peering_event(
-      CephPeeringEvtRef(
-       std::make_shared<CephPeeringEvt>(
-         get_osdmap()->get_epoch(),
-         get_osdmap()->get_epoch(),
-         DoRecovery())));
-  } else if (needs_backfill()) {
-    dout(10) << "activate queueing backfill" << dendl;
-    queue_peering_event(
-      CephPeeringEvtRef(
-       std::make_shared<CephPeeringEvt>(
-         get_osdmap()->get_epoch(),
-         get_osdmap()->get_epoch(),
-         RequestBackfill())));
-  } else {
-    dout(10) << "activate all replicas clean, no recovery" << dendl;
-    eio_errors_to_process = false;
-    queue_peering_event(
-      CephPeeringEvtRef(
-       std::make_shared<CephPeeringEvt>(
-         get_osdmap()->get_epoch(),
-         get_osdmap()->get_epoch(),
-         AllReplicasRecovered())));
-  }
-
-  publish_stats_to_osd();
-
-  if (!backfill_targets.empty()) {
-    last_backfill_started = earliest_backfill();
-    new_backfill = true;
-    assert(!last_backfill_started.is_max());
-    dout(5) << "on activate: bft=" << backfill_targets
-          << " from " << last_backfill_started << dendl;
-    for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-        i != backfill_targets.end();
-        ++i) {
-      dout(5) << "target shard " << *i
-            << " from " << peer_info[*i].last_backfill
-            << dendl;
-    }
-  }
-
-  hit_set_setup();
-  agent_setup();
-}
-
-void PrimaryLogPG::_on_new_interval()
-{
-  dout(20) << __func__ << "checking missing set deletes flag. missing = " << pg_log.get_missing() << dendl;
-  if (!pg_log.get_missing().may_include_deletes &&
-      get_osdmap()->test_flag(CEPH_OSDMAP_RECOVERY_DELETES)) {
-    pg_log.rebuild_missing_set_with_deletes(osd->store, coll, info);
-  }
-  assert(pg_log.get_missing().may_include_deletes == get_osdmap()->test_flag(CEPH_OSDMAP_RECOVERY_DELETES));
-}
-
-void PrimaryLogPG::on_change(ObjectStore::Transaction *t)
-{
-  dout(10) << "on_change" << dendl;
-
-  if (hit_set && hit_set->insert_count() == 0) {
-    dout(20) << " discarding empty hit_set" << dendl;
-    hit_set_clear();
-  }
-
-  if (recovery_queued) {
-    recovery_queued = false;
-    osd->clear_queued_recovery(this);
-  }
-
-  // requeue everything in the reverse order they should be
-  // reexamined.
-  requeue_ops(waiting_for_peered);
-  requeue_ops(waiting_for_active);
-
-  clear_scrub_reserved();
-
-  cancel_copy_ops(is_primary());
-  cancel_flush_ops(is_primary());
-  cancel_proxy_ops(is_primary());
-
-  // requeue object waiters
-  for (auto& p : waiting_for_unreadable_object) {
-    release_backoffs(p.first);
-  }
-  if (is_primary()) {
-    requeue_object_waiters(waiting_for_unreadable_object);
-  } else {
-    waiting_for_unreadable_object.clear();
-  }
-  for (map<hobject_t,list<OpRequestRef>>::iterator p = waiting_for_degraded_object.begin();
-       p != waiting_for_degraded_object.end();
-       waiting_for_degraded_object.erase(p++)) {
-    release_backoffs(p->first);
-    if (is_primary())
-      requeue_ops(p->second);
-    else
-      p->second.clear();
-    finish_degraded_object(p->first);
-  }
-
-  // requeues waiting_for_scrub
-  scrub_clear_state();
-
-  for (auto p = waiting_for_blocked_object.begin();
-       p != waiting_for_blocked_object.end();
-       waiting_for_blocked_object.erase(p++)) {
-    if (is_primary())
-      requeue_ops(p->second);
-    else
-      p->second.clear();
-  }
-  for (auto i = callbacks_for_degraded_object.begin();
-       i != callbacks_for_degraded_object.end();
-    ) {
-    finish_degraded_object((i++)->first);
-  }
-  assert(callbacks_for_degraded_object.empty());
-
-  if (is_primary()) {
-    requeue_ops(waiting_for_cache_not_full);
-  } else {
-    waiting_for_cache_not_full.clear();
-  }
-  objects_blocked_on_cache_full.clear();
-
-  for (list<pair<OpRequestRef, OpContext*> >::iterator i =
-         in_progress_async_reads.begin();
-       i != in_progress_async_reads.end();
-       in_progress_async_reads.erase(i++)) {
-    close_op_ctx(i->second);
-    if (is_primary())
-      requeue_op(i->first);
-  }
-
-  // this will requeue ops we were working on but didn't finish, and
-  // any dups
-  apply_and_flush_repops(is_primary());
-  cancel_log_updates();
-
-  // do this *after* apply_and_flush_repops so that we catch any newly
-  // registered watches.
-  context_registry_on_change();
-
-  pgbackend->on_change_cleanup(t);
-  scrubber.cleanup_store(t);
-  pgbackend->on_change();
-
-  // clear snap_trimmer state
-  snap_trimmer_machine.process_event(Reset());
-
-  debug_op_order.clear();
-  unstable_stats.clear();
-
-  // we don't want to cache object_contexts through the interval change
-  // NOTE: we actually assert that all currently live references are dead
-  // by the time the flush for the next interval completes.
-  object_contexts.clear();
-
-  // should have been cleared above by finishing all of the degraded objects
-  assert(objects_blocked_on_degraded_snap.empty());
-}
-
-void PrimaryLogPG::on_role_change()
-{
-  dout(10) << "on_role_change" << dendl;
-  if (get_role() != 0 && hit_set) {
-    dout(10) << " clearing hit set" << dendl;
-    hit_set_clear();
-  }
-}
-
-void PrimaryLogPG::on_pool_change()
-{
-  dout(10) << __func__ << dendl;
-  // requeue cache full waiters just in case the cache_mode is
-  // changing away from writeback mode.  note that if we are not
-  // active the normal requeuing machinery is sufficient (and properly
-  // ordered).
-  if (is_active() &&
-      pool.info.cache_mode != pg_pool_t::CACHEMODE_WRITEBACK &&
-      !waiting_for_cache_not_full.empty()) {
-    dout(10) << __func__ << " requeuing full waiters (not in writeback) "
-            << dendl;
-    requeue_ops(waiting_for_cache_not_full);
-    objects_blocked_on_cache_full.clear();
-  }
-  hit_set_setup();
-  agent_setup();
-}
-
-// clear state.  called on recovery completion AND cancellation.
-void PrimaryLogPG::_clear_recovery_state()
-{
-  missing_loc.clear();
-#ifdef DEBUG_RECOVERY_OIDS
-  recovering_oids.clear();
-#endif
-  last_backfill_started = hobject_t();
-  set<hobject_t>::iterator i = backfills_in_flight.begin();
-  while (i != backfills_in_flight.end()) {
-    assert(recovering.count(*i));
-    backfills_in_flight.erase(i++);
-  }
-
-  list<OpRequestRef> blocked_ops;
-  for (map<hobject_t, ObjectContextRef>::iterator i = recovering.begin();
-       i != recovering.end();
-       recovering.erase(i++)) {
-    if (i->second) {
-      i->second->drop_recovery_read(&blocked_ops);
-      requeue_ops(blocked_ops);
-    }
-  }
-  assert(backfills_in_flight.empty());
-  pending_backfill_updates.clear();
-  assert(recovering.empty());
-  pgbackend->clear_recovery_state();
-}
-
-void PrimaryLogPG::cancel_pull(const hobject_t &soid)
-{
-  dout(20) << __func__ << ": " << soid << dendl;
-  assert(recovering.count(soid));
-  ObjectContextRef obc = recovering[soid];
-  if (obc) {
-    list<OpRequestRef> blocked_ops;
-    obc->drop_recovery_read(&blocked_ops);
-    requeue_ops(blocked_ops);
-  }
-  recovering.erase(soid);
-  finish_recovery_op(soid);
-  release_backoffs(soid);
-  if (waiting_for_degraded_object.count(soid)) {
-    dout(20) << " kicking degraded waiters on " << soid << dendl;
-    requeue_ops(waiting_for_degraded_object[soid]);
-    waiting_for_degraded_object.erase(soid);
-  }
-  if (waiting_for_unreadable_object.count(soid)) {
-    dout(20) << " kicking unreadable waiters on " << soid << dendl;
-    requeue_ops(waiting_for_unreadable_object[soid]);
-    waiting_for_unreadable_object.erase(soid);
-  }
-  if (is_missing_object(soid))
-    pg_log.set_last_requested(0); // get recover_primary to start over
-  finish_degraded_object(soid);
-}
-
-void PrimaryLogPG::check_recovery_sources(const OSDMapRef& osdmap)
-{
-  /*
-   * check that any peers we are planning to (or currently) pulling
-   * objects from are dealt with.
-   */
-  missing_loc.check_recovery_sources(osdmap);
-  pgbackend->check_recovery_sources(osdmap);
-
-  for (set<pg_shard_t>::iterator i = peer_log_requested.begin();
-       i != peer_log_requested.end();
-       ) {
-    if (!osdmap->is_up(i->osd)) {
-      dout(10) << "peer_log_requested removing " << *i << dendl;
-      peer_log_requested.erase(i++);
-    } else {
-      ++i;
-    }
-  }
-
-  for (set<pg_shard_t>::iterator i = peer_missing_requested.begin();
-       i != peer_missing_requested.end();
-       ) {
-    if (!osdmap->is_up(i->osd)) {
-      dout(10) << "peer_missing_requested removing " << *i << dendl;
-      peer_missing_requested.erase(i++);
-    } else {
-      ++i;
-    }
-  }
-}
-
-void PG::MissingLoc::check_recovery_sources(const OSDMapRef& osdmap)
-{
-  set<pg_shard_t> now_down;
-  for (set<pg_shard_t>::iterator p = missing_loc_sources.begin();
-       p != missing_loc_sources.end();
-       ) {
-    if (osdmap->is_up(p->osd)) {
-      ++p;
-      continue;
-    }
-    ldout(pg->cct, 10) << "check_recovery_sources source osd." << *p << " now down" << dendl;
-    now_down.insert(*p);
-    missing_loc_sources.erase(p++);
-  }
-
-  if (now_down.empty()) {
-    ldout(pg->cct, 10) << "check_recovery_sources no source osds (" << missing_loc_sources << ") went down" << dendl;
-  } else {
-    ldout(pg->cct, 10) << "check_recovery_sources sources osds " << now_down << " now down, remaining sources are "
-                      << missing_loc_sources << dendl;
-    
-    // filter missing_loc
-    map<hobject_t, set<pg_shard_t>>::iterator p = missing_loc.begin();
-    while (p != missing_loc.end()) {
-      set<pg_shard_t>::iterator q = p->second.begin();
-      while (q != p->second.end())
-       if (now_down.count(*q)) {
-         p->second.erase(q++);
-       } else {
-         ++q;
-       }
-      if (p->second.empty())
-       missing_loc.erase(p++);
-      else
-       ++p;
-    }
-  }
-}
-  
-
-bool PrimaryLogPG::start_recovery_ops(
-  uint64_t max,
-  ThreadPool::TPHandle &handle,
-  uint64_t *ops_started)
-{
-  uint64_t& started = *ops_started;
-  started = 0;
-  bool work_in_progress = false;
-  assert(is_primary());
-
-  if (!state_test(PG_STATE_RECOVERING) &&
-      !state_test(PG_STATE_BACKFILLING)) {
-    /* TODO: I think this case is broken and will make do_recovery()
-     * unhappy since we're returning false */
-    dout(10) << "recovery raced and were queued twice, ignoring!" << dendl;
-    return false;
-  }
-
-  const auto &missing = pg_log.get_missing();
-
-  unsigned int num_missing = missing.num_missing();
-  uint64_t num_unfound = get_num_unfound();
-
-  if (num_missing == 0) {
-    info.last_complete = info.last_update;
-  }
-
-  if (num_missing == num_unfound) {
-    // All of the missing objects we have are unfound.
-    // Recover the replicas.
-    started = recover_replicas(max, handle);
-  }
-  if (!started) {
-    // We still have missing objects that we should grab from replicas.
-    started += recover_primary(max, handle);
-  }
-  if (!started && num_unfound != get_num_unfound()) {
-    // second chance to recovery replicas
-    started = recover_replicas(max, handle);
-  }
-
-  if (started)
-    work_in_progress = true;
-
-  bool deferred_backfill = false;
-  if (recovering.empty() &&
-      state_test(PG_STATE_BACKFILLING) &&
-      !backfill_targets.empty() && started < max &&
-      missing.num_missing() == 0 &&
-      waiting_on_backfill.empty()) {
-    if (get_osdmap()->test_flag(CEPH_OSDMAP_NOBACKFILL)) {
-      dout(10) << "deferring backfill due to NOBACKFILL" << dendl;
-      deferred_backfill = true;
-    } else if (get_osdmap()->test_flag(CEPH_OSDMAP_NOREBALANCE) &&
-              !is_degraded())  {
-      dout(10) << "deferring backfill due to NOREBALANCE" << dendl;
-      deferred_backfill = true;
-    } else if (!backfill_reserved) {
-      dout(10) << "deferring backfill due to !backfill_reserved" << dendl;
-      if (!backfill_reserving) {
-       dout(10) << "queueing RequestBackfill" << dendl;
-       backfill_reserving = true;
-       queue_peering_event(
-         CephPeeringEvtRef(
-           std::make_shared<CephPeeringEvt>(
-             get_osdmap()->get_epoch(),
-             get_osdmap()->get_epoch(),
-             RequestBackfill())));
-      }
-      deferred_backfill = true;
-    } else {
-      started += recover_backfill(max - started, handle, &work_in_progress);
-    }
-  }
-
-  dout(10) << " started " << started << dendl;
-  osd->logger->inc(l_osd_rop, started);
-
-  if (!recovering.empty() ||
-      work_in_progress || recovery_ops_active > 0 || deferred_backfill)
-    return work_in_progress;
-
-  assert(recovering.empty());
-  assert(recovery_ops_active == 0);
-
-  dout(10) << __func__ << " needs_recovery: "
-          << missing_loc.get_needs_recovery()
-          << dendl;
-  dout(10) << __func__ << " missing_loc: "
-          << missing_loc.get_missing_locs()
-          << dendl;
-  int unfound = get_num_unfound();
-  if (unfound) {
-    dout(10) << " still have " << unfound << " unfound" << dendl;
-    return work_in_progress;
-  }
-
-  if (missing.num_missing() > 0) {
-    // this shouldn't happen!
-    osd->clog->error() << info.pgid << " Unexpected Error: recovery ending with "
-                      << missing.num_missing() << ": " << missing.get_items();
-    return work_in_progress;
-  }
-
-  if (needs_recovery()) {
-    // this shouldn't happen!
-    // We already checked num_missing() so we must have missing replicas
-    osd->clog->error() << info.pgid 
-                       << " Unexpected Error: recovery ending with missing replicas";
-    return work_in_progress;
-  }
-
-  if (state_test(PG_STATE_RECOVERING)) {
-    state_clear(PG_STATE_RECOVERING);
-    state_clear(PG_STATE_FORCED_RECOVERY);
-    if (get_osdmap()->get_pg_size(info.pgid.pgid) <= acting.size()) {
-      state_clear(PG_STATE_DEGRADED);
-    }
-    if (needs_backfill()) {
-      dout(10) << "recovery done, queuing backfill" << dendl;
-      queue_peering_event(
-        CephPeeringEvtRef(
-          std::make_shared<CephPeeringEvt>(
-            get_osdmap()->get_epoch(),
-            get_osdmap()->get_epoch(),
-            RequestBackfill())));
-    } else {
-      dout(10) << "recovery done, no backfill" << dendl;
-      eio_errors_to_process = false;
-      state_clear(PG_STATE_FORCED_BACKFILL);
-      queue_peering_event(
-        CephPeeringEvtRef(
-          std::make_shared<CephPeeringEvt>(
-            get_osdmap()->get_epoch(),
-            get_osdmap()->get_epoch(),
-            AllReplicasRecovered())));
-    }
-  } else { // backfilling
-    state_clear(PG_STATE_BACKFILLING);
-    state_clear(PG_STATE_FORCED_BACKFILL);
-    state_clear(PG_STATE_FORCED_RECOVERY);
-    dout(10) << "recovery done, backfill done" << dendl;
-    eio_errors_to_process = false;
-    queue_peering_event(
-      CephPeeringEvtRef(
-        std::make_shared<CephPeeringEvt>(
-          get_osdmap()->get_epoch(),
-          get_osdmap()->get_epoch(),
-          Backfilled())));
-  }
-
-  return false;
-}
-
-/**
- * do one recovery op.
- * return true if done, false if nothing left to do.
- */
-uint64_t PrimaryLogPG::recover_primary(uint64_t max, ThreadPool::TPHandle &handle)
-{
-  assert(is_primary());
-
-  const auto &missing = pg_log.get_missing();
-
-  dout(10) << "recover_primary recovering " << recovering.size()
-          << " in pg" << dendl;
-  dout(10) << "recover_primary " << missing << dendl;
-  dout(25) << "recover_primary " << missing.get_items() << dendl;
-
-  // look at log!
-  pg_log_entry_t *latest = 0;
-  unsigned started = 0;
-  int skipped = 0;
-
-  PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op();
-  map<version_t, hobject_t>::const_iterator p =
-    missing.get_rmissing().lower_bound(pg_log.get_log().last_requested);
-  while (p != missing.get_rmissing().end()) {
-    handle.reset_tp_timeout();
-    hobject_t soid;
-    version_t v = p->first;
-
-    if (pg_log.get_log().objects.count(p->second)) {
-      latest = pg_log.get_log().objects.find(p->second)->second;
-      assert(latest->is_update() || latest->is_delete());
-      soid = latest->soid;
-    } else {
-      latest = 0;
-      soid = p->second;
-    }
-    const pg_missing_item& item = missing.get_items().find(p->second)->second;
-    ++p;
-
-    hobject_t head = soid.get_head();
-
-    eversion_t need = item.need;
-
-    dout(10) << "recover_primary "
-             << soid << " " << item.need
-            << (missing.is_missing(soid) ? " (missing)":"")
-            << (missing.is_missing(head) ? " (missing head)":"")
-             << (recovering.count(soid) ? " (recovering)":"")
-            << (recovering.count(head) ? " (recovering head)":"")
-             << dendl;
-
-    if (latest) {
-      switch (latest->op) {
-      case pg_log_entry_t::CLONE:
-       /*
-        * Handling for this special case removed for now, until we
-        * can correctly construct an accurate SnapSet from the old
-        * one.
-        */
-       break;
-
-      case pg_log_entry_t::LOST_REVERT:
-       {
-         if (item.have == latest->reverting_to) {
-           ObjectContextRef obc = get_object_context(soid, true);
-           
-           if (obc->obs.oi.version == latest->version) {
-             // I'm already reverting
-             dout(10) << " already reverting " << soid << dendl;
-           } else {
-             dout(10) << " reverting " << soid << " to " << latest->prior_version << dendl;
-             obc->ondisk_write_lock();
-             obc->obs.oi.version = latest->version;
-
-             ObjectStore::Transaction t;
-             bufferlist b2;
-             obc->obs.oi.encode(
-               b2,
-               get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-             assert(!pool.info.require_rollback());
-             t.setattr(coll, ghobject_t(soid), OI_ATTR, b2);
-
-             recover_got(soid, latest->version);
-             missing_loc.add_location(soid, pg_whoami);
-
-             ++active_pushes;
-
-             osd->store->queue_transaction(osr.get(), std::move(t),
-                                           new C_OSD_AppliedRecoveredObject(this, obc),
-                                           new C_OSD_CommittedPushedObject(
-                                             this,
-                                             get_osdmap()->get_epoch(),
-                                             info.last_complete),
-                                           new C_OSD_OndiskWriteUnlock(obc));
-             continue;
-           }
-         } else {
-           /*
-            * Pull the old version of the object.  Update missing_loc here to have the location
-            * of the version we want.
-            *
-            * This doesn't use the usual missing_loc paths, but that's okay:
-            *  - if we have it locally, we hit the case above, and go from there.
-            *  - if we don't, we always pass through this case during recovery and set up the location
-            *    properly.
-            *  - this way we don't need to mangle the missing code to be general about needing an old
-            *    version...
-            */
-           eversion_t alternate_need = latest->reverting_to;
-           dout(10) << " need to pull prior_version " << alternate_need << " for revert " << item << dendl;
-
-           for (map<pg_shard_t, pg_missing_t>::iterator p = peer_missing.begin();
-                p != peer_missing.end();
-                ++p)
-             if (p->second.is_missing(soid, need) &&
-                 p->second.get_items().at(soid).have == alternate_need) {
-               missing_loc.add_location(soid, p->first);
-             }
-           dout(10) << " will pull " << alternate_need << " or " << need
-                    << " from one of " << missing_loc.get_locations(soid)
-                    << dendl;
-         }
-       }
-       break;
-      }
-    }
-   
-    if (!recovering.count(soid)) {
-      if (recovering.count(head)) {
-       ++skipped;
-      } else {
-       int r = recover_missing(
-         soid, need, get_recovery_op_priority(), h);
-       switch (r) {
-       case PULL_YES:
-         ++started;
-         break;
-       case PULL_OTHER:
-         ++started;
-       case PULL_NONE:
-         ++skipped;
-         break;
-       default:
-         ceph_abort();
-       }
-       if (started >= max)
-         break;
-      }
-    }
-    
-    // only advance last_requested if we haven't skipped anything
-    if (!skipped)
-      pg_log.set_last_requested(v);
-  }
-  pgbackend->run_recovery_op(h, get_recovery_op_priority());
-  return started;
-}
-
-bool PrimaryLogPG::primary_error(
-  const hobject_t& soid, eversion_t v)
-{
-  pg_log.missing_add(soid, v, eversion_t());
-  pg_log.set_last_requested(0);
-  missing_loc.remove_location(soid, pg_whoami);
-  bool uhoh = true;
-  assert(!actingbackfill.empty());
-  for (set<pg_shard_t>::iterator i = actingbackfill.begin();
-       i != actingbackfill.end();
-       ++i) {
-    if (*i == get_primary()) continue;
-    pg_shard_t peer = *i;
-    if (!peer_missing[peer].is_missing(soid, v)) {
-      missing_loc.add_location(soid, peer);
-      dout(10) << info.pgid << " unexpectedly missing " << soid << " v" << v
-              << ", there should be a copy on shard " << peer << dendl;
-      uhoh = false;
-    }
-  }
-  if (uhoh)
-    osd->clog->error() << info.pgid << " missing primary copy of " << soid << ", unfound";
-  else
-    osd->clog->error() << info.pgid << " missing primary copy of " << soid
-                        << ", will try copies on " << missing_loc.get_locations(soid);
-  return uhoh;
-}
-
-int PrimaryLogPG::prep_object_replica_deletes(
-  const hobject_t& soid, eversion_t v,
-  PGBackend::RecoveryHandle *h)
-{
-  assert(is_primary());
-  dout(10) << __func__ << ": on " << soid << dendl;
-
-  start_recovery_op(soid);
-  assert(!recovering.count(soid));
-  recovering.insert(make_pair(soid, ObjectContextRef()));
-
-  pgbackend->recover_delete_object(soid, v, h);
-  return 1;
-}
-
-int PrimaryLogPG::prep_object_replica_pushes(
-  const hobject_t& soid, eversion_t v,
-  PGBackend::RecoveryHandle *h)
-{
-  assert(is_primary());
-  dout(10) << __func__ << ": on " << soid << dendl;
-
-  // NOTE: we know we will get a valid oloc off of disk here.
-  ObjectContextRef obc = get_object_context(soid, false);
-  if (!obc) {
-    primary_error(soid, v);
-    return 0;
-  }
-
-  if (!obc->get_recovery_read()) {
-    dout(20) << "recovery delayed on " << soid
-            << "; could not get rw_manager lock" << dendl;
-    return 0;
-  } else {
-    dout(20) << "recovery got recovery read lock on " << soid
-            << dendl;
-  }
-
-  start_recovery_op(soid);
-  assert(!recovering.count(soid));
-  recovering.insert(make_pair(soid, obc));
-
-  /* We need this in case there is an in progress write on the object.  In fact,
-   * the only possible write is an update to the xattr due to a lost_revert --
-   * a client write would be blocked since the object is degraded.
-   * In almost all cases, therefore, this lock should be uncontended.
-   */
-  obc->ondisk_read_lock();
-  int r = pgbackend->recover_object(
-    soid,
-    v,
-    ObjectContextRef(),
-    obc, // has snapset context
-    h);
-  obc->ondisk_read_unlock();
-  if (r < 0) {
-    dout(0) << __func__ << " Error " << r << " on oid " << soid << dendl;
-    primary_failed(soid);
-    primary_error(soid, v);
-    return 0;
-  }
-  return 1;
-}
-
-uint64_t PrimaryLogPG::recover_replicas(uint64_t max, ThreadPool::TPHandle &handle)
-{
-  dout(10) << __func__ << "(" << max << ")" << dendl;
-  uint64_t started = 0;
-
-  PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op();
-
-  // this is FAR from an optimal recovery order.  pretty lame, really.
-  assert(!actingbackfill.empty());
-  for (set<pg_shard_t>::iterator i = actingbackfill.begin();
-       i != actingbackfill.end();
-       ++i) {
-    if (*i == get_primary()) continue;
-    pg_shard_t peer = *i;
-    map<pg_shard_t, pg_missing_t>::const_iterator pm = peer_missing.find(peer);
-    assert(pm != peer_missing.end());
-    map<pg_shard_t, pg_info_t>::const_iterator pi = peer_info.find(peer);
-    assert(pi != peer_info.end());
-    size_t m_sz = pm->second.num_missing();
-
-    dout(10) << " peer osd." << peer << " missing " << m_sz << " objects." << dendl;
-    dout(20) << " peer osd." << peer << " missing " << pm->second.get_items() << dendl;
-
-    // oldest first!
-    const pg_missing_t &m(pm->second);
-    for (map<version_t, hobject_t>::const_iterator p = m.get_rmissing().begin();
-        p != m.get_rmissing().end() && started < max;
-          ++p) {
-      handle.reset_tp_timeout();
-      const hobject_t soid(p->second);
-
-      if (missing_loc.is_unfound(soid)) {
-       dout(10) << __func__ << ": " << soid << " still unfound" << dendl;
-       continue;
-      }
-
-      if (soid > pi->second.last_backfill) {
-       if (!recovering.count(soid)) {
-          derr << __func__ << ": object " << soid << " last_backfill " << pi->second.last_backfill << dendl;
-         derr << __func__ << ": object added to missing set for backfill, but "
-              << "is not in recovering, error!" << dendl;
-         ceph_abort();
-       }
-       continue;
-      }
-
-      if (recovering.count(soid)) {
-       dout(10) << __func__ << ": already recovering " << soid << dendl;
-       continue;
-      }
-
-      if (missing_loc.is_deleted(soid)) {
-       dout(10) << __func__ << ": " << soid << " is a delete, removing" << dendl;
-       map<hobject_t,pg_missing_item>::const_iterator r = m.get_items().find(soid);
-       started += prep_object_replica_deletes(soid, r->second.need, h);
-       continue;
-      }
-
-      if (soid.is_snap() && pg_log.get_missing().is_missing(soid.get_head())) {
-       dout(10) << __func__ << ": " << soid.get_head()
-                << " still missing on primary" << dendl;
-       continue;
-      }
-
-      if (soid.is_snap() && pg_log.get_missing().is_missing(soid.get_snapdir())) {
-       dout(10) << __func__ << ": " << soid.get_snapdir()
-                << " still missing on primary" << dendl;
-       continue;
-      }
-
-      if (pg_log.get_missing().is_missing(soid)) {
-       dout(10) << __func__ << ": " << soid << " still missing on primary" << dendl;
-       continue;
-      }
-
-      dout(10) << __func__ << ": recover_object_replicas(" << soid << ")" << dendl;
-      map<hobject_t,pg_missing_item>::const_iterator r = m.get_items().find(soid);
-      started += prep_object_replica_pushes(soid, r->second.need,
-                                           h);
-    }
-  }
-
-  pgbackend->run_recovery_op(h, get_recovery_op_priority());
-  return started;
-}
-
-hobject_t PrimaryLogPG::earliest_peer_backfill() const
-{
-  hobject_t e = hobject_t::get_max();
-  for (set<pg_shard_t>::const_iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    pg_shard_t peer = *i;
-    map<pg_shard_t, BackfillInterval>::const_iterator iter =
-      peer_backfill_info.find(peer);
-    assert(iter != peer_backfill_info.end());
-    if (iter->second.begin < e)
-      e = iter->second.begin;
-  }
-  return e;
-}
-
-bool PrimaryLogPG::all_peer_done() const
-{
-  // Primary hasn't got any more objects
-  assert(backfill_info.empty());
-
-  for (set<pg_shard_t>::const_iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    pg_shard_t bt = *i;
-    map<pg_shard_t, BackfillInterval>::const_iterator piter =
-      peer_backfill_info.find(bt);
-    assert(piter != peer_backfill_info.end());
-    const BackfillInterval& pbi = piter->second;
-    // See if peer has more to process
-    if (!pbi.extends_to_end() || !pbi.empty())
-       return false;
-  }
-  return true;
-}
-
-/**
- * recover_backfill
- *
- * Invariants:
- *
- * backfilled: fully pushed to replica or present in replica's missing set (both
- * our copy and theirs).
- *
- * All objects on a backfill_target in
- * [MIN,peer_backfill_info[backfill_target].begin) are valid; logically-removed
- * objects have been actually deleted and all logically-valid objects are replicated.
- * There may be PG objects in this interval yet to be backfilled.
- *
- * All objects in PG in [MIN,backfill_info.begin) have been backfilled to all
- * backfill_targets.  There may be objects on backfill_target(s) yet to be deleted.
- *
- * For a backfill target, all objects < MIN(peer_backfill_info[target].begin,
- *     backfill_info.begin) in PG are backfilled.  No deleted objects in this
- * interval remain on the backfill target.
- *
- * For a backfill target, all objects <= peer_info[target].last_backfill
- * have been backfilled to target
- *
- * There *MAY* be missing/outdated objects between last_backfill_started and
- * MIN(peer_backfill_info[*].begin, backfill_info.begin) in the event that client
- * io created objects since the last scan.  For this reason, we call
- * update_range() again before continuing backfill.
- */
-uint64_t PrimaryLogPG::recover_backfill(
-  uint64_t max,
-  ThreadPool::TPHandle &handle, bool *work_started)
-{
-  dout(10) << "recover_backfill (" << max << ")"
-           << " bft=" << backfill_targets
-          << " last_backfill_started " << last_backfill_started
-          << (new_backfill ? " new_backfill":"")
-          << dendl;
-  assert(!backfill_targets.empty());
-
-  // Initialize from prior backfill state
-  if (new_backfill) {
-    // on_activate() was called prior to getting here
-    assert(last_backfill_started == earliest_backfill());
-    new_backfill = false;
-
-    // initialize BackfillIntervals
-    for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-        i != backfill_targets.end();
-        ++i) {
-      peer_backfill_info[*i].reset(peer_info[*i].last_backfill);
-    }
-    backfill_info.reset(last_backfill_started);
-
-    backfills_in_flight.clear();
-    pending_backfill_updates.clear();
-  }
-
-  for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    dout(10) << "peer osd." << *i
-          << " info " << peer_info[*i]
-          << " interval " << peer_backfill_info[*i].begin
-          << "-" << peer_backfill_info[*i].end
-          << " " << peer_backfill_info[*i].objects.size() << " objects"
-          << dendl;
-  }
-
-  // update our local interval to cope with recent changes
-  backfill_info.begin = last_backfill_started;
-  update_range(&backfill_info, handle);
-
-  unsigned ops = 0;
-  vector<boost::tuple<hobject_t, eversion_t, pg_shard_t> > to_remove;
-  set<hobject_t> add_to_stat;
-
-  for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    peer_backfill_info[*i].trim_to(
-      std::max(peer_info[*i].last_backfill, last_backfill_started));
-  }
-  backfill_info.trim_to(last_backfill_started);
-
-  PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op();
-  while (ops < max) {
-    if (backfill_info.begin <= earliest_peer_backfill() &&
-       !backfill_info.extends_to_end() && backfill_info.empty()) {
-      hobject_t next = backfill_info.end;
-      backfill_info.reset(next);
-      backfill_info.end = hobject_t::get_max();
-      update_range(&backfill_info, handle);
-      backfill_info.trim();
-    }
-
-    dout(20) << "   my backfill interval " << backfill_info << dendl;
-
-    bool sent_scan = false;
-    for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-        i != backfill_targets.end();
-        ++i) {
-      pg_shard_t bt = *i;
-      BackfillInterval& pbi = peer_backfill_info[bt];
-
-      dout(20) << " peer shard " << bt << " backfill " << pbi << dendl;
-      if (pbi.begin <= backfill_info.begin &&
-         !pbi.extends_to_end() && pbi.empty()) {
-       dout(10) << " scanning peer osd." << bt << " from " << pbi.end << dendl;
-       epoch_t e = get_osdmap()->get_epoch();
-       MOSDPGScan *m = new MOSDPGScan(
-         MOSDPGScan::OP_SCAN_GET_DIGEST, pg_whoami, e, last_peering_reset,
-         spg_t(info.pgid.pgid, bt.shard),
-         pbi.end, hobject_t());
-       osd->send_message_osd_cluster(bt.osd, m, get_osdmap()->get_epoch());
-       assert(waiting_on_backfill.find(bt) == waiting_on_backfill.end());
-       waiting_on_backfill.insert(bt);
-        sent_scan = true;
-      }
-    }
-
-    // Count simultaneous scans as a single op and let those complete
-    if (sent_scan) {
-      ops++;
-      start_recovery_op(hobject_t::get_max()); // XXX: was pbi.end
-      break;
-    }
-
-    if (backfill_info.empty() && all_peer_done()) {
-      dout(10) << " reached end for both local and all peers" << dendl;
-      break;
-    }
-
-    // Get object within set of peers to operate on and
-    // the set of targets for which that object applies.
-    hobject_t check = earliest_peer_backfill();
-
-    if (check < backfill_info.begin) {
-
-      set<pg_shard_t> check_targets;
-      for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-          i != backfill_targets.end();
-          ++i) {
-        pg_shard_t bt = *i;
-        BackfillInterval& pbi = peer_backfill_info[bt];
-        if (pbi.begin == check)
-          check_targets.insert(bt);
-      }
-      assert(!check_targets.empty());
-
-      dout(20) << " BACKFILL removing " << check
-              << " from peers " << check_targets << dendl;
-      for (set<pg_shard_t>::iterator i = check_targets.begin();
-          i != check_targets.end();
-          ++i) {
-        pg_shard_t bt = *i;
-        BackfillInterval& pbi = peer_backfill_info[bt];
-        assert(pbi.begin == check);
-
-        to_remove.push_back(boost::make_tuple(check, pbi.objects.begin()->second, bt));
-        pbi.pop_front();
-      }
-
-      /* This requires a bit of explanation.  We compare head against
-       * last_backfill to determine whether to send an operation
-       * to the replica.  A single write operation can touch up to three
-       * objects: head, the snapdir, and a new clone which sorts closer to
-       * head than any existing clone.  If last_backfill points at a clone,
-       * the transaction won't be sent and all 3 must lie on the right side
-       * of the line (i.e., we'll backfill them later).  If last_backfill
-       * points at snapdir, it sorts greater than head, so we send the
-       * transaction which is correct because all three must lie to the left
-       * of the line.
-       *
-       * If it points at head, we have a bit of an issue.  If head actually
-       * exists, no problem, because any transaction which touches snapdir
-       * must end up creating it (and deleting head), so sending the
-       * operation won't pose a problem -- we'll end up having to scan it,
-       * but it'll end up being the right version so we won't bother to
-       * rebackfill it.  However, if head doesn't exist, any write on head
-       * will remove snapdir.  For a replicated pool, this isn't a problem,
-       * ENOENT on remove isn't an issue and it's in backfill future anyway.
-       * It only poses a problem for EC pools, because we never just delete
-       * an object, we rename it into a rollback object.  That operation
-       * will end up crashing the osd with ENOENT.  Tolerating the failure
-       * wouldn't work either, even if snapdir exists, we'd be creating a
-       * rollback object past the last_backfill line which wouldn't get
-       * cleaned up (no rollback objects past the last_backfill line is an
-       * existing important invariant).  Thus, let's avoid the whole issue
-       * by just not updating last_backfill_started here if head doesn't
-       * exist and snapdir does.  We aren't using up a recovery count here,
-       * so we're going to recover snapdir immediately anyway.  We'll only
-       * fail "backward" if we fail to get the rw lock and that just means
-       * we'll re-process this section of the hash space again.
-       *
-       * I'm choosing this hack here because the really "correct" answer is
-       * going to be to unify snapdir and head into a single object (a
-       * snapdir is really just a confusing way to talk about head existing
-       * as a whiteout), but doing that is going to be a somewhat larger
-       * undertaking.
-       *
-       * @see http://tracker.ceph.com/issues/17668
-       */
-      if (!(check.is_head() &&
-           backfill_info.begin.is_snapdir() &&
-           check == backfill_info.begin.get_head()))
-       last_backfill_started = check;
-
-      // Don't increment ops here because deletions
-      // are cheap and not replied to unlike real recovery_ops,
-      // and we can't increment ops without requeueing ourself
-      // for recovery.
-    } else {
-      eversion_t& obj_v = backfill_info.objects.begin()->second;
-
-      vector<pg_shard_t> need_ver_targs, missing_targs, keep_ver_targs, skip_targs;
-      for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-          i != backfill_targets.end();
-          ++i) {
-       pg_shard_t bt = *i;
-       BackfillInterval& pbi = peer_backfill_info[bt];
-        // Find all check peers that have the wrong version
-       if (check == backfill_info.begin && check == pbi.begin) {
-         if (pbi.objects.begin()->second != obj_v) {
-           need_ver_targs.push_back(bt);
-         } else {
-           keep_ver_targs.push_back(bt);
-         }
-        } else {
-         pg_info_t& pinfo = peer_info[bt];
-
-          // Only include peers that we've caught up to their backfill line
-         // otherwise, they only appear to be missing this object
-         // because their pbi.begin > backfill_info.begin.
-          if (backfill_info.begin > pinfo.last_backfill)
-           missing_targs.push_back(bt);
-         else
-           skip_targs.push_back(bt);
-       }
-      }
-
-      if (!keep_ver_targs.empty()) {
-        // These peers have version obj_v
-       dout(20) << " BACKFILL keeping " << check
-                << " with ver " << obj_v
-                << " on peers " << keep_ver_targs << dendl;
-       //assert(!waiting_for_degraded_object.count(check));
-      }
-      if (!need_ver_targs.empty() || !missing_targs.empty()) {
-       ObjectContextRef obc = get_object_context(backfill_info.begin, false);
-       assert(obc);
-       if (obc->get_recovery_read()) {
-         if (!need_ver_targs.empty()) {
-           dout(20) << " BACKFILL replacing " << check
-                  << " with ver " << obj_v
-                  << " to peers " << need_ver_targs << dendl;
-         }
-         if (!missing_targs.empty()) {
-           dout(20) << " BACKFILL pushing " << backfill_info.begin
-                << " with ver " << obj_v
-                << " to peers " << missing_targs << dendl;
-         }
-         vector<pg_shard_t> all_push = need_ver_targs;
-         all_push.insert(all_push.end(), missing_targs.begin(), missing_targs.end());
-
-         handle.reset_tp_timeout();
-         int r = prep_backfill_object_push(backfill_info.begin, obj_v, obc, all_push, h);
-         if (r < 0) {
-           *work_started = true;
-           dout(0) << __func__ << " Error " << r << " trying to backfill " << backfill_info.begin << dendl;
-           break;
-         }
-         ops++;
-       } else {
-         *work_started = true;
-         dout(20) << "backfill blocking on " << backfill_info.begin
-                  << "; could not get rw_manager lock" << dendl;
-         break;
-       }
-      }
-      dout(20) << "need_ver_targs=" << need_ver_targs
-              << " keep_ver_targs=" << keep_ver_targs << dendl;
-      dout(20) << "backfill_targets=" << backfill_targets
-              << " missing_targs=" << missing_targs
-              << " skip_targs=" << skip_targs << dendl;
-
-      last_backfill_started = backfill_info.begin;
-      add_to_stat.insert(backfill_info.begin); // XXX: Only one for all pushes?
-      backfill_info.pop_front();
-      vector<pg_shard_t> check_targets = need_ver_targs;
-      check_targets.insert(check_targets.end(), keep_ver_targs.begin(), keep_ver_targs.end());
-      for (vector<pg_shard_t>::iterator i = check_targets.begin();
-          i != check_targets.end();
-          ++i) {
-        pg_shard_t bt = *i;
-        BackfillInterval& pbi = peer_backfill_info[bt];
-        pbi.pop_front();
-      }
-    }
-  }
-
-  hobject_t backfill_pos =
-    std::min(backfill_info.begin, earliest_peer_backfill());
-
-  for (set<hobject_t>::iterator i = add_to_stat.begin();
-       i != add_to_stat.end();
-       ++i) {
-    ObjectContextRef obc = get_object_context(*i, false);
-    assert(obc);
-    pg_stat_t stat;
-    add_object_context_to_pg_stat(obc, &stat);
-    pending_backfill_updates[*i] = stat;
-  }
-  if (HAVE_FEATURE(get_min_upacting_features(), SERVER_LUMINOUS)) {
-    map<pg_shard_t,MOSDPGBackfillRemove*> reqs;
-    for (unsigned i = 0; i < to_remove.size(); ++i) {
-      handle.reset_tp_timeout();
-      const hobject_t& oid = to_remove[i].get<0>();
-      eversion_t v = to_remove[i].get<1>();
-      pg_shard_t peer = to_remove[i].get<2>();
-      MOSDPGBackfillRemove *m;
-      auto it = reqs.find(peer);
-      if (it != reqs.end()) {
-       m = it->second;
-      } else {
-       m = reqs[peer] = new MOSDPGBackfillRemove(
-         spg_t(info.pgid.pgid, peer.shard),
-         get_osdmap()->get_epoch());
-      }
-      m->ls.push_back(make_pair(oid, v));
-
-      if (oid <= last_backfill_started)
-       pending_backfill_updates[oid]; // add empty stat!
-    }
-    for (auto p : reqs) {
-      osd->send_message_osd_cluster(p.first.osd, p.second,
-                                   get_osdmap()->get_epoch());
-    }
-  } else {
-    // for jewel targets
-    for (unsigned i = 0; i < to_remove.size(); ++i) {
-      handle.reset_tp_timeout();
-
-      // ordered before any subsequent updates
-      send_remove_op(to_remove[i].get<0>(), to_remove[i].get<1>(),
-                    to_remove[i].get<2>());
-
-      if (to_remove[i].get<0>() <= last_backfill_started)
-       pending_backfill_updates[to_remove[i].get<0>()]; // add empty stat!
-    }
-  }
-
-  pgbackend->run_recovery_op(h, get_recovery_op_priority());
-
-  dout(5) << "backfill_pos is " << backfill_pos << dendl;
-  for (set<hobject_t>::iterator i = backfills_in_flight.begin();
-       i != backfills_in_flight.end();
-       ++i) {
-    dout(20) << *i << " is still in flight" << dendl;
-  }
-
-  hobject_t next_backfill_to_complete = backfills_in_flight.empty() ?
-    backfill_pos : *(backfills_in_flight.begin());
-  hobject_t new_last_backfill = earliest_backfill();
-  dout(10) << "starting new_last_backfill at " << new_last_backfill << dendl;
-  for (map<hobject_t, pg_stat_t>::iterator i =
-        pending_backfill_updates.begin();
-       i != pending_backfill_updates.end() &&
-        i->first < next_backfill_to_complete;
-       pending_backfill_updates.erase(i++)) {
-    dout(20) << " pending_backfill_update " << i->first << dendl;
-    assert(i->first > new_last_backfill);
-    for (set<pg_shard_t>::iterator j = backfill_targets.begin();
-        j != backfill_targets.end();
-        ++j) {
-      pg_shard_t bt = *j;
-      pg_info_t& pinfo = peer_info[bt];
-      //Add stats to all peers that were missing object
-      if (i->first > pinfo.last_backfill)
-        pinfo.stats.add(i->second);
-    }
-    new_last_backfill = i->first;
-  }
-  dout(10) << "possible new_last_backfill at " << new_last_backfill << dendl;
-
-  assert(!pending_backfill_updates.empty() ||
-        new_last_backfill == last_backfill_started);
-  if (pending_backfill_updates.empty() &&
-      backfill_pos.is_max()) {
-    assert(backfills_in_flight.empty());
-    new_last_backfill = backfill_pos;
-    last_backfill_started = backfill_pos;
-  }
-  dout(10) << "final new_last_backfill at " << new_last_backfill << dendl;
-
-  // If new_last_backfill == MAX, then we will send OP_BACKFILL_FINISH to
-  // all the backfill targets.  Otherwise, we will move last_backfill up on
-  // those targets need it and send OP_BACKFILL_PROGRESS to them.
-  for (set<pg_shard_t>::iterator i = backfill_targets.begin();
-       i != backfill_targets.end();
-       ++i) {
-    pg_shard_t bt = *i;
-    pg_info_t& pinfo = peer_info[bt];
-
-    if (new_last_backfill > pinfo.last_backfill) {
-      pinfo.set_last_backfill(new_last_backfill);
-      epoch_t e = get_osdmap()->get_epoch();
-      MOSDPGBackfill *m = NULL;
-      if (pinfo.last_backfill.is_max()) {
-        m = new MOSDPGBackfill(
-         MOSDPGBackfill::OP_BACKFILL_FINISH,
-         e,
-         last_peering_reset,
-         spg_t(info.pgid.pgid, bt.shard));
-        // Use default priority here, must match sub_op priority
-        /* pinfo.stats might be wrong if we did log-based recovery on the
-         * backfilled portion in addition to continuing backfill.
-         */
-        pinfo.stats = info.stats;
-        start_recovery_op(hobject_t::get_max());
-      } else {
-        m = new MOSDPGBackfill(
-         MOSDPGBackfill::OP_BACKFILL_PROGRESS,
-         e,
-         last_peering_reset,
-         spg_t(info.pgid.pgid, bt.shard));
-        // Use default priority here, must match sub_op priority
-      }
-      m->last_backfill = pinfo.last_backfill;
-      m->stats = pinfo.stats;
-      osd->send_message_osd_cluster(bt.osd, m, get_osdmap()->get_epoch());
-      dout(10) << " peer " << bt
-              << " num_objects now " << pinfo.stats.stats.sum.num_objects
-              << " / " << info.stats.stats.sum.num_objects << dendl;
-    }
-  }
-
-  if (ops)
-    *work_started = true;
-  return ops;
-}
-
-int PrimaryLogPG::prep_backfill_object_push(
-  hobject_t oid, eversion_t v,
-  ObjectContextRef obc,
-  vector<pg_shard_t> peers,
-  PGBackend::RecoveryHandle *h)
-{
-  dout(10) << __func__ << " " << oid << " v " << v << " to peers " << peers << dendl;
-  assert(!peers.empty());
-
-  backfills_in_flight.insert(oid);
-  for (unsigned int i = 0 ; i < peers.size(); ++i) {
-    map<pg_shard_t, pg_missing_t>::iterator bpm = peer_missing.find(peers[i]);
-    assert(bpm != peer_missing.end());
-    bpm->second.add(oid, eversion_t(), eversion_t(), false);
-  }
-
-  assert(!recovering.count(oid));
-
-  start_recovery_op(oid);
-  recovering.insert(make_pair(oid, obc));
-
-  // We need to take the read_lock here in order to flush in-progress writes
-  obc->ondisk_read_lock();
-  int r = pgbackend->recover_object(
-    oid,
-    v,
-    ObjectContextRef(),
-    obc,
-    h);
-  obc->ondisk_read_unlock();
-  if (r < 0) {
-    dout(0) << __func__ << " Error " << r << " on oid " << oid << dendl;
-    primary_failed(oid);
-    primary_error(oid, v);
-    backfills_in_flight.erase(oid);
-    missing_loc.add_missing(oid, v, eversion_t());
-  }
-  return r;
-}
-
-void PrimaryLogPG::update_range(
-  BackfillInterval *bi,
-  ThreadPool::TPHandle &handle)
-{
-  int local_min = cct->_conf->osd_backfill_scan_min;
-  int local_max = cct->_conf->osd_backfill_scan_max;
-
-  if (bi->version < info.log_tail) {
-    dout(10) << __func__<< ": bi is old, rescanning local backfill_info"
-            << dendl;
-    if (last_update_applied >= info.log_tail) {
-      bi->version = last_update_applied;
-    } else {
-      osr->flush();
-      bi->version = info.last_update;
-    }
-    scan_range(local_min, local_max, bi, handle);
-  }
-
-  if (bi->version >= projected_last_update) {
-    dout(10) << __func__<< ": bi is current " << dendl;
-    assert(bi->version == projected_last_update);
-  } else if (bi->version >= info.log_tail) {
-    if (pg_log.get_log().empty() && projected_log.empty()) {
-      /* Because we don't move log_tail on split, the log might be
-       * empty even if log_tail != last_update.  However, the only
-       * way to get here with an empty log is if log_tail is actually
-       * eversion_t(), because otherwise the entry which changed
-       * last_update since the last scan would have to be present.
-       */
-      assert(bi->version == eversion_t());
-      return;
-    }
-
-    dout(10) << __func__<< ": bi is old, (" << bi->version
-            << ") can be updated with log to projected_last_update "
-            << projected_last_update << dendl;
-
-    auto func = [&](const pg_log_entry_t &e) {
-      dout(10) << __func__ << ": updating from version " << e.version
-               << dendl;
-      const hobject_t &soid = e.soid;
-      if (soid >= bi->begin &&
-         soid < bi->end) {
-       if (e.is_update()) {
-         dout(10) << __func__ << ": " << e.soid << " updated to version "
-                  << e.version << dendl;
-         bi->objects.erase(e.soid);
-         bi->objects.insert(
-           make_pair(
-             e.soid,
-             e.version));
-       } else if (e.is_delete()) {
-         dout(10) << __func__ << ": " << e.soid << " removed" << dendl;
-         bi->objects.erase(e.soid);
-       }
-      }
-    };
-    dout(10) << "scanning pg log first" << dendl;
-    pg_log.get_log().scan_log_after(bi->version, func);
-    dout(10) << "scanning projected log" << dendl;
-    projected_log.scan_log_after(bi->version, func);
-    bi->version = projected_last_update;
-  } else {
-    assert(0 == "scan_range should have raised bi->version past log_tail");
-  }
-}
-
-void PrimaryLogPG::scan_range(
-  int min, int max, BackfillInterval *bi,
-  ThreadPool::TPHandle &handle)
-{
-  assert(is_locked());
-  dout(10) << "scan_range from " << bi->begin << dendl;
-  bi->clear_objects();
-
-  vector<hobject_t> ls;
-  ls.reserve(max);
-  int r = pgbackend->objects_list_partial(bi->begin, min, max, &ls, &bi->end);
-  assert(r >= 0);
-  dout(10) << " got " << ls.size() << " items, next " << bi->end << dendl;
-  dout(20) << ls << dendl;
-
-  for (vector<hobject_t>::iterator p = ls.begin(); p != ls.end(); ++p) {
-    handle.reset_tp_timeout();
-    ObjectContextRef obc;
-    if (is_primary())
-      obc = object_contexts.lookup(*p);
-    if (obc) {
-      bi->objects[*p] = obc->obs.oi.version;
-      dout(20) << "  " << *p << " " << obc->obs.oi.version << dendl;
-    } else {
-      bufferlist bl;
-      int r = pgbackend->objects_get_attr(*p, OI_ATTR, &bl);
-
-      /* If the object does not exist here, it must have been removed
-        * between the collection_list_partial and here.  This can happen
-        * for the first item in the range, which is usually last_backfill.
-        */
-      if (r == -ENOENT)
-       continue;
-
-      assert(r >= 0);
-      object_info_t oi(bl);
-      bi->objects[*p] = oi.version;
-      dout(20) << "  " << *p << " " << oi.version << dendl;
-    }
-  }
-}
-
-
-/** check_local
- * 
- * verifies that stray objects have been deleted
- */
-void PrimaryLogPG::check_local()
-{
-  dout(10) << __func__ << dendl;
-
-  assert(info.last_update >= pg_log.get_tail());  // otherwise we need some help!
-
-  if (!cct->_conf->osd_debug_verify_stray_on_activate)
-    return;
-
-  // just scan the log.
-  set<hobject_t> did;
-  for (list<pg_log_entry_t>::const_reverse_iterator p = pg_log.get_log().log.rbegin();
-       p != pg_log.get_log().log.rend();
-       ++p) {
-    if (did.count(p->soid))
-      continue;
-    did.insert(p->soid);
-
-    if (p->is_delete() && !is_missing_object(p->soid)) {
-      dout(10) << " checking " << p->soid
-              << " at " << p->version << dendl;
-      struct stat st;
-      int r = osd->store->stat(
-       ch,
-       ghobject_t(p->soid, ghobject_t::NO_GEN, pg_whoami.shard),
-       &st);
-      if (r != -ENOENT) {
-       derr << __func__ << " " << p->soid << " exists, but should have been "
-            << "deleted" << dendl;
-       assert(0 == "erroneously present object");
-      }
-    } else {
-      // ignore old(+missing) objects
-    }
-  }
-}
-
-
-
-// ===========================
-// hit sets
-
-hobject_t PrimaryLogPG::get_hit_set_current_object(utime_t stamp)
-{
-  ostringstream ss;
-  ss << "hit_set_" << info.pgid.pgid << "_current_" << stamp;
-  hobject_t hoid(sobject_t(ss.str(), CEPH_NOSNAP), "",
-                info.pgid.ps(), info.pgid.pool(),
-                cct->_conf->osd_hit_set_namespace);
-  dout(20) << __func__ << " " << hoid << dendl;
-  return hoid;
-}
-
-hobject_t PrimaryLogPG::get_hit_set_archive_object(utime_t start,
-                                                  utime_t end,
-                                                  bool using_gmt)
-{
-  ostringstream ss;
-  ss << "hit_set_" << info.pgid.pgid << "_archive_";
-  if (using_gmt) {
-    start.gmtime(ss) << "_";
-    end.gmtime(ss);
-  } else {
-    start.localtime(ss) << "_";
-    end.localtime(ss);
-  }
-  hobject_t hoid(sobject_t(ss.str(), CEPH_NOSNAP), "",
-                info.pgid.ps(), info.pgid.pool(),
-                cct->_conf->osd_hit_set_namespace);
-  dout(20) << __func__ << " " << hoid << dendl;
-  return hoid;
-}
-
-void PrimaryLogPG::hit_set_clear()
-{
-  dout(20) << __func__ << dendl;
-  hit_set.reset();
-  hit_set_start_stamp = utime_t();
-}
-
-void PrimaryLogPG::hit_set_setup()
-{
-  if (!is_active() ||
-      !is_primary()) {
-    hit_set_clear();
-    return;
-  }
-
-  if (is_active() && is_primary() &&
-      (!pool.info.hit_set_count ||
-       !pool.info.hit_set_period ||
-       pool.info.hit_set_params.get_type() == HitSet::TYPE_NONE)) {
-    hit_set_clear();
-
-    // only primary is allowed to remove all the hit set objects
-    hit_set_remove_all();
-    return;
-  }
-
-  // FIXME: discard any previous data for now
-  hit_set_create();
-
-  // include any writes we know about from the pg log.  this doesn't
-  // capture reads, but it is better than nothing!
-  hit_set_apply_log();
-}
-
-void PrimaryLogPG::hit_set_remove_all()
-{
-  // If any archives are degraded we skip this
-  for (list<pg_hit_set_info_t>::iterator p = info.hit_set.history.begin();
-       p != info.hit_set.history.end();
-       ++p) {
-    hobject_t aoid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-
-    // Once we hit a degraded object just skip
-    if (is_degraded_or_backfilling_object(aoid))
-      return;
-    if (scrubber.write_blocked_by_scrub(aoid))
-      return;
-  }
-
-  if (!info.hit_set.history.empty()) {
-    list<pg_hit_set_info_t>::reverse_iterator p = info.hit_set.history.rbegin();
-    assert(p != info.hit_set.history.rend());
-    hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-    assert(!is_degraded_or_backfilling_object(oid));
-    ObjectContextRef obc = get_object_context(oid, false);
-    assert(obc);
-
-    OpContextUPtr ctx = simple_opc_create(obc);
-    ctx->at_version = get_next_version();
-    ctx->updated_hset_history = info.hit_set;
-    utime_t now = ceph_clock_now();
-    ctx->mtime = now;
-    hit_set_trim(ctx, 0);
-    simple_opc_submit(std::move(ctx));
-  }
-
-  info.hit_set = pg_hit_set_history_t();
-  if (agent_state) {
-    agent_state->discard_hit_sets();
-  }
-}
-
-void PrimaryLogPG::hit_set_create()
-{
-  utime_t now = ceph_clock_now();
-  // make a copy of the params to modify
-  HitSet::Params params(pool.info.hit_set_params);
-
-  dout(20) << __func__ << " " << params << dendl;
-  if (pool.info.hit_set_params.get_type() == HitSet::TYPE_BLOOM) {
-    BloomHitSet::Params *p =
-      static_cast<BloomHitSet::Params*>(params.impl.get());
-
-    // convert false positive rate so it holds up across the full period
-    p->set_fpp(p->get_fpp() / pool.info.hit_set_count);
-    if (p->get_fpp() <= 0.0)
-      p->set_fpp(.01);  // fpp cannot be zero!
-
-    // if we don't have specified size, estimate target size based on the
-    // previous bin!
-    if (p->target_size == 0 && hit_set) {
-      utime_t dur = now - hit_set_start_stamp;
-      unsigned unique = hit_set->approx_unique_insert_count();
-      dout(20) << __func__ << " previous set had approx " << unique
-              << " unique items over " << dur << " seconds" << dendl;
-      p->target_size = (double)unique * (double)pool.info.hit_set_period
-                    / (double)dur;
-    }
-    if (p->target_size <
-       static_cast<uint64_t>(cct->_conf->osd_hit_set_min_size))
-      p->target_size = cct->_conf->osd_hit_set_min_size;
-
-    if (p->target_size
-       > static_cast<uint64_t>(cct->_conf->osd_hit_set_max_size))
-      p->target_size = cct->_conf->osd_hit_set_max_size;
-
-    p->seed = now.sec();
-
-    dout(10) << __func__ << " target_size " << p->target_size
-            << " fpp " << p->get_fpp() << dendl;
-  }
-  hit_set.reset(new HitSet(params));
-  hit_set_start_stamp = now;
-}
-
-/**
- * apply log entries to set
- *
- * this would only happen after peering, to at least capture writes
- * during an interval that was potentially lost.
- */
-bool PrimaryLogPG::hit_set_apply_log()
-{
-  if (!hit_set)
-    return false;
-
-  eversion_t to = info.last_update;
-  eversion_t from = info.hit_set.current_last_update;
-  if (to <= from) {
-    dout(20) << __func__ << " no update" << dendl;
-    return false;
-  }
-
-  dout(20) << __func__ << " " << to << " .. " << info.last_update << dendl;
-  list<pg_log_entry_t>::const_reverse_iterator p = pg_log.get_log().log.rbegin();
-  while (p != pg_log.get_log().log.rend() && p->version > to)
-    ++p;
-  while (p != pg_log.get_log().log.rend() && p->version > from) {
-    hit_set->insert(p->soid);
-    ++p;
-  }
-
-  return true;
-}
-
-void PrimaryLogPG::hit_set_persist()
-{
-  dout(10) << __func__  << dendl;
-  bufferlist bl;
-  unsigned max = pool.info.hit_set_count;
-
-  utime_t now = ceph_clock_now();
-  hobject_t oid;
-
-  // If any archives are degraded we skip this persist request
-  // account for the additional entry being added below
-  for (list<pg_hit_set_info_t>::iterator p = info.hit_set.history.begin();
-       p != info.hit_set.history.end();
-       ++p) {
-    hobject_t aoid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-
-    // Once we hit a degraded object just skip further trim
-    if (is_degraded_or_backfilling_object(aoid))
-      return;
-    if (scrubber.write_blocked_by_scrub(aoid))
-      return;
-  }
-
-  // If backfill is in progress and we could possibly overlap with the
-  // hit_set_* objects, back off.  Since these all have
-  // hobject_t::hash set to pgid.ps(), and those sort first, we can
-  // look just at that.  This is necessary because our transactions
-  // may include a modify of the new hit_set *and* a delete of the
-  // old one, and this may span the backfill boundary.
-  for (set<pg_shard_t>::iterator p = backfill_targets.begin();
-       p != backfill_targets.end();
-       ++p) {
-    assert(peer_info.count(*p));
-    const pg_info_t& pi = peer_info[*p];
-    if (pi.last_backfill == hobject_t() ||
-       pi.last_backfill.get_hash() == info.pgid.ps()) {
-      dout(10) << __func__ << " backfill target osd." << *p
-              << " last_backfill has not progressed past pgid ps"
-              << dendl;
-      return;
-    }
-  }
-
-
-  pg_hit_set_info_t new_hset = pg_hit_set_info_t(pool.info.use_gmt_hitset);
-  new_hset.begin = hit_set_start_stamp;
-  new_hset.end = now;
-  oid = get_hit_set_archive_object(
-    new_hset.begin,
-    new_hset.end,
-    new_hset.using_gmt);
-
-  // If the current object is degraded we skip this persist request
-  if (scrubber.write_blocked_by_scrub(oid))
-    return;
-
-  hit_set->seal();
-  ::encode(*hit_set, bl);
-  dout(20) << __func__ << " archive " << oid << dendl;
-
-  if (agent_state) {
-    agent_state->add_hit_set(new_hset.begin, hit_set);
-    uint32_t size = agent_state->hit_set_map.size();
-    if (size >= pool.info.hit_set_count) {
-      size = pool.info.hit_set_count > 0 ? pool.info.hit_set_count - 1: 0;
-    }
-    hit_set_in_memory_trim(size);
-  }
-
-  ObjectContextRef obc = get_object_context(oid, true);
-  OpContextUPtr ctx = simple_opc_create(obc);
-
-  ctx->at_version = get_next_version();
-  ctx->updated_hset_history = info.hit_set;
-  pg_hit_set_history_t &updated_hit_set_hist = *(ctx->updated_hset_history);
-
-  updated_hit_set_hist.current_last_update = info.last_update;
-  new_hset.version = ctx->at_version;
-
-  updated_hit_set_hist.history.push_back(new_hset);
-  hit_set_create();
-
-  // fabricate an object_info_t and SnapSet
-  obc->obs.oi.version = ctx->at_version;
-  obc->obs.oi.mtime = now;
-  obc->obs.oi.size = bl.length();
-  obc->obs.exists = true;
-  obc->obs.oi.set_data_digest(bl.crc32c(-1));
-
-  ctx->new_obs = obc->obs;
-
-  obc->ssc->snapset.head_exists = true;
-  ctx->new_snapset = obc->ssc->snapset;
-
-  ctx->delta_stats.num_objects++;
-  ctx->delta_stats.num_objects_hit_set_archive++;
-  ctx->delta_stats.num_bytes += bl.length();
-  ctx->delta_stats.num_bytes_hit_set_archive += bl.length();
-
-  bufferlist bss;
-  ::encode(ctx->new_snapset, bss);
-  bufferlist boi(sizeof(ctx->new_obs.oi));
-  ::encode(ctx->new_obs.oi, boi,
-          get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr));
-
-  ctx->op_t->create(oid);
-  if (bl.length()) {
-    ctx->op_t->write(oid, 0, bl.length(), bl, 0);
-  }
-  map <string, bufferlist> attrs;
-  attrs[OI_ATTR].claim(boi);
-  attrs[SS_ATTR].claim(bss);
-  setattrs_maybe_cache(ctx->obc, ctx.get(), ctx->op_t.get(), attrs);
-  ctx->log.push_back(
-    pg_log_entry_t(
-      pg_log_entry_t::MODIFY,
-      oid,
-      ctx->at_version,
-      eversion_t(),
-      0,
-      osd_reqid_t(),
-      ctx->mtime,
-      0)
-    );
-
-  hit_set_trim(ctx, max);
-
-  simple_opc_submit(std::move(ctx));
-}
-
-void PrimaryLogPG::hit_set_trim(OpContextUPtr &ctx, unsigned max)
-{
-  assert(ctx->updated_hset_history);
-  pg_hit_set_history_t &updated_hit_set_hist =
-    *(ctx->updated_hset_history);
-  for (unsigned num = updated_hit_set_hist.history.size(); num > max; --num) {
-    list<pg_hit_set_info_t>::iterator p = updated_hit_set_hist.history.begin();
-    assert(p != updated_hit_set_hist.history.end());
-    hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-
-    assert(!is_degraded_or_backfilling_object(oid));
-
-    dout(20) << __func__ << " removing " << oid << dendl;
-    ++ctx->at_version.version;
-    ctx->log.push_back(
-        pg_log_entry_t(pg_log_entry_t::DELETE,
-                      oid,
-                      ctx->at_version,
-                      p->version,
-                      0,
-                      osd_reqid_t(),
-                      ctx->mtime,
-                      0));
-
-    ctx->op_t->remove(oid);
-    updated_hit_set_hist.history.pop_front();
-
-    ObjectContextRef obc = get_object_context(oid, false);
-    assert(obc);
-    --ctx->delta_stats.num_objects;
-    --ctx->delta_stats.num_objects_hit_set_archive;
-    ctx->delta_stats.num_bytes -= obc->obs.oi.size;
-    ctx->delta_stats.num_bytes_hit_set_archive -= obc->obs.oi.size;
-  }
-}
-
-void PrimaryLogPG::hit_set_in_memory_trim(uint32_t max_in_memory)
-{
-  while (agent_state->hit_set_map.size() > max_in_memory) {
-    agent_state->remove_oldest_hit_set();
-  }
-}
-
-
-// =======================================
-// cache agent
-
-void PrimaryLogPG::agent_setup()
-{
-  assert(is_locked());
-  if (!is_active() ||
-      !is_primary() ||
-      pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE ||
-      pool.info.tier_of < 0 ||
-      !get_osdmap()->have_pg_pool(pool.info.tier_of)) {
-    agent_clear();
-    return;
-  }
-  if (!agent_state) {
-    agent_state.reset(new TierAgentState);
-
-    // choose random starting position
-    agent_state->position = hobject_t();
-    agent_state->position.pool = info.pgid.pool();
-    agent_state->position.set_hash(pool.info.get_random_pg_position(
-      info.pgid.pgid,
-      rand()));
-    agent_state->start = agent_state->position;
-
-    dout(10) << __func__ << " allocated new state, position "
-            << agent_state->position << dendl;
-  } else {
-    dout(10) << __func__ << " keeping existing state" << dendl;
-  }
-
-  if (info.stats.stats_invalid) {
-    osd->clog->warn() << "pg " << info.pgid << " has invalid (post-split) stats; must scrub before tier agent can activate";
-  }
-
-  agent_choose_mode();
-}
-
-void PrimaryLogPG::agent_clear()
-{
-  agent_stop();
-  agent_state.reset(NULL);
-}
-
-// Return false if no objects operated on since start of object hash space
-bool PrimaryLogPG::agent_work(int start_max, int agent_flush_quota)
-{
-  lock();
-  if (!agent_state) {
-    dout(10) << __func__ << " no agent state, stopping" << dendl;
-    unlock();
-    return true;
-  }
-
-  assert(!deleting);
-
-  if (agent_state->is_idle()) {
-    dout(10) << __func__ << " idle, stopping" << dendl;
-    unlock();
-    return true;
-  }
-
-  osd->logger->inc(l_osd_agent_wake);
-
-  dout(10) << __func__
-          << " max " << start_max
-          << ", flush " << agent_state->get_flush_mode_name()
-          << ", evict " << agent_state->get_evict_mode_name()
-          << ", pos " << agent_state->position
-          << dendl;
-  assert(is_primary());
-  assert(is_active());
-
-  agent_load_hit_sets();
-
-  const pg_pool_t *base_pool = get_osdmap()->get_pg_pool(pool.info.tier_of);
-  assert(base_pool);
-
-  int ls_min = 1;
-  int ls_max = cct->_conf->osd_pool_default_cache_max_evict_check_size;
-
-  // list some objects.  this conveniently lists clones (oldest to
-  // newest) before heads... the same order we want to flush in.
-  //
-  // NOTE: do not flush the Sequencer.  we will assume that the
-  // listing we get back is imprecise.
-  vector<hobject_t> ls;
-  hobject_t next;
-  int r = pgbackend->objects_list_partial(agent_state->position, ls_min, ls_max,
-                                         &ls, &next);
-  assert(r >= 0);
-  dout(20) << __func__ << " got " << ls.size() << " objects" << dendl;
-  int started = 0;
-  for (vector<hobject_t>::iterator p = ls.begin();
-       p != ls.end();
-       ++p) {
-    if (p->nspace == cct->_conf->osd_hit_set_namespace) {
-      dout(20) << __func__ << " skip (hit set) " << *p << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (is_degraded_or_backfilling_object(*p)) {
-      dout(20) << __func__ << " skip (degraded) " << *p << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (is_missing_object(p->get_head())) {
-      dout(20) << __func__ << " skip (missing head) " << *p << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    ObjectContextRef obc = get_object_context(*p, false, NULL);
-    if (!obc) {
-      // we didn't flush; we may miss something here.
-      dout(20) << __func__ << " skip (no obc) " << *p << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (!obc->obs.exists) {
-      dout(20) << __func__ << " skip (dne) " << obc->obs.oi.soid << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (scrubber.write_blocked_by_scrub(obc->obs.oi.soid)) {
-      dout(20) << __func__ << " skip (scrubbing) " << obc->obs.oi << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (obc->is_blocked()) {
-      dout(20) << __func__ << " skip (blocked) " << obc->obs.oi << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-    if (obc->is_request_pending()) {
-      dout(20) << __func__ << " skip (request pending) " << obc->obs.oi << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-
-    // be careful flushing omap to an EC pool.
-    if (!base_pool->supports_omap() &&
-       obc->obs.oi.is_omap()) {
-      dout(20) << __func__ << " skip (omap to EC) " << obc->obs.oi << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      continue;
-    }
-
-    if (agent_state->evict_mode != TierAgentState::EVICT_MODE_IDLE &&
-       agent_maybe_evict(obc, false))
-      ++started;
-    else if (agent_state->flush_mode != TierAgentState::FLUSH_MODE_IDLE &&
-             agent_flush_quota > 0 && agent_maybe_flush(obc)) {
-      ++started;
-      --agent_flush_quota;
-    }
-    if (started >= start_max) {
-      // If finishing early, set "next" to the next object
-      if (++p != ls.end())
-       next = *p;
-      break;
-    }
-  }
-
-  if (++agent_state->hist_age > cct->_conf->osd_agent_hist_halflife) {
-    dout(20) << __func__ << " resetting atime and temp histograms" << dendl;
-    agent_state->hist_age = 0;
-    agent_state->temp_hist.decay();
-  }
-
-  // Total objects operated on so far
-  int total_started = agent_state->started + started;
-  bool need_delay = false;
-
-  dout(20) << __func__ << " start pos " << agent_state->position
-    << " next start pos " << next
-    << " started " << total_started << dendl;
-
-  // See if we've made a full pass over the object hash space
-  // This might check at most ls_max objects a second time to notice that
-  // we've checked every objects at least once.
-  if (agent_state->position < agent_state->start &&
-      next >= agent_state->start) {
-    dout(20) << __func__ << " wrap around " << agent_state->start << dendl;
-    if (total_started == 0)
-      need_delay = true;
-    else
-      total_started = 0;
-    agent_state->start = next;
-  }
-  agent_state->started = total_started;
-
-  // See if we are starting from beginning
-  if (next.is_max())
-    agent_state->position = hobject_t();
-  else
-    agent_state->position = next;
-
-  // Discard old in memory HitSets
-  hit_set_in_memory_trim(pool.info.hit_set_count);
-
-  if (need_delay) {
-    assert(agent_state->delaying == false);
-    agent_delay();
-    unlock();
-    return false;
-  }
-  agent_choose_mode();
-  unlock();
-  return true;
-}
-
-void PrimaryLogPG::agent_load_hit_sets()
-{
-  if (agent_state->evict_mode == TierAgentState::EVICT_MODE_IDLE) {
-    return;
-  }
-
-  if (agent_state->hit_set_map.size() < info.hit_set.history.size()) {
-    dout(10) << __func__ << dendl;
-    for (list<pg_hit_set_info_t>::iterator p = info.hit_set.history.begin();
-        p != info.hit_set.history.end(); ++p) {
-      if (agent_state->hit_set_map.count(p->begin.sec()) == 0) {
-       dout(10) << __func__ << " loading " << p->begin << "-"
-                << p->end << dendl;
-       if (!pool.info.is_replicated()) {
-         // FIXME: EC not supported here yet
-         derr << __func__ << " on non-replicated pool" << dendl;
-         break;
-       }
-
-       hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt);
-       if (is_unreadable_object(oid)) {
-         dout(10) << __func__ << " unreadable " << oid << ", waiting" << dendl;
-         break;
-       }
-
-       ObjectContextRef obc = get_object_context(oid, false);
-       if (!obc) {
-         derr << __func__ << ": could not load hitset " << oid << dendl;
-         break;
-       }
-
-       bufferlist bl;
-       {
-         obc->ondisk_read_lock();
-         int r = osd->store->read(ch, ghobject_t(oid), 0, 0, bl);
-         assert(r >= 0);
-         obc->ondisk_read_unlock();
-       }
-       HitSetRef hs(new HitSet);
-       bufferlist::iterator pbl = bl.begin();
-       ::decode(*hs, pbl);
-       agent_state->add_hit_set(p->begin.sec(), hs);
-      }
-    }
-  }
-}
-
-bool PrimaryLogPG::agent_maybe_flush(ObjectContextRef& obc)
-{
-  if (!obc->obs.oi.is_dirty()) {
-    dout(20) << __func__ << " skip (clean) " << obc->obs.oi << dendl;
-    osd->logger->inc(l_osd_agent_skip);
-    return false;
-  }
-  if (obc->obs.oi.is_cache_pinned()) {
-    dout(20) << __func__ << " skip (cache_pinned) " << obc->obs.oi << dendl;
-    osd->logger->inc(l_osd_agent_skip);
-    return false;
-  }
-
-  utime_t now = ceph_clock_now();
-  utime_t ob_local_mtime;
-  if (obc->obs.oi.local_mtime != utime_t()) {
-    ob_local_mtime = obc->obs.oi.local_mtime;
-  } else {
-    ob_local_mtime = obc->obs.oi.mtime;
-  }
-  bool evict_mode_full =
-    (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL);
-  if (!evict_mode_full &&
-      obc->obs.oi.soid.snap == CEPH_NOSNAP &&  // snaps immutable; don't delay
-      (ob_local_mtime + utime_t(pool.info.cache_min_flush_age, 0) > now)) {
-    dout(20) << __func__ << " skip (too young) " << obc->obs.oi << dendl;
-    osd->logger->inc(l_osd_agent_skip);
-    return false;
-  }
-
-  if (osd->agent_is_active_oid(obc->obs.oi.soid)) {
-    dout(20) << __func__ << " skip (flushing) " << obc->obs.oi << dendl;
-    osd->logger->inc(l_osd_agent_skip);
-    return false;
-  }
-
-  dout(10) << __func__ << " flushing " << obc->obs.oi << dendl;
-
-  // FIXME: flush anything dirty, regardless of what distribution of
-  // ages we expect.
-
-  hobject_t oid = obc->obs.oi.soid;
-  osd->agent_start_op(oid);
-  // no need to capture a pg ref, can't outlive fop or ctx
-  std::function<void()> on_flush = [this, oid]() {
-    osd->agent_finish_op(oid);
-  };
-
-  int result = start_flush(
-    OpRequestRef(), obc, false, NULL,
-    on_flush);
-  if (result != -EINPROGRESS) {
-    on_flush();
-    dout(10) << __func__ << " start_flush() failed " << obc->obs.oi
-      << " with " << result << dendl;
-    osd->logger->inc(l_osd_agent_skip);
-    return false;
-  }
-
-  osd->logger->inc(l_osd_agent_flush);
-  return true;
-}
-
-bool PrimaryLogPG::agent_maybe_evict(ObjectContextRef& obc, bool after_flush)
-{
-  const hobject_t& soid = obc->obs.oi.soid;
-  if (!after_flush && obc->obs.oi.is_dirty()) {
-    dout(20) << __func__ << " skip (dirty) " << obc->obs.oi << dendl;
-    return false;
-  }
-  if (!obc->obs.oi.watchers.empty()) {
-    dout(20) << __func__ << " skip (watchers) " << obc->obs.oi << dendl;
-    return false;
-  }
-  if (obc->is_blocked()) {
-    dout(20) << __func__ << " skip (blocked) " << obc->obs.oi << dendl;
-    return false;
-  }
-  if (obc->obs.oi.is_cache_pinned()) {
-    dout(20) << __func__ << " skip (cache_pinned) " << obc->obs.oi << dendl;
-    return false;
-  }
-
-  if (soid.snap == CEPH_NOSNAP) {
-    int result = _verify_no_head_clones(soid, obc->ssc->snapset);
-    if (result < 0) {
-      dout(20) << __func__ << " skip (clones) " << obc->obs.oi << dendl;
-      return false;
-    }
-  }
-
-  if (agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL) {
-    // is this object old than cache_min_evict_age?
-    utime_t now = ceph_clock_now();
-    utime_t ob_local_mtime;
-    if (obc->obs.oi.local_mtime != utime_t()) {
-      ob_local_mtime = obc->obs.oi.local_mtime;
-    } else {
-      ob_local_mtime = obc->obs.oi.mtime;
-    }
-    if (ob_local_mtime + utime_t(pool.info.cache_min_evict_age, 0) > now) {
-      dout(20) << __func__ << " skip (too young) " << obc->obs.oi << dendl;
-      osd->logger->inc(l_osd_agent_skip);
-      return false;
-    }
-    // is this object old and/or cold enough?
-    int temp = 0;
-    uint64_t temp_upper = 0, temp_lower = 0;
-    if (hit_set)
-      agent_estimate_temp(soid, &temp);
-    agent_state->temp_hist.add(temp);
-    agent_state->temp_hist.get_position_micro(temp, &temp_lower, &temp_upper);
-
-    dout(20) << __func__
-            << " temp " << temp
-            << " pos " << temp_lower << "-" << temp_upper
-            << ", evict_effort " << agent_state->evict_effort
-            << dendl;
-    dout(30) << "agent_state:\n";
-    Formatter *f = Formatter::create("");
-    f->open_object_section("agent_state");
-    agent_state->dump(f);
-    f->close_section();
-    f->flush(*_dout);
-    delete f;
-    *_dout << dendl;
-
-    if (1000000 - temp_upper >= agent_state->evict_effort)
-      return false;
-  }
-
-  dout(10) << __func__ << " evicting " << obc->obs.oi << dendl;
-  OpContextUPtr ctx = simple_opc_create(obc);
-
-  if (!ctx->lock_manager.get_lock_type(
-       ObjectContext::RWState::RWWRITE,
-       obc->obs.oi.soid,
-       obc,
-       OpRequestRef())) {
-    close_op_ctx(ctx.release());
-    dout(20) << __func__ << " skip (cannot get lock) " << obc->obs.oi << dendl;
-    return false;
-  }
-
-  osd->agent_start_evict_op();
-  ctx->register_on_finish(
-    [this]() {
-      osd->agent_finish_evict_op();
-    });
-
-  ctx->at_version = get_next_version();
-  assert(ctx->new_obs.exists);
-  int r = _delete_oid(ctx.get(), true, false);
-  if (obc->obs.oi.is_omap())
-    ctx->delta_stats.num_objects_omap--;
-  ctx->delta_stats.num_evict++;
-  ctx->delta_stats.num_evict_kb += SHIFT_ROUND_UP(obc->obs.oi.size, 10);
-  if (obc->obs.oi.is_dirty())
-    --ctx->delta_stats.num_objects_dirty;
-  assert(r == 0);
-  finish_ctx(ctx.get(), pg_log_entry_t::DELETE, false);
-  simple_opc_submit(std::move(ctx));
-  osd->logger->inc(l_osd_tier_evict);
-  osd->logger->inc(l_osd_agent_evict);
-  return true;
-}
-
-void PrimaryLogPG::agent_stop()
-{
-  dout(20) << __func__ << dendl;
-  if (agent_state && !agent_state->is_idle()) {
-    agent_state->evict_mode = TierAgentState::EVICT_MODE_IDLE;
-    agent_state->flush_mode = TierAgentState::FLUSH_MODE_IDLE;
-    osd->agent_disable_pg(this, agent_state->evict_effort);
-  }
-}
-
-void PrimaryLogPG::agent_delay()
-{
-  dout(20) << __func__ << dendl;
-  if (agent_state && !agent_state->is_idle()) {
-    assert(agent_state->delaying == false);
-    agent_state->delaying = true;
-    osd->agent_disable_pg(this, agent_state->evict_effort);
-  }
-}
-
-void PrimaryLogPG::agent_choose_mode_restart()
-{
-  dout(20) << __func__ << dendl;
-  lock();
-  if (agent_state && agent_state->delaying) {
-    agent_state->delaying = false;
-    agent_choose_mode(true);
-  }
-  unlock();
-}
-
-bool PrimaryLogPG::agent_choose_mode(bool restart, OpRequestRef op)
-{
-  bool requeued = false;
-  // Let delay play out
-  if (agent_state->delaying) {
-    dout(20) << __func__ << this << " delaying, ignored" << dendl;
-    return requeued;
-  }
-
-  TierAgentState::flush_mode_t flush_mode = TierAgentState::FLUSH_MODE_IDLE;
-  TierAgentState::evict_mode_t evict_mode = TierAgentState::EVICT_MODE_IDLE;
-  unsigned evict_effort = 0;
-
-  if (info.stats.stats_invalid) {
-    // idle; stats can't be trusted until we scrub.
-    dout(20) << __func__ << " stats invalid (post-split), idle" << dendl;
-    goto skip_calc;
-  }
-
-  {
-  uint64_t divisor = pool.info.get_pg_num_divisor(info.pgid.pgid);
-  assert(divisor > 0);
-
-  // adjust (effective) user objects down based on the number
-  // of HitSet objects, which should not count toward our total since
-  // they cannot be flushed.
-  uint64_t unflushable = info.stats.stats.sum.num_objects_hit_set_archive;
-
-  // also exclude omap objects if ec backing pool
-  const pg_pool_t *base_pool = get_osdmap()->get_pg_pool(pool.info.tier_of);
-  assert(base_pool);
-  if (!base_pool->supports_omap())
-    unflushable += info.stats.stats.sum.num_objects_omap;
-
-  uint64_t num_user_objects = info.stats.stats.sum.num_objects;
-  if (num_user_objects > unflushable)
-    num_user_objects -= unflushable;
-  else
-    num_user_objects = 0;
-
-  uint64_t num_user_bytes = info.stats.stats.sum.num_bytes;
-  uint64_t unflushable_bytes = info.stats.stats.sum.num_bytes_hit_set_archive;
-  num_user_bytes -= unflushable_bytes;
-  uint64_t num_overhead_bytes = osd->store->estimate_objects_overhead(num_user_objects);
-  num_user_bytes += num_overhead_bytes;
-
-  // also reduce the num_dirty by num_objects_omap
-  int64_t num_dirty = info.stats.stats.sum.num_objects_dirty;
-  if (!base_pool->supports_omap()) {
-    if (num_dirty > info.stats.stats.sum.num_objects_omap)
-      num_dirty -= info.stats.stats.sum.num_objects_omap;
-    else
-      num_dirty = 0;
-  }
-
-  dout(10) << __func__
-          << " flush_mode: "
-          << TierAgentState::get_flush_mode_name(agent_state->flush_mode)
-          << " evict_mode: "
-          << TierAgentState::get_evict_mode_name(agent_state->evict_mode)
-          << " num_objects: " << info.stats.stats.sum.num_objects
-          << " num_bytes: " << info.stats.stats.sum.num_bytes
-          << " num_objects_dirty: " << info.stats.stats.sum.num_objects_dirty
-          << " num_objects_omap: " << info.stats.stats.sum.num_objects_omap
-          << " num_dirty: " << num_dirty
-          << " num_user_objects: " << num_user_objects
-          << " num_user_bytes: " << num_user_bytes
-          << " num_overhead_bytes: " << num_overhead_bytes
-          << " pool.info.target_max_bytes: " << pool.info.target_max_bytes
-          << " pool.info.target_max_objects: " << pool.info.target_max_objects
-          << dendl;
-
-  // get dirty, full ratios
-  uint64_t dirty_micro = 0;
-  uint64_t full_micro = 0;
-  if (pool.info.target_max_bytes && num_user_objects > 0) {
-    uint64_t avg_size = num_user_bytes / num_user_objects;
-    dirty_micro =
-      num_dirty * avg_size * 1000000 /
-      MAX(pool.info.target_max_bytes / divisor, 1);
-    full_micro =
-      num_user_objects * avg_size * 1000000 /
-      MAX(pool.info.target_max_bytes / divisor, 1);
-  }
-  if (pool.info.target_max_objects > 0) {
-    uint64_t dirty_objects_micro =
-      num_dirty * 1000000 /
-      MAX(pool.info.target_max_objects / divisor, 1);
-    if (dirty_objects_micro > dirty_micro)
-      dirty_micro = dirty_objects_micro;
-    uint64_t full_objects_micro =
-      num_user_objects * 1000000 /
-      MAX(pool.info.target_max_objects / divisor, 1);
-    if (full_objects_micro > full_micro)
-      full_micro = full_objects_micro;
-  }
-  dout(20) << __func__ << " dirty " << ((float)dirty_micro / 1000000.0)
-          << " full " << ((float)full_micro / 1000000.0)
-          << dendl;
-
-  // flush mode
-  uint64_t flush_target = pool.info.cache_target_dirty_ratio_micro;
-  uint64_t flush_high_target = pool.info.cache_target_dirty_high_ratio_micro;
-  uint64_t flush_slop = (float)flush_target * cct->_conf->osd_agent_slop;
-  if (restart || agent_state->flush_mode == TierAgentState::FLUSH_MODE_IDLE) {
-    flush_target += flush_slop;
-    flush_high_target += flush_slop;
-  } else {
-    flush_target -= MIN(flush_target, flush_slop);
-    flush_high_target -= MIN(flush_high_target, flush_slop);
-  }
-
-  if (dirty_micro > flush_high_target) {
-    flush_mode = TierAgentState::FLUSH_MODE_HIGH;
-  } else if (dirty_micro > flush_target) {
-    flush_mode = TierAgentState::FLUSH_MODE_LOW;
-  }
-
-  // evict mode
-  uint64_t evict_target = pool.info.cache_target_full_ratio_micro;
-  uint64_t evict_slop = (float)evict_target * cct->_conf->osd_agent_slop;
-  if (restart || agent_state->evict_mode == TierAgentState::EVICT_MODE_IDLE)
-    evict_target += evict_slop;
-  else
-    evict_target -= MIN(evict_target, evict_slop);
-
-  if (full_micro > 1000000) {
-    // evict anything clean
-    evict_mode = TierAgentState::EVICT_MODE_FULL;
-    evict_effort = 1000000;
-  } else if (full_micro > evict_target) {
-    // set effort in [0..1] range based on where we are between
-    evict_mode = TierAgentState::EVICT_MODE_SOME;
-    uint64_t over = full_micro - evict_target;
-    uint64_t span  = 1000000 - evict_target;
-    evict_effort = MAX(over * 1000000 / span,
-                      (unsigned)(1000000.0 * cct->_conf->osd_agent_min_evict_effort));
-
-    // quantize effort to avoid too much reordering in the agent_queue.
-    uint64_t inc = cct->_conf->osd_agent_quantize_effort * 1000000;
-    assert(inc > 0);
-    uint64_t was = evict_effort;
-    evict_effort -= evict_effort % inc;
-    if (evict_effort < inc)
-      evict_effort = inc;
-    assert(evict_effort >= inc && evict_effort <= 1000000);
-    dout(30) << __func__ << " evict_effort " << was << " quantized by " << inc << " to " << evict_effort << dendl;
-  }
-  }
-
-  skip_calc:
-  bool old_idle = agent_state->is_idle();
-  if (flush_mode != agent_state->flush_mode) {
-    dout(5) << __func__ << " flush_mode "
-           << TierAgentState::get_flush_mode_name(agent_state->flush_mode)
-           << " -> "
-           << TierAgentState::get_flush_mode_name(flush_mode)
-           << dendl;
-    if (flush_mode == TierAgentState::FLUSH_MODE_HIGH) {
-      osd->agent_inc_high_count();
-      info.stats.stats.sum.num_flush_mode_high = 1;
-    } else if (flush_mode == TierAgentState::FLUSH_MODE_LOW) {
-      info.stats.stats.sum.num_flush_mode_low = 1;
-    }
-    if (agent_state->flush_mode == TierAgentState::FLUSH_MODE_HIGH) {
-      osd->agent_dec_high_count();
-      info.stats.stats.sum.num_flush_mode_high = 0;
-    } else if (agent_state->flush_mode == TierAgentState::FLUSH_MODE_LOW) {
-      info.stats.stats.sum.num_flush_mode_low = 0;
-    }
-    agent_state->flush_mode = flush_mode;
-  }
-  if (evict_mode != agent_state->evict_mode) {
-    dout(5) << __func__ << " evict_mode "
-           << TierAgentState::get_evict_mode_name(agent_state->evict_mode)
-           << " -> "
-           << TierAgentState::get_evict_mode_name(evict_mode)
-           << dendl;
-    if (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL &&
-       is_active()) {
-      if (op)
-       requeue_op(op);
-      requeue_ops(waiting_for_active);
-      requeue_ops(waiting_for_scrub);
-      requeue_ops(waiting_for_cache_not_full);
-      objects_blocked_on_cache_full.clear();
-      requeued = true;
-    }
-    if (evict_mode == TierAgentState::EVICT_MODE_SOME) {
-      info.stats.stats.sum.num_evict_mode_some = 1;
-    } else if (evict_mode == TierAgentState::EVICT_MODE_FULL) {
-      info.stats.stats.sum.num_evict_mode_full = 1;
-    }
-    if (agent_state->evict_mode == TierAgentState::EVICT_MODE_SOME) {
-      info.stats.stats.sum.num_evict_mode_some = 0;
-    } else if (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) {
-      info.stats.stats.sum.num_evict_mode_full = 0;
-    }
-    agent_state->evict_mode = evict_mode;
-  }
-  uint64_t old_effort = agent_state->evict_effort;
-  if (evict_effort != agent_state->evict_effort) {
-    dout(5) << __func__ << " evict_effort "
-           << ((float)agent_state->evict_effort / 1000000.0)
-           << " -> "
-           << ((float)evict_effort / 1000000.0)
-           << dendl;
-    agent_state->evict_effort = evict_effort;
-  }
-
-  // NOTE: we are using evict_effort as a proxy for *all* agent effort
-  // (including flush).  This is probably fine (they should be
-  // correlated) but it is not precisely correct.
-  if (agent_state->is_idle()) {
-    if (!restart && !old_idle) {
-      osd->agent_disable_pg(this, old_effort);
-    }
-  } else {
-    if (restart || old_idle) {
-      osd->agent_enable_pg(this, agent_state->evict_effort);
-    } else if (old_effort != agent_state->evict_effort) {
-      osd->agent_adjust_pg(this, old_effort, agent_state->evict_effort);
-    }
-  }
-  return requeued;
-}
-
-void PrimaryLogPG::agent_estimate_temp(const hobject_t& oid, int *temp)
-{
-  assert(hit_set);
-  assert(temp);
-  *temp = 0;
-  if (hit_set->contains(oid))
-    *temp = 1000000;
-  unsigned i = 0;
-  int last_n = pool.info.hit_set_search_last_n;
-  for (map<time_t,HitSetRef>::reverse_iterator p =
-       agent_state->hit_set_map.rbegin(); last_n > 0 &&
-       p != agent_state->hit_set_map.rend(); ++p, ++i) {
-    if (p->second->contains(oid)) {
-      *temp += pool.info.get_grade(i);
-      --last_n;
-    }
-  }
-}
-
-// Dup op detection
-
-bool PrimaryLogPG::already_complete(eversion_t v)
-{
-  dout(20) << __func__ << ": " << v << dendl;
-  for (xlist<RepGather*>::iterator i = repop_queue.begin();
-       !i.end();
-       ++i) {
-    dout(20) << __func__ << ": " << **i << dendl;
-    // skip copy from temp object ops
-    if ((*i)->v == eversion_t()) {
-      dout(20) << __func__ << ": " << **i
-              << " version is empty" << dendl;
-      continue;
-    }
-    if ((*i)->v > v) {
-      dout(20) << __func__ << ": " << **i
-              << " (*i)->v past v" << dendl;
-      break;
-    }
-    if (!(*i)->all_committed) {
-      dout(20) << __func__ << ": " << **i
-              << " not committed, returning false"
-              << dendl;
-      return false;
-    }
-  }
-  dout(20) << __func__ << ": returning true" << dendl;
-  return true;
-}
-
-bool PrimaryLogPG::already_ack(eversion_t v)
-{
-  dout(20) << __func__ << ": " << v << dendl;
-  for (xlist<RepGather*>::iterator i = repop_queue.begin();
-       !i.end();
-       ++i) {
-    // skip copy from temp object ops
-    if ((*i)->v == eversion_t()) {
-      dout(20) << __func__ << ": " << **i
-              << " version is empty" << dendl;
-      continue;
-    }
-    if ((*i)->v > v) {
-      dout(20) << __func__ << ": " << **i
-              << " (*i)->v past v" << dendl;
-      break;
-    }
-    if (!(*i)->all_applied) {
-      dout(20) << __func__ << ": " << **i
-              << " not applied, returning false"
-              << dendl;
-      return false;
-    }
-  }
-  dout(20) << __func__ << ": returning true" << dendl;
-  return true;
-}
-
-
-// ==========================================================================================
-// SCRUB
-
-
-bool PrimaryLogPG::_range_available_for_scrub(
-  const hobject_t &begin, const hobject_t &end)
-{
-  pair<hobject_t, ObjectContextRef> next;
-  next.second = object_contexts.lookup(begin);
-  next.first = begin;
-  bool more = true;
-  while (more && next.first < end) {
-    if (next.second && next.second->is_blocked()) {
-      next.second->requeue_scrub_on_unblock = true;
-      dout(10) << __func__ << ": scrub delayed, "
-              << next.first << " is blocked"
-              << dendl;
-      return false;
-    }
-    more = object_contexts.get_next(next.first, &next);
-  }
-  return true;
-}
-
-static bool doing_clones(const boost::optional<SnapSet> &snapset,
-                        const vector<snapid_t>::reverse_iterator &curclone) {
-    return snapset && curclone != snapset.get().clones.rend();
-}
-
-void PrimaryLogPG::log_missing(unsigned missing,
-                       const boost::optional<hobject_t> &head,
-                       LogChannelRef clog,
-                       const spg_t &pgid,
-                       const char *func,
-                       const char *mode,
-                       bool allow_incomplete_clones)
-{
-  assert(head);
-  if (allow_incomplete_clones) {
-    dout(20) << func << " " << mode << " " << pgid << " " << head.get()
-               << " skipped " << missing << " clone(s) in cache tier" << dendl;
-  } else {
-    clog->info() << mode << " " << pgid << " " << head.get()
-                      << " " << missing << " missing clone(s)";
-  }
-}
-
-unsigned PrimaryLogPG::process_clones_to(const boost::optional<hobject_t> &head,
-  const boost::optional<SnapSet> &snapset,
-  LogChannelRef clog,
-  const spg_t &pgid,
-  const char *mode,
-  bool allow_incomplete_clones,
-  boost::optional<snapid_t> target,
-  vector<snapid_t>::reverse_iterator *curclone,
-  inconsistent_snapset_wrapper &e)
-{
-  assert(head);
-  assert(snapset);
-  unsigned missing = 0;
-
-  // NOTE: clones are in descending order, thus **curclone > target test here
-  hobject_t next_clone(head.get());
-  while(doing_clones(snapset, *curclone) && (!target || **curclone > *target)) {
-    ++missing;
-    // it is okay to be missing one or more clones in a cache tier.
-    // skip higher-numbered clones in the list.
-    if (!allow_incomplete_clones) {
-      next_clone.snap = **curclone;
-      clog->error() << mode << " " << pgid << " " << head.get()
-                        << " expected clone " << next_clone << " " << missing
-                         << " missing";
-      ++scrubber.shallow_errors;
-      e.set_clone_missing(next_clone.snap);
-    }
-    // Clones are descending
-    ++(*curclone);
-  }
-  return missing;
-}
-
-/*
- * Validate consistency of the object info and snap sets.
- *
- * We are sort of comparing 2 lists. The main loop is on objmap.objects. But
- * the comparison of the objects is against multiple snapset.clones. There are
- * multiple clone lists and in between lists we expect head or snapdir.
- *
- * Example
- *
- * objects              expected
- * =======              =======
- * obj1 snap 1          head/snapdir, unexpected obj1 snap 1
- * obj2 head            head/snapdir, head ok
- *              [SnapSet clones 6 4 2 1]
- * obj2 snap 7          obj2 snap 6, unexpected obj2 snap 7
- * obj2 snap 6          obj2 snap 6, match
- * obj2 snap 4          obj2 snap 4, match
- * obj3 head            obj2 snap 2 (expected), obj2 snap 1 (expected), head ok
- *              [Snapset clones 3 1]
- * obj3 snap 3          obj3 snap 3 match
- * obj3 snap 1          obj3 snap 1 match
- * obj4 snapdir         head/snapdir, snapdir ok
- *              [Snapset clones 4]
- * EOL                  obj4 snap 4, (expected)
- */
-void PrimaryLogPG::scrub_snapshot_metadata(
-  ScrubMap &scrubmap,
-  const map<hobject_t, pair<uint32_t, uint32_t>> &missing_digest)
-{
-  dout(10) << __func__ << dendl;
-
-  coll_t c(info.pgid);
-  bool repair = state_test(PG_STATE_REPAIR);
-  bool deep_scrub = state_test(PG_STATE_DEEP_SCRUB);
-  const char *mode = (repair ? "repair": (deep_scrub ? "deep-scrub" : "scrub"));
-  boost::optional<snapid_t> all_clones;   // Unspecified snapid_t or boost::none
-
-  /// snapsets to repair
-  map<hobject_t,SnapSet> snapset_to_repair;
-
-  // traverse in reverse order.
-  boost::optional<hobject_t> head;
-  boost::optional<SnapSet> snapset; // If initialized so will head (above)
-  vector<snapid_t>::reverse_iterator curclone; // Defined only if snapset initialized
-  unsigned missing = 0;
-  inconsistent_snapset_wrapper soid_error, head_error;
-
-  bufferlist last_data;
-
-  for (map<hobject_t,ScrubMap::object>::reverse_iterator
-       p = scrubmap.objects.rbegin(); p != scrubmap.objects.rend(); ++p) {
-    const hobject_t& soid = p->first;
-    soid_error = inconsistent_snapset_wrapper{soid};
-    object_stat_sum_t stat;
-    boost::optional<object_info_t> oi;
-
-    if (!soid.is_snapdir())
-      stat.num_objects++;
-
-    if (soid.nspace == cct->_conf->osd_hit_set_namespace)
-      stat.num_objects_hit_set_archive++;
-
-    if (soid.is_snap()) {
-      // it's a clone
-      stat.num_object_clones++;
-    }
-
-    // basic checks.
-    if (p->second.attrs.count(OI_ATTR) == 0) {
-      oi = boost::none;
-      osd->clog->error() << mode << " " << info.pgid << " " << soid
-                       << " no '" << OI_ATTR << "' attr";
-      ++scrubber.shallow_errors;
-      soid_error.set_oi_attr_missing();
-    } else {
-      bufferlist bv;
-      bv.push_back(p->second.attrs[OI_ATTR]);
-      try {
-       oi = object_info_t(); // Initialize optional<> before decode into it
-       oi.get().decode(bv);
-      } catch (buffer::error& e) {
-       oi = boost::none;
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-               << " can't decode '" << OI_ATTR << "' attr " << e.what();
-       ++scrubber.shallow_errors;
-       soid_error.set_oi_attr_corrupted();
-        soid_error.set_oi_attr_missing(); // Not available too
-      }
-    }
-
-    if (oi) {
-      if (pgbackend->be_get_ondisk_size(oi->size) != p->second.size) {
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-                          << " on disk size (" << p->second.size
-                          << ") does not match object info size ("
-                          << oi->size << ") adjusted for ondisk to ("
-                          << pgbackend->be_get_ondisk_size(oi->size)
-                          << ")";
-       soid_error.set_size_mismatch();
-       ++scrubber.shallow_errors;
-      }
-
-      dout(20) << mode << "  " << soid << " " << oi.get() << dendl;
-
-      // A clone num_bytes will be added later when we have snapset
-      if (!soid.is_snap()) {
-       stat.num_bytes += oi->size;
-      }
-      if (soid.nspace == cct->_conf->osd_hit_set_namespace)
-       stat.num_bytes_hit_set_archive += oi->size;
-
-      if (!soid.is_snapdir()) {
-       if (oi->is_dirty())
-         ++stat.num_objects_dirty;
-       if (oi->is_whiteout())
-         ++stat.num_whiteouts;
-       if (oi->is_omap())
-         ++stat.num_objects_omap;
-       if (oi->is_cache_pinned())
-         ++stat.num_objects_pinned;
-      }
-    } else {
-      // pessimistic assumption that this object might contain a
-      // legacy SnapSet
-      stat.num_legacy_snapsets++;
-    }
-
-    // Check for any problems while processing clones
-    if (doing_clones(snapset, curclone)) {
-      boost::optional<snapid_t> target;
-      // Expecting an object with snap for current head
-      if (soid.has_snapset() || soid.get_head() != head->get_head()) {
-
-       dout(10) << __func__ << " " << mode << " " << info.pgid << " new object "
-                << soid << " while processing " << head.get() << dendl;
-
-        target = all_clones;
-      } else {
-        assert(soid.is_snap());
-        target = soid.snap;
-      }
-
-      // Log any clones we were expecting to be there up to target
-      // This will set missing, but will be a no-op if snap.soid == *curclone.
-      missing += process_clones_to(head, snapset, osd->clog, info.pgid, mode,
-                       pool.info.allow_incomplete_clones(), target, &curclone,
-                       head_error);
-    }
-    bool expected;
-    // Check doing_clones() again in case we ran process_clones_to()
-    if (doing_clones(snapset, curclone)) {
-      // A head/snapdir would have processed all clones above
-      // or all greater than *curclone.
-      assert(soid.is_snap() && *curclone <= soid.snap);
-
-      // After processing above clone snap should match the expected curclone
-      expected = (*curclone == soid.snap);
-    } else {
-      // If we aren't doing clones any longer, then expecting head/snapdir
-      expected = soid.has_snapset();
-    }
-    if (!expected) {
-      // If we couldn't read the head's snapset, just ignore clones
-      if (head && !snapset) {
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-                         << " clone ignored due to missing snapset";
-      } else {
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-                          << " is an unexpected clone";
-      }
-      ++scrubber.shallow_errors;
-      soid_error.set_headless();
-      scrubber.store->add_snap_error(pool.id, soid_error);
-      if (head && soid.get_head() == head->get_head())
-       head_error.set_clone(soid.snap);
-      continue;
-    }
-
-    // new snapset?
-    if (soid.has_snapset()) {
-
-      if (missing) {
-       log_missing(missing, head, osd->clog, info.pgid, __func__, mode,
-                   pool.info.allow_incomplete_clones());
-      }
-
-      // Save previous head error information
-      if (head && head_error.errors)
-       scrubber.store->add_snap_error(pool.id, head_error);
-      // Set this as a new head object
-      head = soid;
-      missing = 0;
-      head_error = soid_error;
-
-      dout(20) << __func__ << " " << mode << " new head " << head << dendl;
-
-      if (p->second.attrs.count(SS_ATTR) == 0) {
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-                         << " no '" << SS_ATTR << "' attr";
-        ++scrubber.shallow_errors;
-       snapset = boost::none;
-       head_error.set_ss_attr_missing();
-      } else {
-       bufferlist bl;
-       bl.push_back(p->second.attrs[SS_ATTR]);
-       bufferlist::iterator blp = bl.begin();
-        try {
-         snapset = SnapSet(); // Initialize optional<> before decoding into it
-         ::decode(snapset.get(), blp);
-        } catch (buffer::error& e) {
-         snapset = boost::none;
-          osd->clog->error() << mode << " " << info.pgid << " " << soid
-               << " can't decode '" << SS_ATTR << "' attr " << e.what();
-         ++scrubber.shallow_errors;
-         head_error.set_ss_attr_corrupted();
-        }
-      }
-
-      if (snapset) {
-       // what will be next?
-       curclone = snapset->clones.rbegin();
-
-       if (!snapset->clones.empty()) {
-         dout(20) << "  snapset " << snapset.get() << dendl;
-         if (snapset->seq == 0) {
-           osd->clog->error() << mode << " " << info.pgid << " " << soid
-                              << " snaps.seq not set";
-           ++scrubber.shallow_errors;
-           head_error.set_snapset_mismatch();
-          }
-       }
-
-       if (soid.is_head() && !snapset->head_exists) {
-         osd->clog->error() << mode << " " << info.pgid << " " << soid
-                         << " snapset.head_exists=false, but head exists";
-         ++scrubber.shallow_errors;
-         head_error.set_head_mismatch();
-         // Fix head_exists locally so is_legacy() returns correctly
-          snapset->head_exists = true;
-       }
-       if (soid.is_snapdir() && snapset->head_exists) {
-         osd->clog->error() << mode << " " << info.pgid << " " << soid
-                         << " snapset.head_exists=true, but snapdir exists";
-         ++scrubber.shallow_errors;
-         head_error.set_head_mismatch();
-         // For symmetry fix this too, but probably doesn't matter
-          snapset->head_exists = false;
-       }
-
-       if (get_osdmap()->require_osd_release >= CEPH_RELEASE_LUMINOUS) {
-         if (soid.is_snapdir()) {
-           dout(10) << " will move snapset to head from " << soid << dendl;
-           snapset_to_repair[soid.get_head()] = *snapset;
-         } else if (snapset->is_legacy()) {
-           dout(10) << " will convert legacy snapset on " << soid << " " << *snapset
-                    << dendl;
-           snapset_to_repair[soid.get_head()] = *snapset;
-         }
-       } else {
-         stat.num_legacy_snapsets++;
-       }
-      } else {
-       // pessimistic assumption that this object might contain a
-       // legacy SnapSet
-       stat.num_legacy_snapsets++;
-      }
-    } else {
-      assert(soid.is_snap());
-      assert(head);
-      assert(snapset);
-      assert(soid.snap == *curclone);
-
-      dout(20) << __func__ << " " << mode << " matched clone " << soid << dendl;
-
-      if (snapset->clone_size.count(soid.snap) == 0) {
-       osd->clog->error() << mode << " " << info.pgid << " " << soid
-                          << " is missing in clone_size";
-       ++scrubber.shallow_errors;
-       soid_error.set_size_mismatch();
-      } else {
-        if (oi && oi->size != snapset->clone_size[soid.snap]) {
-         osd->clog->error() << mode << " " << info.pgid << " " << soid
-                            << " size " << oi->size << " != clone_size "
-                            << snapset->clone_size[*curclone];
-         ++scrubber.shallow_errors;
-         soid_error.set_size_mismatch();
-        }
-
-        if (snapset->clone_overlap.count(soid.snap) == 0) {
-         osd->clog->error() << mode << " " << info.pgid << " " << soid
-                            << " is missing in clone_overlap";
-         ++scrubber.shallow_errors;
-         soid_error.set_size_mismatch();
-       } else {
-         // This checking is based on get_clone_bytes().  The first 2 asserts
-         // can't happen because we know we have a clone_size and
-         // a clone_overlap.  Now we check that the interval_set won't
-         // cause the last assert.
-         uint64_t size = snapset->clone_size.find(soid.snap)->second;
-         const interval_set<uint64_t> &overlap =
-               snapset->clone_overlap.find(soid.snap)->second;
-         bool bad_interval_set = false;
-         for (interval_set<uint64_t>::const_iterator i = overlap.begin();
-              i != overlap.end(); ++i) {
-           if (size < i.get_len()) {
-             bad_interval_set = true;
-             break;
-           }
-           size -= i.get_len();
-         }
-
-         if (bad_interval_set) {
-           osd->clog->error() << mode << " " << info.pgid << " " << soid
-                              << " bad interval_set in clone_overlap";
-           ++scrubber.shallow_errors;
-           soid_error.set_size_mismatch();
-         } else {
-            stat.num_bytes += snapset->get_clone_bytes(soid.snap);
-         }
-        }
-      }
-
-      // migrate legacy_snaps to snapset?
-      auto p = snapset_to_repair.find(soid.get_head());
-      if (p != snapset_to_repair.end()) {
-       if (!oi || oi->legacy_snaps.empty()) {
-         osd->clog->error() << mode << " " << info.pgid << " " << soid
-                            << " has no oi or legacy_snaps; cannot convert "
-                            << *snapset;
-         ++scrubber.shallow_errors;
-       } else {
-         dout(20) << __func__ << "   copying legacy_snaps " << oi->legacy_snaps
-                  << " to snapset " << p->second << dendl;
-         p->second.clone_snaps[soid.snap] = oi->legacy_snaps;
-       }
-      }
-
-      // what's next?
-      ++curclone;
-      if (soid_error.errors)
-        scrubber.store->add_snap_error(pool.id, soid_error);
-    }
-
-    scrub_cstat.add(stat);
-  }
-
-  if (doing_clones(snapset, curclone)) {
-    dout(10) << __func__ << " " << mode << " " << info.pgid
-            << " No more objects while processing " << head.get() << dendl;
-
-    missing += process_clones_to(head, snapset, osd->clog, info.pgid, mode,
-                     pool.info.allow_incomplete_clones(), all_clones, &curclone,
-                     head_error);
-  }
-  // There could be missing found by the test above or even
-  // before dropping out of the loop for the last head.
-  if (missing) {
-    log_missing(missing, head, osd->clog, info.pgid, __func__,
-               mode, pool.info.allow_incomplete_clones());
-  }
-  if (head && head_error.errors)
-    scrubber.store->add_snap_error(pool.id, head_error);
-
-  for (map<hobject_t,pair<uint32_t,uint32_t>>::const_iterator p =
-        missing_digest.begin();
-       p != missing_digest.end();
-       ++p) {
-    if (p->first.is_snapdir())
-      continue;
-    dout(10) << __func__ << " recording digests for " << p->first << dendl;
-    ObjectContextRef obc = get_object_context(p->first, false);
-    if (!obc) {
-      osd->clog->error() << info.pgid << " " << mode
-                        << " cannot get object context for object "
-                        << p->first;
-      continue;
-    } else if (obc->obs.oi.soid != p->first) {
-      osd->clog->error() << info.pgid << " " << mode
-                        << " object " << p->first
-                        << " has a valid oi attr with a mismatched name, "
-                        << " obc->obs.oi.soid: " << obc->obs.oi.soid;
-      continue;
-    }
-    OpContextUPtr ctx = simple_opc_create(obc);
-    ctx->at_version = get_next_version();
-    ctx->mtime = utime_t();      // do not update mtime
-    ctx->new_obs.oi.set_data_digest(p->second.first);
-    ctx->new_obs.oi.set_omap_digest(p->second.second);
-    finish_ctx(ctx.get(), pg_log_entry_t::MODIFY);
-
-    ctx->register_on_success(
-      [this]() {
-       dout(20) << "updating scrub digest" << dendl;
-       if (--scrubber.num_digest_updates_pending == 0) {
-         requeue_scrub();
-       }
-      });
-
-    simple_opc_submit(std::move(ctx));
-    ++scrubber.num_digest_updates_pending;
-  }
-  for (auto& p : snapset_to_repair) {
-    // cache pools may not have the clones, which means we won't know
-    // what snaps they have.  fake out the clone_snaps entries anyway (with
-    // blank snap lists).
-    p.second.head_exists = true;
-    if (pool.info.allow_incomplete_clones()) {
-      for (auto s : p.second.clones) {
-       if (p.second.clone_snaps.count(s) == 0) {
-         dout(10) << __func__ << " " << p.first << " faking clone_snaps for "
-                  << s << dendl;
-         p.second.clone_snaps[s];
-       }
-      }
-    }
-    if (p.second.clones.size() != p.second.clone_snaps.size() ||
-       p.second.is_legacy()) {
-      // this happens if we encounter other errors above, like a missing
-      // or extra clone.
-      dout(10) << __func__ << " not writing snapset to " << p.first
-              << " snapset " << p.second << " clones " << p.second.clones
-              << "; didn't convert fully" << dendl;
-      scrub_cstat.sum.num_legacy_snapsets++;
-      continue;
-    }
-    dout(10) << __func__ << " writing snapset to " << p.first
-            << " " << p.second << dendl;
-    ObjectContextRef obc = get_object_context(p.first, true);
-    if (!obc) {
-      osd->clog->error() << info.pgid << " " << mode
-                        << " cannot get object context for object "
-                        << p.first;
-      continue;
-    } else if (obc->obs.oi.soid != p.first) {
-      osd->clog->error() << info.pgid << " " << mode
-                        << " object " << p.first
-                        << " has a valid oi attr with a mismatched name, "
-                        << " obc->obs.oi.soid: " << obc->obs.oi.soid;
-      continue;
-    }
-    ObjectContextRef snapset_obc;
-    if (!obc->obs.exists) {
-      snapset_obc = get_object_context(p.first.get_snapdir(), false);
-      if (!snapset_obc) {
-       osd->clog->error() << info.pgid << " " << mode
-                          << " cannot get object context for "
-                          << p.first.get_snapdir();
-       continue;
-      }
-    }
-    OpContextUPtr ctx = simple_opc_create(obc);
-    PGTransaction *t = ctx->op_t.get();
-    ctx->snapset_obc = snapset_obc;
-    ctx->at_version = get_next_version();
-    ctx->mtime = utime_t();      // do not update mtime
-    ctx->new_snapset = p.second;
-    if (!ctx->new_obs.exists) {
-      dout(20) << __func__ << "   making " << p.first << " a whiteout" << dendl;
-      ctx->new_obs.exists = true;
-      ctx->new_snapset.head_exists = true;
-      ctx->new_obs.oi.set_flag(object_info_t::FLAG_WHITEOUT);
-      ++ctx->delta_stats.num_whiteouts;
-      ++ctx->delta_stats.num_objects;
-      t->create(p.first);
-      if (p.first < scrubber.start) {
-       dout(20) << __func__ << " kludging around update outside of scrub range"
-                << dendl;
-      } else {
-       scrub_cstat.add(ctx->delta_stats);
-      }
-    }
-    dout(20) << __func__ << "   final snapset " << ctx->new_snapset << dendl;
-    assert(!ctx->new_snapset.is_legacy());
-    finish_ctx(ctx.get(), pg_log_entry_t::MODIFY);
-    ctx->register_on_success(
-      [this]() {
-       dout(20) << "updating snapset" << dendl;
-       if (--scrubber.num_digest_updates_pending == 0) {
-         requeue_scrub();
-       }
-      });
-
-    simple_opc_submit(std::move(ctx));
-    ++scrubber.num_digest_updates_pending;
-  }
-
-  dout(10) << __func__ << " (" << mode << ") finish" << dendl;
-}
-
-void PrimaryLogPG::_scrub_clear_state()
-{
-  scrub_cstat = object_stat_collection_t();
-}
-
-void PrimaryLogPG::_scrub_finish()
-{
-  bool repair = state_test(PG_STATE_REPAIR);
-  bool deep_scrub = state_test(PG_STATE_DEEP_SCRUB);
-  const char *mode = (repair ? "repair": (deep_scrub ? "deep-scrub" : "scrub"));
-
-  if (info.stats.stats_invalid) {
-    info.stats.stats = scrub_cstat;
-    info.stats.stats_invalid = false;
-
-    if (agent_state)
-      agent_choose_mode();
-  }
-
-  dout(10) << mode << " got "
-          << scrub_cstat.sum.num_objects << "/" << info.stats.stats.sum.num_objects << " objects, "
-          << scrub_cstat.sum.num_object_clones << "/" << info.stats.stats.sum.num_object_clones << " clones, "
-          << scrub_cstat.sum.num_objects_dirty << "/" << info.stats.stats.sum.num_objects_dirty << " dirty, "
-          << scrub_cstat.sum.num_objects_omap << "/" << info.stats.stats.sum.num_objects_omap << " omap, "
-          << scrub_cstat.sum.num_objects_pinned << "/" << info.stats.stats.sum.num_objects_pinned << " pinned, "
-          << scrub_cstat.sum.num_objects_hit_set_archive << "/" << info.stats.stats.sum.num_objects_hit_set_archive << " hit_set_archive, "
-          << scrub_cstat.sum.num_bytes << "/" << info.stats.stats.sum.num_bytes << " bytes, "
-          << scrub_cstat.sum.num_bytes_hit_set_archive << "/" << info.stats.stats.sum.num_bytes_hit_set_archive << " hit_set_archive bytes."
-          << dendl;
-
-  if (scrub_cstat.sum.num_objects != info.stats.stats.sum.num_objects ||
-      scrub_cstat.sum.num_object_clones != info.stats.stats.sum.num_object_clones ||
-      (scrub_cstat.sum.num_objects_dirty != info.stats.stats.sum.num_objects_dirty &&
-       !info.stats.dirty_stats_invalid) ||
-      (scrub_cstat.sum.num_objects_omap != info.stats.stats.sum.num_objects_omap &&
-       !info.stats.omap_stats_invalid) ||
-      (scrub_cstat.sum.num_objects_pinned != info.stats.stats.sum.num_objects_pinned &&
-       !info.stats.pin_stats_invalid) ||
-      (scrub_cstat.sum.num_objects_hit_set_archive != info.stats.stats.sum.num_objects_hit_set_archive &&
-       !info.stats.hitset_stats_invalid) ||
-      (scrub_cstat.sum.num_bytes_hit_set_archive != info.stats.stats.sum.num_bytes_hit_set_archive &&
-       !info.stats.hitset_bytes_stats_invalid) ||
-      scrub_cstat.sum.num_whiteouts != info.stats.stats.sum.num_whiteouts ||
-      scrub_cstat.sum.num_bytes != info.stats.stats.sum.num_bytes) {
-    osd->clog->error() << info.pgid << " " << mode
-                     << " stat mismatch, got "
-                     << scrub_cstat.sum.num_objects << "/" << info.stats.stats.sum.num_objects << " objects, "
-                     << scrub_cstat.sum.num_object_clones << "/" << info.stats.stats.sum.num_object_clones << " clones, "
-                     << scrub_cstat.sum.num_objects_dirty << "/" << info.stats.stats.sum.num_objects_dirty << " dirty, "
-                     << scrub_cstat.sum.num_objects_omap << "/" << info.stats.stats.sum.num_objects_omap << " omap, "
-                     << scrub_cstat.sum.num_objects_pinned << "/" << info.stats.stats.sum.num_objects_pinned << " pinned, "
-                     << scrub_cstat.sum.num_objects_hit_set_archive << "/" << info.stats.stats.sum.num_objects_hit_set_archive << " hit_set_archive, "
-                     << scrub_cstat.sum.num_whiteouts << "/" << info.stats.stats.sum.num_whiteouts << " whiteouts, "
-                     << scrub_cstat.sum.num_bytes << "/" << info.stats.stats.sum.num_bytes << " bytes, "
-                     << scrub_cstat.sum.num_bytes_hit_set_archive << "/" << info.stats.stats.sum.num_bytes_hit_set_archive << " hit_set_archive bytes.";
-    ++scrubber.shallow_errors;
-
-    if (repair) {
-      ++scrubber.fixed;
-      info.stats.stats = scrub_cstat;
-      info.stats.dirty_stats_invalid = false;
-      info.stats.omap_stats_invalid = false;
-      info.stats.hitset_stats_invalid = false;
-      info.stats.hitset_bytes_stats_invalid = false;
-      publish_stats_to_osd();
-      share_pg_info();
-    }
-  } else if (scrub_cstat.sum.num_legacy_snapsets !=
-            info.stats.stats.sum.num_legacy_snapsets) {
-    osd->clog->info() << info.pgid << " " << mode << " updated num_legacy_snapsets"
-                     << " from " << info.stats.stats.sum.num_legacy_snapsets
-                     << " -> " << scrub_cstat.sum.num_legacy_snapsets << "\n";
-    info.stats.stats.sum.num_legacy_snapsets = scrub_cstat.sum.num_legacy_snapsets;
-    publish_stats_to_osd();
-    share_pg_info();
-  }
-  // Clear object context cache to get repair information
-  if (repair)
-    object_contexts.clear();
-}
-
-bool PrimaryLogPG::check_osdmap_full(const set<pg_shard_t> &missing_on)
-{
-    return osd->check_osdmap_full(missing_on);
-}
-
-int PrimaryLogPG::rep_repair_primary_object(const hobject_t& soid, OpRequestRef op)
-{
-  // Only supports replicated pools
-  assert(!pool.info.require_rollback());
-  assert(is_primary());
-
-  dout(10) << __func__ << " " << soid
-          << " peers osd.{" << actingbackfill << "}" << dendl;
-
-  if (!is_clean()) {
-    block_for_clean(soid, op);
-    return -EAGAIN;
-  }
-
-  assert(!pg_log.get_missing().is_missing(soid));
-  bufferlist bv;
-  object_info_t oi;
-  eversion_t v;
-  int r = get_pgbackend()->objects_get_attr(soid, OI_ATTR, &bv);
-  if (r < 0) {
-    // Leave v and try to repair without a version, getting attr failed
-    dout(0) << __func__ << ": Need version of replica, objects_get_attr failed: "
-           << soid << " error=" << r << dendl;
-  } else try {
-    bufferlist::iterator bliter = bv.begin();
-    ::decode(oi, bliter);
-    v = oi.version;
-  } catch (...) {
-    // Leave v as default constructed. This will fail when sent to older OSDs, but
-    // not much worse than failing here.
-    dout(0) << __func__ << ": Need version of replica, bad object_info_t: " << soid << dendl;
-  }
-
-  missing_loc.add_missing(soid, v, eversion_t());
-  if (primary_error(soid, v)) {
-    dout(0) << __func__ << " No other replicas available for " << soid << dendl;
-    // XXX: If we knew that there is no down osd which could include this
-    // object, it would be nice if we could return EIO here.
-    // If a "never fail" flag was available, that could be used
-    // for rbd to NOT return EIO until object marked lost.
-
-    // Drop through to save this op in case an osd comes up with the object.
-  }
-
-  // Restart the op after object becomes readable again
-  waiting_for_unreadable_object[soid].push_back(op);
-  op->mark_delayed("waiting for missing object");
-
-  if (!eio_errors_to_process) {
-    eio_errors_to_process = true;
-    assert(is_clean());
-    queue_peering_event(
-        CephPeeringEvtRef(
-         std::make_shared<CephPeeringEvt>(
-         get_osdmap()->get_epoch(),
-         get_osdmap()->get_epoch(),
-         DoRecovery())));
-  } else {
-    // A prior error must have already cleared clean state and queued recovery
-    // or a map change has triggered re-peering.
-    // Not inlining the recovery by calling maybe_kick_recovery(soid);
-    dout(5) << __func__<< ": Read error on " << soid << ", but already seen errors" << dendl;
-  }
-
-  return -EAGAIN;
-}
-
-/*---SnapTrimmer Logging---*/
-#undef dout_prefix
-#define dout_prefix *_dout << pg->gen_prefix() 
-
-void PrimaryLogPG::SnapTrimmer::log_enter(const char *state_name)
-{
-  ldout(pg->cct, 20) << "enter " << state_name << dendl;
-}
-
-void PrimaryLogPG::SnapTrimmer::log_exit(const char *state_name, utime_t enter_time)
-{
-  ldout(pg->cct, 20) << "exit " << state_name << dendl;
-}
-
-/*---SnapTrimmer states---*/
-#undef dout_prefix
-#define dout_prefix (*_dout << context< SnapTrimmer >().pg->gen_prefix() \
-                    << "SnapTrimmer state<" << get_state_name() << ">: ")
-
-/* NotTrimming */
-PrimaryLogPG::NotTrimming::NotTrimming(my_context ctx)
-  : my_base(ctx), 
-    NamedState(context< SnapTrimmer >().pg, "NotTrimming")
-{
-  context< SnapTrimmer >().log_enter(state_name);
-}
-
-void PrimaryLogPG::NotTrimming::exit()
-{
-  context< SnapTrimmer >().log_exit(state_name, enter_time);
-}
-
-boost::statechart::result PrimaryLogPG::NotTrimming::react(const KickTrim&)
-{
-  PrimaryLogPG *pg = context< SnapTrimmer >().pg;
-  ldout(pg->cct, 10) << "NotTrimming react KickTrim" << dendl;
-
-  if (!(pg->is_primary() && pg->is_active())) {
-    ldout(pg->cct, 10) << "NotTrimming not primary or active" << dendl;
-    return discard_event();
-  }
-  if (!pg->is_clean() ||
-      pg->snap_trimq.empty()) {
-    ldout(pg->cct, 10) << "NotTrimming not clean or nothing to trim" << dendl;
-    return discard_event();
-  }
-  if (pg->scrubber.active) {
-    ldout(pg->cct, 10) << " scrubbing, will requeue snap_trimmer after" << dendl;
-    return transit< WaitScrub >();
-  } else {
-    return transit< Trimming >();
-  }
-}
-
-boost::statechart::result PrimaryLogPG::WaitReservation::react(const SnapTrimReserved&)
-{
-  PrimaryLogPG *pg = context< SnapTrimmer >().pg;
-  ldout(pg->cct, 10) << "WaitReservation react SnapTrimReserved" << dendl;
-
-  pending = nullptr;
-  if (!context< SnapTrimmer >().can_trim()) {
-    post_event(KickTrim());
-    return transit< NotTrimming >();
-  }
-
-  context<Trimming>().snap_to_trim = pg->snap_trimq.range_start();
-  ldout(pg->cct, 10) << "NotTrimming: trimming "
-                    << pg->snap_trimq.range_start()
-                    << dendl;
-  return transit< AwaitAsyncWork >();
-}
-
-/* AwaitAsyncWork */
-PrimaryLogPG::AwaitAsyncWork::AwaitAsyncWork(my_context ctx)
-  : my_base(ctx),
-    NamedState(context< SnapTrimmer >().pg, "Trimming/AwaitAsyncWork")
-{
-  auto *pg = context< SnapTrimmer >().pg;
-  context< SnapTrimmer >().log_enter(state_name);
-  context< SnapTrimmer >().pg->osd->queue_for_snap_trim(pg);
-  pg->state_set(PG_STATE_SNAPTRIM);
-  pg->state_clear(PG_STATE_SNAPTRIM_ERROR);
-  pg->publish_stats_to_osd();
-}
-
-boost::statechart::result PrimaryLogPG::AwaitAsyncWork::react(const DoSnapWork&)
-{
-  PrimaryLogPGRef pg = context< SnapTrimmer >().pg;
-  snapid_t snap_to_trim = context<Trimming>().snap_to_trim;
-  auto &in_flight = context<Trimming>().in_flight;
-  assert(in_flight.empty());
-
-  assert(pg->is_primary() && pg->is_active());
-  if (!context< SnapTrimmer >().can_trim()) {
-    ldout(pg->cct, 10) << "something changed, reverting to NotTrimming" << dendl;
-    post_event(KickTrim());
-    return transit< NotTrimming >();
-  }
-
-  ldout(pg->cct, 10) << "AwaitAsyncWork: trimming snap " << snap_to_trim << dendl;
-
-  vector<hobject_t> to_trim;
-  unsigned max = pg->cct->_conf->osd_pg_max_concurrent_snap_trims;
-  to_trim.reserve(max);
-  int r = pg->snap_mapper.get_next_objects_to_trim(
-    snap_to_trim,
-    max,
-    &to_trim);
-  if (r != 0 && r != -ENOENT) {
-    lderr(pg->cct) << "get_next_objects_to_trim returned "
-                  << cpp_strerror(r) << dendl;
-    assert(0 == "get_next_objects_to_trim returned an invalid code");
-  } else if (r == -ENOENT) {
-    // Done!
-    ldout(pg->cct, 10) << "got ENOENT" << dendl;
-
-    ldout(pg->cct, 10) << "adding snap " << snap_to_trim
-                      << " to purged_snaps"
-                      << dendl;
-    pg->info.purged_snaps.insert(snap_to_trim);
-    pg->snap_trimq.erase(snap_to_trim);
-    ldout(pg->cct, 10) << "purged_snaps now "
-                      << pg->info.purged_snaps << ", snap_trimq now "
-                      << pg->snap_trimq << dendl;
-
-    ObjectStore::Transaction t;
-    pg->dirty_big_info = true;
-    pg->write_if_dirty(t);
-    int tr = pg->osd->store->queue_transaction(pg->osr.get(), std::move(t), NULL);
-    assert(tr == 0);
-
-    pg->share_pg_info();
-    post_event(KickTrim());
-    return transit< NotTrimming >();
-  }
-  assert(!to_trim.empty());
-
-  for (auto &&object: to_trim) {
-    // Get next
-    ldout(pg->cct, 10) << "AwaitAsyncWork react trimming " << object << dendl;
-    OpContextUPtr ctx;
-    int error = pg->trim_object(in_flight.empty(), object, &ctx);
-    if (error) {
-      if (error == -ENOLCK) {
-       ldout(pg->cct, 10) << "could not get write lock on obj "
-                          << object << dendl;
-      } else {
-       pg->state_set(PG_STATE_SNAPTRIM_ERROR);
-       ldout(pg->cct, 10) << "Snaptrim error=" << error << dendl;
-      }
-      if (!in_flight.empty()) {
-       ldout(pg->cct, 10) << "letting the ones we already started finish" << dendl;
-       return transit< WaitRepops >();
-      }
-      if (error == -ENOLCK) {
-       ldout(pg->cct, 10) << "waiting for it to clear"
-                          << dendl;
-       return transit< WaitRWLock >();
-      } else {
-        return transit< NotTrimming >();
-      }
-    }
-
-    in_flight.insert(object);
-    ctx->register_on_success(
-      [pg, object, &in_flight]() {
-       assert(in_flight.find(object) != in_flight.end());
-       in_flight.erase(object);
-       if (in_flight.empty()) {
-         if (pg->state_test(PG_STATE_SNAPTRIM_ERROR)) {
-           pg->snap_trimmer_machine.process_event(Reset());
-         } else {
-           pg->snap_trimmer_machine.process_event(RepopsComplete());
-         }
-       }
-      });
-
-    pg->simple_opc_submit(std::move(ctx));
-  }
-
-  return transit< WaitRepops >();
-}
-
-void PrimaryLogPG::setattr_maybe_cache(
-  ObjectContextRef obc,
-  OpContext *op,
-  PGTransaction *t,
-  const string &key,
-  bufferlist &val)
-{
-  t->setattr(obc->obs.oi.soid, key, val);
-}
-
-void PrimaryLogPG::setattrs_maybe_cache(
-  ObjectContextRef obc,
-  OpContext *op,
-  PGTransaction *t,
-  map<string, bufferlist> &attrs)
-{
-  t->setattrs(obc->obs.oi.soid, attrs);
-}
-
-void PrimaryLogPG::rmattr_maybe_cache(
-  ObjectContextRef obc,
-  OpContext *op,
-  PGTransaction *t,
-  const string &key)
-{
-  t->rmattr(obc->obs.oi.soid, key);
-}
-
-int PrimaryLogPG::getattr_maybe_cache(
-  ObjectContextRef obc,
-  const string &key,
-  bufferlist *val)
-{
-  if (pool.info.require_rollback()) {
-    map<string, bufferlist>::iterator i = obc->attr_cache.find(key);
-    if (i != obc->attr_cache.end()) {
-      if (val)
-       *val = i->second;
-      return 0;
-    } else {
-      return -ENODATA;
-    }
-  }
-  return pgbackend->objects_get_attr(obc->obs.oi.soid, key, val);
-}
-
-int PrimaryLogPG::getattrs_maybe_cache(
-  ObjectContextRef obc,
-  map<string, bufferlist> *out,
-  bool user_only)
-{
-  int r = 0;
-  if (pool.info.require_rollback()) {
-    if (out)
-      *out = obc->attr_cache;
-  } else {
-    r = pgbackend->objects_get_attrs(obc->obs.oi.soid, out);
-  }
-  if (out && user_only) {
-    map<string, bufferlist> tmp;
-    for (map<string, bufferlist>::iterator i = out->begin();
-        i != out->end();
-        ++i) {
-      if (i->first.size() > 1 && i->first[0] == '_')
-       tmp[i->first.substr(1, i->first.size())].claim(i->second);
-    }
-    tmp.swap(*out);
-  }
-  return r;
-}
-
-bool PrimaryLogPG::check_failsafe_full(ostream &ss) {
-    return osd->check_failsafe_full(ss);
-}
-
-void intrusive_ptr_add_ref(PrimaryLogPG *pg) { pg->get("intptr"); }
-void intrusive_ptr_release(PrimaryLogPG *pg) { pg->put("intptr"); }
-
-#ifdef PG_DEBUG_REFS
-uint64_t get_with_id(PrimaryLogPG *pg) { return pg->get_with_id(); }
-void put_with_id(PrimaryLogPG *pg, uint64_t id) { return pg->put_with_id(id); }
-#endif
-
-void intrusive_ptr_add_ref(PrimaryLogPG::RepGather *repop) { repop->get(); }
-void intrusive_ptr_release(PrimaryLogPG::RepGather *repop) { repop->put(); }