10#include <unordered_map>
13#include "../attributes.hpp"
14#include "../core/completion.hpp"
16#include "../core/executor.hpp"
17#include "../core/logger.hpp"
18#include "../core/observability.hpp"
20#include "offline_queue.hpp"
22namespace morph::offline {
33enum class ReplayOutcome : std::uint8_t {
186 [replay = std::move(replay)](const std::string& payload) {
187 return replay(payload) ? ReplayOutcome::Succeeded : ReplayOutcome::Rejected;
189 std::move(deadLetterSink)} {}
218 : _owner{owner}, _queue{queue}, _replay{std::move(replay)}, _deadLetterSink{std::move(deadLetterSink)} {}
242 if (::morph::exec::runningOn(_owner)) {
243 settleable.second.resolve(drainHere());
244 return std::move(settleable.first);
247 std::move(settleable.second))] { promise->resolve(drainHere()); });
248 return std::move(settleable.first);
257 void stop() { _stopped.store(
true); }
264 ::morph::exec::detail::noteOwner(
"SyncWorker::run", _owner.coreExecutor(), ::morph::exec::runningOn(_owner));
265 bool const wasStoppedBeforeRun = _stopped.exchange(
false);
267 if (wasStoppedBeforeRun) {
271 auto items = _queue.drain();
272 ::morph::observe::detail::emitMetric(::morph::observe::Metric::queueDepth,
static_cast<double>(items.size()));
273 for (
auto& item : items) {
274 if (_stopped.load()) {
278 ReplayOutcome outcome = ReplayOutcome::Rejected;
280 outcome = _replay(item.payload);
287 outcome = ReplayOutcome::Rejected;
289 if (outcome == ReplayOutcome::Succeeded) {
290 _queue.markDone(item.id);
291 _attempts.erase(item.id);
295 if (outcome == ReplayOutcome::Undelivered) {
309 auto& counter = _attempts[item.id];
310 counter = std::max(counter, item.attempts);
312 _queue.setAttempts(item.id, counter);
313 if (counter >= kMaxAttempts) {
314 if (_deadLetterSink) {
316 poisoned.attempts = counter;
318 _deadLetterSink(poisoned);
320 ::morph::log::logError(
"[sync_worker] dead-letter sink threw while handling payload: " +
324 ::morph::log::logError(
"[sync_worker] dropping payload after " + std::to_string(kMaxAttempts) +
325 " failed attempts: " + item.payload);
327 _queue.markDone(item.id);
328 _attempts.erase(item.id);
339 static constexpr uint32_t kMaxAttempts = 5;
341 ::morph::exec::IExecutor& _owner;
345 DetailedReplayFunction _replay;
346 DeadLetterSink _deadLetterSink;
347 std::atomic<bool> _stopped{
false};
348 std::unordered_map<uint64_t, uint32_t> _attempts;
Producer-side handle paired with a Completion<T> by makeSettleable().
Move-only handle representing the eventual result of an asynchronous operation.
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
std::function< void(const QueueItem &poisoned)> DeadLetterSink
Callable invoked when an item exhausts its retry budget, just before it is removed from the queue.
std::function< bool(const std::string &payload)> ReplayFunction
Callable that attempts to replay a single queued item, reporting only success or failure.
SyncWorker(::morph::exec::IExecutor &owner, IOfflineQueue &queue, DetailedReplayFunction replay, DeadLetterSink deadLetterSink=nullptr)
Constructs a worker that drains queue using replay, which distinguishes an undelivered replay from a ...
std::function< ReplayOutcome(const std::string &payload)> DetailedReplayFunction
Callable that attempts to replay a single queued item and reports what it achieved.
::morph::async::Completion< SyncResult > run(::morph::exec::IExecutor &replyExec)
Drains the queue, replaying each item via the replay function, on the owner.
void stop()
Signals an in-progress drain to stop after the current item.
SyncWorker(::morph::exec::IExecutor &owner, IOfflineQueue &queue, ReplayFunction replay, DeadLetterSink deadLetterSink=nullptr)
Constructs a worker that drains queue using replay, on owner.
The check every loop-owned body makes on entry: "am I on my owner?".
Compile-time profiler instrumentation: Tracy zones, plots, messages and thread names,...
#define MORPH_ZONE(name)
Opens a profiler zone named name that ends with the enclosing scope. At most one per scope.
Abstract executor interface.
Interface for durable storage of actions that could not be delivered.
An item stored in the offline queue.
Aggregated result returned by SyncWorker::run().
int undelivered
Number of items whose replay never reached the peer. They remain in the queue with their attempt coun...
int deadLettered
Number of items that exhausted their retry budget and were dropped from the queue (handed to the Dead...
int successful
Number of items successfully replayed and removed from the queue.
int failed
Number of items that were delivered, refused, and left in the queue for retry, each having spent one ...