Conversation
There was a problem hiding this comment.
Hey - 我发现了两个问题,并留下了一些整体性的反馈:
- 在 async 函数中改用
std::fs::File::create会在异步运行时线程上引入阻塞的系统调用;可以考虑将文件创建移动到spawn_blocking段(或其他阻塞任务)中,以保证下载路径完全非阻塞。 - 在发送循环中使用
chunk.to_vec()会让每个分块多一次内存拷贝;可以考虑把 channel 的类型改成Bytes或Box<[u8]>之类,这样可以通过转移所有权来避免额外的分配和拷贝。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- 在 async 函数中改用 `std::fs::File::create` 会在异步运行时线程上引入阻塞的系统调用;可以考虑将文件创建移动到 `spawn_blocking` 段(或其他阻塞任务)中,以保证下载路径完全非阻塞。
- 在发送循环中使用 `chunk.to_vec()` 会让每个分块多一次内存拷贝;可以考虑把 channel 的类型改成 `Bytes` 或 `Box<[u8]>` 之类,这样可以通过转移所有权来避免额外的分配和拷贝。
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download.rs" line_range="385-394" />
<code_context>
- .get_ref()
- .sync_all()
+ // 等待写入线程完成,确保文件句柄关闭后再进行重命名等后续操作
+ let write_thread_result = write_handle
.await
- .map_err(|e| format!("同步文件失败: {}", e))?;
+ .map_err(|e| format!("写入任务异常: {}", e))?;
- // 显式关闭文件句柄,避免 Windows 上 rename 失败
- drop(writer);
+ if let Some(err) = download_err {
+ if is_cancelled {
+ // 取消路径:cancel_download() 已处理临时文件清理,新下载会用 File::create 覆盖。
+ // 此处 disarm 避免 guard 在 write_handle.await 之后才触发,误删新任务的临时文件。
+ temp_guard.disarm();
+ }
+ return Err(err);
+ }
+ write_thread_result?;
// 发送最终进度
</code_context>
<issue_to_address>
**suggestion:** 写入线程在网络错误和磁盘错误这两条路径上的错误上报略有不一致。
在磁盘错误路径中,`write_handle` 很可能会返回一个更详细的 `Err("写入文件失败: …")`,但当前逻辑只要 `download_err` 是 `Some`,就始终返回这个通用的错误信息,而忽略 `write_thread_result`。这会导致磁盘错误的详细信息丢失。建议在可能的情况下优先返回写入线程的错误(如果存在),或将两者结合起来,以便在磁盘错误场景下能暴露尽可能详细的信息。
</issue_to_address>
### Comment 2
<location path="src-tauri/src/commands/download.rs" line_range="389-395" />
<code_context>
- // 显式关闭文件句柄,避免 Windows 上 rename 失败
- drop(writer);
+ if let Some(err) = download_err {
+ if is_cancelled {
+ // 取消路径:cancel_download() 已处理临时文件清理,新下载会用 File::create 覆盖。
+ // 此处 disarm 避免 guard 在 write_handle.await 之后才触发,误删新任务的临时文件。
+ temp_guard.disarm();
+ }
+ return Err(err);
+ }
+ write_thread_result?;
</code_context>
<issue_to_address>
**question (bug_risk):** 取消路径现在依赖外部清理,并且会解除临时文件 guard,这可能改变了之前的清理行为。
在取消时,目前的流程是:(1) 刷新剩余分块,(2) 等待写入线程完成,然后 (3) 解除 `temp_guard`,假设 `cancel_download()` 已经删除了临时文件,并且未来的下载会重用/覆盖该路径。这样相当于把清理的所有权从 guard 转移到了 `cancel_download()`。因此,如果那个外部清理失败或者被跳过,临时文件就可能被泄漏。请确认这种变更在所有调用点上都是有意为之并且行为一致,或者考虑保持 guard 生效,并通过协调的方式确保对于同一路径只有最新的 guard 被允许执行删除。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The switch to std::fs::File::create inside an async function introduces a blocking syscall on the async runtime thread; consider moving file creation into the spawn_blocking section (or another blocking task) to keep the download path fully non-blocking.
- The use of chunk.to_vec() in the send loop doubles memory copies for each chunk; consider changing the channel type to something like Bytes or Box<[u8]> so you can transfer ownership without an extra allocation and copy.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The switch to std::fs::File::create inside an async function introduces a blocking syscall on the async runtime thread; consider moving file creation into the spawn_blocking section (or another blocking task) to keep the download path fully non-blocking.
- The use of chunk.to_vec() in the send loop doubles memory copies for each chunk; consider changing the channel type to something like Bytes or Box<[u8]> so you can transfer ownership without an extra allocation and copy.
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download.rs" line_range="385-394" />
<code_context>
- .get_ref()
- .sync_all()
+ // 等待写入线程完成,确保文件句柄关闭后再进行重命名等后续操作
+ let write_thread_result = write_handle
.await
- .map_err(|e| format!("同步文件失败: {}", e))?;
+ .map_err(|e| format!("写入任务异常: {}", e))?;
- // 显式关闭文件句柄,避免 Windows 上 rename 失败
- drop(writer);
+ if let Some(err) = download_err {
+ if is_cancelled {
+ // 取消路径:cancel_download() 已处理临时文件清理,新下载会用 File::create 覆盖。
+ // 此处 disarm 避免 guard 在 write_handle.await 之后才触发,误删新任务的临时文件。
+ temp_guard.disarm();
+ }
+ return Err(err);
+ }
+ write_thread_result?;
// 发送最终进度
</code_context>
<issue_to_address>
**suggestion:** Error reporting from the writer thread is slightly inconsistent between network-error and disk-error paths.
In the disk-error path, `write_handle` likely returns a detailed `Err("写入文件失败: …")`, but the current logic always returns the generic `download_err` whenever it is `Some`, ignoring `write_thread_result`. This loses useful detail for disk failures. Consider preferring the writer thread’s error (if present) over the generic channel-close message, or combining them so disk errors surface with maximum detail.
</issue_to_address>
### Comment 2
<location path="src-tauri/src/commands/download.rs" line_range="389-395" />
<code_context>
- // 显式关闭文件句柄,避免 Windows 上 rename 失败
- drop(writer);
+ if let Some(err) = download_err {
+ if is_cancelled {
+ // 取消路径:cancel_download() 已处理临时文件清理,新下载会用 File::create 覆盖。
+ // 此处 disarm 避免 guard 在 write_handle.await 之后才触发,误删新任务的临时文件。
+ temp_guard.disarm();
+ }
+ return Err(err);
+ }
+ write_thread_result?;
</code_context>
<issue_to_address>
**question (bug_risk):** The cancellation path now relies on external cleanup and disarms the temp-file guard, which may change the previous cleanup behavior.
On cancel, this now (1) flushes remaining chunks, (2) awaits the writer, then (3) disarms `temp_guard` assuming `cancel_download()` has already deleted the temp file and that future downloads will reuse/overwrite that path. This moves ownership of cleanup from the guard to `cancel_download()`, so if that external cleanup ever fails or is skipped, the temp file could be leaked. Please confirm this change is intentional and consistent at all call sites, or consider keeping the guard active and coordinating so only the latest guard for a given path is allowed to delete.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - 我发现了 1 个问题
给 AI Agents 的提示
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download.rs" line_range="262" />
<code_context>
+ .map_err(|e| format!("无法创建文件: {}", e))?;
+ let mut writer = std::io::BufWriter::with_capacity(512 * 1024, file);
+ let mut write_rx = write_rx;
+ while let Some(chunk) = write_rx.blocking_recv() {
+ writer
+ .write_all(&chunk)
</code_context>
<issue_to_address>
**issue (bug_risk):** 在 `tokio::sync::mpsc::Receiver` 上使用 `blocking_recv` 需要启用 tokio 的 `sync` 特性,但这里并没有启用。
在 `Cargo.toml` 中,这个 crate 只为 `tokio` 启用了 `features = ["rt"]`,所以这个调用在没有显式添加 `sync` 的情况下将无法通过编译(我们不能依赖它被以传递方式启用)。请在该 crate 中为 `tokio` 启用 `sync` 特性,或者重构代码以避免使用 `blocking_recv`(例如使用 `std::sync::mpsc`、crossbeam,或其他非阻塞模式)。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download.rs" line_range="262" />
<code_context>
+ .map_err(|e| format!("无法创建文件: {}", e))?;
+ let mut writer = std::io::BufWriter::with_capacity(512 * 1024, file);
+ let mut write_rx = write_rx;
+ while let Some(chunk) = write_rx.blocking_recv() {
+ writer
+ .write_all(&chunk)
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `blocking_recv` on `tokio::sync::mpsc::Receiver` requires the `sync` feature on tokio, which is not enabled here.
In `Cargo.toml` this crate only enables `tokio` with `features = ["rt"]`, so this call will fail to compile unless `sync` is explicitly added here (we can’t rely on it being pulled in transitively). Please either enable the `sync` feature for `tokio` in this crate, or refactor to avoid `blocking_recv` (e.g., use `std::sync::mpsc`, crossbeam, or another non-blocking pattern).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
该 PR 旨在提升 Tauri 后端文件下载的吞吐与健壮性,通过将异步网络读取与同步磁盘写入解耦,减少 runtime 被磁盘 I/O 阻塞的情况,同时加强取消/错误传播。
Changes:
- 使用有界
mpsc通道将下载数据从 async 下载循环传递到spawn_blocking的同步写入线程。 - 写入侧改为
std::io::BufWriter同步写入,并在结束时flush+sync_all确保持久化。 - 增加
bytes依赖以在通道中传递bytes::Bytes。
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src-tauri/src/commands/download.rs | 引入通道+阻塞写入任务以提升下载性能,并调整取消/错误处理流程 |
| src-tauri/Cargo.toml | 添加 bytes = "1" 依赖 |
| src-tauri/Cargo.lock | 锁文件同步新增的 bytes 依赖 |
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - 我已经审阅了你的更改,看起来非常棒!
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈来改进入代码评审。
Original comment in English
Hey - I've reviewed your changes and they look great!
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
该 PR 通过将下载过程中的网络读取与磁盘写入解耦,改善后端流式下载的吞吐与可靠性,并强化错误/取消场景下的收尾处理,属于 Tauri 后端命令层(src-tauri/src/commands)的下载管线优化。
Changes:
- 将下载写入改为“异步读取 + 有界通道 + spawn_blocking 同步 BufWriter 写入”的流水线模式,并在写入端显式 flush + fsync。
- 临时文件名引入
session_id,以减少取消后立刻重试时的临时文件竞争。 - 构建侧增加
bytes依赖,并调整tokiofeatures 以使用tokio::sync::mpsc。
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src-tauri/src/commands/download.rs | 重构下载写入路径:通道传输 chunk、阻塞写入线程、取消/错误传播与临时文件命名调整 |
| src-tauri/Cargo.toml | 增加 bytes 依赖;为 tokio 启用 sync feature |
| src-tauri/Cargo.lock | 锁文件更新以反映新增依赖 |
| // 使用包含 session_id 的临时文件名,避免取消后立即重试时新旧任务竞争同一临时文件 | ||
| let temp_path = format!("{}.{}.downloading", actual_save_path, session_id); |
| while let Some(chunk) = stream.next().await { | ||
| // 检查取消标志或 session 是否已过期 | ||
| if DOWNLOAD_CANCELLED.load(Ordering::SeqCst) | ||
| || CURRENT_DOWNLOAD_SESSION.load(Ordering::SeqCst) != session_id | ||
| { | ||
| info!("download_file cancelled (session {})", session_id); | ||
| drop(writer); | ||
| return Err("下载已取消".to_string()); | ||
| download_err = Some("下载已取消".to_string()); | ||
| break; | ||
| } |
| writer | ||
| .flush() | ||
| .map_err(|e| format!("刷新写入缓冲区失败: {}", e))?; | ||
| writer | ||
| .get_ref() | ||
| .sync_all() | ||
| .map_err(|e| format!("同步文件失败: {}", e))?; |
由 Sourcery 提供的总结
改进文件下载流水线,将网络读取与磁盘写入解耦,从而实现更快、更可靠的下载,并提供更健壮的取消处理。
Bug 修复:
增强内容:
构建:
bytescrate 依赖,用于通过下载写入通道以分块方式传递数据。Original summary in English
Summary by Sourcery
改进文件下载流水线,将网络 I/O 与磁盘写入解耦,以提升吞吐量并增强取消操作的健壮性。
Bug 修复:
增强项:
构建:
sync特性,并添加bytes依赖,以支持新的下载缓冲和基于 channel 的写入流水线。Original summary in English
Summary by Sourcery
Improve the file download pipeline to decouple network I/O from disk writes for higher throughput and more robust cancellation handling.
Bug Fixes:
Enhancements:
Build:
Bug 修复:
增强点:
Original summary in English
由 Sourcery 提供的总结
改进文件下载流水线,将网络读取与磁盘写入解耦,从而实现更快、更可靠的下载,并提供更健壮的取消处理。
Bug 修复:
增强内容:
构建:
bytescrate 依赖,用于通过下载写入通道以分块方式传递数据。Original summary in English
Summary by Sourcery
改进文件下载流水线,将网络 I/O 与磁盘写入解耦,以提升吞吐量并增强取消操作的健壮性。
Bug 修复:
增强项:
构建:
sync特性,并添加bytes依赖,以支持新的下载缓冲和基于 channel 的写入流水线。Original summary in English
Summary by Sourcery
Improve the file download pipeline to decouple network I/O from disk writes for higher throughput and more robust cancellation handling.
Bug Fixes:
Enhancements:
Build: