6#include <core/net/EventLoop.hpp>
7#include <core/platform/Clock.hpp>
12#include <unordered_map>
15#include "../attributes.hpp"
42namespace morph::async::detail {
56class TimeoutScheduler {
60 using Handle = std::uint64_t;
65 explicit TimeoutScheduler(::morph::exec::IoLoop& loop MORPH_LIFETIMEBOUND)
66 : _loop{&loop}, _state{std::make_shared<State>(loop)} {}
71 : _ownedLoop{std::make_unique<::morph::exec::IoLoop>()},
72 _loop{_ownedLoop.get()},
73 _state{std::make_shared<State>(*_ownedLoop)} {}
84 _loop->runAndWait([state = _state, &dropped] { dropped = state->close(); });
87 TimeoutScheduler(
const TimeoutScheduler&) =
delete;
88 TimeoutScheduler& operator=(
const TimeoutScheduler&) =
delete;
89 TimeoutScheduler(TimeoutScheduler&&) =
delete;
90 TimeoutScheduler& operator=(TimeoutScheduler&&) =
delete;
98 Handle schedule(std::chrono::milliseconds delay, std::function<
void()> callback) {
99 auto const deadline = _loop->loop().clock().now() +
delay;
100 Handle
const handle = _nextHandle.fetch_add(1, std::memory_order_relaxed) + 1;
101 _loop->post([state = _state, handle, deadline, callback = std::move(callback)]()
mutable {
105 state->arm(handle, deadline, std::exchange(callback,
nullptr));
140 void cancel(Handle handle) {
141 _loop->post([state = _state, handle] { state->disarm(handle); });
150 State* owner{
nullptr};
152 ::core::net::TimerId
id{};
153 std::function<void()> callback;
156 using Pending = std::unordered_map<Handle, Entry>;
160 explicit State(::morph::exec::IoLoop& ioLoop) : loop{ioLoop} {}
163 void arm(Handle handle, ::core::platform::SteadyTimePoint deadline, std::function<
void()> callback) {
164 ::morph::exec::detail::noteOwner(
"TimeoutScheduler::schedule", loop.loop(), loop.runningHere());
168 auto [
slot, inserted] = pending.try_emplace(
169 handle, Entry{.owner =
this, .handle = handle, .id = {}, .callback = std::move(callback)});
171 slot->second.id = loop.loop().addTimer(deadline, &State::fire, &
slot->second);
176 void disarm(Handle handle) {
177 ::morph::exec::detail::noteOwner(
"TimeoutScheduler::cancel", loop.loop(), loop.runningHere());
178 auto node = pending.extract(handle);
182 static_cast<void>(loop.loop().cancelTimer(node.mapped().id));
188 for (
auto const& entry : pending) {
189 static_cast<void>(loop.loop().cancelTimer(entry.second.id));
191 return std::exchange(pending, {});
196 static void fire(
void* entryPtr) {
197 auto const& entry = *
static_cast<Entry const*
>(entryPtr);
198 State&
self = *entry.owner;
203 auto node =
self.pending.extract(entry.handle);
207 std::function<void()>
const callback = std::move(node.mapped().callback);
211 }
catch (
const std::exception& exc) {
212 ::morph::log::logError(
"[timeout-scheduler] callback threw: {}", exc.what());
214 ::morph::log::logError(
"[timeout-scheduler] callback threw unknown exception");
219 ::morph::exec::IoLoop& loop;
226 std::unique_ptr<::morph::exec::IoLoop> _ownedLoop;
227 ::morph::exec::IoLoop* _loop;
228 std::atomic<Handle> _nextHandle{0};
229 std::shared_ptr<State> _state;
constexpr Self self
The dialog or drawer a command list runs in.
DelayAwaiter delay(detail::TimeoutScheduler &scheduler, std::chrono::milliseconds duration)
Suspends the awaiting coroutine for at least duration.
@ Pending
{"pending": "q"}: whether a query or mutation is in flight.
The check every loop-owned body makes on entry: "am I on my owner?".
SlotBinding< std::decay_t< F > > slot(SlotId slotId, F &&read)
A slot with an id.