|
morph 0.1.0
Asynchronous bridge between GUI thread and business models
|
Append-only, newline-delimited-JSON IActionLog backed by a local file. More...
#include <file_action_log.hpp>
Public Member Functions | |
| FileActionLog (::morph::exec::IExecutor &owner, std::filesystem::path path, ::morph::core::FileIoOps ioOps={}) | |
Opens (creating if necessary) path for appending, owned by owner. | |
| FileActionLog (const FileActionLog &)=delete | |
| FileActionLog & | operator= (const FileActionLog &)=delete |
| FileActionLog (FileActionLog &&)=delete | |
| FileActionLog & | operator= (FileActionLog &&)=delete |
| void | append (LogEntry entry) override |
Appends entry as one JSON line, on the owner. Buffered until flush(). | |
| void | flush () override |
| Flushes stdio's buffer, then fsyncs the file descriptor. On the owner. | |
| std::vector< LogEntry > | entries (std::string_view entityKey={}) const override |
| Re-reads the file from disk and decodes every line. On the owner. | |
| ::morph::exec::IExecutor * | owner () const noexcept override |
| The executor given at construction. | |
| void | rotate (const std::filesystem::path &sealedPath) |
| Seals the active file and reopens a fresh, empty one at the same path. On the owner. | |
| ::morph::async::Completion< std::vector< LogEntry > > | entries (::morph::exec::IExecutor &replyExec, std::string entityKey={}) const |
Returns recorded entries in append order, read on the log's owner and delivered on replyExec. | |
| ::morph::async::Completion< bool > | flush (::morph::exec::IExecutor &replyExec) |
Pushes buffered entries to the durable backend on the log's owner; the answer is delivered on replyExec. | |
| Public Member Functions inherited from morph::journal::IActionLog | |
| ::morph::async::Completion< std::vector< LogEntry > > | entries (::morph::exec::IExecutor &replyExec, std::string entityKey={}) const |
Returns recorded entries in append order, read on the log's owner and delivered on replyExec. | |
| ::morph::async::Completion< bool > | flush (::morph::exec::IExecutor &replyExec) |
Pushes buffered entries to the durable backend on the log's owner; the answer is delivered on replyExec. | |
Protected Member Functions | |
| ::morph::async::Completion< std::vector< LogEntry > > | askEntries (::morph::exec::IExecutor &replyExec, std::string entityKey) const override |
Reads the file on the owner, delivered on replyExec. | |
| ::morph::async::Completion< bool > | askFlush (::morph::exec::IExecutor &replyExec) override |
Flushes on the owner, answered on replyExec. | |
Append-only, newline-delimited-JSON IActionLog backed by a local file.
Each entry is written as one journal::toJson-encoded line. flush() flushes the C stdio buffer and then issues a real fsync (POSIX fsync / Windows _commit), so a crash immediately after flush() returns cannot lose data — this is the "local file" sink from the original request, and the natural target for SessionLog::checkpoint() at a "Save" action. Dedups append() on a non-empty LogEntry::idempotencyKey, rebuilding the seen-key set from disk at open time — see IActionLog's class docs and the constructor's docs.
Definition at line 65 of file file_action_log.hpp.
|
inline |
Opens (creating if necessary) path for appending, owned by owner.
Also rebuilds the idempotencyKey dedup set (see append()) from whatever is already on disk at path — an O(n) scan of the existing file's contents, paid once here, not on every append(). Runs on the constructing thread, before anything else can reach the log.
| owner | The executor every access runs on; must run one task at a time. Borrowed: it must outlive this log and run what it posts. |
| path | File to append entries to. |
| ioOps | Injectable file-I/O primitives; defaults to the real syscalls. Test-only seam — see morph::core::FileIoOps's own docs — for forcing the failure branches that need a real OS-level I/O error to reach. |
| std::runtime_error | if the file cannot be opened. |
| SerializationError | if an existing file at path has a malformed interior line (a malformed trailing line is tolerated — see entries()). |
Definition at line 87 of file file_action_log.hpp.
|
inlineoverridevirtual |
Appends entry as one JSON line, on the owner. Buffered until flush().
The entry's idempotencyKey is only remembered as seen once flush() confirms it reached the disk (see flush()), so a write that fails is still retryable. Recording the key up front — before the bytes were durable — silently deduplicated the retry of a row that was never written, turning a transient I/O error into permanent data loss.
Called off the owner, the write is posted there and this returns at once. A posted write that fails is logged, and the next flush() throws for it: the entry did not reach the backend.
| entry | Entry to append; seq is overwritten regardless of the input value. |
| std::runtime_error,on | the owner, if the write fails, or if the log has no open file (only reachable after a rotate() that failed to reopen). |
Implements morph::journal::IActionLog.
Definition at line 114 of file file_action_log.hpp.
|
inlineoverridevirtual |
Flushes stdio's buffer, then fsyncs the file descriptor. On the owner.
Throws rather than returning a status because the interface is void-returning and the failure is not one a caller may ignore: OutboxRelay::relay() calls markRelayed() immediately after this, and on a silent failure would record rows as relayed in the model's own store while nothing reached the durable sink — the rows are then gone from the outbox and absent from the log. Throwing aborts relay() before markRelayed, leaving the batch to be retried.
On failure the keys buffered since the last successful flush are forgotten, so a retry writes them again instead of being deduplicated away. A duplicated audit row is recoverable; a dropped one is not.
A write posted from another thread that failed since the last flush is reported here too, after the rest is made durable: that entry is not on disk.
| std::runtime_error | if the buffer flush or the fsync fails, if a posted append failed since the last flush, or if the log has no open file. |
Implements morph::journal::IActionLog.
Definition at line 150 of file file_action_log.hpp.
|
inlinenodiscardoverridevirtual |
Re-reads the file from disk and decodes every line. On the owner.
Reads whatever is currently on disk, including anything written but not yet flush()ed if the platform's stdio buffering has already handed it to the OS — callers that need a guaranteed-durable view should flush() first.
| entityKey | If non-empty, restricts the result to that entity's entries. |
Implements morph::journal::IActionLog.
Definition at line 160 of file file_action_log.hpp.
|
inlinenodiscardoverridevirtualnoexcept |
The executor given at construction.
Reimplemented from morph::journal::IActionLog.
Definition at line 166 of file file_action_log.hpp.
|
inline |
Seals the active file and reopens a fresh, empty one at the same path. On the owner.
Flushes (fflush + fsync/_commit) and closes the current active file, renames it to sealedPath, then reopens a fresh, empty active file at the original path. It runs on the owner, as every append() does, so no append is ever split across the sealed and the new active file. entries() keeps reading only the (post-rotation, empty) active file; composing full history across a rotation is a host-side recipe (concatenate each sealed segment's entries() oldest-to-newest, then the active file's), not a new API.
| sealedPath | Destination path for the sealed segment. Platform rename semantics decide what happens if a filesystem entry already exists there (POSIX silently replaces it) — pass a path that does not collide with an existing segment. |
| std::runtime_error | if the pre-rotation flush/fsync fails (raised before anything is closed or renamed, so no segment is ever sealed around entries that never reached the disk); if renaming to sealedPath fails — in that case the original active file is reopened in place (still holding every entry recorded before the call, so no data is lost; the rotation simply did not happen) — or if reopening the active path afterward fails outright (only possible if the rename itself succeeded and the original path's directory then became unwritable). After that last case the log has no open file: append(), flush(), and a further rotate() all throw with that diagnosis rather than dereferencing a null handle, and destruction is still safe. |
Definition at line 204 of file file_action_log.hpp.
|
inlinenodiscardoverrideprotectedvirtual |
Reads the file on the owner, delivered on replyExec.
| replyExec | Where the answer is delivered. |
| entityKey | If non-empty, restricts the result to that entity's entries. |
Reimplemented from morph::journal::IActionLog.
Definition at line 211 of file file_action_log.hpp.
|
inlinenodiscardoverrideprotectedvirtual |
Flushes on the owner, answered on replyExec.
| replyExec | Where the answer is delivered. |
Reimplemented from morph::journal::IActionLog.
Definition at line 221 of file file_action_log.hpp.
|
inlinenodiscard |
Returns recorded entries in append order, read on the log's owner and delivered on replyExec.
The form for a caller that is not on the owner: a model's strand, an application thread the log does not belong to. The read is ordered after every append() posted to the owner before it.
| replyExec | Where the answer is delivered, and where the caller attaches to it. Borrowed: it must outlive the completion. |
| entityKey | If non-empty, restricts the result to that entity's entries. |
Definition at line 231 of file action_log.hpp.
|
inlinenodiscard |
Pushes buffered entries to the durable backend on the log's owner; the answer is delivered on replyExec.
The form of flush() for a caller that is not on the owner. It is ordered after every append() posted to the owner before it.
| replyExec | Where the answer is delivered. Borrowed: it must outlive the completion. |
Definition at line 245 of file action_log.hpp.