]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
osd/scrubber: add generic interface for scheduling a future event
authorSamuel Just <sjust@redhat.com>
Tue, 14 Feb 2023 05:19:44 +0000 (21:19 -0800)
committerRonen Friedman <rfriedma@redhat.com>
Wed, 26 Mar 2025 14:27:56 +0000 (14:27 +0000)
PgScrubber/ScrubMachine have several examples of needing to schedule a
callback to fire later.  We'll gradually convert them to use this
interface.

Signed-off-by: Samuel Just <sjust@redhat.com>
(cherry picked from commit a90b46b774d3c3d508b0563f8327892e4232ce66)

src/osd/scrubber/pg_scrubber.cc
src/osd/scrubber/pg_scrubber.h
src/osd/scrubber/scrub_machine.h
src/osd/scrubber/scrub_machine_lstnr.h

index 893e59e74d8cd52b7a945d4306221630e209d462..dbfbce7c34262dd75d37bdb075000e65d373c048 100644 (file)
@@ -660,6 +660,29 @@ int size_from_conf(
 }
 }  // anonymous namespace
 
+PgScrubber::scrubber_callback_cancel_token_t
+PgScrubber::schedule_callback_after(
+  ceph::timespan duration, scrubber_callback_t &&cb)
+{
+  std::lock_guard l(m_osds->sleep_lock);
+  return m_osds->sleep_timer.add_event_after(
+    duration,
+    new LambdaContext(
+      [this, pg=PGRef(m_pg), cb=std::move(cb), epoch=get_osdmap_epoch()] {
+       pg->lock();
+       if (check_interval(epoch)) {
+         cb();
+       }
+       pg->unlock();
+      }));
+}
+
+void PgScrubber::cancel_callback(scrubber_callback_cancel_token_t token)
+{
+  std::lock_guard l(m_osds->sleep_lock);
+  m_osds->sleep_timer.cancel_event(token);
+}
+
 /*
  * The selected range is set directly into 'm_start' and 'm_end'
  * setting:
index f172f5fe9bde5cee26429c3ec328e69624f8a842..a3d25fd3439002e1472f3654cda20c2672f58bd8 100644 (file)
@@ -498,6 +498,11 @@ class PgScrubber : public ScrubPgIF,
   // the I/F used by the state-machine (i.e. the implementation of
   // ScrubMachineListener)
 
+  scrubber_callback_cancel_token_t schedule_callback_after(
+    ceph::timespan duration, scrubber_callback_t &&cb);
+
+  void cancel_callback(scrubber_callback_cancel_token_t);
+
   [[nodiscard]] bool is_primary() const final
   {
     return m_pg->recovery_state.is_primary();
index 038668fb264ca3988c1eb7541dae4d83069a4242..ecbdc2d63a1589080582bab983fc25e8e6193fee 100644 (file)
@@ -153,6 +153,126 @@ class ScrubMachine : public sc::state_machine<ScrubMachine, NotActive> {
   void assert_not_active() const;
   [[nodiscard]] bool is_reserving() const;
   [[nodiscard]] bool is_accepting_updates() const;
+
+private:
+  /**
+   * scheduled_event_state_t
+   *
+   * Heap allocated, ref-counted state shared between scheduled event callback
+   * and timer_event_token_t.  Ensures that callback and timer_event_token_t
+   * can be safetly destroyed in either order while still allowing for
+   * cancellation.
+   */
+  struct scheduled_event_state_t {
+    bool canceled = false;
+    ScrubMachineListener::scrubber_callback_cancel_token_t cb_token = nullptr;
+
+    operator bool() const {
+      return nullptr != cb_token;
+    }
+
+    ~scheduled_event_state_t() {
+      /* For the moment, this assert encodes an assumption that we always
+       * retain the token until the event either fires or is canceled.
+       * If a user needs/wants to relaxt that requirement, this assert can
+       * be removed */
+      assert(!cb_token);
+    }
+  };
+public:
+  /**
+   * timer_event_token_t
+   *
+   * Represents in-flight timer event.  Destroying the object or invoking
+   * release() directly will cancel the in-flight timer event preventing it
+   * from being delivered.  The intended usage is to invoke
+   * schedule_timer_event_after in the constructor of the state machine state
+   * intended to handle the event and assign the returned timer_event_token_t
+   * to a member of that state. That way, exiting the state will implicitely
+   * cancel the event.  See RangedBlocked::m_timeout_token and
+   * RangeBlockedAlarm for an example usage.
+   */
+  class timer_event_token_t {
+    friend ScrubMachine;
+
+    // invariant: (bool)parent == (bool)event_state
+    ScrubMachine *parent = nullptr;
+    std::shared_ptr<scheduled_event_state_t> event_state;
+
+    timer_event_token_t(
+      ScrubMachine *parent,
+      std::shared_ptr<scheduled_event_state_t> event_state)
+      :  parent(parent), event_state(event_state) {
+      assert(*this);
+    }
+
+    void swap(timer_event_token_t &rhs) {
+      std::swap(parent, rhs.parent);
+      std::swap(event_state, rhs.event_state);
+    }
+
+  public:
+    timer_event_token_t() = default;
+    timer_event_token_t(timer_event_token_t &&rhs) {
+      swap(rhs);
+      assert(static_cast<bool>(parent) == static_cast<bool>(event_state));
+    }
+
+    timer_event_token_t &operator=(timer_event_token_t &&rhs) {
+      swap(rhs);
+      assert(static_cast<bool>(parent) == static_cast<bool>(event_state));
+      return *this;
+    }
+
+    operator bool() const {
+      assert(static_cast<bool>(parent) == static_cast<bool>(event_state));
+      return parent;
+    }
+
+    void release() {
+      if (*this) {
+       if (*event_state) {
+         parent->m_scrbr->cancel_callback(event_state->cb_token);
+         event_state->canceled = true;
+         event_state->cb_token = nullptr;
+       }
+       event_state.reset();
+       parent = nullptr;
+      }
+    }
+
+    ~timer_event_token_t() {
+      release();
+    }
+  };
+
+  /**
+   * schedule_timer_event_after
+   *
+   * Schedules event EventT{Args...} to be delivered duration in the future.
+   * The implementation implicitely drops the event on interval change.  The
+   * returned timer_event_token_t can be used to cancel the event prior to
+   * its delivery -- it should generally be embedded as a member in the state
+   * intended to handle the event.  See the comment on timer_event_token_t
+   * for further information.
+   */
+  template <typename EventT, typename... Args>
+  timer_event_token_t schedule_timer_event_after(
+    ceph::timespan duration, Args&&... args) {
+    auto token = std::make_shared<scheduled_event_state_t>();
+    token->cb_token = m_scrbr->schedule_callback_after(
+      duration,
+      [this, token, event=EventT(std::forward<Args>(args)...)] {
+       if (!token->canceled) {
+         token->cb_token = nullptr;
+         process_event(std::move(event));
+       } else {
+         assert(nullptr == token->cb_token);
+       }
+      }
+    );
+    return timer_event_token_t{this, token};
+  }
 };
 
 /**
index a8e77d075eb158bdbc092eb279c09dde8c0fe532..542d9f52b6f44101e6296f5cf565cd554234dd4e 100644 (file)
@@ -72,6 +72,32 @@ using BlockedRangeWarning = std::unique_ptr<blocked_range_t>;
 }  // namespace Scrub
 
 struct ScrubMachineListener {
+  using scrubber_callback_t = std::function<void(void)>;
+  using scrubber_callback_cancel_token_t = Context*;
+
+  /**
+   * schedule_callback_after
+   *
+   * cb will be invoked after least duration time has elapsed.
+   * Interface implementation is responsible for maintaining and locking
+   * a PG reference.  cb will be silently discarded if the interval has changed
+   * between the call to schedule_callback_after and when the pg is locked.
+   *
+   * Returns an associated token to be used in cancel_callback below.
+   */
+  virtual scrubber_callback_cancel_token_t schedule_callback_after(
+    ceph::timespan duration, scrubber_callback_t &&cb) = 0;
+
+  /**
+   * cancel_callback
+   *
+   * Attempts to cancel the callback to whcih the passed token is associated.
+   * cancel_callback is best effort, the callback may still fire.
+   * cancel_callback guarrantees that exactly one of the two things will happen:
+   * - the callback is destroyed and will not be invoked
+   * - the callback will be invoked
+   */
+  virtual void cancel_callback(scrubber_callback_cancel_token_t) = 0;
 
   struct MsgAndEpoch {
     MessageRef m_msg;