Skip to content

feat(opt): 提高日志缓存和增量读取性能、优化屏幕检测超时逻辑#597

Merged
Yundi339 merged 4 commits intomasterfrom
feat_optlogscache
Apr 15, 2026
Merged

feat(opt): 提高日志缓存和增量读取性能、优化屏幕检测超时逻辑#597
Yundi339 merged 4 commits intomasterfrom
feat_optlogscache

Conversation

@Yundi339
Copy link
Copy Markdown
Collaborator

@Yundi339 Yundi339 commented Apr 14, 2026

各场景与新设计的交互(偏移量模式)

场景 文件 ≤ 4MB(缓存模式) 文件 > 4MB(偏移量模式)
1 下载 不走缓存,直接流式读取 不走缓存,直接流式读取
2 轮询 从内存缓存返回增量 从磁盘按 offset 读增量
3 Fatal 检测 offset=0 → 返回完整缓存 offset=0 → read_file_tail 读最后 4MB
4 AI 诊断 同 3 同 3

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

Walkthrough

将服务器日志缓存重构为原子快照(包含内容与元数据)、新增按区间磁盘读取,按文件大小选择内存缓存或磁盘读取;客户端统一在请求 /api/logs 时发送 X-Log-Offset,下载流程改为先拉取文本并尝试 Tauri 保存,前端增加日志显示截断为末尾 4MB。

变更详情

Cohort / File(s) Summary
服务器:日志缓存与读取重构
src/confighttp.cpp
新增 confighttp::LogCacheSnapshotshared_ptr<const string> + file_size,mtime_ns,start_offset);新增 read_file_range(path, offset, length);移除旧的多原子缓存字段,改为通过 CAS 原子发布快照;getLogs 分两路:无 X-Log-Offset 时流式磁盘完整下载(Content-Disposition: attachment,省略 Content-Length);有 X-Log-Offset 时基于快照服务:<=4MB 时缓存内容并返回增量或全量,>4MB 时不缓存内容并按需从磁盘读取区间或尾部;响应头使用快照的 file_size,304 判定改为比较客户端偏移与快照文件大小。
前端:日志下载组件
src_assets/common/assets/web/components/LogsSection.vue
downloadLogs 改为先通过 GET /api/logs 获取文本(response.text()),在 Tauri 环境下调用 save_text_file(处理取消),若 Tauri 保存失败(非取消)回退到浏览器直接下载;移除本地 Blob 缓冲并添加 try/catch,失败时 console.error
前端:日志获取与显示截断
src_assets/common/assets/web/composables/useLogs.js, src_assets/common/assets/web/composables/useTroubleshooting.js
useLogs.js 现在在请求头始终发送 X-Log-Offset: 0useTroubleshooting.js 新增 MAX_LOG_DISPLAY_SIZE(4MB),每次成功获取后将 logs.value 截为末尾 4MB;对 /api/logs 的请求统一携带 X-Log-Offset(NaN 时使用 0)。
视频捕获:避免空循环自旋
src/video.cpp
在异步捕获循环中,当 display_wp 弱指针过期时加入短暂休眠(20ms),防止紧密重试自旋。

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (Browser / Tauri)
    participant Server as Server (/api/logs)
    participant Disk as Disk I/O

    Client->>Server: GET /api/logs (X-Log-Offset: N 或 缺省)
    alt X-Log-Offset 缺省
        Server->>Disk: open & stream full file
        Disk-->>Server: stream bytes
        Server-->>Client: 200 + Content-Disposition: attachment (stream)
    else X-Log-Offset 存在
        Server->>Disk: stat(file) -> (size, mtime_ns)
        Server-->>Server: 构建/刷新 LogCacheSnapshot(原子 CAS)
        alt snapshot.content != null (<=4MB)
            Server-->>Client: 200 + X-Log-Size/X-Log-Range + body (snapshot 子串)
        else snapshot.content == null (>4MB)
            Server->>Disk: read_file_range(path, offset, length) 或 read_file_tail(MAX)
            Disk-->>Server: bytes
            Server-->>Client: 200 + X-Log-Size/X-Log-Range + body (磁盘读取)
        end
    end
Loading

代码审查工作量估计

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed PR描述通过详细的表格清晰地阐述了不同文件大小场景下的缓存和读取策略,与changeset的设计目标和实现完全相关。
Title check ✅ Passed PR标题清晰准确,概括了主要改动:日志缓存优化、增量读取性能提升和屏幕检测超时逻辑优化,与变更集内容高度一致。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat_optlogscache

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src_assets/common/assets/web/components/LogsSection.vue`:
- Around line 130-164: The current code fetches the full log into JS memory via
fetch('/api/logs') then response.text() for all branches, causing large-memory
buffering; fix by only reading the response body for the Tauri branch.
Concretely: detect window.__TAURI_INTERNALS__ first and if present call
fetch('/api/logs') then await response.text() and invoke
window.__TAURI_INTERNALS__.invoke('save_text_file', { content, defaultName:
filename, ... }); for the standard browser branch avoid response.text()/Blob
creation and instead create an anchor with href set directly to '/api/logs' (or
'/api/logs?download=1' if needed) and download: filename, append/click/remove
the link so the browser streams the file without loading it into JS memory; keep
error handling around save_text_file and the overall try/catch.

In `@src/confighttp.cpp`:
- Around line 658-667: The code currently treats a nullptr from
read_file_tail/read_file_range as an empty log and proceeds to write a 200 with
an empty body; instead, detect when new_content is nullptr after calling
read_file_tail (and similarly for read_file_range in the other block) and
immediately return a 5xx error via response->write (e.g.,
SimpleWeb::StatusCode::server_error_internal_server_error with a clear message
like "Failed to read log file") rather than creating an empty shared_ptr or
continuing to set X-Log-Size; update both the new_content handling around
read_file_tail and the analogous handling around read_file_range so I/O failures
propagate as server errors.
- Around line 600-615: The code currently calls
std::filesystem::file_size(log_path) then streams an open std::ifstream "in" to
response->write with a fixed "Content-Length" header, which creates a TOCTOU bug
for a growing/rotating log; remove the headers.emplace("Content-Length", ...)
line (and any use of fsize) so the response is sent chunked, or alternatively
implement a stable snapshot by copying the log to a temp file or reading it
fully into a buffer and then set a correct Content-Length from that snapshot
before calling response->write; update confighttp.cpp around the std::ifstream
"in" / response->write usage accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f8c5bdab-0595-406a-8f2d-0ae570bc8616

📥 Commits

Reviewing files that changed from the base of the PR and between 7f11979 and f185cd4.

📒 Files selected for processing (4)
  • src/confighttp.cpp
  • src_assets/common/assets/web/components/LogsSection.vue
  • src_assets/common/assets/web/composables/useLogs.js
  • src_assets/common/assets/web/composables/useTroubleshooting.js
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src_assets/**/*.{vue,js,html}

⚙️ CodeRabbit configuration file

src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。

Files:

  • src_assets/common/assets/web/composables/useLogs.js
  • src_assets/common/assets/web/composables/useTroubleshooting.js
  • src_assets/common/assets/web/components/LogsSection.vue
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/confighttp.cpp
🔇 Additional comments (1)
src/confighttp.cpp (1)

624-628: C++ 标准已是 C++23,std::atomic<std::shared_ptr<>> 完全支持。

项目 CMake 配置明确设置了 CXX_STANDARD 23(见 cmake/targets/common.cmake),而 std::atomic<std::shared_ptr<T>> 的特化在 C++20 中已标准化,C++23 完全支持该特性。代码无需改动。

			> Likely an incorrect or invalid review comment.

Comment thread src_assets/common/assets/web/components/LogsSection.vue Outdated
Comment thread src/confighttp.cpp Outdated
Comment thread src/confighttp.cpp Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/confighttp.cpp (1)

654-659: ⚠️ Potential issue | 🟠 Major

不要把读取失败伪装成空日志。

read_file_tail() 返回 nullptr 后,这里仍然发布 content="" 的快照并继续走成功分支。前端会把现有日志清空并把 offset 推进到 current_size,真实的 I/O 故障会被误判成“日志已空”。这里应该直接返回 5xx,而不是构造空快照。

💡 可参考的改法
-        if (!new_content) {
-          new_content = std::make_shared<const std::string>();
-        }
+        if (!new_content) {
+          response->write(SimpleWeb::StatusCode::server_error_internal_server_error, "Failed to read log file");
+          return;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/confighttp.cpp` around lines 654 - 659, The code masks I/O failures by
treating read_file_tail(log_path, MAX_LOG_CACHE_SIZE) returning nullptr as an
empty log: when new_content is nullptr the handler currently creates an empty
shared_ptr and proceeds; instead, detect the nullptr from read_file_tail in the
request handling path (the block around read_file_tail / new_content) and return
an HTTP 5xx error response (e.g., 500) immediately, logging the underlying I/O
error, so you do not produce an empty snapshot or advance offsets; remove or
bypass the std::make_shared<const std::string>() fallback and ensure the client
receives a failure status rather than an empty content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/confighttp.cpp`:
- Around line 535-552: read_file_tail currently seeks from the live file end,
causing a race where size/mtime were sampled earlier (snapshot->file_size /
X-Log-Size) but the read can include new appended bytes and make current_size -
content->size() underflow; change the logic to read a fixed region based on the
sampled size/offset instead of re-reading from the current EOF: update
read_file_tail (or add an overload) to accept an explicit offset and length and
read exactly that range (compute offset = sampled_file_size - sampled_read_size)
so the body matches the previously recorded snapshot->file_size and the
X-Log-Size header, and ensure callers that use current_size - content->size()
keep using the sampled values rather than the live EOF.
- Around line 640-675: The current refresh logic unconditionally calls
log_cache.store(new_snap) which lets a slower thread overwrite a newer snapshot
and cause stale file_size/mtime to be used; change this to a CAS-style publish:
build new_snap, then attempt to atomically replace the current snapshot only if
the stored pointer still equals the earlier loaded snapshot (use
std::atomic_compare_exchange_* on log_cache or compare_exchange_strong on the
underlying atomic<std::shared_ptr<LogCacheSnapshot>>); if the CAS fails, discard
new_snap and reuse the already stored snapshot (do not call store), ensuring
subsequent logic (304 checks, try_incremental_log_read, start_offset) uses the
up-to-date snapshot.

---

Duplicate comments:
In `@src/confighttp.cpp`:
- Around line 654-659: The code masks I/O failures by treating
read_file_tail(log_path, MAX_LOG_CACHE_SIZE) returning nullptr as an empty log:
when new_content is nullptr the handler currently creates an empty shared_ptr
and proceeds; instead, detect the nullptr from read_file_tail in the request
handling path (the block around read_file_tail / new_content) and return an HTTP
5xx error response (e.g., 500) immediately, logging the underlying I/O error, so
you do not produce an empty snapshot or advance offsets; remove or bypass the
std::make_shared<const std::string>() fallback and ensure the client receives a
failure status rather than an empty content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8becf0fa-88c2-4126-893d-b03055d1d66c

📥 Commits

Reviewing files that changed from the base of the PR and between f185cd4 and 7a697e0.

📒 Files selected for processing (2)
  • src/confighttp.cpp
  • src_assets/common/assets/web/components/LogsSection.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src_assets/common/assets/web/components/LogsSection.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/confighttp.cpp
🔇 Additional comments (1)
src/confighttp.cpp (1)

597-612: 下载分支去掉 Content-Length 这个改动是对的。

热日志文件会持续增长,这里改成直接流式输出能避免响应长度和实际内容不一致的问题。

Comment thread src/confighttp.cpp Outdated
Comment thread src/confighttp.cpp
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/confighttp.cpp (1)

634-640: ⚠️ Potential issue | 🟠 Major

缓存模式下 I/O 失败时仍可能返回 200 + 空内容。

read_file_range 返回 nullptr(I/O 失败)时,代码将 new_content 设为空字符串,然后仅在文件不存在时返回错误。这意味着如果文件存在但因权限问题或被锁定而无法读取,会以 200 状态码返回空内容,可能导致前端误清空日志显示。

对比偏移量模式(第 718-721 行)正确地在读取失败时返回 500 错误。建议在此处保持一致的错误处理逻辑。
,

💡 建议改法
       if (!new_content) {
-        new_content = std::make_shared<const std::string>();
-      }
-      if (new_content->empty() && !std::filesystem::exists(log_path, ec)) {
-        response->write(SimpleWeb::StatusCode::server_error_internal_server_error, "Log file not available");
-        return;
+        if (current_size > 0) {
+          response->write(SimpleWeb::StatusCode::server_error_internal_server_error, "Failed to read log file");
+          return;
+        }
+        new_content = std::make_shared<const std::string>();
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/confighttp.cpp` around lines 634 - 640, When read_file_range fails and
returns nullptr the code currently replaces new_content with an empty string and
only returns 500 when the file does not exist; instead detect the read failure
(new_content == nullptr from read_file_range) and return a
server_error_internal_server_error via response->write just like the offset mode
handling (see the branch around lines 718-721). Concretely, do not overwrite a
nullptr with an empty shared string for new_content; if new_content is nullptr,
call response->write(SimpleWeb::StatusCode::server_error_internal_server_error,
"Log file not available") and return, otherwise continue with the existing
empty-file/file-exists check that uses log_path and ec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/confighttp.cpp`:
- Around line 634-640: When read_file_range fails and returns nullptr the code
currently replaces new_content with an empty string and only returns 500 when
the file does not exist; instead detect the read failure (new_content == nullptr
from read_file_range) and return a server_error_internal_server_error via
response->write just like the offset mode handling (see the branch around lines
718-721). Concretely, do not overwrite a nullptr with an empty shared string for
new_content; if new_content is nullptr, call
response->write(SimpleWeb::StatusCode::server_error_internal_server_error, "Log
file not available") and return, otherwise continue with the existing
empty-file/file-exists check that uses log_path and ec.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 005481f2-1efc-4bee-b167-702cf47829db

📥 Commits

Reviewing files that changed from the base of the PR and between 7a697e0 and 2272dcb.

📒 Files selected for processing (1)
  • src/confighttp.cpp
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/confighttp.cpp
🔇 Additional comments (5)
src/confighttp.cpp (5)

500-508: LGTM!

快照结构设计合理,使用 shared_ptr<const std::string> 确保内容不可变,适合原子发布模式。在偏移量模式下 contentnullptr 的设计也很清晰。


534-549: LGTM!

read_file_range 实现简洁,错误处理正确(失败返回 nullptr),ifstream 使用 RAII 模式自动管理资源。


576-590: LGTM!

下载模式正确省略了 Content-Length,避免了对活跃写入日志文件的 TOCTOU 风险,让响应走分块传输。


650-657: LGTM!

CAS 发布模式正确实现,避免了并发线程用旧快照覆盖新快照的问题。compare_exchange_strong 失败时会自动更新 snapshot 为当前已发布的值,确保后续逻辑使用最新数据。


688-724: LGTM!

响应处理逻辑清晰:

  • 缓存模式正确计算增量偏移并返回差异内容
  • 偏移量模式使用 snapshot->file_size 界定读取范围,避免 TOCTOU 问题
  • 偏移量模式下读取失败正确返回 500 错误(第 718-721 行)

边界检查和模式切换逻辑完善。

@Yundi339 Yundi339 changed the title feat(logs): 优化日志缓存和增量读取以提升面板性能 feat(opt): 提高日志缓存和增量读取性能、优化屏幕检测超时逻辑 Apr 15, 2026
@Yundi339 Yundi339 merged commit b4d5e20 into master Apr 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant