Skip to content

Commit 6951fed

Browse files
fix(download): 对并发共享的 DownloadFile.Errors/Loaders 加锁,避免下载崩溃 (#3308)
* fix(download): 对并发共享的 DownloadFile.Errors/Loaders 加锁,避免下载崩溃 DownloadFile.Errors 与 Loaders 是普通 List<T>,但同一 DownloadFile 可被多个下载加载器共享并在并行下载中并发读写(LoaderDownload 中 AddError、RegisterLoader、OnFail 的 AddRange、OnFileFail 的读取),无任何同步。并发修改 普通 List 会抛 InvalidOperationException(集合已被修改),导致下载中断。 将两个裸 List 私有化,统一经内部锁保护,对外提供线程安全接口: - AddError / AddErrors:加锁追加; - RegisterLoader:原子化“不存在才登记”,消除 Contains-then-Add 的 TOCTOU; - Errors / Loaders:返回快照,避免调用方在枚举时被并发修改。 LoaderDownload 的 4 处访问点相应改用上述方法,语义等价。 * refactor(download): DownloadFile 改用 using 引入 LoaderDownload 短名
1 parent 64037c2 commit 6951fed

2 files changed

Lines changed: 46 additions & 7 deletions

File tree

Plain Craft Launcher 2/Modules/Network/Loaders/LoaderDownload.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private void Run(CancellationToken cancellationToken)
7777
}
7878
catch (Exception ex)
7979
{
80-
file.Errors.Add(ex);
80+
file.AddError(ex);
8181
file.State = PCL.Network.NetState.Interrupted;
8282
exceptions.Enqueue(ex);
8383
_cancellationTokenSource?.Cancel();
@@ -111,8 +111,7 @@ private int GetMaxParallelFiles()
111111
private async Task ProcessFileAsync(PCL.Network.DownloadFile file, CancellationToken cancellationToken)
112112
{
113113
cancellationToken.ThrowIfCancellationRequested();
114-
if (!file.Loaders.Contains(this))
115-
file.Loaders.Add(this);
114+
file.RegisterLoader(this);
116115

117116
if (State >= ModBase.LoadState.Finished)
118117
return;
@@ -188,7 +187,10 @@ public void OnFinish()
188187

189188
public void OnFileFail(PCL.Network.DownloadFile file)
190189
{
191-
OnFail(file.Errors.Any() ? file.Errors : new List<Exception> { new Exception($"文件下载失败:{file.LocalPath}") });
190+
var errors = file.Errors;
191+
OnFail(errors.Count > 0
192+
? errors.ToList()
193+
: new List<Exception> { new Exception($"文件下载失败:{file.LocalPath}") });
192194
}
193195

194196
public void OnFail(List<Exception> exList)
@@ -207,7 +209,7 @@ public void OnFail(List<Exception> exList)
207209
file.State = PCL.Network.NetState.Interrupted;
208210
file.Speed = 0;
209211
file.ActiveThreads = 0;
210-
file.Errors.AddRange(exList);
212+
file.AddErrors(exList);
211213
}
212214

213215
ModNet.NetManager.Finish(this);

Plain Craft Launcher 2/Modules/Network/Models/DownloadFile.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using PCL.Core.Utils;
2+
using PCL.Network.Loaders;
23

34
namespace PCL.Network;
45

@@ -16,8 +17,44 @@ public class DownloadFile
1617
public bool IsUnknownSize { get; set; } = true;
1718
public long DownloadedBytes { get; set; }
1819
public bool IsCopy { get; set; }
19-
public List<Exception> Errors { get; } = new();
20-
public List<PCL.Network.Loaders.LoaderDownload> Loaders { get; } = new();
20+
// Errors / Loaders 可能被多个并发下载加载器共享访问(同一 DownloadFile 可登记到多个加载器),
21+
// 故所有访问都经 _sync 加锁,避免普通 List 被并发读写而抛 InvalidOperationException。
22+
private readonly object _sync = new();
23+
private readonly List<Exception> _errors = new();
24+
private readonly List<LoaderDownload> _loaders = new();
25+
26+
/// <summary>该文件下载过程中记录的错误(返回快照,线程安全)。</summary>
27+
public IReadOnlyList<Exception> Errors
28+
{
29+
get { lock (_sync) return _errors.ToArray(); }
30+
}
31+
32+
/// <summary>已登记的下载加载器(返回快照,线程安全)。</summary>
33+
public IReadOnlyList<LoaderDownload> Loaders
34+
{
35+
get { lock (_sync) return _loaders.ToArray(); }
36+
}
37+
38+
public void AddError(Exception error)
39+
{
40+
lock (_sync) _errors.Add(error);
41+
}
42+
43+
public void AddErrors(IEnumerable<Exception> errors)
44+
{
45+
lock (_sync) _errors.AddRange(errors);
46+
}
47+
48+
/// <summary>登记一个下载加载器;同一加载器只登记一次。返回是否为首次登记。</summary>
49+
public bool RegisterLoader(LoaderDownload loader)
50+
{
51+
lock (_sync)
52+
{
53+
if (_loaders.Contains(loader)) return false;
54+
_loaders.Add(loader);
55+
return true;
56+
}
57+
}
2158
public long Speed { get; set; }
2259
public int ActiveThreads { get; set; }
2360
public double Progress

0 commit comments

Comments
 (0)