|
morph 0.1.0
Asynchronous bridge between GUI thread and business models
|
Reference SQLite-backed IOfflineQueue: persists payload, idempotencyKey, and attempts across process restarts. More...
#include <sqlite_offline_queue.hpp>
Public Types | |
| enum class | Synchronous : std::uint8_t { normal , full } |
| How hard each committed statement is pushed onto the platter. More... | |
Public Member Functions | |
| int | synchronousLevel () const noexcept |
| SQLite's numeric PRAGMA synchronous level for this connection, as read back at construction. | |
| const std::string & | journalMode () const noexcept |
| The journal_mode this database actually ended up in. | |
| SqliteOfflineQueue (::morph::exec::IExecutor &owner, std::filesystem::path path, std::optional< std::size_t > maxDepth=std::nullopt, ::morph::core::FileIoOps ioOps={}, Synchronous synchronous=Synchronous::normal, std::chrono::milliseconds busyTimeout=std::chrono::milliseconds{kBusyTimeoutMillis}, ::core::platform::WallClockRef wallClock=::core::platform::defaultSystemWallClock()) | |
Opens (or creates) the queue database at path, creating the schema if it does not already exist; the queue belongs to owner. | |
| SqliteOfflineQueue (const SqliteOfflineQueue &)=delete | |
| SqliteOfflineQueue & | operator= (const SqliteOfflineQueue &)=delete |
| SqliteOfflineQueue (SqliteOfflineQueue &&)=delete | |
| SqliteOfflineQueue & | operator= (SqliteOfflineQueue &&)=delete |
| uint64_t | enqueue (std::string payload) override |
Inserts payload with an empty idempotency key. On the owner. | |
| uint64_t | enqueue (std::string payload, std::string idempotencyKey) override |
Inserts payload carrying idempotencyKey in one write. On the owner. A non-empty key already present on a row is deduplicated: the existing row's id is returned and nothing new is inserted. | |
| std::vector< QueueItem > | drain () const override |
| Returns all pending rows in ascending-id (enqueue) order. On the owner. | |
| void | markDone (uint64_t itemId) override |
Deletes the row identified by itemId, on the owner. No-op if absent. | |
| void | setAttempts (uint64_t itemId, Attempts attempts) override |
Persists an updated attempt count for itemId, on the owner. No-op if absent. | |
| std::size_t | size () const override |
| Returns the number of pending rows. On the owner. | |
| std::optional< std::size_t > | maxDepth () const override |
| Returns the configured maximum depth, or std::nullopt if unbounded. Callable anywhere: fixed at construction. | |
| ::morph::async::Completion< std::vector< QueueItem > > | drain (::morph::exec::IExecutor &replyExec) const |
Snapshots the pending items on the queue's owner; delivered on replyExec. | |
| ::morph::async::Completion< uint64_t > | enqueue (::morph::exec::IExecutor &replyExec, std::string payload, std::string idempotencyKey={}) |
Appends payload carrying idempotencyKey on the queue's owner; the new item's id is delivered on replyExec. | |
| ::morph::async::Completion< std::size_t > | size (::morph::exec::IExecutor &replyExec) const |
Counts the pending items on the queue's owner; delivered on replyExec. | |
| Public Member Functions inherited from morph::offline::IOfflineQueue | |
| ::morph::async::Completion< uint64_t > | enqueue (::morph::exec::IExecutor &replyExec, std::string payload, std::string idempotencyKey={}) |
Appends payload carrying idempotencyKey on the queue's owner; the new item's id is delivered on replyExec. | |
| ::morph::async::Completion< std::vector< QueueItem > > | drain (::morph::exec::IExecutor &replyExec) const |
Snapshots the pending items on the queue's owner; delivered on replyExec. | |
| ::morph::async::Completion< std::size_t > | size (::morph::exec::IExecutor &replyExec) const |
Counts the pending items on the queue's owner; delivered on replyExec. | |
Static Public Attributes | |
| static constexpr int | kBusyTimeoutMillis = 5000 |
| PRAGMA busy_timeout set at construction: how long a statement blocks on SQLITE_BUSY before giving up, in milliseconds. | |
Protected Member Functions | |
| void | setIdempotencyKey (uint64_t itemId, std::string idempotencyKey) override |
Stamps an idempotency key onto an already-inserted row, on the owner. No-op if itemId is absent. Reachable only if a caller invokes the base IOfflineQueue::enqueue(payload, key) default through an IOfflineQueue& – this class's own enqueue(payload, key) override above stamps the key inline in the same INSERT instead. | |
| ::morph::async::Completion< uint64_t > | askEnqueue (::morph::exec::IExecutor &replyExec, std::string payload, std::string idempotencyKey) override |
Enqueues on the owner, answered on replyExec. | |
| ::morph::async::Completion< std::vector< QueueItem > > | askDrain (::morph::exec::IExecutor &replyExec) const override |
Reads every pending row on the owner, answered on replyExec. | |
| ::morph::async::Completion< std::size_t > | askSize (::morph::exec::IExecutor &replyExec) const override |
Counts the rows on the owner, answered on replyExec. | |
Reference SQLite-backed IOfflineQueue: persists payload, idempotencyKey, and attempts across process restarts.
Schema (one table, morph_offline_queue):
id is AUTOINCREMENT, so ids are never reused and a re-opened queue re-presents each row under its stored id — stable across restarts. QueueItem::id remains queue-local (per docs/spec/offline/offline.md); cross-restart identity is carried by idempotencyKey, not id.
The partial unique index gives insert-time dedup for a non-empty idempotencyKey: a re-enqueue of the same key is a no-op that returns the existing row's id (INSERT ... ON CONFLICT ... DO NOTHING, then a lookup on a no-op conflict). Empty keys (the default) are exempt, so keyless items behave exactly as InMemoryOfflineQueue does — never deduplicated.
Definition at line 115 of file sqlite_offline_queue.hpp.
|
strong |
How hard each committed statement is pushed onto the platter.
| Enumerator | |
|---|---|
| normal | PRAGMA synchronous=NORMAL — the default, and SQLite's own recommendation under WAL. A crash or power loss can lose the most recent commits, which this queue's at-least-once delivery plus idempotencyKey dedup already absorb. |
| full | PRAGMA synchronous=FULL — fsync on every committed statement. Measured ~18x slower per mutation (~0.08ms to ~1.44ms on NVMe/btrfs, sqlite 3.53.4), and every mutation here is its own commit, so a 200-item SyncWorker drain goes from ~16ms to ~290ms — all of it on the queue's owner, where it holds up the producer's enqueue() too. Opt in when losing the last few commits is genuinely unacceptable. |
Definition at line 129 of file sqlite_offline_queue.hpp.
|
inlineexplicit |
Opens (or creates) the queue database at path, creating the schema if it does not already exist; the queue belongs to owner.
Runs on the constructing thread, before anything else can reach the queue.
| owner | The executor every statement runs on; must run one task at a time. Borrowed: it must outlive this queue and run what it posts. |
| path | SQLite database file. |
| maxDepth | Maximum number of pending rows enqueue() will admit before throwing OfflineQueueFullError; std::nullopt (the default) means unbounded. Not persisted in the database itself — a per-construction parameter, so a reopen must pass it again to keep the same cap enforced. |
| ioOps | Injectable directory-fsync primitive (FileIoOps::syncPath only – the rest of this class talks to SQLite directly, never through FileIoOps); defaults to the real syscall. Test-only seam, mirroring FileActionLog/FileOfflineQueue's own FileIoOps parameter, for forcing the directory-fsync failure branch below without needing a real OS-level failure. |
| synchronous | PRAGMA synchronous level; see Synchronous. Defaults to normal. |
| busyTimeout | How long a statement waits out a lock another connection holds before returning SQLITE_BUSY. Defaults to kBusyTimeoutMillis. One owner running every statement makes SQLITE_BUSY unreachable for a single instance, so the timeout only matters when something else has the database open — but when it does, the wait happens on the owner and holds up every other caller of this instance, a Qt GUI thread included. Pass 0 to restore fail-fast. |
| wallClock | Clock the enqueued_at column is stamped from; the system clock by default. Borrowed, so it must outlive the queue. |
| SqliteOfflineQueueError | if the database cannot be opened, the schema cannot be created, or the containing directory cannot be fsynced after sqlite3_open() creates a brand-new file. A journal_mode that does not end up as wal is warned about, not thrown — see the constructor body. |
Definition at line 199 of file sqlite_offline_queue.hpp.
|
inlinenodiscardnoexcept |
SQLite's numeric PRAGMA synchronous level for this connection, as read back at construction.
0 OFF, 1 NORMAL, 2 FULL, 3 EXTRA. Reported rather than inferred from the constructor argument: the argument is what was asked for, this is what SQLite confirmed.
Definition at line 152 of file sqlite_offline_queue.hpp.
|
inlinenodiscardnoexcept |
The journal_mode this database actually ended up in.
Normally "wal". A filesystem without the shared-memory support WAL needs reports "delete"; :memory: and the temp spellings report "memory". Construction warns rather than throws on anything but "wal" (durability is carried by PRAGMA synchronous), so this is how a caller that cares can tell which mode it got.
Definition at line 162 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverridevirtual |
Inserts payload with an empty idempotency key. On the owner.
| payload | Serialised action to persist. |
| OfflineQueueFullError | if the queue is already at maxDepth(). |
Implements morph::offline::IOfflineQueue.
Definition at line 218 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverridevirtual |
Inserts payload carrying idempotencyKey in one write. On the owner. A non-empty key already present on a row is deduplicated: the existing row's id is returned and nothing new is inserted.
| payload | Serialised action to persist. |
| idempotencyKey | Stable dedup token; empty means "no dedup". |
| OfflineQueueFullError | if the queue is already at maxDepth(). Checked before the insert is attempted, so a call that would have resolved to a dedup hit (inserting nothing) can also be rejected when the queue happens to be full at the same time — a documented, accepted conservatism rather than an extra round trip to special-case it. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 235 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverridevirtual |
Returns all pending rows in ascending-id (enqueue) order. On the owner.
Implements morph::offline::IOfflineQueue.
Definition at line 241 of file sqlite_offline_queue.hpp.
|
inlineoverridevirtual |
Deletes the row identified by itemId, on the owner. No-op if absent.
Called off the owner, the delete is posted there and this returns at once; a statement that fails there is logged, and the row stays queued.
| itemId | Id returned by the corresponding enqueue() call. |
Implements morph::offline::IOfflineQueue.
Definition at line 250 of file sqlite_offline_queue.hpp.
|
inlineoverridevirtual |
Persists an updated attempt count for itemId, on the owner. No-op if absent.
| itemId | Id of the item whose count changed. |
| attempts | New cumulative attempt count to store. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 258 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverridevirtual |
Returns the number of pending rows. On the owner.
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 265 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverridevirtual |
Returns the configured maximum depth, or std::nullopt if unbounded. Callable anywhere: fixed at construction.
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 270 of file sqlite_offline_queue.hpp.
|
inlineoverrideprotectedvirtual |
Stamps an idempotency key onto an already-inserted row, on the owner. No-op if itemId is absent. Reachable only if a caller invokes the base IOfflineQueue::enqueue(payload, key) default through an IOfflineQueue& – this class's own enqueue(payload, key) override above stamps the key inline in the same INSERT instead.
| itemId | Id of the row to stamp. |
| idempotencyKey | Key to store. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 281 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverrideprotectedvirtual |
Enqueues on the owner, answered on replyExec.
| replyExec | Where the answer is delivered. |
| payload | Serialised action to persist. |
| idempotencyKey | Stable dedup token; may be empty. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 293 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverrideprotectedvirtual |
Reads every pending row on the owner, answered on replyExec.
| replyExec | Where the answer is delivered. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 304 of file sqlite_offline_queue.hpp.
|
inlinenodiscardoverrideprotectedvirtual |
Counts the rows on the owner, answered on replyExec.
| replyExec | Where the answer is delivered. |
Reimplemented from morph::offline::IOfflineQueue.
Definition at line 313 of file sqlite_offline_queue.hpp.
|
inlinenodiscard |
Snapshots the pending items on the queue's owner; delivered on replyExec.
| replyExec | Where the answer is delivered. Borrowed: it must outlive the completion. |
Definition at line 335 of file offline_queue.hpp.
|
inlinenodiscard |
Appends payload carrying idempotencyKey on the queue's owner; the new item's id is delivered on replyExec.
The form of enqueue() for a caller that is not on the queue's owner. A full queue rejects the completion with OfflineQueueFullError.
| replyExec | Where the answer is delivered, and where the caller attaches to it. Borrowed: it must outlive the completion. |
| payload | Serialised action to persist. |
| idempotencyKey | Stable dedup token for the logical op; may be empty. |
Definition at line 325 of file offline_queue.hpp.
|
inlinenodiscard |
Counts the pending items on the queue's owner; delivered on replyExec.
| replyExec | Where the answer is delivered. Borrowed: it must outlive the completion. |
Definition at line 345 of file offline_queue.hpp.
|
staticconstexpr |
PRAGMA busy_timeout set at construction: how long a statement blocks on SQLITE_BUSY before giving up, in milliseconds.
Definition at line 126 of file sqlite_offline_queue.hpp.