]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/osd/PG.h
import quincy beta 17.1.0
[ceph.git] / ceph / src / osd / PG.h
index ef4bf189dff24bb3e103d3cd718161b83643c71f..59469602405707ac79535b0c712a77baea5fa294 100644 (file)
 #ifndef CEPH_PG_H
 #define CEPH_PG_H
 
-#include <boost/statechart/custom_reaction.hpp>
-#include <boost/statechart/event.hpp>
-#include <boost/statechart/simple_state.hpp>
-#include <boost/statechart/state.hpp>
-#include <boost/statechart/state_machine.hpp>
-#include <boost/statechart/transition.hpp>
-#include <boost/statechart/event_base.hpp>
 #include <boost/scoped_ptr.hpp>
-#include <boost/circular_buffer.hpp>
 #include <boost/container/flat_set.hpp>
 #include "include/mempool.h"
 
 // re-include our assert to clobber boost's
 #include "include/ceph_assert.h" 
+#include "include/common_fwd.h"
 
 #include "include/types.h"
 #include "include/stringify.h"
 
 #include "PGLog.h"
 #include "OSDMap.h"
-#include "messages/MOSDPGLog.h"
 #include "include/str_list.h"
 #include "PGBackend.h"
 #include "PGPeeringEvent.h"
+#include "PeeringState.h"
+#include "recovery_types.h"
+#include "MissingLoc.h"
+#include "scrubber_common.h"
 
 #include "mgr/OSDPerfMetricTypes.h"
 
 #include <atomic>
 #include <list>
 #include <memory>
-#include <stack>
 #include <string>
 #include <tuple>
 
-//#define DEBUG_RECOVERY_OIDS   // track set of recovering oids explicitly, to find counting bugs
+//#define DEBUG_RECOVERY_OIDS   // track std::set of recovering oids explicitly, to find counting bugs
 //#define PG_DEBUG_REFS    // track provenance of pg refs, helpful for finding leaks
 
 class OSD;
 class OSDService;
 class OSDShard;
 class OSDShardPGSlot;
-class MOSDOp;
-class MOSDPGScan;
-class MOSDPGBackfill;
-class MOSDPGInfo;
 
 class PG;
 struct OpRequest;
 typedef OpRequest::Ref OpRequestRef;
-class MOSDPGLog;
-class CephContext;
 class DynamicPerfStats;
+class PgScrubber;
 
 namespace Scrub {
   class Store;
+  class ReplicaReservations;
+  class LocalReservation;
+  class ReservedByRemotePrimary;
+  enum class schedule_result_t;
 }
 
-using state_history_entry = std::tuple<utime_t, utime_t, const char*>;
-using embedded_state = std::pair<utime_t, const char*>;
-
-struct PGStateInstance {
-  // Time spent in pg states
-
-  void setepoch(const epoch_t current_epoch) {
-    this_epoch = current_epoch;
-  }
-
-  void enter_state(const utime_t entime, const char* state) {
-    embedded_states.push(std::make_pair(entime, state));
-  }
-
-  void exit_state(const utime_t extime) {
-    embedded_state this_state = embedded_states.top();
-    state_history.push_back(state_history_entry{
-        this_state.first, extime, this_state.second});
-    embedded_states.pop();
-  }
-
-  epoch_t this_epoch;
-  utime_t enter_time;
-  std::vector<state_history_entry> state_history;
-  std::stack<embedded_state> embedded_states;
-};
-
-class PGStateHistory {
-  // Member access protected with the PG lock
-public:
-  PGStateHistory() : buffer(10) {}
-
-  void enter(PG* pg, const utime_t entime, const char* state);
-
-  void exit(const char* state);
-
-  void reset() {
-    pi = nullptr;
-  }
-
-  void set_pg_in_destructor() { pg_in_destructor = true; }
-
-  void dump(Formatter* f) const;
-
-  string get_current_state() {
-    if (pi == nullptr) return "unknown";
-    return std::get<1>(pi->embedded_states.top());
-  }
-
-private:
-  bool pg_in_destructor = false;
-  PG* thispg = nullptr;
-  std::unique_ptr<PGStateInstance> tmppi;
-  PGStateInstance* pi = nullptr;
-  boost::circular_buffer<std::unique_ptr<PGStateInstance>> buffer;
-
-};
-
 #ifdef PG_DEBUG_REFS
 #include "common/tracked_int_ptr.hpp"
   uint64_t get_with_id(PG *pg);
@@ -155,11 +91,11 @@ class PGRecoveryStats {
     // cppcheck-suppress unreachableCode
     per_state_info() : enter(0), exit(0), events(0) {}
   };
-  map<const char *,per_state_info> info;
-  Mutex lock;
+  std::map<const char *,per_state_info> info;
+  ceph::mutex lock = ceph::make_mutex("PGRecoverStats::lock");
 
   public:
-  PGRecoveryStats() : lock("PGRecoverStats::lock") {}
+  PGRecoveryStats() = default;
 
   void reset() {
     std::lock_guard l(lock);
@@ -167,7 +103,7 @@ class PGRecoveryStats {
   }
   void dump(ostream& out) {
     std::lock_guard l(lock);
-    for (map<const char *,per_state_info>::iterator p = info.begin(); p != info.end(); ++p) {
+    for (std::map<const char *,per_state_info>::iterator p = info.begin(); p != info.end(); ++p) {
       per_state_info& i = p->second;
       out << i.enter << "\t" << i.exit << "\t"
          << i.events << "\t" << i.event_time << "\t"
@@ -177,10 +113,10 @@ class PGRecoveryStats {
     }
   }
 
-  void dump_formatted(Formatter *f) {
+  void dump_formatted(ceph::Formatter *f) {
     std::lock_guard l(lock);
     f->open_array_section("pg_recovery_stats");
-    for (map<const char *,per_state_info>::iterator p = info.begin();
+    for (std::map<const char *,per_state_info>::iterator p = info.begin();
         p != info.end(); ++p) {
       per_state_info& i = p->second;
       f->open_object_section("recovery_state");
@@ -191,10 +127,10 @@ class PGRecoveryStats {
       f->dump_stream("total_time") << i.total_time;
       f->dump_stream("min_time") << i.min_time;
       f->dump_stream("max_time") << i.max_time;
-      vector<string> states;
+      std::vector<std::string> states;
       get_str_vec(p->first, "/", states);
       f->open_array_section("nested_states");
-      for (vector<string>::iterator st = states.begin();
+      for (std::vector<std::string>::iterator st = states.begin();
           st != states.end(); ++st) {
        f->dump_string("state", *st);
       }
@@ -222,49 +158,49 @@ class PGRecoveryStats {
   }
 };
 
-struct PGPool {
-  CephContext* cct;
-  epoch_t cached_epoch;
-  int64_t id;
-  string name;
-
-  pg_pool_t info;      
-  SnapContext snapc;   // the default pool snapc, ready to go.
-
-  // these two sets are for < mimic only
-  interval_set<snapid_t> cached_removed_snaps;      // current removed_snaps set
-  interval_set<snapid_t> newly_removed_snaps;  // newly removed in the last epoch
-
-  PGPool(CephContext* cct, OSDMapRef map, int64_t i, const pg_pool_t& info,
-        const string& name)
-    : cct(cct),
-      cached_epoch(map->get_epoch()),
-      id(i),
-      name(name),
-      info(info) {
-    snapc = info.get_snap_context();
-    if (map->require_osd_release < CEPH_RELEASE_MIMIC) {
-      info.build_removed_snaps(cached_removed_snaps);
-    }
-  }
-
-  void update(CephContext *cct, OSDMapRef map);
-};
-
 /** PG - Replica Placement Group
  *
  */
 
-class PG : public DoutPrefixProvider {
+/// Facilitating scrub-realated object access to private PG data
+class ScrubberPasskey {
+private:
+  friend class Scrub::ReplicaReservations;
+  friend class PrimaryLogScrub;
+  ScrubberPasskey() {}
+  ScrubberPasskey(const ScrubberPasskey&) = default;
+  ScrubberPasskey& operator=(const ScrubberPasskey&) = delete;
+};
+
+class PG : public DoutPrefixProvider, public PeeringState::PeeringListener {
+  friend struct NamedState;
+  friend class PeeringState;
+  friend class PgScrubber;
+
 public:
-  // -- members --
+  const pg_shard_t pg_whoami;
   const spg_t pg_id;
+
+  /// the 'scrubber'. Will be allocated in the derivative (PrimaryLogPG) ctor,
+  /// and be removed only in the PrimaryLogPG destructor.
+  std::unique_ptr<ScrubPgIF> m_scrubber;
+
+  /// flags detailing scheduling/operation characteristics of the next scrub 
+  requested_scrub_t m_planned_scrub;
+
+  /// scrubbing state for both Primary & replicas
+  bool is_scrub_active() const { return m_scrubber->is_scrub_active(); }
+
+  /// set when the scrub request is queued, and reset after scrubbing fully
+  /// cleaned up.
+  bool is_scrub_queued_or_active() const { return m_scrubber->is_queued_or_active(); }
+
+public:
+  // -- members --
   const coll_t coll;
 
   ObjectStore::CollectionHandle ch;
 
-  struct RecoveryCtx;
-
   // -- methods --
   std::ostream& gen_prefix(std::ostream& out) const override;
   CephContext *get_cct() const override {
@@ -274,30 +210,29 @@ public:
     return ceph_subsys_osd;
   }
 
+  const char* const get_current_state() const {
+    return recovery_state.get_current_state();
+  }
+
   const OSDMapRef& get_osdmap() const {
     ceph_assert(is_locked());
-    ceph_assert(osdmap_ref);
-    return osdmap_ref;
-  }
-  epoch_t get_osdmap_epoch() const {
-    return osdmap_ref->get_epoch();
+    return recovery_state.get_osdmap();
   }
 
-  void lock_suspend_timeout(ThreadPool::TPHandle &handle) {
-    handle.suspend_tp_timeout();
-    lock();
-    handle.reset_tp_timeout();
+  epoch_t get_osdmap_epoch() const override final {
+    return recovery_state.get_osdmap()->get_epoch();
   }
+
+  PerfCounters &get_peering_perf() override;
+  PerfCounters &get_perf_logger() override;
+  void log_state_enter(const char *state) override;
+  void log_state_exit(
+    const char *state_name, utime_t enter_time,
+    uint64_t events, utime_t event_dur) override;
+
   void lock(bool no_lockdep = false) const;
-  void unlock() const {
-    //generic_dout(0) << this << " " << info.pgid << " unlock" << dendl;
-    ceph_assert(!dirty_info);
-    ceph_assert(!dirty_big_info);
-    _lock.Unlock();
-  }
-  bool is_locked() const {
-    return _lock.is_locked();
-  }
+  void unlock() const;
+  bool is_locked() const;
 
   const spg_t& get_pgid() const {
     return pg_id;
@@ -313,85 +248,145 @@ public:
     return info.history;
   }
   bool get_need_up_thru() const {
-    return need_up_thru;
+    return recovery_state.get_need_up_thru();
   }
   epoch_t get_same_interval_since() const {
     return info.history.same_interval_since;
   }
 
+  static void set_last_scrub_stamp(
+    utime_t t, pg_history_t &history, pg_stat_t &stats) {
+    stats.last_scrub_stamp = t;
+    history.last_scrub_stamp = t;
+  }
+
   void set_last_scrub_stamp(utime_t t) {
-    info.stats.last_scrub_stamp = t;
-    info.history.last_scrub_stamp = t;
+    recovery_state.update_stats(
+      [=](auto &history, auto &stats) {
+       set_last_scrub_stamp(t, history, stats);
+       return true;
+      });
+  }
+
+  static void set_last_deep_scrub_stamp(
+    utime_t t, pg_history_t &history, pg_stat_t &stats) {
+    stats.last_deep_scrub_stamp = t;
+    history.last_deep_scrub_stamp = t;
   }
 
   void set_last_deep_scrub_stamp(utime_t t) {
-    info.stats.last_deep_scrub_stamp = t;
-    info.history.last_deep_scrub_stamp = t;
+    recovery_state.update_stats(
+      [=](auto &history, auto &stats) {
+       set_last_deep_scrub_stamp(t, history, stats);
+       return true;
+      });
+  }
+
+  static void add_objects_scrubbed_count(
+    int64_t count, pg_stat_t &stats) {
+    stats.objects_scrubbed += count;
+  }
+
+  void add_objects_scrubbed_count(int64_t count) {
+    recovery_state.update_stats(
+      [=](auto &history, auto &stats) {
+       add_objects_scrubbed_count(count, stats);
+       return true;
+      });
+  }
+
+  static void reset_objects_scrubbed(pg_stat_t &stats) {
+    stats.objects_scrubbed = 0;
+  }
+
+  void reset_objects_scrubbed() {
+    recovery_state.update_stats(
+      [=](auto &history, auto &stats) {
+  reset_objects_scrubbed(stats);
+  return true;
+      });
   }
 
   bool is_deleting() const {
-    return deleting;
+    return recovery_state.is_deleting();
   }
   bool is_deleted() const {
-    return deleted;
+    return recovery_state.is_deleted();
   }
-  bool is_replica() const {
-    return role > 0;
+  bool is_nonprimary() const {
+    return recovery_state.is_nonprimary();
   }
   bool is_primary() const {
-    return pg_whoami == primary;
+    return recovery_state.is_primary();
   }
   bool pg_has_reset_since(epoch_t e) {
     ceph_assert(is_locked());
-    return deleted || e < get_last_peering_reset();
+    return recovery_state.pg_has_reset_since(e);
   }
 
   bool is_ec_pg() const {
-    return pool.info.is_erasure();
+    return recovery_state.is_ec_pg();
   }
   int get_role() const {
-    return role;
+    return recovery_state.get_role();
   }
-  const vector<int> get_acting() const {
-    return acting;
+  const std::vector<int> get_acting() const {
+    return recovery_state.get_acting();
+  }
+  const std::set<pg_shard_t> &get_actingset() const {
+    return recovery_state.get_actingset();
   }
   int get_acting_primary() const {
-    return primary.osd;
+    return recovery_state.get_acting_primary();
   }
   pg_shard_t get_primary() const {
-    return primary;
+    return recovery_state.get_primary();
   }
-  const vector<int> get_up() const {
-    return up;
+  const std::vector<int> get_up() const {
+    return recovery_state.get_up();
   }
   int get_up_primary() const {
-    return up_primary.osd;
+    return recovery_state.get_up_primary();
   }
   const PastIntervals& get_past_intervals() const {
-    return past_intervals;
+    return recovery_state.get_past_intervals();
+  }
+  bool is_acting_recovery_backfill(pg_shard_t osd) const {
+    return recovery_state.is_acting_recovery_backfill(osd);
+  }
+  const std::set<pg_shard_t> &get_acting_recovery_backfill() const {
+    return recovery_state.get_acting_recovery_backfill();
+  }
+  bool is_acting(pg_shard_t osd) const {
+    return recovery_state.is_acting(osd);
+  }
+  bool is_up(pg_shard_t osd) const {
+    return recovery_state.is_up(osd);
+  }
+  static bool has_shard(bool ec, const std::vector<int>& v, pg_shard_t osd) {
+    return PeeringState::has_shard(ec, v, osd);
   }
 
   /// initialize created PG
   void init(
     int role,
-    const vector<int>& up,
+    const std::vector<int>& up,
     int up_primary,
-    const vector<int>& acting,
+    const std::vector<int>& acting,
     int acting_primary,
     const pg_history_t& history,
     const PastIntervals& pim,
-    bool backfill,
-    ObjectStore::Transaction *t);
+    ObjectStore::Transaction &t);
 
   /// read existing pg state off disk
   void read_state(ObjectStore *store);
   static int peek_map_epoch(ObjectStore *store, spg_t pgid, epoch_t *pepoch);
 
   static int get_latest_struct_v() {
-    return latest_struct_v;
+    return pg_latest_struct_v;
   }
   static int get_compat_struct_v() {
-    return compat_struct_v;
+    return pg_compat_struct_v;
   }
   static int read_info(
     ObjectStore *store, spg_t pgid, const coll_t &coll,
@@ -399,46 +394,251 @@ public:
     __u8 &);
   static bool _has_removal_flag(ObjectStore *store, spg_t pgid);
 
-  void rm_backoff(BackoffRef b);
+  void rm_backoff(const ceph::ref_t<Backoff>& b);
 
   void update_snap_mapper_bits(uint32_t bits) {
     snap_mapper.update_bits(bits);
   }
-  void start_split_stats(const set<spg_t>& childpgs, vector<object_stat_sum_t> *v);
+  void start_split_stats(const std::set<spg_t>& childpgs, std::vector<object_stat_sum_t> *v);
   virtual void split_colls(
     spg_t child,
     int split_bits,
     int seed,
     const pg_pool_t *pool,
-    ObjectStore::Transaction *t) = 0;
+    ObjectStore::Transaction &t) = 0;
   void split_into(pg_t child_pgid, PG *child, unsigned split_bits);
-  void merge_from(map<spg_t,PGRef>& sources, RecoveryCtx *rctx,
+  void merge_from(std::map<spg_t,PGRef>& sources, PeeringCtx &rctx,
                  unsigned split_bits,
                  const pg_merge_meta_t& last_pg_merge_meta);
-  void finish_split_stats(const object_stat_sum_t& stats, ObjectStore::Transaction *t);
+  void finish_split_stats(const object_stat_sum_t& stats,
+                         ObjectStore::Transaction &t);
+
+  void scrub(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    // a new scrub
+    forward_scrub_event(&ScrubPgIF::initiate_regular_scrub, queued, "StartScrub");
+  }
+
+  /**
+   *  a special version of PG::scrub(), which:
+   *  - is initiated after repair, and
+   * (not true anymore:)
+   *  - is not required to allocate local/remote OSD scrub resources
+   */
+  void recovery_scrub(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    // a new scrub
+    forward_scrub_event(&ScrubPgIF::initiate_scrub_after_repair, queued,
+                       "AfterRepairScrub");
+  }
+
+  void replica_scrub(epoch_t queued,
+                    Scrub::act_token_t act_token,
+                    ThreadPool::TPHandle& handle);
+
+  void replica_scrub_resched(epoch_t queued,
+                            Scrub::act_token_t act_token,
+                            ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_sched_replica, queued, act_token,
+                       "SchedReplica");
+  }
+
+  void scrub_send_resources_granted(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_remotes_reserved, queued, "RemotesReserved");
+  }
+
+  void scrub_send_resources_denied(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_reservation_failure, queued,
+                       "ReservationFailure");
+  }
+
+  void scrub_send_scrub_resched(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_scrub_resched, queued, "InternalSchedScrub");
+  }
+
+  void scrub_send_pushes_update(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::active_pushes_notification, queued,
+                       "ActivePushesUpd");
+  }
+
+  void scrub_send_applied_update(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::update_applied_notification, queued,
+                       "UpdatesApplied");
+  }
+
+  void scrub_send_unblocking(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_scrub_unblock, queued, "Unblocked");
+  }
+
+  void scrub_send_digest_update(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::digest_update_notification, queued, "DigestUpdate");
+  }
+
+  void scrub_send_local_map_ready(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_local_map_done, queued, "IntLocalMapDone");
+  }
+
+  void scrub_send_replmaps_ready(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_replica_maps_ready, queued, "GotReplicas");
+  }
+
+  void scrub_send_replica_pushes(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_replica_pushes_upd, queued,
+                       "ReplicaPushesUpd");
+  }
+
+  void scrub_send_maps_compared(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_maps_compared, queued, "MapsCompared");
+  }
+
+  void scrub_send_get_next_chunk(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_get_next_chunk, queued, "NextChunk");
+  }
+
+  void scrub_send_scrub_is_finished(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_scrub_is_finished, queued, "ScrubFinished");
+  }
+
+  void scrub_send_chunk_free(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_chunk_free, queued, "SelectedChunkFree");
+  }
+
+  void scrub_send_chunk_busy(epoch_t queued, ThreadPool::TPHandle& handle)
+  {
+    forward_scrub_event(&ScrubPgIF::send_chunk_busy, queued, "ChunkIsBusy");
+  }
+
+  void queue_want_pg_temp(const std::vector<int> &wanted) override;
+  void clear_want_pg_temp() override;
+
+  void on_new_interval() override;
+
+  void on_role_change() override;
+  virtual void plpg_on_role_change() = 0;
+
+  void init_collection_pool_opts();
+  void on_pool_change() override;
+  virtual void plpg_on_pool_change() = 0;
+
+  void on_info_history_change() override;
+
+  void on_primary_status_change(bool was_primary, bool now_primary) override;
+
+  void reschedule_scrub() override;
+
+  void scrub_requested(scrub_level_t scrub_level, scrub_type_t scrub_type) override;
+
+  uint64_t get_snap_trimq_size() const override {
+    return snap_trimq.size();
+  }
+  unsigned get_target_pg_log_entries() const override;
 
-  void scrub(epoch_t queued, ThreadPool::TPHandle &handle);
-  void reg_next_scrub();
-  void unreg_next_scrub();
+  void clear_publish_stats() override;
+  void clear_primary_state() override;
+
+  epoch_t oldest_stored_osdmap() override;
+  OstreamTemp get_clog_error() override;
+  OstreamTemp get_clog_info() override;
+  OstreamTemp get_clog_debug() override;
+
+  void schedule_event_after(
+    PGPeeringEventRef event,
+    float delay) override;
+  void request_local_background_io_reservation(
+    unsigned priority,
+    PGPeeringEventURef on_grant,
+    PGPeeringEventURef on_preempt) override;
+  void update_local_background_io_priority(
+    unsigned priority) override;
+  void cancel_local_background_io_reservation() override;
+
+  void request_remote_recovery_reservation(
+    unsigned priority,
+    PGPeeringEventURef on_grant,
+    PGPeeringEventURef on_preempt) override;
+  void cancel_remote_recovery_reservation() override;
+
+  void schedule_event_on_commit(
+    ObjectStore::Transaction &t,
+    PGPeeringEventRef on_commit) override;
+
+  void on_active_exit() override;
+
+  Context *on_clean() override {
+    if (is_active()) {
+      kick_snap_trim();
+    }
+    requeue_ops(waiting_for_clean_to_primary_repair);
+    return finish_recovery();
+  }
+
+  void on_activate(interval_set<snapid_t> snaps) override;
+
+  void on_activate_committed() override;
+
+  void on_active_actmap() override;
+  void on_active_advmap(const OSDMapRef &osdmap) override;
+
+  void queue_snap_retrim(snapid_t snap);
+
+  void on_backfill_reserved() override;
+  void on_backfill_canceled() override;
+  void on_recovery_reserved() override;
 
   bool is_forced_recovery_or_backfill() const {
-    return get_state() & (PG_STATE_FORCED_RECOVERY | PG_STATE_FORCED_BACKFILL);
+    return recovery_state.is_forced_recovery_or_backfill();
+  }
+
+  PGLog::LogEntryHandlerRef get_log_handler(
+    ObjectStore::Transaction &t) override {
+    return std::make_unique<PG::PGLogEntryHandler>(this, &t);
   }
-  bool set_force_recovery(bool b);
-  bool set_force_backfill(bool b);
+
+  std::pair<ghobject_t, bool> do_delete_work(ObjectStore::Transaction &t,
+    ghobject_t _next) override;
+
+  void clear_ready_to_merge() override;
+  void set_not_ready_to_merge_target(pg_t pgid, pg_t src) override;
+  void set_not_ready_to_merge_source(pg_t pgid) override;
+  void set_ready_to_merge_target(eversion_t lu, epoch_t les, epoch_t lec) override;
+  void set_ready_to_merge_source(eversion_t lu) override;
+
+  void send_pg_created(pg_t pgid) override;
+
+  ceph::signedspan get_mnow() override;
+  HeartbeatStampsRef get_hb_stamps(int peer) override;
+  void schedule_renew_lease(epoch_t lpr, ceph::timespan delay) override;
+  void queue_check_readable(epoch_t lpr, ceph::timespan delay) override;
+
+  void rebuild_missing_set_with_deletes(PGLog &pglog) override;
 
   void queue_peering_event(PGPeeringEventRef evt);
-  void do_peering_event(PGPeeringEventRef evt, RecoveryCtx *rcx);
+  void do_peering_event(PGPeeringEventRef evt, PeeringCtx &rcx);
   void queue_null(epoch_t msg_epoch, epoch_t query_epoch);
   void queue_flushed(epoch_t started_at);
   void handle_advance_map(
     OSDMapRef osdmap, OSDMapRef lastmap,
-    vector<int>& newup, int up_primary,
-    vector<int>& newacting, int acting_primary,
-    RecoveryCtx *rctx);
-  void handle_activate_map(RecoveryCtx *rctx);
-  void handle_initialize(RecoveryCtx *rctx);
-  void handle_query_state(Formatter *f);
+    std::vector<int>& newup, int up_primary,
+    std::vector<int>& newacting, int acting_primary,
+    PeeringCtx &rctx);
+  void handle_activate_map(PeeringCtx &rctx);
+  void handle_initialize(PeeringCtx &rxcx);
+  void handle_query_state(ceph::Formatter *f);
 
   /**
    * @param ops_begun returns how many recovery ops the function started
@@ -449,25 +649,61 @@ public:
     ThreadPool::TPHandle &handle,
     uint64_t *ops_begun) = 0;
 
-  // more work after the above, but with a RecoveryCtx
-  void find_unfound(epoch_t queued, RecoveryCtx *rctx);
+  // more work after the above, but with a PeeringCtx
+  void find_unfound(epoch_t queued, PeeringCtx &rctx);
 
   virtual void get_watchers(std::list<obj_watch_item_t> *ls) = 0;
 
-  void dump_pgstate_history(Formatter *f);
-  void dump_missing(Formatter *f);
+  void dump_pgstate_history(ceph::Formatter *f);
+  void dump_missing(ceph::Formatter *f);
 
-  void get_pg_stats(std::function<void(const pg_stat_t&, epoch_t lec)> f);
-  void with_heartbeat_peers(std::function<void(int)> f);
+  void with_pg_stats(std::function<void(const pg_stat_t&, epoch_t lec)>&& f);
+  void with_heartbeat_peers(std::function<void(int)>&& f);
 
   void shutdown();
   virtual void on_shutdown() = 0;
 
-  bool get_must_scrub() const {
-    return scrubber.must_scrub;
-  }
-  bool sched_scrub();
+  bool get_must_scrub() const;
+  Scrub::schedule_result_t sched_scrub();
 
+  unsigned int scrub_requeue_priority(Scrub::scrub_prio_t with_priority, unsigned int suggested_priority) const;
+  /// the version that refers to flags_.priority
+  unsigned int scrub_requeue_priority(Scrub::scrub_prio_t with_priority) const;
+private:
+  // auxiliaries used by sched_scrub():
+  double next_deepscrub_interval() const;
+
+  /// should we perform deep scrub?
+  bool is_time_for_deep(bool allow_deep_scrub,
+                       bool allow_scrub,
+                       bool has_deep_errors,
+                       const requested_scrub_t& planned) const;
+
+  /**
+   * Verify the various 'next scrub' flags in m_planned_scrub against configuration
+   * and scrub-related timestamps.
+   *
+   * @returns an updated copy of the m_planned_flags (or nothing if no scrubbing)
+   */
+  std::optional<requested_scrub_t> verify_scrub_mode() const;
+
+  bool verify_periodic_scrub_mode(bool allow_deep_scrub,
+                                 bool try_to_auto_repair,
+                                 bool allow_regular_scrub,
+                                 bool has_deep_errors,
+                                 requested_scrub_t& planned) const;
+
+  using ScrubAPI = void (ScrubPgIF::*)(epoch_t epoch_queued);
+  void forward_scrub_event(ScrubAPI fn, epoch_t epoch_queued, std::string_view desc);
+  // and for events that carry a meaningful 'activation token'
+  using ScrubSafeAPI = void (ScrubPgIF::*)(epoch_t epoch_queued,
+                                          Scrub::act_token_t act_token);
+  void forward_scrub_event(ScrubSafeAPI fn,
+                          epoch_t epoch_queued,
+                          Scrub::act_token_t act_token,
+                          std::string_view desc);
+
+public:
   virtual void do_request(
     OpRequestRef& op,
     ThreadPool::TPHandle &handle
@@ -476,13 +712,11 @@ public:
   virtual int get_cache_obj_count() = 0;
 
   virtual void snap_trimmer(epoch_t epoch_queued) = 0;
-  virtual int do_command(
-    cmdmap_t cmdmap,
-    ostream& ss,
-    bufferlist& idata,
-    bufferlist& odata,
-    ConnectionRef conn,
-    ceph_tid_t tid) = 0;
+  virtual void do_command(
+    const std::string_view& prefix,
+    const cmdmap_t& cmdmap,
+    const ceph::buffer::list& idata,
+    std::function<void(int,const std::string&,ceph::buffer::list&)> on_finish) = 0;
 
   virtual bool agent_work(int max) = 0;
   virtual bool agent_work(int max, int agent_flush_quota) = 0;
@@ -491,9 +725,15 @@ public:
   virtual void agent_clear() = 0;
   virtual void agent_choose_mode_restart() = 0;
 
-  virtual void on_removal(ObjectStore::Transaction *t) = 0;
-
-  void _delete_some(ObjectStore::Transaction *t);
+  struct C_DeleteMore : public Context {
+    PGRef pg;
+    epoch_t epoch;
+    C_DeleteMore(PG *p, epoch_t e) : pg(p), epoch(e) {}
+    void finish(int r) override {
+      ceph_abort();
+    }
+    void complete(int r) override;
+  };
 
   virtual void set_dynamic_perf_stats_queries(
     const std::list<OSDPerfMetricQuery> &queries) {
@@ -501,6 +741,8 @@ public:
   virtual void get_dynamic_perf_stats(DynamicPerfStats *stats) {
   }
 
+  uint64_t get_min_alloc_size() const;
+
   // reference counting
 #ifdef PG_DEBUG_REFS
   uint64_t get_with_id();
@@ -532,25 +774,22 @@ public:
 protected:
   CephContext *cct;
 
-  // osdmap
-  OSDMapRef osdmap_ref;
-
-  PGPool pool;
-
   // locking and reference counting.
   // I destroy myself when the reference count hits zero.
   // lock() should be called before doing anything.
   // get() should be called on pointer copy (to another thread, etc.).
   // put() should be called on destruction of some previously copied pointer.
   // unlock() when done with the current pointer (_most common_).
-  mutable Mutex _lock = {"PG::_lock"};
-
+  mutable ceph::mutex _lock = ceph::make_mutex("PG::_lock");
+#ifndef CEPH_DEBUG_MUTEX
+  mutable std::thread::id locked_by;
+#endif
   std::atomic<unsigned int> ref{0};
 
 #ifdef PG_DEBUG_REFS
-  Mutex _ref_id_lock = {"PG::_ref_id_lock"};
-  map<uint64_t, string> _live_ids;
-  map<string, uint64_t> _tag_counts;
+  ceph::mutex _ref_id_lock = ceph::make_mutex("PG::_ref_id_lock");
+  std::map<uint64_t, std::string> _live_ids;
+  std::map<std::string, uint64_t> _tag_counts;
   uint64_t _ref_id = 0;
 
   friend uint64_t get_with_id(PG *pg) { return pg->get_with_id(); }
@@ -571,639 +810,65 @@ private:
 protected:
   OSDriver osdriver;
   SnapMapper snap_mapper;
-  bool eio_errors_to_process = false;
 
   virtual PGBackend *get_pgbackend() = 0;
   virtual const PGBackend* get_pgbackend() const = 0;
 
 protected:
-  /*** PG ****/
-  /// get_is_recoverable_predicate: caller owns returned pointer and must delete when done
-  IsPGRecoverablePredicate *get_is_recoverable_predicate() const {
-    return get_pgbackend()->get_is_recoverable_predicate();
-  }
-protected:
-  epoch_t last_persisted_osdmap;
-
   void requeue_map_waiters();
 
-  void update_osdmap_ref(OSDMapRef newmap) {
-    ceph_assert(_lock.is_locked_by_me());
-    osdmap_ref = std::move(newmap);
-  }
-
 protected:
 
-
-  bool deleting;  // true while in removing or OSD is shutting down
-  atomic<bool> deleted = {false};
-
   ZTracer::Endpoint trace_endpoint;
 
 
 protected:
-  bool dirty_info, dirty_big_info;
-
-protected:
-  // pg state
-  pg_info_t info;               ///< current pg info
-  pg_info_t last_written_info;  ///< last written info
   __u8 info_struct_v = 0;
-  static const __u8 latest_struct_v = 10;
-  // v10 is the new past_intervals encoding
-  // v9 was fastinfo_key addition
-  // v8 was the move to a per-pg pgmeta object
-  // v7 was SnapMapper addition in 86658392516d5175b2756659ef7ffaaf95b0f8ad
-  // (first appeared in cuttlefish).
-  static const __u8 compat_struct_v = 10;
   void upgrade(ObjectStore *store);
 
 protected:
-  PGLog  pg_log;
   ghobject_t    pgmeta_oid;
 
   // ------------------
-  // MissingLoc
-  
-  class MissingLoc {
-  public:
-    // a loc_count indicates how many locations we know in each of
-    // these distinct sets
-    struct loc_count_t {
-      int up = 0;        //< up
-      int other = 0;    //< other
-
-      friend bool operator<(const loc_count_t& l,
-                           const loc_count_t& r) {
-       return (l.up < r.up ||
-               (l.up == r.up &&
-                  (l.other < r.other)));
-      }
-      friend ostream& operator<<(ostream& out, const loc_count_t& l) {
-       ceph_assert(l.up >= 0);
-       ceph_assert(l.other >= 0);
-       return out << "(" << l.up << "+" << l.other << ")";
-      }
-    };
-
-
-  private:
-
-    loc_count_t _get_count(const set<pg_shard_t>& shards) {
-      loc_count_t r;
-      for (auto s : shards) {
-        if (pg->upset.count(s)) {
-         r.up++;
-       } else {
-         r.other++;
-       }
-      }
-      return r;
-    }
-
-    map<hobject_t, pg_missing_item> needs_recovery_map;
-    map<hobject_t, set<pg_shard_t> > missing_loc;
-    set<pg_shard_t> missing_loc_sources;
-
-    // for every entry in missing_loc, we count how many of each type of shard we have,
-    // and maintain totals here.  The sum of the values for this map will always equal
-    // missing_loc.size().
-    map < shard_id_t, map<loc_count_t,int> > missing_by_count;
-
-   void pgs_by_shard_id(const set<pg_shard_t>& s, map< shard_id_t, set<pg_shard_t> >& pgsbs) {
-      if (pg->get_osdmap()->pg_is_ec(pg->info.pgid.pgid)) {
-        int num_shards = pg->get_osdmap()->get_pg_size(pg->info.pgid.pgid);
-        // For completely missing shards initialize with empty set<pg_shard_t>
-       for (int i = 0 ; i < num_shards ; ++i) {
-         shard_id_t shard(i);
-         pgsbs[shard];
-       }
-       for (auto pgs: s)
-         pgsbs[pgs.shard].insert(pgs);
-      } else {
-        pgsbs[shard_id_t::NO_SHARD] = s;
-      }
-    }
-
-    void _inc_count(const set<pg_shard_t>& s) {
-      map< shard_id_t, set<pg_shard_t> > pgsbs;
-      pgs_by_shard_id(s, pgsbs);
-      for (auto shard: pgsbs)
-        ++missing_by_count[shard.first][_get_count(shard.second)];
-    }
-    void _dec_count(const set<pg_shard_t>& s) {
-      map< shard_id_t, set<pg_shard_t> > pgsbs;
-      pgs_by_shard_id(s, pgsbs);
-      for (auto shard: pgsbs) {
-        auto p = missing_by_count[shard.first].find(_get_count(shard.second));
-        ceph_assert(p != missing_by_count[shard.first].end());
-        if (--p->second == 0) {
-         missing_by_count[shard.first].erase(p);
-        }
-      }
-    }
-
-    PG *pg;
-    set<pg_shard_t> empty_set;
-  public:
-    boost::scoped_ptr<IsPGReadablePredicate> is_readable;
-    boost::scoped_ptr<IsPGRecoverablePredicate> is_recoverable;
-    explicit MissingLoc(PG *pg)
-      : pg(pg) { }
-    void set_backend_predicates(
-      IsPGReadablePredicate *_is_readable,
-      IsPGRecoverablePredicate *_is_recoverable) {
-      is_readable.reset(_is_readable);
-      is_recoverable.reset(_is_recoverable);
-    }
-    std::ostream& gen_prefix(std::ostream& out) const {
-      return pg->gen_prefix(out);
-    }
-    bool needs_recovery(
-      const hobject_t &hoid,
-      eversion_t *v = 0) const {
-      map<hobject_t, pg_missing_item>::const_iterator i =
-       needs_recovery_map.find(hoid);
-      if (i == needs_recovery_map.end())
-       return false;
-      if (v)
-       *v = i->second.need;
-      return true;
-    }
-    bool is_deleted(const hobject_t &hoid) const {
-      auto i = needs_recovery_map.find(hoid);
-      if (i == needs_recovery_map.end())
-       return false;
-      return i->second.is_delete();
-    }
-    bool is_unfound(const hobject_t &hoid) const {
-      auto it = needs_recovery_map.find(hoid);
-      if (it == needs_recovery_map.end()) {
-        return false;
-      }
-      if (it->second.is_delete()) {
-        return false;
-      }
-      auto mit = missing_loc.find(hoid);
-      return mit == missing_loc.end() || !(*is_recoverable)(mit->second);
-    }
-    bool readable_with_acting(
-      const hobject_t &hoid,
-      const set<pg_shard_t> &acting) const;
-    uint64_t num_unfound() const {
-      uint64_t ret = 0;
-      for (map<hobject_t, pg_missing_item>::const_iterator i =
-            needs_recovery_map.begin();
-          i != needs_recovery_map.end();
-          ++i) {
-       if (i->second.is_delete())
-         continue;
-       auto mi = missing_loc.find(i->first);
-       if (mi == missing_loc.end() || !(*is_recoverable)(mi->second))
-         ++ret;
-      }
-      return ret;
-    }
-
-    bool have_unfound() const {
-      for (map<hobject_t, pg_missing_item>::const_iterator i =
-            needs_recovery_map.begin();
-          i != needs_recovery_map.end();
-          ++i) {
-        if (i->second.is_delete())
-          continue;
-        auto mi = missing_loc.find(i->first);
-        if (mi == missing_loc.end() || !(*is_recoverable)(mi->second))
-         return true;
-      }
-      return false;
-    }
-    void clear() {
-      needs_recovery_map.clear();
-      missing_loc.clear();
-      missing_loc_sources.clear();
-      missing_by_count.clear();
-    }
-
-    void add_location(const hobject_t &hoid, pg_shard_t location) {
-      auto p = missing_loc.find(hoid);
-      if (p == missing_loc.end()) {
-       p = missing_loc.emplace(hoid, set<pg_shard_t>()).first;
-      } else {
-       _dec_count(p->second);
-      }
-      p->second.insert(location);
-      _inc_count(p->second);
-    }
-    void remove_location(const hobject_t &hoid, pg_shard_t location) {
-      auto p = missing_loc.find(hoid);
-      if (p != missing_loc.end()) {
-       _dec_count(p->second);
-       p->second.erase(location);
-        if (p->second.empty()) {
-          missing_loc.erase(p);
-        } else {
-          _inc_count(p->second);
-        }
-      }
-    }
-
-    void clear_location(const hobject_t &hoid) {
-      auto p = missing_loc.find(hoid);
-      if (p != missing_loc.end()) {
-       _dec_count(p->second);
-        missing_loc.erase(p);
-      }
-    }
-
-    void add_active_missing(const pg_missing_t &missing) {
-      for (map<hobject_t, pg_missing_item>::const_iterator i =
-            missing.get_items().begin();
-          i != missing.get_items().end();
-          ++i) {
-       map<hobject_t, pg_missing_item>::const_iterator j =
-         needs_recovery_map.find(i->first);
-       if (j == needs_recovery_map.end()) {
-         needs_recovery_map.insert(*i);
-       } else {
-         lgeneric_dout(pg->cct, 0) << this << " " << pg->info.pgid << " unexpected need for "
-                                   << i->first << " have " << j->second
-                                   << " tried to add " << i->second << dendl;
-         ceph_assert(i->second.need == j->second.need);
-       }
-      }
-    }
-
-    void add_missing(const hobject_t &hoid, eversion_t need, eversion_t have, bool is_delete=false) {
-      needs_recovery_map[hoid] = pg_missing_item(need, have, is_delete);
-    }
-    void revise_need(const hobject_t &hoid, eversion_t need) {
-      auto it = needs_recovery_map.find(hoid);
-      ceph_assert(it != needs_recovery_map.end());
-      it->second.need = need;
-    }
-
-    /// Adds info about a possible recovery source
-    bool add_source_info(
-      pg_shard_t source,           ///< [in] source
-      const pg_info_t &oinfo,      ///< [in] info
-      const pg_missing_t &omissing, ///< [in] (optional) missing
-      ThreadPool::TPHandle* handle  ///< [in] ThreadPool handle
-      ); ///< @return whether a new object location was discovered
-
-    /// Adds recovery sources in batch
-    void add_batch_sources_info(
-      const set<pg_shard_t> &sources,  ///< [in] a set of resources which can be used for all objects
-      ThreadPool::TPHandle* handle  ///< [in] ThreadPool handle
-      );
-
-    /// Uses osdmap to update structures for now down sources
-    void check_recovery_sources(const OSDMapRef& osdmap);
-
-    /// Call when hoid is no longer missing in acting set
-    void recovered(const hobject_t &hoid) {
-      needs_recovery_map.erase(hoid);
-      auto p = missing_loc.find(hoid);
-      if (p != missing_loc.end()) {
-       _dec_count(p->second);
-       missing_loc.erase(p);
-      }
-    }
-
-    /// Call to update structures for hoid after a change
-    void rebuild(
-      const hobject_t &hoid,
-      pg_shard_t self,
-      const set<pg_shard_t> to_recover,
-      const pg_info_t &info,
-      const pg_missing_t &missing,
-      const map<pg_shard_t, pg_missing_t> &pmissing,
-      const map<pg_shard_t, pg_info_t> &pinfo) {
-      recovered(hoid);
-      boost::optional<pg_missing_item> item;
-      auto miter = missing.get_items().find(hoid);
-      if (miter != missing.get_items().end()) {
-       item = miter->second;
-      } else {
-       for (auto &&i: to_recover) {
-         if (i == self)
-           continue;
-         auto pmiter = pmissing.find(i);
-         ceph_assert(pmiter != pmissing.end());
-         miter = pmiter->second.get_items().find(hoid);
-         if (miter != pmiter->second.get_items().end()) {
-           item = miter->second;
-           break;
-         }
-       }
-      }
-      if (!item)
-       return; // recovered!
-
-      needs_recovery_map[hoid] = *item;
-      if (item->is_delete())
-       return;
-      auto mliter =
-       missing_loc.insert(make_pair(hoid, set<pg_shard_t>())).first;
-      ceph_assert(info.last_backfill.is_max());
-      ceph_assert(info.last_update >= item->need);
-      if (!missing.is_missing(hoid))
-       mliter->second.insert(self);
-      for (auto &&i: pmissing) {
-       if (i.first == self)
-         continue;
-       auto pinfoiter = pinfo.find(i.first);
-       ceph_assert(pinfoiter != pinfo.end());
-       if (item->need <= pinfoiter->second.last_update &&
-           hoid <= pinfoiter->second.last_backfill &&
-           !i.second.is_missing(hoid))
-         mliter->second.insert(i.first);
-      }
-      _inc_count(mliter->second);
-    }
-
-    const set<pg_shard_t> &get_locations(const hobject_t &hoid) const {
-      auto it = missing_loc.find(hoid);
-      return it == missing_loc.end() ? empty_set : it->second;
-    }
-    const map<hobject_t, set<pg_shard_t>> &get_missing_locs() const {
-      return missing_loc;
-    }
-    const map<hobject_t, pg_missing_item> &get_needs_recovery() const {
-      return needs_recovery_map;
-    }
-    const map < shard_id_t, map<loc_count_t,int> > &get_missing_by_count() const {
-      return missing_by_count;
-    }
-  } missing_loc;
-  
-  PastIntervals past_intervals;
-
   interval_set<snapid_t> snap_trimq;
+  std::set<snapid_t> snap_trimq_repeat;
 
   /* You should not use these items without taking their respective queue locks
    * (if they have one) */
   xlist<PG*>::item stat_queue_item;
-  bool scrub_queued;
   bool recovery_queued;
 
   int recovery_ops_active;
-  set<pg_shard_t> waiting_on_backfill;
+  std::set<pg_shard_t> waiting_on_backfill;
 #ifdef DEBUG_RECOVERY_OIDS
   multiset<hobject_t> recovering_oids;
 #endif
 
-protected:
-  int         role;    // 0 = primary, 1 = replica, -1=none.
-  uint64_t    state;   // PG_STATE_*
-
-  bool send_notify;    ///< true if we are non-primary and should notify the primary
-
-protected:
-  eversion_t  last_update_ondisk;    // last_update that has committed; ONLY DEFINED WHEN is_active()
-  eversion_t  last_complete_ondisk;  // last_complete that has committed.
-  eversion_t  last_update_applied;
-
-  // entries <= last_rollback_info_trimmed_to_applied have been trimmed
-  eversion_t  last_rollback_info_trimmed_to_applied;
-
-  // primary state
-protected:
-  pg_shard_t primary;
-  pg_shard_t pg_whoami;
-  pg_shard_t up_primary;
-  vector<int> up, acting, want_acting;
-  // acting_recovery_backfill contains shards that are acting,
-  // async recovery targets, or backfill targets.
-  set<pg_shard_t> acting_recovery_backfill, actingset, upset;
-  map<pg_shard_t,eversion_t> peer_last_complete_ondisk;
-  eversion_t  min_last_complete_ondisk;  // up: min over last_complete_ondisk, peer_last_complete_ondisk
-  eversion_t  pg_trim_to;
-
-  set<int> blocked_by; ///< osds we are blocked by (for pg stats)
-
-protected:
-  // [primary only] content recovery state
-  struct BufferedRecoveryMessages {
-    map<int, map<spg_t, pg_query_t> > query_map;
-    map<int, vector<pair<pg_notify_t, PastIntervals> > > info_map;
-    map<int, vector<pair<pg_notify_t, PastIntervals> > > notify_list;
-  };
-
 public:
   bool dne() { return info.dne(); }
-  struct RecoveryCtx {
-    utime_t start_time;
-    map<int, map<spg_t, pg_query_t> > *query_map;
-    map<int, vector<pair<pg_notify_t, PastIntervals> > > *info_map;
-    map<int, vector<pair<pg_notify_t, PastIntervals> > > *notify_list;
-    ObjectStore::Transaction *transaction;
-    ThreadPool::TPHandle* handle;
-    RecoveryCtx(map<int, map<spg_t, pg_query_t> > *query_map,
-               map<int,
-                   vector<pair<pg_notify_t, PastIntervals> > > *info_map,
-               map<int,
-                   vector<pair<pg_notify_t, PastIntervals> > > *notify_list,
-               ObjectStore::Transaction *transaction)
-      : query_map(query_map), info_map(info_map), 
-       notify_list(notify_list),
-       transaction(transaction),
-        handle(NULL) {}
-
-    RecoveryCtx(BufferedRecoveryMessages &buf, RecoveryCtx &rctx)
-      : query_map(&(buf.query_map)),
-       info_map(&(buf.info_map)),
-       notify_list(&(buf.notify_list)),
-       transaction(rctx.transaction),
-        handle(rctx.handle) {}
-
-    void accept_buffered_messages(BufferedRecoveryMessages &m) {
-      ceph_assert(query_map);
-      ceph_assert(info_map);
-      ceph_assert(notify_list);
-      for (map<int, map<spg_t, pg_query_t> >::iterator i = m.query_map.begin();
-          i != m.query_map.end();
-          ++i) {
-       map<spg_t, pg_query_t> &omap = (*query_map)[i->first];
-       for (map<spg_t, pg_query_t>::iterator j = i->second.begin();
-            j != i->second.end();
-            ++j) {
-         omap[j->first] = j->second;
-       }
-      }
-      for (map<int, vector<pair<pg_notify_t, PastIntervals> > >::iterator i
-            = m.info_map.begin();
-          i != m.info_map.end();
-          ++i) {
-       vector<pair<pg_notify_t, PastIntervals> > &ovec =
-         (*info_map)[i->first];
-       ovec.reserve(ovec.size() + i->second.size());
-       ovec.insert(ovec.end(), i->second.begin(), i->second.end());
-      }
-      for (map<int, vector<pair<pg_notify_t, PastIntervals> > >::iterator i
-            = m.notify_list.begin();
-          i != m.notify_list.end();
-          ++i) {
-       vector<pair<pg_notify_t, PastIntervals> > &ovec =
-         (*notify_list)[i->first];
-       ovec.reserve(ovec.size() + i->second.size());
-       ovec.insert(ovec.end(), i->second.begin(), i->second.end());
-      }
-    }
-
-    void send_notify(pg_shard_t to,
-                    const pg_notify_t &info, const PastIntervals &pi) {
-      ceph_assert(notify_list);
-      (*notify_list)[to.osd].push_back(make_pair(info, pi));
-    }
-  };
-protected:
-
-  PGStateHistory pgstate_history;
-
-  struct NamedState {
-    const char *state_name;
-    utime_t enter_time;
-    PG* pg;
-    const char *get_state_name() { return state_name; }
-    NamedState(PG *pg_, const char *state_name_)
-      : state_name(state_name_), enter_time(ceph_clock_now()), pg(pg_) {
-        pg->pgstate_history.enter(pg, enter_time, state_name);
-      }
-    virtual ~NamedState() { pg->pgstate_history.exit(state_name); }
-  };
-
 
+  void send_cluster_message(
+    int osd, MessageRef m, epoch_t epoch, bool share_map_update) override;
 
 protected:
-
-  /*
-   * peer_info    -- projected (updates _before_ replicas ack)
-   * peer_missing -- committed (updates _after_ replicas ack)
-   */
-  
-  bool        need_up_thru;
-  set<pg_shard_t>    stray_set;   // non-acting osds that have PG data.
-  map<pg_shard_t, pg_info_t>    peer_info;   // info from peers (stray or prior)
-  map<pg_shard_t, int64_t>    peer_bytes;   // Peer's num_bytes from peer_info
-  set<pg_shard_t> peer_purged; // peers purged
-  map<pg_shard_t, pg_missing_t> peer_missing;
-  set<pg_shard_t> peer_log_requested;  // logs i've requested (and start stamps)
-  set<pg_shard_t> peer_missing_requested;
-
-  // i deleted these strays; ignore racing PGInfo from them
-  set<pg_shard_t> peer_activated;
-
-  // primary-only, recovery-only state
-  set<pg_shard_t> might_have_unfound;  // These osds might have objects on them
-                                       // which are unfound on the primary
-  epoch_t last_peering_reset;
-
   epoch_t get_last_peering_reset() const {
-    return last_peering_reset;
+    return recovery_state.get_last_peering_reset();
   }
 
   /* heartbeat peers */
-  void set_probe_targets(const set<pg_shard_t> &probe_set);
-  void clear_probe_targets();
-
-  Mutex heartbeat_peer_lock;
-  set<int> heartbeat_peers;
-  set<int> probe_targets;
-
-public:
-  /**
-   * BackfillInterval
-   *
-   * Represents the objects in a range [begin, end)
-   *
-   * Possible states:
-   * 1) begin == end == hobject_t() indicates the the interval is unpopulated
-   * 2) Else, objects contains all objects in [begin, end)
-   */
-  struct BackfillInterval {
-    // info about a backfill interval on a peer
-    eversion_t version; /// version at which the scan occurred
-    map<hobject_t,eversion_t> objects;
-    hobject_t begin;
-    hobject_t end;
-
-    /// clear content
-    void clear() {
-      *this = BackfillInterval();
-    }
-
-    /// clear objects list only
-    void clear_objects() {
-      objects.clear();
-    }
-
-    /// reinstantiate with a new start+end position and sort order
-    void reset(hobject_t start) {
-      clear();
-      begin = end = start;
-    }
+  void set_probe_targets(const std::set<pg_shard_t> &probe_set) override;
+  void clear_probe_targets() override;
 
-    /// true if there are no objects in this interval
-    bool empty() const {
-      return objects.empty();
-    }
-
-    /// true if interval extends to the end of the range
-    bool extends_to_end() const {
-      return end.is_max();
-    }
-
-    /// removes items <= soid and adjusts begin to the first object
-    void trim_to(const hobject_t &soid) {
-      trim();
-      while (!objects.empty() &&
-            objects.begin()->first <= soid) {
-       pop_front();
-      }
-    }
-
-    /// Adjusts begin to the first object
-    void trim() {
-      if (!objects.empty())
-       begin = objects.begin()->first;
-      else
-       begin = end;
-    }
-
-    /// drop first entry, and adjust @begin accordingly
-    void pop_front() {
-      ceph_assert(!objects.empty());
-      objects.erase(objects.begin());
-      trim();
-    }
-
-    /// dump
-    void dump(Formatter *f) const {
-      f->dump_stream("begin") << begin;
-      f->dump_stream("end") << end;
-      f->open_array_section("objects");
-      for (map<hobject_t, eversion_t>::const_iterator i =
-            objects.begin();
-          i != objects.end();
-          ++i) {
-       f->open_object_section("object");
-       f->dump_stream("object") << i->first;
-       f->dump_stream("version") << i->second;
-       f->close_section();
-      }
-      f->close_section();
-    }
-  };
+  ceph::mutex heartbeat_peer_lock =
+    ceph::make_mutex("PG::heartbeat_peer_lock");
+  std::set<int> heartbeat_peers;
+  std::set<int> probe_targets;
 
 protected:
   BackfillInterval backfill_info;
-  map<pg_shard_t, BackfillInterval> peer_backfill_info;
-  bool backfill_reserved;
+  std::map<pg_shard_t, BackfillInterval> peer_backfill_info;
   bool backfill_reserving;
 
-  set<pg_shard_t> backfill_targets,  async_recovery_targets;
-
   // The primary's num_bytes and local num_bytes for this pg, only valid
   // during backfill for non-primary shards.
   // Both of these are adjusted for EC to reflect the on-disk bytes
@@ -1211,10 +876,6 @@ protected:
   std::atomic<int64_t> local_num_bytes = 0;
 
 public:
-  bool is_backfill_targets(pg_shard_t osd) {
-    return backfill_targets.count(osd);
-  }
-
   // Space reserved for backfill is primary_num_bytes - local_num_bytes
   // Don't care that difference itself isn't atomic
   uint64_t get_reserved_num_bytes() {
@@ -1230,8 +891,8 @@ public:
     return primary_num_bytes.load() > 0;
   }
 
-  void set_reserved_num_bytes(int64_t primary, int64_t local);
-  void clear_reserved_num_bytes();
+  bool try_reserve_recovery_space(int64_t primary, int64_t local) override;
+  void unreserve_recovery_space() override;
 
   // If num_bytes are inconsistent and local_num- goes negative
   // it's ok, because it would then be ignored.
@@ -1264,28 +925,36 @@ public:
   // The value of num_bytes could be negative,
   // but we don't let info.stats.stats.sum.num_bytes go negative.
   void add_num_bytes(int64_t num_bytes) {
-    ceph_assert(_lock.is_locked_by_me());
+    ceph_assert(ceph_mutex_is_locked_by_me(_lock));
     if (num_bytes) {
-      info.stats.stats.sum.num_bytes += num_bytes;
-      if (info.stats.stats.sum.num_bytes < 0) {
-        info.stats.stats.sum.num_bytes = 0;
-      }
+      recovery_state.update_stats(
+       [num_bytes](auto &history, auto &stats) {
+         stats.stats.sum.num_bytes += num_bytes;
+         if (stats.stats.sum.num_bytes < 0) {
+           stats.stats.sum.num_bytes = 0;
+         }
+         return false;
+       });
     }
   }
   void sub_num_bytes(int64_t num_bytes) {
-    ceph_assert(_lock.is_locked_by_me());
+    ceph_assert(ceph_mutex_is_locked_by_me(_lock));
     ceph_assert(num_bytes >= 0);
     if (num_bytes) {
-      info.stats.stats.sum.num_bytes -= num_bytes;
-      if (info.stats.stats.sum.num_bytes < 0) {
-        info.stats.stats.sum.num_bytes = 0;
-      }
+      recovery_state.update_stats(
+       [num_bytes](auto &history, auto &stats) {
+         stats.stats.sum.num_bytes -= num_bytes;
+         if (stats.stats.sum.num_bytes < 0) {
+           stats.stats.sum.num_bytes = 0;
+         }
+         return false;
+       });
     }
   }
 
   // Only used in testing so not worried about needing the PG lock here
   int64_t get_stats_num_bytes() {
-    Mutex::Locker l(_lock);
+    std::lock_guard l{_lock};
     int num_bytes = info.stats.stats.sum.num_bytes;
     if (pool.info.is_erasure()) {
       num_bytes /= (int)get_pgbackend()->get_ec_data_chunk_count();
@@ -1322,14 +991,17 @@ protected:
    *  - waiting_for_map
    *    - may start or stop blocking at any time (depending on client epoch)
    *  - waiting_for_peered
-   *    - !is_peered() or flushes_in_progress
+   *    - !is_peered()
    *    - only starts blocking on interval change; never restarts
+   *  - waiting_for_flush
+   *    - flushes_in_progress
+   *    - waiting for final flush during activate
    *  - waiting_for_active
    *    - !is_active()
    *    - only starts blocking on interval change; never restarts
-   *  - waiting_for_flush
-   *    - is_active() and flushes_in_progress
-   *    - waiting for final flush during activate
+   *  - waiting_for_readable
+   *    - now > readable_until
+   *    - unblocks when we get fresh(er) osd_pings
    *  - waiting_for_scrub
    *    - starts and stops blocking for varying intervals during scrub
    *  - waiting_for_unreadable_object
@@ -1350,7 +1022,7 @@ protected:
    *     queues because we assume they cannot apply at that time (this is
    *     probably mostly true).
    *
-   *  3. The requeue_ops helper will push ops onto the waiting_for_map list if
+   *  3. The requeue_ops helper will push ops onto the waiting_for_map std::list if
    *     it is non-empty.
    *
    * These three behaviors are generally sufficient to maintain ordering, with
@@ -1359,141 +1031,63 @@ protected:
    * encounter an unexpected error.  FIXME.
    */
 
-  // pg waiters
-  unsigned flushes_in_progress;
-
   // ops with newer maps than our (or blocked behind them)
   // track these by client, since inter-request ordering doesn't otherwise
   // matter.
-  unordered_map<entity_name_t,list<OpRequestRef>> waiting_for_map;
+  std::unordered_map<entity_name_t,std::list<OpRequestRef>> waiting_for_map;
 
   // ops waiting on peered
-  list<OpRequestRef>            waiting_for_peered;
+  std::list<OpRequestRef>            waiting_for_peered;
+
+  /// ops waiting on readble
+  std::list<OpRequestRef>            waiting_for_readable;
 
   // ops waiting on active (require peered as well)
-  list<OpRequestRef>            waiting_for_active;
-  list<OpRequestRef>            waiting_for_flush;
-  list<OpRequestRef>            waiting_for_scrub;
+  std::list<OpRequestRef>            waiting_for_active;
+  std::list<OpRequestRef>            waiting_for_flush;
+  std::list<OpRequestRef>            waiting_for_scrub;
 
-  list<OpRequestRef>            waiting_for_cache_not_full;
-  list<OpRequestRef>            waiting_for_clean_to_primary_repair;
-  map<hobject_t, list<OpRequestRef>> waiting_for_unreadable_object,
+  std::list<OpRequestRef>            waiting_for_cache_not_full;
+  std::list<OpRequestRef>            waiting_for_clean_to_primary_repair;
+  std::map<hobject_t, std::list<OpRequestRef>> waiting_for_unreadable_object,
                             waiting_for_degraded_object,
                             waiting_for_blocked_object;
 
-  set<hobject_t> objects_blocked_on_cache_full;
-  map<hobject_t,snapid_t> objects_blocked_on_degraded_snap;
-  map<hobject_t,ObjectContextRef> objects_blocked_on_snap_promotion;
+  std::set<hobject_t> objects_blocked_on_cache_full;
+  std::map<hobject_t,snapid_t> objects_blocked_on_degraded_snap;
+  std::map<hobject_t,ObjectContextRef> objects_blocked_on_snap_promotion;
 
   // Callbacks should assume pg (and nothing else) is locked
-  map<hobject_t, list<Context*>> callbacks_for_degraded_object;
+  std::map<hobject_t, std::list<Context*>> callbacks_for_degraded_object;
 
-  map<eversion_t,
-      list<tuple<OpRequestRef, version_t, int> > > waiting_for_ondisk;
+  std::map<eversion_t,
+      std::list<
+       std::tuple<OpRequestRef, version_t, int,
+                  std::vector<pg_log_op_return_item_t>>>> waiting_for_ondisk;
 
-  void requeue_object_waiters(map<hobject_t, list<OpRequestRef>>& m);
+  void requeue_object_waiters(std::map<hobject_t, std::list<OpRequestRef>>& m);
   void requeue_op(OpRequestRef op);
-  void requeue_ops(list<OpRequestRef> &l);
+  void requeue_ops(std::list<OpRequestRef> &l);
 
   // stats that persist lazily
   object_stat_collection_t unstable_stats;
 
   // publish stats
-  Mutex pg_stats_publish_lock;
-  bool pg_stats_publish_valid;
-  pg_stat_t pg_stats_publish;
+  ceph::mutex pg_stats_publish_lock =
+    ceph::make_mutex("PG::pg_stats_publish_lock");
+  std::optional<pg_stat_t> pg_stats_publish;
 
-  void _update_calc_stats();
-  void _update_blocked_by();
   friend class TestOpsSocketHook;
-  void publish_stats_to_osd();
-  void clear_publish_stats();
-
-  void clear_primary_state();
+  void publish_stats_to_osd() override;
 
-  bool is_acting_recovery_backfill(pg_shard_t osd) const {
-    return acting_recovery_backfill.count(osd);
+  bool needs_recovery() const {
+    return recovery_state.needs_recovery();
   }
-  bool is_acting(pg_shard_t osd) const {
-    return has_shard(pool.info.is_erasure(), acting, osd);
-  }
-  bool is_up(pg_shard_t osd) const {
-    return has_shard(pool.info.is_erasure(), up, osd);
-  }
-  static bool has_shard(bool ec, const vector<int>& v, pg_shard_t osd) {
-    if (ec) {
-      return v.size() > (unsigned)osd.shard && v[osd.shard] == osd.osd;
-    } else {
-      return std::find(v.begin(), v.end(), osd.osd) != v.end();
-    }
+  bool needs_backfill() const {
+    return recovery_state.needs_backfill();
   }
-  
-  bool needs_recovery() const;
-  bool needs_backfill() const;
-
-  /// clip calculated priority to reasonable range
-  int clamp_recovery_priority(int prio, int pool_recovery_prio, int max);
-  /// get log recovery reservation priority
-  unsigned get_recovery_priority();
-  /// get backfill reservation priority
-  unsigned get_backfill_priority();
-  /// get priority for pg deletion
-  unsigned get_delete_priority();
-
-  void try_mark_clean();  ///< mark an active pg clean
-
-  /// return [start,end) bounds for required past_intervals
-  static pair<epoch_t, epoch_t> get_required_past_interval_bounds(
-    const pg_info_t &info,
-    epoch_t oldest_map) {
-    epoch_t start = std::max(
-      info.history.last_epoch_clean ? info.history.last_epoch_clean :
-       info.history.epoch_pool_created,
-      oldest_map);
-    epoch_t end = std::max(
-      info.history.same_interval_since,
-      info.history.epoch_pool_created);
-    return make_pair(start, end);
-  }
-  void check_past_interval_bounds() const;
-  PastIntervals::PriorSet build_prior();
-
-  void remove_down_peer_info(const OSDMapRef osdmap);
-
-  bool adjust_need_up_thru(const OSDMapRef osdmap);
 
   bool all_unfound_are_queried_or_lost(const OSDMapRef osdmap) const;
-  virtual void dump_recovery_info(Formatter *f) const = 0;
-
-  void calc_min_last_complete_ondisk() {
-    eversion_t min = last_complete_ondisk;
-    ceph_assert(!acting_recovery_backfill.empty());
-    for (set<pg_shard_t>::iterator i = acting_recovery_backfill.begin();
-        i != acting_recovery_backfill.end();
-        ++i) {
-      if (*i == get_primary()) continue;
-      if (peer_last_complete_ondisk.count(*i) == 0)
-       return;   // we don't have complete info
-      eversion_t a = peer_last_complete_ondisk[*i];
-      if (a < min)
-       min = a;
-    }
-    if (min == min_last_complete_ondisk)
-      return;
-    min_last_complete_ondisk = min;
-    return;
-  }
-
-  virtual void calc_trim_to() = 0;
-
-  virtual void calc_trim_to_aggressive() = 0;
-
-  void proc_replica_log(pg_info_t &oinfo, const pg_log_t &olog,
-                       pg_missing_t& omissing, pg_shard_t from);
-  void proc_master_log(ObjectStore::Transaction& t, pg_info_t &oinfo, pg_log_t &olog,
-                      pg_missing_t& omissing, pg_shard_t from);
-  bool proc_replica_info(
-    pg_shard_t from, const pg_info_t &info, epoch_t send_epoch);
 
   struct PGLogEntryHandler : public PGLog::LogEntryHandler {
     PG *pg;
@@ -1518,115 +1112,27 @@ protected:
       pg->get_pgbackend()->trim(entry, t);
     }
   };
-  
+
   void update_object_snap_mapping(
     ObjectStore::Transaction *t, const hobject_t &soid,
-    const set<snapid_t> &snaps);
+    const std::set<snapid_t> &snaps);
   void clear_object_snap_mapping(
     ObjectStore::Transaction *t, const hobject_t &soid);
   void remove_snap_mapped_object(
     ObjectStore::Transaction& t, const hobject_t& soid);
-  void merge_log(
-    ObjectStore::Transaction& t, pg_info_t &oinfo,
-    pg_log_t &olog, pg_shard_t from);
-  void rewind_divergent_log(ObjectStore::Transaction& t, eversion_t newhead);
-  bool search_for_missing(
-    const pg_info_t &oinfo, const pg_missing_t &omissing,
-    pg_shard_t fromosd,
-    RecoveryCtx*);
-
-  void discover_all_missing(std::map<int, map<spg_t,pg_query_t> > &query_map);
-  
-  map<pg_shard_t, pg_info_t>::const_iterator find_best_info(
-    const map<pg_shard_t, pg_info_t> &infos,
-    bool restrict_to_up_acting,
-    bool *history_les_bound) const;
-  static void calc_ec_acting(
-    map<pg_shard_t, pg_info_t>::const_iterator auth_log_shard,
-    unsigned size,
-    const vector<int> &acting,
-    const vector<int> &up,
-    const map<pg_shard_t, pg_info_t> &all_info,
-    bool restrict_to_up_acting,
-    vector<int> *want,
-    set<pg_shard_t> *backfill,
-    set<pg_shard_t> *acting_backfill,
-    ostream &ss);
-  static void calc_replicated_acting(
-    map<pg_shard_t, pg_info_t>::const_iterator auth_log_shard,
-    uint64_t force_auth_primary_missing_objects,
-    unsigned size,
-    const vector<int> &acting,
-    const vector<int> &up,
-    pg_shard_t up_primary,
-    const map<pg_shard_t, pg_info_t> &all_info,
-    bool restrict_to_up_acting,
-    vector<int> *want,
-    set<pg_shard_t> *backfill,
-    set<pg_shard_t> *acting_backfill,
-    const OSDMapRef osdmap,
-    ostream &ss);
-  void choose_async_recovery_ec(const map<pg_shard_t, pg_info_t> &all_info,
-                                const pg_info_t &auth_info,
-                                vector<int> *want,
-                                set<pg_shard_t> *async_recovery,
-                                const OSDMapRef osdmap) const;
-  void choose_async_recovery_replicated(const map<pg_shard_t, pg_info_t> &all_info,
-                                        const pg_info_t &auth_info,
-                                        vector<int> *want,
-                                        set<pg_shard_t> *async_recovery,
-                                        const OSDMapRef osdmap) const;
-
-  bool recoverable_and_ge_min_size(const vector<int> &want) const;
-  bool choose_acting(pg_shard_t &auth_log_shard,
-                    bool restrict_to_up_acting,
-                    bool *history_les_bound);
-  void build_might_have_unfound();
-  void activate(
-    ObjectStore::Transaction& t,
-    epoch_t activation_epoch,
-    map<int, map<spg_t,pg_query_t> >& query_map,
-    map<int,
-      vector<pair<pg_notify_t, PastIntervals> > > *activator_map,
-    RecoveryCtx *ctx);
-
-  struct C_PG_ActivateCommitted : public Context {
-    PGRef pg;
-    epoch_t epoch;
-    epoch_t activation_epoch;
-    C_PG_ActivateCommitted(PG *p, epoch_t e, epoch_t ae)
-      : pg(p), epoch(e), activation_epoch(ae) {}
-    void finish(int r) override {
-      pg->_activate_committed(epoch, activation_epoch);
-    }
-  };
-  void _activate_committed(epoch_t epoch, epoch_t activation_epoch);
-  void all_activated_and_committed();
-
-  void proc_primary_info(ObjectStore::Transaction &t, const pg_info_t &info);
 
   bool have_unfound() const { 
-    return missing_loc.have_unfound();
+    return recovery_state.have_unfound();
   }
   uint64_t get_num_unfound() const {
-    return missing_loc.num_unfound();
-  }
-  bool all_missing_unfound() const {
-    const auto& missing = pg_log.get_missing();
-    if (!missing.have_missing())
-      return false;
-    for (auto& m : missing.get_items()) {
-      if (!missing_loc.is_unfound(m.first))
-        return false;
-    }
-    return true;
+    return recovery_state.get_num_unfound();
   }
 
   virtual void check_local() = 0;
 
   void purge_strays();
 
-  void update_heartbeat_peers();
+  void update_heartbeat_peers(std::set<int> peers) override;
 
   Context *finish_sync_event;
 
@@ -1642,31 +1148,32 @@ protected:
   void cancel_recovery();
   void clear_recovery_state();
   virtual void _clear_recovery_state() = 0;
-  virtual void check_recovery_sources(const OSDMapRef& newmap) = 0;
   void start_recovery_op(const hobject_t& soid);
   void finish_recovery_op(const hobject_t& soid, bool dequeue=false);
 
   virtual void _split_into(pg_t child_pgid, PG *child, unsigned split_bits) = 0;
 
   friend class C_OSD_RepModify_Commit;
-  friend class C_DeleteMore;
+  friend struct C_DeleteMore;
 
   // -- backoff --
-  Mutex backoff_lock;  // orders inside Backoff::lock
-  map<hobject_t,set<BackoffRef>> backoffs;
+  ceph::mutex backoff_lock = // orders inside Backoff::lock
+    ceph::make_mutex("PG::backoff_lock");
+  std::map<hobject_t,std::set<ceph::ref_t<Backoff>>> backoffs;
 
-  void add_backoff(SessionRef s, const hobject_t& begin, const hobject_t& end);
+  void add_backoff(const ceph::ref_t<Session>& s, const hobject_t& begin, const hobject_t& end);
   void release_backoffs(const hobject_t& begin, const hobject_t& end);
   void release_backoffs(const hobject_t& o) {
     release_backoffs(o, o);
   }
   void clear_backoffs();
 
-  void add_pg_backoff(SessionRef s) {
+  void add_pg_backoff(const ceph::ref_t<Session>& s) {
     hobject_t begin = info.pgid.pgid.get_hobj_start();
     hobject_t end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num());
     add_backoff(s, begin, end);
   }
+public:
   void release_pg_backoffs() {
     hobject_t begin = info.pgid.pgid.get_hobj_start();
     hobject_t end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num());
@@ -1674,235 +1181,21 @@ protected:
   }
 
   // -- scrub --
-public:
-  struct Scrubber {
-    Scrubber();
-    ~Scrubber();
-
-    // metadata
-    set<pg_shard_t> reserved_peers;
-    bool reserved, reserve_failed;
-    epoch_t epoch_start;
-
-    // common to both scrubs
-    bool active;
-    set<pg_shard_t> waiting_on_whom;
-    int shallow_errors;
-    int deep_errors;
-    int fixed;
-    ScrubMap primary_scrubmap;
-    ScrubMapBuilder primary_scrubmap_pos;
-    epoch_t replica_scrub_start = 0;
-    ScrubMap replica_scrubmap;
-    ScrubMapBuilder replica_scrubmap_pos;
-    map<pg_shard_t, ScrubMap> received_maps;
-    OpRequestRef active_rep_scrub;
-    utime_t scrub_reg_stamp;  // stamp we registered for
-
-    omap_stat_t omap_stats  = (const struct omap_stat_t){ 0 };
-
-    // For async sleep
-    bool sleeping = false;
-    bool needs_sleep = true;
-    utime_t sleep_start;
-
-    // flags to indicate explicitly requested scrubs (by admin)
-    bool must_scrub, must_deep_scrub, must_repair;
-
-    // Priority to use for scrub scheduling
-    unsigned priority = 0;
-
-    // this flag indicates whether we would like to do auto-repair of the PG or not
-    bool auto_repair;
-    // this flag indicates that we are scrubbing post repair to verify everything is fixed
-    bool check_repair;
-    // this flag indicates that if a regular scrub detects errors <= osd_scrub_auto_repair_num_errors,
-    // we should deep scrub in order to auto repair
-    bool deep_scrub_on_error;
-
-    // Maps from objects with errors to missing/inconsistent peers
-    map<hobject_t, set<pg_shard_t>> missing;
-    map<hobject_t, set<pg_shard_t>> inconsistent;
-
-    // Map from object with errors to good peers
-    map<hobject_t, list<pair<ScrubMap::object, pg_shard_t> >> authoritative;
-
-    // Cleaned map pending snap metadata scrub
-    ScrubMap cleaned_meta_map;
-
-    void clean_meta_map(ScrubMap &for_meta_scrub) {
-      if (end.is_max() ||
-          cleaned_meta_map.objects.empty()) {
-         cleaned_meta_map.swap(for_meta_scrub);
-      } else {
-        auto iter = cleaned_meta_map.objects.end();
-        --iter; // not empty, see if clause
-        auto begin = cleaned_meta_map.objects.begin();
-        if (iter->first.has_snapset()) {
-          ++iter;
-        } else {
-          while (iter != begin) {
-            auto next = iter--;
-            if (next->first.get_head() != iter->first.get_head()) {
-             ++iter;
-             break;
-            }
-          }
-        }
-        for_meta_scrub.objects.insert(begin, iter);
-        cleaned_meta_map.objects.erase(begin, iter);
-      }
-    }
-
-    // digest updates which we are waiting on
-    int num_digest_updates_pending;
-
-    // chunky scrub
-    hobject_t start, end;    // [start,end)
-    hobject_t max_end;       // Largest end that may have been sent to replicas
-    eversion_t subset_last_update;
-
-    // chunky scrub state
-    enum State {
-      INACTIVE,
-      NEW_CHUNK,
-      WAIT_PUSHES,
-      WAIT_LAST_UPDATE,
-      BUILD_MAP,
-      BUILD_MAP_DONE,
-      WAIT_REPLICAS,
-      COMPARE_MAPS,
-      WAIT_DIGEST_UPDATES,
-      FINISH,
-      BUILD_MAP_REPLICA,
-    } state;
-
-    std::unique_ptr<Scrub::Store> store;
-    // deep scrub
-    bool deep;
-    int preempt_left;
-    int preempt_divisor;
-
-    list<Context*> callbacks;
-    void add_callback(Context *context) {
-      callbacks.push_back(context);
-    }
-    void run_callbacks() {
-      list<Context*> to_run;
-      to_run.swap(callbacks);
-      for (list<Context*>::iterator i = to_run.begin();
-          i != to_run.end();
-          ++i) {
-       (*i)->complete(0);
-      }
-    }
-
-    static const char *state_string(const PG::Scrubber::State& state) {
-      const char *ret = NULL;
-      switch( state )
-      {
-        case INACTIVE: ret = "INACTIVE"; break;
-        case NEW_CHUNK: ret = "NEW_CHUNK"; break;
-        case WAIT_PUSHES: ret = "WAIT_PUSHES"; break;
-        case WAIT_LAST_UPDATE: ret = "WAIT_LAST_UPDATE"; break;
-        case BUILD_MAP: ret = "BUILD_MAP"; break;
-        case BUILD_MAP_DONE: ret = "BUILD_MAP_DONE"; break;
-        case WAIT_REPLICAS: ret = "WAIT_REPLICAS"; break;
-        case COMPARE_MAPS: ret = "COMPARE_MAPS"; break;
-        case WAIT_DIGEST_UPDATES: ret = "WAIT_DIGEST_UPDATES"; break;
-        case FINISH: ret = "FINISH"; break;
-        case BUILD_MAP_REPLICA: ret = "BUILD_MAP_REPLICA"; break;
-      }
-      return ret;
-    }
-
-    bool is_chunky_scrub_active() const { return state != INACTIVE; }
-
-    // clear all state
-    void reset() {
-      active = false;
-      waiting_on_whom.clear();
-      if (active_rep_scrub) {
-        active_rep_scrub = OpRequestRef();
-      }
-      received_maps.clear();
-
-      must_scrub = false;
-      must_deep_scrub = false;
-      must_repair = false;
-      auto_repair = false;
-      check_repair = false;
-      deep_scrub_on_error = false;
-
-      state = PG::Scrubber::INACTIVE;
-      start = hobject_t();
-      end = hobject_t();
-      max_end = hobject_t();
-      subset_last_update = eversion_t();
-      shallow_errors = 0;
-      deep_errors = 0;
-      fixed = 0;
-      omap_stats = (const struct omap_stat_t){ 0 };
-      deep = false;
-      run_callbacks();
-      inconsistent.clear();
-      missing.clear();
-      authoritative.clear();
-      num_digest_updates_pending = 0;
-      primary_scrubmap = ScrubMap();
-      primary_scrubmap_pos.reset();
-      replica_scrubmap = ScrubMap();
-      replica_scrubmap_pos.reset();
-      cleaned_meta_map = ScrubMap();
-      sleeping = false;
-      needs_sleep = true;
-      sleep_start = utime_t();
-    }
-
-    void create_results(const hobject_t& obj);
-    void cleanup_store(ObjectStore::Transaction *t);
-  } scrubber;
-
 protected:
   bool scrub_after_recovery;
 
   int active_pushes;
 
-  bool scrub_can_preempt = false;
-  bool scrub_preempted = false;
-
-  // we allow some number of preemptions of the scrub, which mean we do
-  // not block.  then we start to block.  once we start blocking, we do
-  // not stop until the scrub range is completed.
-  bool write_blocked_by_scrub(const hobject_t &soid);
-
-  /// true if the given range intersects the scrub interval in any way
-  bool range_intersects_scrub(const hobject_t &start, const hobject_t& end);
-
   void repair_object(
-    const hobject_t& soid, list<pair<ScrubMap::object, pg_shard_t> > *ok_peers,
-    pg_shard_t bad_peer);
+    const hobject_t &soid,
+    const std::list<std::pair<ScrubMap::object, pg_shard_t> > &ok_peers,
+    const std::set<pg_shard_t> &bad_peers);
+
+  [[nodiscard]] bool ops_blocked_by_scrub() const;
+  [[nodiscard]] Scrub::scrub_prio_t is_scrub_blocking_ops() const;
 
-  void chunky_scrub(ThreadPool::TPHandle &handle);
-  void scrub_compare_maps();
-  /**
-   * return true if any inconsistency/missing is repaired, false otherwise
-   */
-  bool scrub_process_inconsistent();
-  bool ops_blocked_by_scrub() const;
-  void scrub_finish();
-  void scrub_clear_state(bool keep_repair = false);
-  void _scan_snaps(ScrubMap &map);
   void _repair_oinfo_oid(ScrubMap &map);
-  void _scan_rollback_obs(const vector<ghobject_t> &rollback_obs);
-  void _request_scrub_map(pg_shard_t replica, eversion_t version,
-                          hobject_t start, hobject_t end, bool deep,
-                         bool allow_preemption);
-  int build_scrub_map_chunk(
-    ScrubMap &map,
-    ScrubMapBuilder &pos,
-    hobject_t start, hobject_t end, bool deep,
-    ThreadPool::TPHandle &handle);
+  void _scan_rollback_obs(const std::vector<ghobject_t> &rollback_obs);
   /**
    * returns true if [begin, end) is good to scrub at this time
    * a false return value obliges the implementer to requeue scrub when the
@@ -1910,1190 +1203,136 @@ protected:
    */
   virtual bool _range_available_for_scrub(
     const hobject_t &begin, const hobject_t &end) = 0;
-  virtual void scrub_snapshot_metadata(
-    ScrubMap &map,
-    const std::map<hobject_t,
-                   pair<boost::optional<uint32_t>,
-                        boost::optional<uint32_t>>> &missing_digest) { }
-  virtual void _scrub_clear_state() { }
-  virtual void _scrub_finish() { }
-  void clear_scrub_reserved();
-  void scrub_reserve_replicas();
-  void scrub_unreserve_replicas();
-  bool scrub_all_replicas_reserved() const;
-
-  void replica_scrub(
-    OpRequestRef op,
-    ThreadPool::TPHandle &handle);
-  void do_replica_scrub_map(OpRequestRef op);
-
-  void handle_scrub_reserve_request(OpRequestRef op);
-  void handle_scrub_reserve_grant(OpRequestRef op, pg_shard_t from);
-  void handle_scrub_reserve_reject(OpRequestRef op, pg_shard_t from);
-  void handle_scrub_reserve_release(OpRequestRef op);
-
-  void reject_reservation();
-  void schedule_backfill_retry(float retry);
-  void schedule_recovery_retry(float retry);
+
+  /**
+   * Initiate the process that will create our scrub map for the Primary.
+   * (triggered by MSG_OSD_REP_SCRUB)
+   */
+  void replica_scrub(OpRequestRef op, ThreadPool::TPHandle &handle);
 
   // -- recovery state --
 
-  template <class EVT>
   struct QueuePeeringEvt : Context {
     PGRef pg;
-    epoch_t epoch;
-    EVT evt;
+    PGPeeringEventRef evt;
+
+    template <class EVT>
     QueuePeeringEvt(PG *pg, epoch_t epoch, EVT evt) :
-      pg(pg), epoch(epoch), evt(evt) {}
+      pg(pg), evt(std::make_shared<PGPeeringEvent>(epoch, epoch, evt)) {}
+
+    QueuePeeringEvt(PG *pg, PGPeeringEventRef evt) :
+      pg(pg), evt(std::move(evt)) {}
+
     void finish(int r) override {
       pg->lock();
-      pg->queue_peering_event(PGPeeringEventRef(
-                               new PGPeeringEvent(
-                                 epoch,
-                                 epoch,
-                                 evt)));
+      pg->queue_peering_event(std::move(evt));
       pg->unlock();
     }
   };
 
 
-  struct QueryState : boost::statechart::event< QueryState > {
-    Formatter *f;
-    explicit QueryState(Formatter *f) : f(f) {}
-    void print(std::ostream *out) const {
-      *out << "Query";
-    }
-  };
-
 public:
   int pg_stat_adjust(osd_stat_t *new_stat);
 protected:
-
-  struct AdvMap : boost::statechart::event< AdvMap > {
-    OSDMapRef osdmap;
-    OSDMapRef lastmap;
-    vector<int> newup, newacting;
-    int up_primary, acting_primary;
-    AdvMap(
-      OSDMapRef osdmap, OSDMapRef lastmap,
-      vector<int>& newup, int up_primary,
-      vector<int>& newacting, int acting_primary):
-      osdmap(osdmap), lastmap(lastmap),
-      newup(newup),
-      newacting(newacting),
-      up_primary(up_primary),
-      acting_primary(acting_primary) {}
-    void print(std::ostream *out) const {
-      *out << "AdvMap";
-    }
-  };
-
-  struct ActMap : boost::statechart::event< ActMap > {
-    ActMap() : boost::statechart::event< ActMap >() {}
-    void print(std::ostream *out) const {
-      *out << "ActMap";
-    }
-  };
-  struct Activate : boost::statechart::event< Activate > {
-    epoch_t activation_epoch;
-    explicit Activate(epoch_t q) : boost::statechart::event< Activate >(),
-                         activation_epoch(q) {}
-    void print(std::ostream *out) const {
-      *out << "Activate from " << activation_epoch;
-    }
-  };
-public:
-  struct UnfoundBackfill : boost::statechart::event<UnfoundBackfill> {
-    explicit UnfoundBackfill() {}
-    void print(std::ostream *out) const {
-      *out << "UnfoundBackfill";
-    }
-  };
-  struct UnfoundRecovery : boost::statechart::event<UnfoundRecovery> {
-    explicit UnfoundRecovery() {}
-    void print(std::ostream *out) const {
-      *out << "UnfoundRecovery";
-    }
-  };
-
-  struct RequestScrub : boost::statechart::event<RequestScrub> {
-    bool deep;
-    bool repair;
-    explicit RequestScrub(bool d, bool r) : deep(d), repair(r) {}
-    void print(std::ostream *out) const {
-      *out << "RequestScrub(" << (deep ? "deep" : "shallow")
-          << (repair ? " repair" : "");
-    }
-  };
-
-protected:
-  TrivialEvent(Initialize)
-  TrivialEvent(GotInfo)
-  TrivialEvent(NeedUpThru)
-  TrivialEvent(Backfilled)
-  TrivialEvent(LocalBackfillReserved)
-  TrivialEvent(RejectRemoteReservation)
-  public:
-  TrivialEvent(RequestBackfill)
-  protected:
-  TrivialEvent(RemoteRecoveryPreempted)
-  TrivialEvent(RemoteBackfillPreempted)
-  TrivialEvent(BackfillTooFull)
-  TrivialEvent(RecoveryTooFull)
-
-  TrivialEvent(MakePrimary)
-  TrivialEvent(MakeStray)
-  TrivialEvent(NeedActingChange)
-  TrivialEvent(IsIncomplete)
-  TrivialEvent(IsDown)
-
-  TrivialEvent(AllReplicasRecovered)
-  TrivialEvent(DoRecovery)
-  TrivialEvent(LocalRecoveryReserved)
-  public:
-  protected:
-  TrivialEvent(AllRemotesReserved)
-  TrivialEvent(AllBackfillsReserved)
-  TrivialEvent(GoClean)
-
-  TrivialEvent(AllReplicasActivated)
-
-  TrivialEvent(IntervalFlush)
-
-  public:
-  TrivialEvent(DeleteStart)
-  TrivialEvent(DeleteSome)
-
-  TrivialEvent(SetForceRecovery)
-  TrivialEvent(UnsetForceRecovery)
-  TrivialEvent(SetForceBackfill)
-  TrivialEvent(UnsetForceBackfill)
-
-  protected:
-  TrivialEvent(DeleteReserved)
-  TrivialEvent(DeleteInterrupted)
-
-  /* Encapsulates PG recovery process */
-  class RecoveryState {
-    void start_handle(RecoveryCtx *new_ctx);
-    void end_handle();
-  public:
-    void begin_block_outgoing();
-    void end_block_outgoing();
-    void clear_blocked_outgoing();
-  private:
-
-    /* States */
-    struct Initial;
-    class RecoveryMachine : public boost::statechart::state_machine< RecoveryMachine, Initial > {
-      RecoveryState *state;
-    public:
-      PG *pg;
-
-      utime_t event_time;
-      uint64_t event_count;
-      
-      void clear_event_counters() {
-       event_time = utime_t();
-       event_count = 0;
-      }
-
-      void log_enter(const char *state_name);
-      void log_exit(const char *state_name, utime_t duration);
-
-      RecoveryMachine(RecoveryState *state, PG *pg) : state(state), pg(pg), event_count(0) {}
-
-      /* Accessor functions for state methods */
-      ObjectStore::Transaction* get_cur_transaction() {
-       ceph_assert(state->rctx);
-       ceph_assert(state->rctx->transaction);
-       return state->rctx->transaction;
-      }
-
-      void send_query(pg_shard_t to, const pg_query_t &query) {
-       ceph_assert(state->rctx);
-       ceph_assert(state->rctx->query_map);
-       (*state->rctx->query_map)[to.osd][spg_t(pg->info.pgid.pgid, to.shard)] =
-         query;
-      }
-
-      map<int, map<spg_t, pg_query_t> > *get_query_map() {
-       ceph_assert(state->rctx);
-       ceph_assert(state->rctx->query_map);
-       return state->rctx->query_map;
-      }
-
-      map<int, vector<pair<pg_notify_t, PastIntervals> > > *get_info_map() {
-       ceph_assert(state->rctx);
-       ceph_assert(state->rctx->info_map);
-       return state->rctx->info_map;
-      }
-
-      RecoveryCtx *get_recovery_ctx() { return &*(state->rctx); }
-
-      void send_notify(pg_shard_t to,
-                      const pg_notify_t &info, const PastIntervals &pi) {
-       ceph_assert(state->rctx);
-       state->rctx->send_notify(to, info, pi);
-      }
-    };
-    friend class RecoveryMachine;
-
-    /* States */
-    // Initial
-    // Reset
-    // Start
-    //   Started
-    //     Primary
-    //       WaitActingChange
-    //       Peering
-    //         GetInfo
-    //         GetLog
-    //         GetMissing
-    //         WaitUpThru
-    //         Incomplete
-    //       Active
-    //         Activating
-    //         Clean
-    //         Recovered
-    //         Backfilling
-    //         WaitRemoteBackfillReserved
-    //         WaitLocalBackfillReserved
-    //         NotBackfilling
-    //         NotRecovering
-    //         Recovering
-    //         WaitRemoteRecoveryReserved
-    //         WaitLocalRecoveryReserved
-    //     ReplicaActive
-    //       RepNotRecovering
-    //       RepRecovering
-    //       RepWaitBackfillReserved
-    //       RepWaitRecoveryReserved
-    //     Stray
-    //     ToDelete
-    //       WaitDeleteReserved
-    //       Deleting
-    // Crashed
-
-    struct Crashed : boost::statechart::state< Crashed, RecoveryMachine >, NamedState {
-      explicit Crashed(my_context ctx);
-    };
-
-    struct Reset;
-
-    struct Initial : boost::statechart::state< Initial, RecoveryMachine >, NamedState {
-      explicit Initial(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::transition< Initialize, Reset >,
-       boost::statechart::custom_reaction< NullEvt >,
-       boost::statechart::transition< boost::statechart::event_base, Crashed >
-       > reactions;
-
-      boost::statechart::result react(const MNotifyRec&);
-      boost::statechart::result react(const MInfoRec&);
-      boost::statechart::result react(const MLogRec&);
-      boost::statechart::result react(const boost::statechart::event_base&) {
-       return discard_event();
-      }
-    };
-
-    struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState {
-      explicit Reset(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< NullEvt >,
-       boost::statechart::custom_reaction< IntervalFlush >,
-       boost::statechart::transition< boost::statechart::event_base, Crashed >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const AdvMap&);
-      boost::statechart::result react(const ActMap&);
-      boost::statechart::result react(const IntervalFlush&);
-      boost::statechart::result react(const boost::statechart::event_base&) {
-       return discard_event();
-      }
-    };
-
-    struct Start;
-
-    struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {
-      explicit Started(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::custom_reaction< IntervalFlush >,
-       // ignored
-       boost::statechart::custom_reaction< NullEvt >,
-       boost::statechart::custom_reaction<SetForceRecovery>,
-       boost::statechart::custom_reaction<UnsetForceRecovery>,
-       boost::statechart::custom_reaction<SetForceBackfill>,
-       boost::statechart::custom_reaction<UnsetForceBackfill>,
-       boost::statechart::custom_reaction<RequestScrub>,
-       // crash
-       boost::statechart::transition< boost::statechart::event_base, Crashed >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const AdvMap&);
-      boost::statechart::result react(const IntervalFlush&);
-      boost::statechart::result react(const boost::statechart::event_base&) {
-       return discard_event();
-      }
-    };
-
-    struct Primary;
-    struct Stray;
-
-    struct Start : boost::statechart::state< Start, Started >, NamedState {
-      explicit Start(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::transition< MakePrimary, Primary >,
-       boost::statechart::transition< MakeStray, Stray >
-       > reactions;
-    };
-
-    struct Peering;
-    struct WaitActingChange;
-    struct Incomplete;
-    struct Down;
-
-    struct Primary : boost::statechart::state< Primary, Started, Peering >, NamedState {
-      explicit Primary(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< MNotifyRec >,
-       boost::statechart::transition< NeedActingChange, WaitActingChange >,
-       boost::statechart::custom_reaction<SetForceRecovery>,
-       boost::statechart::custom_reaction<UnsetForceRecovery>,
-       boost::statechart::custom_reaction<SetForceBackfill>,
-       boost::statechart::custom_reaction<UnsetForceBackfill>,
-       boost::statechart::custom_reaction<RequestScrub>
-       > reactions;
-      boost::statechart::result react(const ActMap&);
-      boost::statechart::result react(const MNotifyRec&);
-      boost::statechart::result react(const SetForceRecovery&);
-      boost::statechart::result react(const UnsetForceRecovery&);
-      boost::statechart::result react(const SetForceBackfill&);
-      boost::statechart::result react(const UnsetForceBackfill&);
-      boost::statechart::result react(const RequestScrub&);
-    };
-
-    struct WaitActingChange : boost::statechart::state< WaitActingChange, Primary>,
-                             NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::custom_reaction< MInfoRec >,
-       boost::statechart::custom_reaction< MNotifyRec >
-       > reactions;
-      explicit WaitActingChange(my_context ctx);
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const AdvMap&);
-      boost::statechart::result react(const MLogRec&);
-      boost::statechart::result react(const MInfoRec&);
-      boost::statechart::result react(const MNotifyRec&);
-      void exit();
-    };
-
-    struct GetInfo;
-    struct Active;
-
-    struct Peering : boost::statechart::state< Peering, Primary, GetInfo >, NamedState {
-      PastIntervals::PriorSet prior_set;
-      bool history_les_bound;  //< need osd_find_best_info_ignore_history_les
-
-      explicit Peering(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::transition< Activate, Active >,
-       boost::statechart::custom_reaction< AdvMap >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const AdvMap &advmap);
-    };
-
-    struct WaitLocalRecoveryReserved;
-    struct Activating;
-    struct Active : boost::statechart::state< Active, Primary, Activating >, NamedState {
-      explicit Active(my_context ctx);
-      void exit();
-
-      const set<pg_shard_t> remote_shards_to_reserve_recovery;
-      const set<pg_shard_t> remote_shards_to_reserve_backfill;
-      bool all_replicas_activated;
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::custom_reaction< MInfoRec >,
-       boost::statechart::custom_reaction< MNotifyRec >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::custom_reaction< MTrim >,
-       boost::statechart::custom_reaction< Backfilled >,
-       boost::statechart::custom_reaction< AllReplicasActivated >,
-       boost::statechart::custom_reaction< DeferRecovery >,
-       boost::statechart::custom_reaction< DeferBackfill >,
-       boost::statechart::custom_reaction< UnfoundRecovery >,
-       boost::statechart::custom_reaction< UnfoundBackfill >,
-       boost::statechart::custom_reaction< RemoteReservationRevokedTooFull>,
-       boost::statechart::custom_reaction< RemoteReservationRevoked>,
-       boost::statechart::custom_reaction< DoRecovery>
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const ActMap&);
-      boost::statechart::result react(const AdvMap&);
-      boost::statechart::result react(const MInfoRec& infoevt);
-      boost::statechart::result react(const MNotifyRec& notevt);
-      boost::statechart::result react(const MLogRec& logevt);
-      boost::statechart::result react(const MTrim& trimevt);
-      boost::statechart::result react(const Backfilled&) {
-       return discard_event();
-      }
-      boost::statechart::result react(const AllReplicasActivated&);
-      boost::statechart::result react(const DeferRecovery& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const DeferBackfill& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const UnfoundRecovery& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const UnfoundBackfill& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteReservationRevokedTooFull&) {
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteReservationRevoked&) {
-       return discard_event();
-      }
-      boost::statechart::result react(const DoRecovery&) {
-       return discard_event();
-      }
-    };
-
-    struct Clean : boost::statechart::state< Clean, Active >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< DoRecovery, WaitLocalRecoveryReserved >,
-       boost::statechart::custom_reaction<SetForceRecovery>,
-       boost::statechart::custom_reaction<SetForceBackfill>
-      > reactions;
-      explicit Clean(my_context ctx);
-      void exit();
-      boost::statechart::result react(const boost::statechart::event_base&) {
-       return discard_event();
-      }
-    };
-
-    struct Recovered : boost::statechart::state< Recovered, Active >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< GoClean, Clean >,
-       boost::statechart::transition< DoRecovery, WaitLocalRecoveryReserved >,
-       boost::statechart::custom_reaction< AllReplicasActivated >
-      > reactions;
-      explicit Recovered(my_context ctx);
-      void exit();
-      boost::statechart::result react(const AllReplicasActivated&) {
-       post_event(GoClean());
-       return forward_event();
-      }
-    };
-
-    struct Backfilling : boost::statechart::state< Backfilling, Active >, NamedState {
-      typedef boost::mpl::list<
-        boost::statechart::custom_reaction< Backfilled >,
-       boost::statechart::custom_reaction< DeferBackfill >,
-       boost::statechart::custom_reaction< UnfoundBackfill >,
-       boost::statechart::custom_reaction< RemoteReservationRejected >,
-       boost::statechart::custom_reaction< RemoteReservationRevokedTooFull>,
-       boost::statechart::custom_reaction< RemoteReservationRevoked>
-       > reactions;
-      explicit Backfilling(my_context ctx);
-      boost::statechart::result react(const RemoteReservationRejected& evt) {
-       // for compat with old peers
-       post_event(RemoteReservationRevokedTooFull());
-       return discard_event();
-      }
-      void backfill_release_reservations();
-      boost::statechart::result react(const Backfilled& evt);
-      boost::statechart::result react(const RemoteReservationRevokedTooFull& evt);
-      boost::statechart::result react(const RemoteReservationRevoked& evt);
-      boost::statechart::result react(const DeferBackfill& evt);
-      boost::statechart::result react(const UnfoundBackfill& evt);
-      void cancel_backfill();
-      void exit();
-    };
-
-    struct WaitRemoteBackfillReserved : boost::statechart::state< WaitRemoteBackfillReserved, Active >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::custom_reaction< RemoteBackfillReserved >,
-       boost::statechart::custom_reaction< RemoteReservationRejected >,
-       boost::statechart::custom_reaction< RemoteReservationRevoked >,
-       boost::statechart::transition< AllBackfillsReserved, Backfilling >
-       > reactions;
-      set<pg_shard_t>::const_iterator backfill_osd_it;
-      explicit WaitRemoteBackfillReserved(my_context ctx);
-      void retry();
-      void exit();
-      boost::statechart::result react(const RemoteBackfillReserved& evt);
-      boost::statechart::result react(const RemoteReservationRejected& evt);
-      boost::statechart::result react(const RemoteReservationRevoked& evt);
-    };
-
-    struct WaitLocalBackfillReserved : boost::statechart::state< WaitLocalBackfillReserved, Active >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< LocalBackfillReserved, WaitRemoteBackfillReserved >
-       > reactions;
-      explicit WaitLocalBackfillReserved(my_context ctx);
-      void exit();
-    };
-
-    struct NotBackfilling : boost::statechart::state< NotBackfilling, Active>, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< RequestBackfill, WaitLocalBackfillReserved>,
-       boost::statechart::custom_reaction< RemoteBackfillReserved >,
-       boost::statechart::custom_reaction< RemoteReservationRejected >
-       > reactions;
-      explicit NotBackfilling(my_context ctx);
-      void exit();
-      boost::statechart::result react(const RemoteBackfillReserved& evt);
-      boost::statechart::result react(const RemoteReservationRejected& evt);
-    };
-
-    struct NotRecovering : boost::statechart::state< NotRecovering, Active>, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< DoRecovery, WaitLocalRecoveryReserved >,
-       boost::statechart::custom_reaction< DeferRecovery >,
-       boost::statechart::custom_reaction< UnfoundRecovery >
-       > reactions;
-      explicit NotRecovering(my_context ctx);
-      boost::statechart::result react(const DeferRecovery& evt) {
-       /* no-op */
-       return discard_event();
-      }
-      boost::statechart::result react(const UnfoundRecovery& evt) {
-       /* no-op */
-       return discard_event();
-      }
-      void exit();
-    };
-
-    struct ToDelete;
-    struct RepNotRecovering;
-    struct ReplicaActive : boost::statechart::state< ReplicaActive, Started, RepNotRecovering >, NamedState {
-      explicit ReplicaActive(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< MQuery >,
-       boost::statechart::custom_reaction< MInfoRec >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::custom_reaction< MTrim >,
-       boost::statechart::custom_reaction< Activate >,
-       boost::statechart::custom_reaction< DeferRecovery >,
-       boost::statechart::custom_reaction< DeferBackfill >,
-       boost::statechart::custom_reaction< UnfoundRecovery >,
-       boost::statechart::custom_reaction< UnfoundBackfill >,
-       boost::statechart::custom_reaction< RemoteBackfillPreempted >,
-       boost::statechart::custom_reaction< RemoteRecoveryPreempted >,
-       boost::statechart::custom_reaction< RecoveryDone >,
-       boost::statechart::transition<DeleteStart, ToDelete>
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const MInfoRec& infoevt);
-      boost::statechart::result react(const MLogRec& logevt);
-      boost::statechart::result react(const MTrim& trimevt);
-      boost::statechart::result react(const ActMap&);
-      boost::statechart::result react(const MQuery&);
-      boost::statechart::result react(const Activate&);
-      boost::statechart::result react(const RecoveryDone&) {
-       return discard_event();
-      }
-      boost::statechart::result react(const DeferRecovery& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const DeferBackfill& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const UnfoundRecovery& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const UnfoundBackfill& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteBackfillPreempted& evt) {
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteRecoveryPreempted& evt) {
-       return discard_event();
-      }
-    };
-
-    struct RepRecovering : boost::statechart::state< RepRecovering, ReplicaActive >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::transition< RecoveryDone, RepNotRecovering >,
-       // for compat with old peers
-       boost::statechart::transition< RemoteReservationRejected, RepNotRecovering >,
-       boost::statechart::transition< RemoteReservationCanceled, RepNotRecovering >,
-       boost::statechart::custom_reaction< BackfillTooFull >,
-       boost::statechart::custom_reaction< RemoteRecoveryPreempted >,
-       boost::statechart::custom_reaction< RemoteBackfillPreempted >
-       > reactions;
-      explicit RepRecovering(my_context ctx);
-      boost::statechart::result react(const RemoteRecoveryPreempted &evt);
-      boost::statechart::result react(const BackfillTooFull &evt);
-      boost::statechart::result react(const RemoteBackfillPreempted &evt);
-      void exit();
-    };
-
-    struct RepWaitBackfillReserved : boost::statechart::state< RepWaitBackfillReserved, ReplicaActive >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::custom_reaction< RemoteBackfillReserved >,
-       boost::statechart::custom_reaction< RejectRemoteReservation >,
-       boost::statechart::custom_reaction< RemoteReservationRejected >,
-       boost::statechart::custom_reaction< RemoteReservationCanceled >
-       > reactions;
-      explicit RepWaitBackfillReserved(my_context ctx);
-      void exit();
-      boost::statechart::result react(const RemoteBackfillReserved &evt);
-      boost::statechart::result react(const RejectRemoteReservation &evt);
-      boost::statechart::result react(const RemoteReservationRejected &evt);
-      boost::statechart::result react(const RemoteReservationCanceled &evt);
-    };
-
-    struct RepWaitRecoveryReserved : boost::statechart::state< RepWaitRecoveryReserved, ReplicaActive >, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::custom_reaction< RemoteRecoveryReserved >,
-       // for compat with old peers
-       boost::statechart::custom_reaction< RemoteReservationRejected >,
-       boost::statechart::custom_reaction< RemoteReservationCanceled >
-       > reactions;
-      explicit RepWaitRecoveryReserved(my_context ctx);
-      void exit();
-      boost::statechart::result react(const RemoteRecoveryReserved &evt);
-      boost::statechart::result react(const RemoteReservationRejected &evt) {
-       // for compat with old peers
-       post_event(RemoteReservationCanceled());
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteReservationCanceled &evt);
-    };
-
-    struct RepNotRecovering : boost::statechart::state< RepNotRecovering, ReplicaActive>, NamedState {
-      typedef boost::mpl::list<
-       boost::statechart::custom_reaction< RequestRecoveryPrio >,
-       boost::statechart::custom_reaction< RequestBackfillPrio >,
-       boost::statechart::custom_reaction< RejectRemoteReservation >,
-       boost::statechart::transition< RemoteReservationRejected, RepNotRecovering >,
-       boost::statechart::transition< RemoteReservationCanceled, RepNotRecovering >,
-       boost::statechart::custom_reaction< RemoteRecoveryReserved >,
-       boost::statechart::custom_reaction< RemoteBackfillReserved >,
-       boost::statechart::transition< RecoveryDone, RepNotRecovering >  // for compat with pre-reservation peers
-       > reactions;
-      explicit RepNotRecovering(my_context ctx);
-      boost::statechart::result react(const RequestRecoveryPrio &evt);
-      boost::statechart::result react(const RequestBackfillPrio &evt);
-      boost::statechart::result react(const RemoteBackfillReserved &evt) {
-       // my reservation completion raced with a RELEASE from primary
-       return discard_event();
-      }
-      boost::statechart::result react(const RemoteRecoveryReserved &evt) {
-       // my reservation completion raced with a RELEASE from primary
-       return discard_event();
-      }
-      boost::statechart::result react(const RejectRemoteReservation &evt);
-      void exit();
-    };
-
-    struct Recovering : boost::statechart::state< Recovering, Active >, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< AllReplicasRecovered >,
-       boost::statechart::custom_reaction< DeferRecovery >,
-       boost::statechart::custom_reaction< UnfoundRecovery >,
-       boost::statechart::custom_reaction< RequestBackfill >
-       > reactions;
-      explicit Recovering(my_context ctx);
-      void exit();
-      void release_reservations(bool cancel = false);
-      boost::statechart::result react(const AllReplicasRecovered &evt);
-      boost::statechart::result react(const DeferRecovery& evt);
-      boost::statechart::result react(const UnfoundRecovery& evt);
-      boost::statechart::result react(const RequestBackfill &evt);
-    };
-
-    struct WaitRemoteRecoveryReserved : boost::statechart::state< WaitRemoteRecoveryReserved, Active >, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< RemoteRecoveryReserved >,
-       boost::statechart::transition< AllRemotesReserved, Recovering >
-       > reactions;
-      set<pg_shard_t>::const_iterator remote_recovery_reservation_it;
-      explicit WaitRemoteRecoveryReserved(my_context ctx);
-      boost::statechart::result react(const RemoteRecoveryReserved &evt);
-      void exit();
-    };
-
-    struct WaitLocalRecoveryReserved : boost::statechart::state< WaitLocalRecoveryReserved, Active >, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::transition< LocalRecoveryReserved, WaitRemoteRecoveryReserved >,
-       boost::statechart::custom_reaction< RecoveryTooFull >
-       > reactions;
-      explicit WaitLocalRecoveryReserved(my_context ctx);
-      void exit();
-      boost::statechart::result react(const RecoveryTooFull &evt);
-    };
-
-    struct Activating : boost::statechart::state< Activating, Active >, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::transition< AllReplicasRecovered, Recovered >,
-       boost::statechart::transition< DoRecovery, WaitLocalRecoveryReserved >,
-       boost::statechart::transition< RequestBackfill, WaitLocalBackfillReserved >
-       > reactions;
-      explicit Activating(my_context ctx);
-      void exit();
-    };
-
-    struct Stray : boost::statechart::state< Stray, Started >,
-             NamedState {
-      explicit Stray(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< MQuery >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::custom_reaction< MInfoRec >,
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< RecoveryDone >,
-       boost::statechart::transition<DeleteStart, ToDelete>
-       > reactions;
-      boost::statechart::result react(const MQuery& query);
-      boost::statechart::result react(const MLogRec& logevt);
-      boost::statechart::result react(const MInfoRec& infoevt);
-      boost::statechart::result react(const ActMap&);
-      boost::statechart::result react(const RecoveryDone&) {
-       return discard_event();
-      }
-    };
-
-    struct WaitDeleteReserved;
-    struct ToDelete : boost::statechart::state<ToDelete, Started, WaitDeleteReserved>, NamedState {
-      unsigned priority = 0;
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< DeleteSome >
-       > reactions;
-      explicit ToDelete(my_context ctx);
-      boost::statechart::result react(const ActMap &evt);
-      boost::statechart::result react(const DeleteSome &evt) {
-       // happens if we drop out of Deleting due to reprioritization etc.
-       return discard_event();
-      }
-      void exit();
-    };
-
-    struct Deleting;
-    struct WaitDeleteReserved : boost::statechart::state<WaitDeleteReserved,
-                                                        ToDelete>, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::transition<DeleteReserved, Deleting>
-       > reactions;
-      explicit WaitDeleteReserved(my_context ctx);
-      void exit();
-    };
-
-    struct Deleting : boost::statechart::state<Deleting,
-                                              ToDelete>, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< DeleteSome >,
-       boost::statechart::transition<DeleteInterrupted, WaitDeleteReserved>
-       > reactions;
-      explicit Deleting(my_context ctx);
-      boost::statechart::result react(const DeleteSome &evt);
-      void exit();
-    };
-
-    struct GetLog;
-
-    struct GetInfo : boost::statechart::state< GetInfo, Peering >, NamedState {
-      set<pg_shard_t> peer_info_requested;
-
-      explicit GetInfo(my_context ctx);
-      void exit();
-      void get_infos();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::transition< GotInfo, GetLog >,
-       boost::statechart::custom_reaction< MNotifyRec >,
-       boost::statechart::transition< IsDown, Down >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const MNotifyRec& infoevt);
-    };
-
-    struct GotLog : boost::statechart::event< GotLog > {
-      GotLog() : boost::statechart::event< GotLog >() {}
-    };
-
-    struct GetLog : boost::statechart::state< GetLog, Peering >, NamedState {
-      pg_shard_t auth_log_shard;
-      boost::intrusive_ptr<MOSDPGLog> msg;
-
-      explicit GetLog(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::custom_reaction< GotLog >,
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::transition< IsIncomplete, Incomplete >
-       > reactions;
-      boost::statechart::result react(const AdvMap&);
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const MLogRec& logevt);
-      boost::statechart::result react(const GotLog&);
-    };
-
-    struct WaitUpThru;
-
-    struct GetMissing : boost::statechart::state< GetMissing, Peering >, NamedState {
-      set<pg_shard_t> peer_missing_requested;
-
-      explicit GetMissing(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< MLogRec >,
-       boost::statechart::transition< NeedUpThru, WaitUpThru >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const MLogRec& logevt);
-    };
-
-    struct WaitUpThru : boost::statechart::state< WaitUpThru, Peering >, NamedState {
-      explicit WaitUpThru(my_context ctx);
-      void exit();
-
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< ActMap >,
-       boost::statechart::custom_reaction< MLogRec >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const ActMap& am);
-      boost::statechart::result react(const MLogRec& logrec);
-    };
-
-    struct Down : boost::statechart::state< Down, Peering>, NamedState {
-      explicit Down(my_context ctx);
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< QueryState >,
-       boost::statechart::custom_reaction< MNotifyRec >
-       > reactions;
-      boost::statechart::result react(const QueryState& q);
-      boost::statechart::result react(const MNotifyRec& infoevt);
-      void exit();
-    };
-
-    struct Incomplete : boost::statechart::state< Incomplete, Peering>, NamedState {
-      typedef boost::mpl::list <
-       boost::statechart::custom_reaction< AdvMap >,
-       boost::statechart::custom_reaction< MNotifyRec >,
-       boost::statechart::custom_reaction< QueryState >
-       > reactions;
-      explicit Incomplete(my_context ctx);
-      boost::statechart::result react(const AdvMap &advmap);
-      boost::statechart::result react(const MNotifyRec& infoevt);
-      boost::statechart::result react(const QueryState& q);
-      void exit();
-    };
-
-    RecoveryMachine machine;
-    PG *pg;
-
-    /// context passed in by state machine caller
-    RecoveryCtx *orig_ctx;
-
-    /// populated if we are buffering messages pending a flush
-    boost::optional<BufferedRecoveryMessages> messages_pending_flush;
-
-    /**
-     * populated between start_handle() and end_handle(), points into
-     * the message lists for messages_pending_flush while blocking messages
-     * or into orig_ctx otherwise
-     */
-    boost::optional<RecoveryCtx> rctx;
-
-  public:
-    explicit RecoveryState(PG *pg)
-      : machine(this, pg), pg(pg), orig_ctx(0) {
-      machine.initiate();
-    }
-
-    void handle_event(const boost::statechart::event_base &evt,
-                     RecoveryCtx *rctx) {
-      start_handle(rctx);
-      machine.process_event(evt);
-      end_handle();
-    }
-
-    void handle_event(PGPeeringEventRef evt,
-                     RecoveryCtx *rctx) {
-      start_handle(rctx);
-      machine.process_event(evt->get_event());
-      end_handle();
-    }
-
-  } recovery_state;
-
-
-
-  uint64_t peer_features;
-  uint64_t acting_features;
-  uint64_t upacting_features;
-
-  epoch_t last_epoch;
-
-  /// most recently consumed osdmap's require_osd_version
-  unsigned last_require_osd_release = 0;
   bool delete_needs_sleep = false;
 
 protected:
-  void reset_min_peer_features() {
-    peer_features = CEPH_FEATURES_SUPPORTED_DEFAULT;
-  }
-  uint64_t get_min_peer_features() const { return peer_features; }
-  void apply_peer_features(uint64_t f) { peer_features &= f; }
-
-  uint64_t get_min_acting_features() const { return acting_features; }
-  uint64_t get_min_upacting_features() const { return upacting_features; }
-  bool perform_deletes_during_peering() const {
-    return !(get_osdmap()->test_flag(CEPH_OSDMAP_RECOVERY_DELETES));
-  }
-
-  bool hard_limit_pglog() const {
-    return (get_osdmap()->test_flag(CEPH_OSDMAP_PGLOG_HARDLIMIT));
-  }
-
-  void init_primary_up_acting(
-    const vector<int> &newup,
-    const vector<int> &newacting,
-    int new_up_primary,
-    int new_acting_primary) {
-    actingset.clear();
-    acting = newacting;
-    for (uint8_t i = 0; i < acting.size(); ++i) {
-      if (acting[i] != CRUSH_ITEM_NONE)
-       actingset.insert(
-         pg_shard_t(
-           acting[i],
-           pool.info.is_erasure() ? shard_id_t(i) : shard_id_t::NO_SHARD));
-    }
-    upset.clear();
-    up = newup;
-    for (uint8_t i = 0; i < up.size(); ++i) {
-      if (up[i] != CRUSH_ITEM_NONE)
-       upset.insert(
-         pg_shard_t(
-           up[i],
-           pool.info.is_erasure() ? shard_id_t(i) : shard_id_t::NO_SHARD));
-    }
-    if (!pool.info.is_erasure()) {
-      up_primary = pg_shard_t(new_up_primary, shard_id_t::NO_SHARD);
-      primary = pg_shard_t(new_acting_primary, shard_id_t::NO_SHARD);
-      return;
-    }
-    up_primary = pg_shard_t();
-    primary = pg_shard_t();
-    for (uint8_t i = 0; i < up.size(); ++i) {
-      if (up[i] == new_up_primary) {
-       up_primary = pg_shard_t(up[i], shard_id_t(i));
-       break;
-      }
-    }
-    for (uint8_t i = 0; i < acting.size(); ++i) {
-      if (acting[i] == new_acting_primary) {
-       primary = pg_shard_t(acting[i], shard_id_t(i));
-       break;
-      }
-    }
-    ceph_assert(up_primary.osd == new_up_primary);
-    ceph_assert(primary.osd == new_acting_primary);
-  }
-
-  void set_role(int r) {
-    role = r;
-  }
-
-  bool state_test(uint64_t m) const { return (state & m) != 0; }
-  void state_set(uint64_t m) { state |= m; }
-  void state_clear(uint64_t m) { state &= ~m; }
-
-  bool is_complete() const { return info.last_complete == info.last_update; }
-  bool should_send_notify() const { return send_notify; }
-
-  uint64_t get_state() const { return state; }
-  bool is_active() const { return state_test(PG_STATE_ACTIVE); }
-  bool is_activating() const { return state_test(PG_STATE_ACTIVATING); }
-  bool is_peering() const { return state_test(PG_STATE_PEERING); }
-  bool is_down() const { return state_test(PG_STATE_DOWN); }
-  bool is_recovery_unfound() const { return state_test(PG_STATE_RECOVERY_UNFOUND); }
-  bool is_backfill_unfound() const { return state_test(PG_STATE_BACKFILL_UNFOUND); }
-  bool is_incomplete() const { return state_test(PG_STATE_INCOMPLETE); }
-  bool is_clean() const { return state_test(PG_STATE_CLEAN); }
-  bool is_degraded() const { return state_test(PG_STATE_DEGRADED); }
-  bool is_undersized() const { return state_test(PG_STATE_UNDERSIZED); }
-  bool is_scrubbing() const { return state_test(PG_STATE_SCRUBBING); }
-  bool is_remapped() const { return state_test(PG_STATE_REMAPPED); }
-  bool is_peered() const {
-    return state_test(PG_STATE_ACTIVE) || state_test(PG_STATE_PEERED);
-  }
-  bool is_recovering() const { return state_test(PG_STATE_RECOVERING); }
-  bool is_premerge() const { return state_test(PG_STATE_PREMERGE); }
-  bool is_repair() const { return state_test(PG_STATE_REPAIR); }
-
-  bool is_empty() const { return info.last_update == eversion_t(0,0); }
+  bool state_test(uint64_t m) const { return recovery_state.state_test(m); }
+  void state_set(uint64_t m) { recovery_state.state_set(m); }
+  void state_clear(uint64_t m) { recovery_state.state_clear(m); }
+
+  bool is_complete() const {
+    return recovery_state.is_complete();
+  }
+  bool should_send_notify() const {
+    return recovery_state.should_send_notify();
+  }
+
+  bool is_active() const { return recovery_state.is_active(); }
+  bool is_activating() const { return recovery_state.is_activating(); }
+  bool is_peering() const { return recovery_state.is_peering(); }
+  bool is_down() const { return recovery_state.is_down(); }
+  bool is_recovery_unfound() const { return recovery_state.is_recovery_unfound(); }
+  bool is_backfill_unfound() const { return recovery_state.is_backfill_unfound(); }
+  bool is_incomplete() const { return recovery_state.is_incomplete(); }
+  bool is_clean() const { return recovery_state.is_clean(); }
+  bool is_degraded() const { return recovery_state.is_degraded(); }
+  bool is_undersized() const { return recovery_state.is_undersized(); }
+  bool is_scrubbing() const { return state_test(PG_STATE_SCRUBBING); } // Primary only
+  bool is_remapped() const { return recovery_state.is_remapped(); }
+  bool is_peered() const { return recovery_state.is_peered(); }
+  bool is_recovering() const { return recovery_state.is_recovering(); }
+  bool is_premerge() const { return recovery_state.is_premerge(); }
+  bool is_repair() const { return recovery_state.is_repair(); }
+  bool is_laggy() const { return state_test(PG_STATE_LAGGY); }
+  bool is_wait() const { return state_test(PG_STATE_WAIT); }
+
+  bool is_empty() const { return recovery_state.is_empty(); }
 
   // pg on-disk state
   void do_pending_flush();
 
 public:
-  static void _create(ObjectStore::Transaction& t, spg_t pgid, int bits);
-  static void _init(ObjectStore::Transaction& t,
-                   spg_t pgid, const pg_pool_t *pool);
-
-protected:
-  void prepare_write_info(map<string,bufferlist> *km);
-
-  void update_store_with_options();
-
-public:
-  static int _prepare_write_info(
-    CephContext* cct,
-    map<string,bufferlist> *km,
-    epoch_t epoch,
+  void prepare_write(
     pg_info_t &info,
     pg_info_t &last_written_info,
     PastIntervals &past_intervals,
+    PGLog &pglog,
+    bool dirty_info,
     bool dirty_big_info,
-    bool dirty_epoch,
-    bool try_fast_info,
-    PerfCounters *logger = nullptr);
+    bool need_write_epoch,
+    ObjectStore::Transaction &t) override;
 
-  void write_if_dirty(RecoveryCtx *rctx) {
-    write_if_dirty(*rctx->transaction);
+  void write_if_dirty(PeeringCtx &rctx) {
+    write_if_dirty(rctx.transaction);
   }
 protected:
-  void write_if_dirty(ObjectStore::Transaction& t);
+  void write_if_dirty(ObjectStore::Transaction& t) {
+    recovery_state.write_if_dirty(t);
+  }
 
   PGLog::IndexedLog projected_log;
   bool check_in_progress_op(
     const osd_reqid_t &r,
     eversion_t *version,
     version_t *user_version,
-    int *return_code) const;
+    int *return_code,
+    std::vector<pg_log_op_return_item_t> *op_returns) const;
   eversion_t projected_last_update;
   eversion_t get_next_version() const {
     eversion_t at_version(
       get_osdmap_epoch(),
       projected_last_update.version+1);
     ceph_assert(at_version > info.last_update);
-    ceph_assert(at_version > pg_log.get_head());
+    ceph_assert(at_version > recovery_state.get_pg_log().get_head());
     ceph_assert(at_version > projected_last_update);
     return at_version;
   }
 
-  void add_log_entry(const pg_log_entry_t& e, bool applied);
-  void append_log(
-    const vector<pg_log_entry_t>& logv,
-    eversion_t trim_to,
-    eversion_t roll_forward_to,
-    ObjectStore::Transaction &t,
-    bool transaction_applied = true,
-    bool async = false);
   bool check_log_for_corruption(ObjectStore *store);
 
   std::string get_corrupt_pg_log_name() const;
 
   void update_snap_map(
-    const vector<pg_log_entry_t> &log_entries,
+    const std::vector<pg_log_entry_t> &log_entries,
     ObjectStore::Transaction& t);
 
-  void filter_snapc(vector<snapid_t> &snaps);
-
-  void log_weirdness();
+  void filter_snapc(std::vector<snapid_t> &snaps);
 
   virtual void kick_snap_trim() = 0;
   virtual void snap_trimmer_scrub_complete() = 0;
-  bool requeue_scrub(bool high_priority = false);
-  void queue_recovery();
-  bool queue_scrub();
-  unsigned get_scrub_priority();
 
-  /// share pg info after a pg is active
-  void share_pg_info();
+  void queue_recovery();
+  void queue_scrub_after_repair();
+  unsigned int get_scrub_priority();
 
+  bool try_flush_or_schedule_async() override;
+  void start_flush_on_transaction(
+    ObjectStore::Transaction &t) override;
 
-  bool append_log_entries_update_missing(
-    const mempool::osd_pglog::list<pg_log_entry_t> &entries,
-    ObjectStore::Transaction &t,
-    boost::optional<eversion_t> trim_to,
-    boost::optional<eversion_t> roll_forward_to);
-
-  /**
-   * Merge entries updating missing as necessary on all
-   * acting_recovery_backfill logs and missings (also missing_loc)
-   */
-  void merge_new_log_entries(
-    const mempool::osd_pglog::list<pg_log_entry_t> &entries,
-    ObjectStore::Transaction &t,
-    boost::optional<eversion_t> trim_to,
-    boost::optional<eversion_t> roll_forward_to);
-
-  void reset_interval_flush();
-  void start_peering_interval(
-    const OSDMapRef lastmap,
-    const vector<int>& newup, int up_primary,
-    const vector<int>& newacting, int acting_primary,
-    ObjectStore::Transaction *t);
-  void on_new_interval();
-  virtual void _on_new_interval() = 0;
-  void start_flush(ObjectStore::Transaction *t);
-  void set_last_peering_reset();
-
-  void update_history(const pg_history_t& history);
-  void fulfill_info(pg_shard_t from, const pg_query_t &query,
-                   pair<pg_shard_t, pg_info_t> &notify_info);
-  void fulfill_log(pg_shard_t from, const pg_query_t &query, epoch_t query_epoch);
-  void fulfill_query(const MQuery& q, RecoveryCtx *rctx);
-  void check_full_transition(OSDMapRef lastmap, OSDMapRef osdmap);
-
-  bool should_restart_peering(
-    int newupprimary,
-    int newactingprimary,
-    const vector<int>& newup,
-    const vector<int>& newacting,
-    OSDMapRef lastmap,
-    OSDMapRef osdmap);
+  void update_history(const pg_history_t& history) {
+    recovery_state.update_history(history);
+  }
 
   // OpRequest queueing
   bool can_discard_op(OpRequestRef& op);
@@ -3108,34 +1347,37 @@ protected:
   bool old_peering_evt(PGPeeringEventRef evt) {
     return old_peering_msg(evt->get_epoch_sent(), evt->get_epoch_requested());
   }
-  static bool have_same_or_newer_map(epoch_t cur_epoch, epoch_t e) {
-    return e <= cur_epoch;
-  }
   bool have_same_or_newer_map(epoch_t e) {
     return e <= get_osdmap_epoch();
   }
 
   bool op_has_sufficient_caps(OpRequestRef& op);
 
+  // abstract bits
+  friend struct FlushState;
 
-  // recovery bits
-  void take_waiters();
+  friend ostream& operator<<(ostream& out, const PG& pg);
 
+protected:
+  PeeringState recovery_state;
 
-  // abstract bits
-  friend class FlushState;
+  // ref to recovery_state.pool
+  const PGPool &pool;
 
-  virtual void on_role_change() = 0;
-  virtual void on_pool_change() = 0;
-  virtual void on_change(ObjectStore::Transaction *t) = 0;
-  virtual void on_activate() = 0;
-  virtual void on_flushed() = 0;
-  virtual void check_blacklisted_watchers() = 0;
+  // ref to recovery_state.info
+  const pg_info_t &info;
 
-  friend ostream& operator<<(ostream& out, const PG& pg);
-};
 
+// ScrubberPasskey getters:
+public:
+  const pg_info_t& get_pg_info(ScrubberPasskey) const {
+    return info;
+  }
 
-ostream& operator<<(ostream& out, const PG::BackfillInterval& bi);
+  OSDService* get_pg_osd(ScrubberPasskey) const {
+    return osd;
+  }
+
+};
 
 #endif