refactor(console): phase 3 — thread-safe ConsoleModel ingest queue; fix data race
Introduce ConsoleModel (console_model.{h,cpp}): an ImGui-free, thread-safe line
store. Producers on any thread call ingest(), which only briefly locks a small
pending queue; the main thread calls drain() once per frame to move pending
lines into the visible deque and enforce the line cap, reporting how many were
added and evicted from the front.
This fixes the real data race. Previously a single lines_mutex_ guarded both the
line container AND the UI-only interaction state (selection, scroll, auto-scroll
counters), and it was held across the whole render pass — yet render() touched
auto_scroll_ / new_lines_since_scroll_ outside that lock, racing the background
RPC-trace and app-logger producers that call addLine() on worker threads.
Now:
- addLine() only ingests (lock-free w.r.t. the model); safe from any thread.
- render() drains once per frame, then does the cap/selection/scroll bookkeeping
on the main thread (new drainModel()).
- The visible deque + all selection/scroll fields are main-thread-only, so every
lines_mutex_ lock is gone — including the one wrapping the entire renderOutput.
Adds testConsoleModel() covering ingest/drain ordering, the line-cap eviction
counts, view-only clear (queued lines survive), and an 8-thread concurrent
ingest/drain stress with no lost lines. Full-node + lite build clean; ctest
green; source hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "console_channel.h"
|
||||
#include "console_model.h"
|
||||
#include "console_text_layout.h"
|
||||
#include "../layout.h"
|
||||
#include "../../daemon/embedded_daemon.h"
|
||||
@@ -85,16 +86,15 @@ public:
|
||||
static ImU32 COLOR_RPC;
|
||||
|
||||
private:
|
||||
// Each line stores its semantic ConsoleChannel; the text color and left accent-bar
|
||||
// color are both derived from it at draw time (channelTextColor / channelBarColor).
|
||||
struct ConsoleLine {
|
||||
std::string text;
|
||||
ConsoleChannel channel = ConsoleChannel::None;
|
||||
};
|
||||
|
||||
// Text color for a channel, resolved from the current theme (COLOR_* + material).
|
||||
// Text color for a channel, resolved from the current theme (COLOR_* + material). Each
|
||||
// line stores only its semantic ConsoleChannel (in ConsoleModel); the text color and
|
||||
// the left accent-bar color are both derived from it at draw time.
|
||||
ImU32 channelTextColor(ConsoleChannel channel) const;
|
||||
|
||||
// Drain the thread-safe ingest queue into the visible model (once per frame, main
|
||||
// thread), applying the line cap + selection/scroll bookkeeping for the new lines.
|
||||
void drainModel();
|
||||
|
||||
// Format a completed command result (JSON role -> channel) into console lines.
|
||||
void addFormattedResult(const std::string& result, bool is_error);
|
||||
void renderStatusHeader(ConsoleCommandExecutor& exec);
|
||||
@@ -117,7 +117,9 @@ private:
|
||||
TextPos selectionStart() const; // Returns the earlier of sel_anchor_ and sel_end_
|
||||
TextPos selectionEnd() const; // Returns the later of sel_anchor_ and sel_end_
|
||||
|
||||
std::deque<ConsoleLine> lines_;
|
||||
// Thread-safe line store: producers on any thread call model_.ingest(); the main
|
||||
// thread drains it once per frame in render(). The visible deque is main-thread-only.
|
||||
ConsoleModel model_;
|
||||
std::vector<std::string> command_history_;
|
||||
int history_index_ = -1;
|
||||
char input_buffer_[4096] = {0};
|
||||
@@ -141,9 +143,7 @@ private:
|
||||
int rowIndex = 0;
|
||||
};
|
||||
std::vector<ScanlineRow> scanline_rows_;
|
||||
|
||||
std::mutex lines_mutex_;
|
||||
|
||||
|
||||
// Output filter
|
||||
char filter_text_[128] = {0};
|
||||
mutable int filter_match_count_ = 0; // lines matching the text filter (for the toolbar)
|
||||
|
||||
Reference in New Issue
Block a user