Skip to content

refactor(downloader): 重构下载器分片与系统资源分配逻辑 - #3506

Merged
Lokins577 merged 23 commits into
devfrom
refactor/downloader-range
Sep 26, 2026
Merged

Lokins577 merged 23 commits into
devfrom
refactor/downloader-range

Conversation

@Pigeon0v0

@Pigeon0v0 Pigeon0v0 commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

本 PR 对下载器的分片策略与系统资源分配进行了重构。

修改内容:针对文件下载回退到 HTTP/1.1(至少在我这里高峰期 1.1 的速度显著快于 2.0),元数据与 API 请求仍然使用 HTTP/2.0;仅针对大于 4 MiB 的文件尝试分片;批量任务中未知大小的文件直接下载,不进行 Range 探测;大文件下载采用新的动态分片机制,分片大小更加灵活;内存缓存改为全局共享,并及时将内容写入硬盘,避免极端情况下内存过高占用。

测试时进行 Minecraft 26.2 清洁安装,下载高峰阶段相比 PCL 2.13.1.0 的相同阶段内存占用可减少约 2/3(稳定在 180 - 220 MB),同时保持下载速度基本持平甚至略微更快;与 PCL CE 2.15.0 相比内存占用差距不大(此 PR 版本占用略微减少),主要改进了部分情况下下载速度极其缓慢的问题。

同时,close #3356 。

本 PR 主要使用 AI 完成,因此需要较为细致的测试与检查。同时,回退到 HTTP/1.1 还是保留 HTTP/2.0 可能仍需要进一步调查与讨论。

Generated by GPT-5.6 Sol xHigh & GPT-5.6 Terra xHigh

Sourcery 摘要

重构下载器的分片、连接管理和资源调度逻辑,以提升大文件下载性能、稳定性及内存使用效率。

新功能:

  • 为大文件引入自适应 Range 并行下载,并支持慢速分片恢复与失败重试。
  • 增加 HTTP 下载模式、单文件连接数等可配置选项。
  • 引入全局下载资源管理,统一协调连接数、缓冲区、带宽限速和下载监控。

错误修复:

  • 避免批量下载中对小文件和大小未知文件进行不必要的 Range 探测。
  • 改进已验证本地文件复用、下载完整性校验及下载源失败后的重试处理。

增强功能:

  • 将下载调度和状态统计从线程数调整为全局连接数,并支持主机级连接控制。
  • 通过共享缓冲区配额和增量写盘降低下载过程中的内存占用,并优化不同文件规模下的下载策略。

杂项:

  • 移除旧的基于 DownloadService 的分块下载实现及相关接口。
Original summary in English

Summary by Sourcery

重构下载器的分片、连接管理和资源调度逻辑,以提升大文件下载性能、稳定性及内存使用效率。

New Features:

  • 为大文件引入自适应 Range 并行下载,并支持慢速分片恢复与失败重试。
  • 增加 HTTP 下载模式、单文件连接数等可配置选项。
  • 引入全局下载资源管理,统一协调连接数、缓冲区、带宽限速和下载监控。

Bug Fixes:

  • 避免批量下载中对小文件和大小未知文件进行不必要的 Range 探测。
  • 改进已验证本地文件复用、下载完整性校验及下载源失败后的重试处理。

Enhancements:

  • 将下载调度和状态统计从线程数调整为全局连接数,并支持主机级连接控制。
  • 通过共享缓冲区配额和增量写盘降低下载过程中的内存占用,并优化不同文件规模下的下载策略。

Chores:

  • 移除旧的基于 DownloadService 的分块下载实现及相关接口。
Original summary in English
Original summary in English

Sourcery 总结

重构下载器,使其能够根据文件大小自适应分段并共享资源,从而提升大文件下载的可靠性和内存使用效率,同时改进连接控制和 HTTP 行为。

新功能:

  • 添加可配置的 HTTP 协议选择和按文件设置的下载连接数限制。
  • 为大文件引入基于范围的自适应并行下载,并支持慢速或失败分段的恢复。
  • 添加对下载连接、内存缓冲区、带宽限制以及总体监控的全局共享管理。

错误修复:

  • 在批量下载中,避免对小文件和大小未知的文件进行不必要的范围探测。
  • 改进对现有已验证文件的复用,并扩展下载重试处理。

增强功能:

  • 将面向线程的下载控制和监控替换为面向连接的限制和状态报告。
  • 通过共享资源配额并将下载数据增量写入磁盘,降低下载器的内存压力。
  • 改进小文件、大文件及批量文件的下载协议选择和性能。
Original summary in English

Sourcery 总结

重构下载器的分片、资源管理与连接控制逻辑,以改善大文件下载性能、稳定性和内存使用。

新功能:

  • 为大文件提供自适应 Range 并行下载,并支持慢速分片恢复。
  • 新增下载 HTTP 协议选择和单文件连接数配置。
  • 引入全局下载资源管理,统一协调连接、缓冲区、带宽和下载监控。

错误修复:

  • 避免批量下载中对小文件或大小未知文件进行不必要的 Range 探测。
  • 改进已校验本地文件复用、下载完整性校验和失败重试处理。

改进:

  • 将下载控制与监控从线程数调整为连接数,并加入全局及主机级连接限制。
  • 通过共享缓冲区配额和增量写盘降低下载过程中的内存占用。
  • 优化不同文件规模和下载场景下的 HTTP 协议选择与下载策略。
Original summary in English

Sourcery 摘要

重构下载器的分片策略与资源管理,以提升大文件下载性能、稳定性和内存使用效率。

新功能:

  • 为下载任务增加 HTTP 协议模式选择和单文件连接数配置。
  • 为大文件引入自适应 Range 并行下载,并支持慢速分片拆分与失败恢复。
  • 引入全局下载资源管理,统一协调连接、缓冲区、带宽限速和下载监控。

错误修复:

  • 避免批量下载中对小文件或大小未知文件进行不必要的 Range 探测。
  • 改进已校验本地文件复用、下载完整性校验以及下载源失败后的重试处理。

增强功能:

  • 将下载限制和监控从线程数调整为连接数,并增加全局及主机级连接控制。
  • 通过共享缓冲区配额和增量写盘降低下载过程中的内存占用。
  • 根据文件规模和下载场景优化 HTTP/1.1 与 HTTP/2 的使用策略。
Original summary in English

Sourcery 摘要

重构下载器的分片、连接管理和资源调度逻辑,以提升大文件下载性能、稳定性及内存使用效率。

新功能:

  • 为大文件引入自适应 Range 并行下载,并支持慢速分片拆分、失败恢复和协议选择。
  • 新增全局下载资源管理,统一协调连接数、缓冲区、带宽限速和下载监控。
  • 增加单文件连接数限制及 HTTP 下载模式配置,并保留旧配置的兼容映射。

错误修复:

  • 避免批量下载中对小文件和大小未知文件进行不必要的 Range 探测。
  • 改进已校验本地文件复用、临时文件校验、下载完整性检查和失败重试处理。

改进:

  • 将下载控制和监控从线程数调整为连接数,并增加全局及主机级连接管理。
  • 通过共享内存缓冲配额和增量写盘降低下载过程中的内存占用,并优化不同文件规模下的下载策略。

日常维护:

  • 移除旧的基于 DownloadService 的分块下载实现及相关接口。
Original summary in English

@pcl-ce-automation pcl-ce-automation Bot added 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 size: XXL PR 大小评估:巨型 labels Aug 5, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

审查者指南

该 PR 将旧版的逐文件分块下载替换为自适应的、基于 Range 的大文件下载流程,并提供顺序下载回退机制;同时通过全局连接、缓冲区、带宽和监控资源进行协调。该 PR 还新增了 HTTP 协议选择、面向连接的配置、更智能的加载器复用与探测机制,以及相应的 UI 更新。

自适应大文件下载时序图

sequenceDiagram
    participant LoaderDownload
    participant FileDownloader
    participant AdaptiveRangeDownloader
    participant DownloadResourceManager
    participant HTTPServer
    participant Disk

    LoaderDownload->>FileDownloader: DownloadAsync
    FileDownloader->>AdaptiveRangeDownloader: TryDownloadAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
    AdaptiveRangeDownloader->>HTTPServer: Range probe
    HTTPServer-->>AdaptiveRangeDownloader: PartialContent with ContentRange
    AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
    AdaptiveRangeDownloader->>HTTPServer: Range segment requests
    HTTPServer-->>AdaptiveRangeDownloader: PartialContent
    AdaptiveRangeDownloader->>DownloadResourceManager: ReserveBufferAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: ThrottleAsync
    AdaptiveRangeDownloader->>Disk: RandomAccess.WriteAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: RecordDownloadedBytes
    AdaptiveRangeDownloader-->>FileDownloader: completed
    FileDownloader->>FileDownloader: PromoteTempFile
Loading

下载策略选择流程图

flowchart TD
    Start[DownloadSingleAsync] --> Size{expectedSize known}
    Size -->|less than 4 MiB| Sequential[DownloadSequentiallyAsync]
    Size -->|unknown| Probe[TryDownloadAsync]
    Size -->|at least 4 MiB| Range[TryDownloadAsync]
    Probe -->|Range unsupported or small| Sequential
    Range -->|Range supported| Parallel[Adaptive range workers]
    Range -->|Range unsupported| Sequential
    Sequential --> Promote[PromoteTempFile]
    Parallel --> Promote
    Promote --> Complete[MarkDownloadCompleted]
Loading

文件级变更

变更 详细信息 文件
使用顺序流式下载和自适应 HTTP Range 下载替换旧版的分块下载流程,以支持大文件下载。
  • 对小文件和大小未知的批量下载使用顺序下载。
  • 仅在需要时探测 Range 支持情况,动态拆分大文件,并对速度缓慢或失败的分段进行重试或细分。
  • 使用池化缓冲区将分段直接写入预分配的临时文件,同时支持限速、进度报告和完成后提升文件。
  • 新增请求超时、源重试、响应范围验证,并在不支持 Range 时回退到顺序下载。
Plain Craft Launcher 2/Modules/Network/Downloader/AdaptiveRangeDownloader.cs
Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs
Plain Craft Launcher 2/Modules/Network/Downloader/Requester.cs
引入全局下载资源协调机制,统一管理连接、缓冲区、带宽和聚合监控。
  • 强制执行全局连接数、每个主机的连接数以及单个文件的连接数配额。
  • 共享受限的缓冲区预算,并集中管理带宽限速和已下载字节数统计。
  • 向监控界面提供聚合后的活动连接数和下载速度。
Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs
Plain Craft Launcher 2/Modules/Network/Facade/ModNet.cs
Plain Craft Launcher 2/Modules/Network/Management/NetManager.cs
围绕基于连接的限制,重新设计下载协议选择和网络配置。
  • 新增可配置的 HTTP/1.1、HTTP/2 和自动请求模式。
  • 在自动模式下,对小型请求和批量请求使用 HTTP/2;对 Range 探测和大文件分段使用 HTTP/1.1。
  • 将现有线程设置映射为连接数限制,同时新增单文件连接数设置,并限制最终生效值的范围。
PCL.Core/App/Config.cs
PCL.Core/App/ConfigEnums.cs
PCL.Core/IO/Net/NetworkService.cs
Plain Craft Launcher 2/Modules/Base/ModSetup.cs
Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs
Plain Craft Launcher 2/Modules/Network/Facade/ModNet.cs
调整加载器行为,以复用已验证的文件并避免不必要的 Range 探测。
  • 在将现有文件视为已完成之前先进行验证;在适用时,将哈希检查移至后台执行。
  • 将已知的元数据大小传递给下载器,并对已知的小文件或大小未知的批量项目跳过探测。
  • 更新资源索引元数据处理逻辑,以提供预期大小。
Plain Craft Launcher 2/Modules/Network/Loaders/LoaderDownload.cs
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.cs
将配置、UI、本地化和下载监控中的术语从“线程”调整为“连接”。
  • 新增单文件连接数和 HTTP 模式控制,并兼容旧版配置值。
  • 在设置和监控界面中显示连接数及共享下载速度。
  • 更新本地化标签和相关命名,包括下载临时文件常量。
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml.cs
Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.cs
Plain Craft Launcher 2/Modules/Network/Management/NetManager.cs
Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs

针对关联 Issue 的评估

Issue 目标 已解决 说明
#3356 修复 C# 版本 11w49a 至 13w12~ 无法正常下载的问题,使相关版本能够完成文件及元数据下载,而不会因下载器或网络请求异常失败。 ✅
#3504 修复下载任务接近结束时可能出现的严重速度异常放缓问题。 ✅

可能关联的 Issue


提示和命令

与 Sourcery 交互

  • 触发新的审查: 在 pull request 中评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以使用 @sourcery-ai issue 回复审查评论,以从中创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title,以随时重新生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary,以随时重新生成摘要。
  • 生成审查者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。如果你已经处理完所有评论并不想再看到它们,这一功能会很有用。
  • 忽略所有 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss,以忽略所有现有的 Sourcery 审查。如果你想从新的审查开始,这一功能尤其有用——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义使用体验

访问你的控制面板:

  • 启用或禁用审查功能,例如 Sourcery 生成的 pull request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

The PR replaces legacy per-file chunked downloading with an adaptive, range-based large-file pipeline and sequential fallback, coordinated by global connection, buffer, bandwidth, and monitoring resources; it also adds HTTP protocol selection, connection-oriented configuration, smarter loader reuse/probing, and corresponding UI updates.

Sequence diagram for adaptive large-file downloading

sequenceDiagram
    participant LoaderDownload
    participant FileDownloader
    participant AdaptiveRangeDownloader
    participant DownloadResourceManager
    participant HTTPServer
    participant Disk

    LoaderDownload->>FileDownloader: DownloadAsync
    FileDownloader->>AdaptiveRangeDownloader: TryDownloadAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
    AdaptiveRangeDownloader->>HTTPServer: Range probe
    HTTPServer-->>AdaptiveRangeDownloader: PartialContent with ContentRange
    AdaptiveRangeDownloader->>DownloadResourceManager: AcquireConnectionAsync
    AdaptiveRangeDownloader->>HTTPServer: Range segment requests
    HTTPServer-->>AdaptiveRangeDownloader: PartialContent
    AdaptiveRangeDownloader->>DownloadResourceManager: ReserveBufferAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: ThrottleAsync
    AdaptiveRangeDownloader->>Disk: RandomAccess.WriteAsync
    AdaptiveRangeDownloader->>DownloadResourceManager: RecordDownloadedBytes
    AdaptiveRangeDownloader-->>FileDownloader: completed
    FileDownloader->>FileDownloader: PromoteTempFile
Loading

Flow diagram for download strategy selection

flowchart TD
    Start[DownloadSingleAsync] --> Size{expectedSize known}
    Size -->|less than 4 MiB| Sequential[DownloadSequentiallyAsync]
    Size -->|unknown| Probe[TryDownloadAsync]
    Size -->|at least 4 MiB| Range[TryDownloadAsync]
    Probe -->|Range unsupported or small| Sequential
    Range -->|Range supported| Parallel[Adaptive range workers]
    Range -->|Range unsupported| Sequential
    Sequential --> Promote[PromoteTempFile]
    Parallel --> Promote
    Promote --> Complete[MarkDownloadCompleted]
Loading

File-Level Changes

Change Details Files
Replace the legacy chunked download flow with sequential streaming and adaptive HTTP Range downloads for large files.
  • Use sequential downloads for small files and batch downloads with unknown size.
  • Probe Range support only when needed, split large files dynamically, and retry or subdivide slow/failed segments.
  • Write segments directly to a preallocated temporary file with pooled buffers, throttling, progress reporting, and completion promotion.
  • Add request timeouts, source retries, response-range validation, and fallback to sequential downloads when Range is unsupported.
Plain Craft Launcher 2/Modules/Network/Downloader/AdaptiveRangeDownloader.cs
Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs
Plain Craft Launcher 2/Modules/Network/Downloader/Requester.cs
Introduce global download resource coordination for connections, buffers, bandwidth, and aggregate monitoring.
  • Enforce global and per-host connection quotas plus a per-file connection limit.
  • Share a bounded buffer budget and centralize bandwidth throttling and downloaded-byte accounting.
  • Expose aggregate active connection count and download speed to monitoring surfaces.
Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs
Plain Craft Launcher 2/Modules/Network/Facade/ModNet.cs
Plain Craft Launcher 2/Modules/Network/Management/NetManager.cs
Rework download protocol selection and network configuration around connection-based limits.
  • Add configurable HTTP/1.1, HTTP/2, and automatic request modes.
  • Use HTTP/2 for small/batch requests and HTTP/1.1 for Range probes and large-file segments in automatic mode.
  • Map existing thread settings to connection limits while adding a per-file connection setting and clamping effective values.
PCL.Core/App/Config.cs
PCL.Core/App/ConfigEnums.cs
PCL.Core/IO/Net/NetworkService.cs
Plain Craft Launcher 2/Modules/Base/ModSetup.cs
Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs
Plain Craft Launcher 2/Modules/Network/Facade/ModNet.cs
Adjust loader behavior to reuse validated files and avoid unnecessary Range discovery.
  • Validate existing files before treating them as completed, offloading hash checks when applicable.
  • Pass known metadata sizes to the downloader and bypass probing for known small files or unknown-size batch items.
  • Update asset-index metadata handling to supply expected sizes.
Plain Craft Launcher 2/Modules/Network/Loaders/LoaderDownload.cs
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.cs
Update configuration, UI, localization, and download monitoring terminology from threads to connections.
  • Add per-file connection and HTTP mode controls with backward-compatible configuration values.
  • Display connection counts and shared download speed in settings and monitoring views.
  • Update localized labels and related naming, including the download temporary-file constant.
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml.cs
Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.cs
Plain Craft Launcher 2/Modules/Network/Management/NetManager.cs
Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#3356 修复 C# 版本 11w49a 至 13w12~ 无法正常下载的问题,使相关版本能够完成文件及元数据下载,而不会因下载器或网络请求异常失败。 ✅
#3504 修复下载任务接近结束时可能出现的严重速度异常放缓问题。 ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

# Conflicts:
#	PCL.Core/App/Localization/Languages/zh-CN.xaml
#	Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
@Pigeon0v0
Pigeon0v0 marked this pull request as ready for review August 29, 2026 12:59
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 and removed 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 labels Aug 29, 2026

@sourcery-ai sourcery-ai 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.

嘿——我发现了 4 个问题

AI Agent 提示词
请处理这次代码审查中的评论:

## 单独评论

### 评论 1
<location path="Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs" line_range="156-158" />
<code_context>
             if (!File.Exists(fileAddress))
             {
-                FileDownloader.DownloadAsync(address, fileAddress + ModNet.netDownloadEnd).GetAwaiter().GetResult();
+                FileDownloader.DownloadAsync(address, fileAddress + ModNet.NetDownloadEnd).GetAwaiter().GetResult();
                 File.Delete(fileAddress);
-                FileSystem.Rename(fileAddress + ModNet.netDownloadEnd, fileAddress);
+                FileSystem.Rename(fileAddress + ModNet.NetDownloadEnd, fileAddress);
                 ModBase.Log("[Minecraft] 皮肤下载成功:" + fileAddress);
             }
</code_context>
<issue_to_address>
**issue (bug_risk):** `McSkinDownload` 将 `fileAddress + ModNet.NetDownloadEnd` 作为 `localPath` 传入,但重构后的下载器始终会写入 `localPath + ModNet.NetDownloadEnd`。因此,下载会创建 `fileAddress + .PCLDownloading + .PCLDownloading`,而调用方尝试重命名 `fileAddress + .PCLDownloading`,由于该路径不存在,重命名会失败。

**触发条件:** 通过 `McSkinDownload` 下载皮肤时。

**建议修复:** 将 `fileAddress` 传递给 `FileDownloader.DownloadAsync`,或者让下载器统一接受已经添加后缀的临时路径。
</issue_to_address>

### 评论 2
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="138" />
<code_context>
+        var totalSize = response.Content.Headers.ContentLength ?? -1;
+        // 某些源使用 chunked 传输没有 Content-Length,但清单校验信息仍可能带有文件大小
+        // 这个大小只用于慢速判断,完整性仍按响应头的 totalSize 校验
+        var slowCheckSize = totalSize > 0 ? totalSize : trackedFile?.Check?.actualSize ?? -1;
+        if (trackedFile is not null)
+        {
</code_context>
<issue_to_address>
**issue (broader_impact):** 当响应没有 `Content-Length` 时,顺序下载不会验证清单中的 `trackedFile.Check.actualSize`。`slowCheckSize` 仅用于慢速判断,而最终大小检查由于 `totalSize` 为 `-1` 被跳过,因此,如果分块传输的响应提前结束,文件仍会被当作成功完成的完整文件处理。

**触发条件:** 源使用不带 `Content-Length` 的分块传输,并且在达到清单声明的大小之前终止传输时。

**建议修复:** 将预期大小与响应的 `Content-Length` 分开跟踪,并在已下载字节数与 `trackedFile.Check.actualSize` 不一致时拒绝完成下载。
</issue_to_address>

### 评论 3
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs" line_range="54" />
<code_context>
+        CancellationToken cancellationToken)
+    {
+        var host = Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : url;
+        var hostQuota = HostConnectionQuotas.GetOrAdd(host, static _ => new AsyncQuota());
+        var hostLease = await hostQuota.AcquireAsync(1, () => ModNet.NetTaskConnectionsPerHostLimit, cancellationToken)
+            .ConfigureAwait(false);
</code_context>
<issue_to_address>
**issue (bug_risk):** 所有之前见过的主机都会永久保留在 `HostConnectionQuotas` 中,下载完成后也不会移除配额。因此,来自不受限制或由用户控制的主机名的下载会在进程的整个生命周期内为每个主机累积一个 `AsyncQuota` 对象。

**触发条件:** 启动器会话从许多不同的主机下载内容时,例如不断变化的签名 CDN 或镜像 URL。

**建议修复:** 在主机配额的使用量降为零后移除处于空闲状态的配额,或者使用有界的、会驱逐条目的主机配额注册表。
</issue_to_address>

### 评论 4
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="259-270" />
<code_context>
+    internal static async Task<HttpResponseMessage> SendDownloadRequestAsync(string url,
+        HttpRequestMessage request, CancellationToken cancellationToken)
+    {
+        using var requestTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+        requestTimeout.CancelAfter(RequestTimeoutMilliseconds);
+        try
+        {
+            return await GetHttpClient(url)
+                .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, requestTimeout.Token)
+                .ConfigureAwait(false);
+        }
+        catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested &&
+                                                    requestTimeout.IsCancellationRequested)
+        {
+            throw new TimeoutException($"等待下载源响应超时(30 秒):{url}", ex);
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** 等待响应头时发生的请求超时会被转换为 `TimeoutException`,而 `AdaptiveRangeDownloader.TryRecover` 处理的异常类型中不包括该异常。因此,单个分段的响应头超时会取消整个自适应下载,而不是按照所宣称的慢速连接恢复机制重试该分段。

**触发条件:** 某个范围分段请求接收响应头的时间超过 30 秒,而其他分段仍然可用时。

**建议修复:** 将转换后的 `TimeoutException` 视为可恢复的分段失败,或者为范围分段超时保留一种可重试的异常类型。
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查,请考虑分享它们 ✨
帮助我变得更有用!请在每条评论上点击 👍 或 👎,我会利用这些反馈来改进审查结果。
Original comment in English

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs" line_range="156-158" />
<code_context>
             if (!File.Exists(fileAddress))
             {
-                FileDownloader.DownloadAsync(address, fileAddress + ModNet.netDownloadEnd).GetAwaiter().GetResult();
+                FileDownloader.DownloadAsync(address, fileAddress + ModNet.NetDownloadEnd).GetAwaiter().GetResult();
                 File.Delete(fileAddress);
-                FileSystem.Rename(fileAddress + ModNet.netDownloadEnd, fileAddress);
+                FileSystem.Rename(fileAddress + ModNet.NetDownloadEnd, fileAddress);
                 ModBase.Log("[Minecraft] 皮肤下载成功:" + fileAddress);
             }
</code_context>
<issue_to_address>
**issue (bug_risk):** `McSkinDownload` passes `fileAddress + ModNet.NetDownloadEnd` as `localPath`, but the refactored downloader always writes to `localPath + ModNet.NetDownloadEnd`. The download therefore creates `fileAddress + .PCLDownloading + .PCLDownloading`, while the caller renames `fileAddress + .PCLDownloading`, so the rename fails because that path does not exist.

**Triggers:** When downloading a skin through `McSkinDownload`.

**Suggested fix:** Pass `fileAddress` to `FileDownloader.DownloadAsync`, or make the downloader consistently accept an already-suffixed temporary path.
</issue_to_address>

### Comment 2
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="138" />
<code_context>
+        var totalSize = response.Content.Headers.ContentLength ?? -1;
+        // 某些源使用 chunked 传输没有 Content-Length,但清单校验信息仍可能带有文件大小
+        // 这个大小只用于慢速判断,完整性仍按响应头的 totalSize 校验
+        var slowCheckSize = totalSize > 0 ? totalSize : trackedFile?.Check?.actualSize ?? -1;
+        if (trackedFile is not null)
+        {
</code_context>
<issue_to_address>
**issue (broader_impact):** Sequential downloads do not validate the manifest's `trackedFile.Check.actualSize` when the response has no `Content-Length`. `slowCheckSize` is used only for the slow-speed heuristic, and the final size check is skipped because `totalSize` is `-1`, so a chunked response that ends early is promoted as a successful complete file.

**Triggers:** When a source uses chunked transfer without `Content-Length` and terminates before the manifest-declared size.

**Suggested fix:** Track the expected size separately from the response `Content-Length` and reject completion when the downloaded byte count differs from `trackedFile.Check.actualSize`.
</issue_to_address>

### Comment 3
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs" line_range="54" />
<code_context>
+        CancellationToken cancellationToken)
+    {
+        var host = Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri.Host : url;
+        var hostQuota = HostConnectionQuotas.GetOrAdd(host, static _ => new AsyncQuota());
+        var hostLease = await hostQuota.AcquireAsync(1, () => ModNet.NetTaskConnectionsPerHostLimit, cancellationToken)
+            .ConfigureAwait(false);
</code_context>
<issue_to_address>
**issue (bug_risk):** Every previously seen host is retained permanently in `HostConnectionQuotas`, and no quota is removed after its downloads finish. Downloads from unbounded or user-controlled hostnames therefore accumulate one `AsyncQuota` object per host for the lifetime of the process.

**Triggers:** When a launcher session downloads from many distinct hosts, such as changing signed CDN or mirror URLs.

**Suggested fix:** Remove an idle host quota after its usage reaches zero, or use a bounded/evicting host-quota registry.
</issue_to_address>

### Comment 4
<location path="Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs" line_range="259-270" />
<code_context>
+    internal static async Task<HttpResponseMessage> SendDownloadRequestAsync(string url,
+        HttpRequestMessage request, CancellationToken cancellationToken)
+    {
+        using var requestTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+        requestTimeout.CancelAfter(RequestTimeoutMilliseconds);
+        try
+        {
+            return await GetHttpClient(url)
+                .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, requestTimeout.Token)
+                .ConfigureAwait(false);
+        }
+        catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested &&
+                                                    requestTimeout.IsCancellationRequested)
+        {
+            throw new TimeoutException($"等待下载源响应超时(30 秒):{url}", ex);
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** A request timeout while waiting for response headers is converted to `TimeoutException`, which is not one of the exceptions handled by `AdaptiveRangeDownloader.TryRecover`; a single segment header timeout therefore cancels the entire adaptive download instead of retrying that segment under the advertised slow-connection recovery path.

**Triggers:** When a range-segment request takes longer than 30 seconds to receive response headers while other segments are still usable.

**Suggested fix:** Treat the converted `TimeoutException` as a recoverable segment failure, or preserve a retryable exception type for range-segment timeouts.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread Plain Craft Launcher 2/Modules/Minecraft/ModSkin.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a2a8f061d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs Outdated
Comment thread Plain Craft Launcher 2/Pages/PageSetup/PageSetupGameManage.xaml
Generated by GPT-5.6 Terra Max

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 292138af6f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Http/Requester.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70d67f1cd3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@whitecat346 whitecat346 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

如 comment 所示

Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/DownloadResourceManager.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/AdaptiveRangeDownloader.cs Outdated
Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs Outdated
whitecat346
whitecat346 previously approved these changes Sep 14, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65311c96ca

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Plain Craft Launcher 2/Modules/Network/Downloader/FileDownloader.cs
- 在分段与顺序下载中,读取完成后立即重置倒计时,防止限速等待期间误触发超时
- 重构 AsyncQuota 为严格 FIFO 定向转让模式,消除全量广播惊群并提高队列调度性能

本PR由Gemini 3.8对代码进行了审查。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 561c7b5165

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59755ff7ab

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@pcl-ce-automation pcl-ce-automation Bot added 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 and removed 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 labels Sep 26, 2026
@Lokins577
Lokins577 merged commit 1e78997 into dev Sep 26, 2026
3 checks passed
@pcl-ce-automation pcl-ce-automation Bot added 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线 and removed 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 labels Sep 26, 2026
@Lokins577
Lokins577 deleted the refactor/downloader-range branch September 26, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: XXL PR 大小评估:巨型 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[正在调查]下载器在下载任务的末期可能出现速度异常 [C#]: 11w49a至13w12~的版本无法正常下载

5 participants