/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #ifndef LOGRING_H #define LOGRING_H #include #include #include #include /** @brief The LogRing class Fixed-capacity, always-on in-memory ring of the most recent log lines, preallocated once at construction -- append() never allocates. Lock-free by construction, not just "thread-safe": step 4 (see crashhandler.h) reads this ring from inside a POSIX signal handler, where taking any lock is unsafe -- if the crashing thread happens to be the one that already holds it (or any other thread does and never gets scheduled again), the handler hangs forever, and you lose both the ring dump *and* the core dump. So there is no mutex here at all: append() claims a slot with a single atomic fetch-add, and dumpToFd()/snapshot() read the preallocated entries directly. Accepted tradeoff: if dumpToFd() runs while another thread is mid-append into the exact slot being read (only possible in the crash-handler case, and only for at most one slot), that one entry may be read torn -- part old content, part new. Every other entry is unaffected. This is deliberate: the alternative (a seqlock or similar to detect and retry torn reads) adds real complexity for a window that, per discussion #644, is not worth trading "the handler must never block" against. */ class LogRing { public: static constexpr int kCapacityEntries = 4096; static constexpr int kEntryBytes = 512; // 4096 * 512 = 2 MiB total LogRing(); /// Append one already-formatted, already-truncated log line. /// Bytes beyond kEntryBytes are dropped with a truncation marker. /// Never allocates, never blocks. Safe to call from any normal /// (non-signal) thread concurrently. void append(const QByteArray &line) noexcept; /// Snapshot of the entries currently held, oldest first. Normal /// (non-signal) context only. QVector snapshot() const; /// Async-signal-safe: writes every entry currently held to fd via /// write(2) only -- no allocation, no Qt, no locks. May write a /// torn entry under the rare race described above; never blocks. void dumpToFd(int fd) const noexcept; void clear(); private: struct Entry { char data[kEntryBytes]; std::atomic length{0}; // 0 = not yet written this lap }; std::vector m_entries; // preallocated once, capacity fixed std::atomic m_write_cursor{0}; // monotonically increasing }; #endif // LOGRING_H