From: Samuel Just Date: Tue, 14 Feb 2023 05:19:44 +0000 (-0800) Subject: osd/scrubber: add generic interface for scheduling a future event X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=55b1f869aa0af91ff808bcef0e4313753ced3a3d;p=ceph.git osd/scrubber: add generic interface for scheduling a future event 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 (cherry picked from commit a90b46b774d3c3d508b0563f8327892e4232ce66) --- diff --git a/src/osd/scrubber/pg_scrubber.cc b/src/osd/scrubber/pg_scrubber.cc index 893e59e74d8c..dbfbce7c3426 100644 --- a/src/osd/scrubber/pg_scrubber.cc +++ b/src/osd/scrubber/pg_scrubber.cc @@ -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: diff --git a/src/osd/scrubber/pg_scrubber.h b/src/osd/scrubber/pg_scrubber.h index f172f5fe9bde..a3d25fd34390 100644 --- a/src/osd/scrubber/pg_scrubber.h +++ b/src/osd/scrubber/pg_scrubber.h @@ -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(); diff --git a/src/osd/scrubber/scrub_machine.h b/src/osd/scrubber/scrub_machine.h index 038668fb264c..ecbdc2d63a15 100644 --- a/src/osd/scrubber/scrub_machine.h +++ b/src/osd/scrubber/scrub_machine.h @@ -153,6 +153,126 @@ class ScrubMachine : public sc::state_machine { 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 event_state; + + timer_event_token_t( + ScrubMachine *parent, + std::shared_ptr 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(parent) == static_cast(event_state)); + } + + timer_event_token_t &operator=(timer_event_token_t &&rhs) { + swap(rhs); + assert(static_cast(parent) == static_cast(event_state)); + return *this; + } + + operator bool() const { + assert(static_cast(parent) == static_cast(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 + timer_event_token_t schedule_timer_event_after( + ceph::timespan duration, Args&&... args) { + auto token = std::make_shared(); + token->cb_token = m_scrbr->schedule_callback_after( + duration, + [this, token, event=EventT(std::forward(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}; + } }; /** diff --git a/src/osd/scrubber/scrub_machine_lstnr.h b/src/osd/scrubber/scrub_machine_lstnr.h index a8e77d075eb1..542d9f52b6f4 100644 --- a/src/osd/scrubber/scrub_machine_lstnr.h +++ b/src/osd/scrubber/scrub_machine_lstnr.h @@ -72,6 +72,32 @@ using BlockedRangeWarning = std::unique_ptr; } // namespace Scrub struct ScrubMachineListener { + using scrubber_callback_t = std::function; + 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;