Skip to content

fix(tauri): 修复 Windows 后端启动与前端就绪竞态#176

Closed
Li-Zhixian wants to merge 2 commits into
shenminglinyi:masterfrom
Li-Zhixian:fix/bugfix
Closed

fix(tauri): 修复 Windows 后端启动与前端就绪竞态#176
Li-Zhixian wants to merge 2 commits into
shenminglinyi:masterfrom
Li-Zhixian:fix/bugfix

Conversation

@Li-Zhixian

@Li-Zhixian Li-Zhixian commented Jun 2, 2026

Copy link
Copy Markdown

变更类型

Bug 修复

变更说明

修复 Windows/Tauri 启动后首屏请求 /novels/llm-control/prompts/stats 偶发 ERR_NETWORK 的问题。

同时修复干净环境下 Tauri resource 目录缺失导致的 build-script 校验失败,并增强后端子进程启动日志,便于定位后续 Windows 启动问题。

根因

前端只等待 Tauri IPC 返回后端端口,没有等待 FastAPI /health 真正就绪,导致首屏并行请求可能早于 HTTP 服务可用。

Windows dev 启动时,Tauri 后端 cwd 可能没有定位到仓库根目录,导致 Python 后端无法正确导入 interfaces

Tauri 配置引用了 out/tauri/plotpilot-backend 资源目录,但干净仓库没有该空目录,会触发 build-script 的 resource path 校验失败。

部分 Windows multiprocessing 相关类型标注存在导入期求值风险,可能影响后端/daemon 子进程启动。

修复

前端在 Tauri 模式下等待 /health 返回 200 后再设置 API baseURL 并挂载应用。

Tauri 后端启动逻辑向上查找包含 interfaces/main.py 的仓库根目录。

保留 out/tauri/plotpilot-backend/.gitkeep,只提交资源目录占位,不提交 PyInstaller 产物。

将共享状态相关 multiprocessing 类型标注改为不会触发导入期求值的形式。

后端子进程 stdout/stderr 转发到 Tauri 日志,避免静默吞掉 Python 启动错误。

架构影响

不修改 REST API 契约。

不修改数据库 schema。

不改变后端 FastAPI / daemon / shared-state 架构。

本次变更仅聚焦 Windows/Tauri 启动与前端就绪竞态修复。

验证

  • npm run test:tauri-readiness
  • Windows 环境 npm run build
  • Windows 环境 npm run tauri:dev
  • /health 返回 200
  • /api/v1/novels 返回 200
  • /api/v1/llm-control/prompts/stats 返回 200

Summary by CodeRabbit

  • New Features

    • Dedicated startup-failure UI with clear error messaging when backend init fails.
    • Longer Tauri backend wait and improved readiness verification.
  • Bug Fixes

    • Better backend startup reliability, clearer classified logs for backend output, and more robust project resource detection.
    • Ensure backend processes use UTF-8 and unbuffered output for reliable logging.
  • Tests

    • Added a Tauri readiness validation script and corresponding npm script.
  • Chores

    • Updated ignore rules to keep repository placeholders while excluding build/data artifacts.
  • Refactor

    • Internal typing and initialization code cleaned up for shared state handling.

@Li-Zhixian Li-Zhixian requested a review from shenminglinyi as a code owner June 2, 2026 13:20
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4726214-d045-4df5-83ae-997d5d98abbb

📥 Commits

Reviewing files that changed from the base of the PR and between 0309e4c and 465d661.

📒 Files selected for processing (3)
  • .gitignore
  • application/engine/services/shared_state_repository.py
  • frontend/src-tauri/src/backend.rs
✅ Files skipped from review due to trivial changes (1)
  • application/engine/services/shared_state_repository.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .gitignore
  • frontend/src-tauri/src/backend.rs

📝 Walkthrough

Walkthrough

This PR implements Tauri backend readiness detection and verification during startup, adds a startup-failure UI, improves Rust backend process logging and project-root detection, decouples Python shared-state typings from multiprocessing, and tightens .gitignore rules.

Changes

Tauri Backend Readiness Initialization

Layer / File(s) Summary
Backend readiness detection contract and helpers
frontend/src/api/config.ts
Frontend introduces waitForTauriBackendReady and supporting utilities to poll Tauri IPC for backend port and verify /health endpoint availability within a 120-second timeout, with specific error handling for port/health/IPC failure scenarios. initApiClient now uses this readiness routine before setting axios baseURL.
Startup failure UI and error handling
frontend/src/main.ts
Frontend renders a styled failure page with error details when API client initialization fails. Bootstrap flow logs errors, calls failure UI renderer, and exits early instead of mounting with uninitialized backend.
Rust backend process logging and root detection
frontend/src-tauri/src/backend.rs
Rust backend now uses line-based readers to classify child process stdout/stderr by severity keywords and log appropriately, and implements find_source_project_root helper to walk upward from resource/executable directories searching for interfaces/main.py instead of checking fixed paths. Also sets PYTHONIOENCODING and PYTHONUNBUFFERED for frozen backend processes.
Readiness validation test script and npm entry
frontend/scripts/test-tauri-readiness-gate.mjs, frontend/package.json
New Node-based test validates Tauri readiness behavior via regex assertions on source files, verifying timeout values, helper function presence, absence of hardcoded fallbacks, and proper bootstrap abort on init failure. npm test:tauri-readiness script added.

Python Type Decoupling and Build Configuration

Layer / File(s) Summary
Python type system decoupling from multiprocessing
application/engine/services/shared_state_repository.py, interfaces/main.py
Backend repository and interface type annotations decoupled from multiprocessing.Manager specifics using a SharedStateDict TypeAlias (MutableMapping[str, Any]). Constructor, method, and helper function signatures updated across SharedStateRepository, init_shared_state_repository, and inject_shared_dict. Removed multiprocessing import used only for typing.
Build artifact ignoring rules
.gitignore
PyInstaller out/ patterns refined to preserve out/tauri/plotpilot-backend/.gitkeep while ignoring output. Data directory rules updated to ignore data/ content while preserving data/.gitkeep placeholder.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through startup gates,
Backend readiness now validates!
Health checks bloom, no more silent waits,
Errors rendered, failures clearly state—
Type decoupling celebrates! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title is in Chinese and describes the main fix: addressing Windows backend startup and frontend readiness race condition in Tauri, which aligns with the changeset's core objective.
Description check ✅ Passed Description covers change type, detailed explanation, root causes, fixes, architecture impact, and verification steps, closely matching the template structure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
.gitignore (1)

168-169: ⚡ Quick win

data/.gitkeep 例外规则可能不会生效(父目录被整体忽略)

当前是先 data/!data/.gitkeep,在 Git ignore 语义下通常需要先重新包含父目录,或改成 data/* 才能稳定保留占位文件。建议改为下面这种更稳妥的写法:

Suggested diff
-# ── 运行时数据目录(整体忽略) ──
-data/
-!data/.gitkeep
+# ── 运行时数据目录(整体忽略,保留占位) ──
+data/*
+!data/.gitkeep
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 168 - 169, The current .gitignore uses "data/" then
"!data/.gitkeep", which can fail because ignoring the directory prevents
re-including the file; update the patterns so the directory itself isn't
ignored: replace "data/" with "data/*" (to ignore contents) and keep the
exception "!data/.gitkeep" (ensure the filename matches the actual placeholder),
e.g. use "data/*" followed by "!data/.gitkeep" so the placeholder file is
reliably preserved.
application/engine/services/shared_state_repository.py (1)

20-20: ⚡ Quick win

Keep the shared-state alias mapping-shaped instead of Any.

Line 20 turns the module’s main cross-process contract into Any, so invalid injections will now type-check and only fail later on the first .get/[]/del path. A MutableMapping[str, Any] alias keeps the import-time decoupling goal, but still preserves useful checking across __init__, set_shared_dict, init_shared_state_repository, and inject_shared_dict.

♻️ Suggested typing-only tightening
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, MutableMapping, Optional, TypeAlias

-SharedStateDict = Any
+SharedStateDict: TypeAlias = MutableMapping[str, Any]

Also applies to: 123-123, 142-142, 497-497, 513-513

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/engine/services/shared_state_repository.py` at line 20, Replace
the overly-broad SharedStateDict = Any alias with a mapping-shaped typing to
catch invalid injections earlier: change SharedStateDict to MutableMapping[str,
Any] (import MutableMapping and Any from typing) and update signatures/usages in
__init__, set_shared_dict, init_shared_state_repository, and inject_shared_dict
to use this alias so the cross-process contract remains decoupled but enforces
key/value mapping shape at type-check time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src-tauri/src/backend.rs`:
- Around line 31-55: The drainer in spawn_stdio_drainers uses BufReader::lines()
and breaks on Err (e.g., invalid UTF-8), which can stop draining stdout/stderr
and deadlock the frozen backend; update the frozen backend Command to set
PYTHONIOENCODING=utf-8 and PYTHONUNBUFFERED=1 (in the same code that configures
HF_*_OFFLINE envs) and change the drainer loops that currently use
BufReader::lines() (and call log::info! or log_backend_stderr_line) to be
resilient: do not break on Err from lines(), instead continue draining and
decode using a loss-tolerant approach (e.g., read bytes and use
std::str::from_utf8_lossy or handle the Err by logging the raw bytes and
continuing) so stdout/stderr pipes are always fully drained.

---

Nitpick comments:
In @.gitignore:
- Around line 168-169: The current .gitignore uses "data/" then
"!data/.gitkeep", which can fail because ignoring the directory prevents
re-including the file; update the patterns so the directory itself isn't
ignored: replace "data/" with "data/*" (to ignore contents) and keep the
exception "!data/.gitkeep" (ensure the filename matches the actual placeholder),
e.g. use "data/*" followed by "!data/.gitkeep" so the placeholder file is
reliably preserved.

In `@application/engine/services/shared_state_repository.py`:
- Line 20: Replace the overly-broad SharedStateDict = Any alias with a
mapping-shaped typing to catch invalid injections earlier: change
SharedStateDict to MutableMapping[str, Any] (import MutableMapping and Any from
typing) and update signatures/usages in __init__, set_shared_dict,
init_shared_state_repository, and inject_shared_dict to use this alias so the
cross-process contract remains decoupled but enforces key/value mapping shape at
type-check time.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2079c6ae-dd19-4da6-86b8-de02877184c8

📥 Commits

Reviewing files that changed from the base of the PR and between 1008f81 and 0309e4c.

📒 Files selected for processing (9)
  • .gitignore
  • application/engine/services/shared_state_repository.py
  • frontend/package.json
  • frontend/scripts/test-tauri-readiness-gate.mjs
  • frontend/src-tauri/src/backend.rs
  • frontend/src/api/config.ts
  • frontend/src/main.ts
  • interfaces/main.py
  • out/tauri/plotpilot-backend/.gitkeep

Comment thread frontend/src-tauri/src/backend.rs Outdated
@shenminglinyi

Copy link
Copy Markdown
Owner

感谢修 Windows / Tauri 启动竞态,这个方向很重要,安装包启动稳定性确实是近期重点。

这块在 v4.5.1 附近主线已经做过较大重写:后端启动、health check、端口探测、Tauri sidecar、前端 API 就绪逻辑都调整过。当前 PR 基于旧实现,继续合并会和现有启动链路冲突,所以我先关闭。

如果 readiness gate 或测试脚本里还有可以复用的部分,欢迎单独拆一个小 PR,或者联系我加入 dev 仓库先一起验证。确认没问题后再合 master,会更稳。感谢帮忙盯这个问题。

@shenminglinyi

Copy link
Copy Markdown
Owner

再补充说明一下:这次关闭 PR 只是基于当前 master 稳定性、冲突风险和合并策略做的整理,不代表否定大家的贡献。

大家已经投入的工作我都看到了,也会统一计入项目贡献者名单/贡献记录里。很多思路后续仍然会被吸收,只是现在为了尽量避免开源 master 出问题,我会更倾向于先在 dev 仓库里拆小块开发、验证、跑通,再合并到 master。

如果后续还愿意一起推进,欢迎直接联系我,我可以开放 dev 仓库权限。我们按模块拆 PR,一块一块稳着来。再次感谢大家愿意在这么早期、不成熟的阶段帮忙共建,真的感谢。

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.

2 participants