Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot]andLTCatt 97fcd89275 Add safety checks and UI improvements for dynamic thread limiting
Co-authored-by: LTCatt <[email protected]>
2025-08-11 16:21:07 +00:00
copilot-swe-agent[bot]andLTCatt fce2402600 Implement socket buffer exhaustion handling
Co-authored-by: LTCatt <[email protected]>
2025-08-11 16:17:45 +00:00
Copilot fbe1abe12b Initial plan 2025-08-11 16:07:31 +00:00
4 changed files with 84 additions and 12 deletions

No files matched your search

@@ -134,6 +134,8 @@ WaitRetry:
Setup.Load("SystemDebugMode")
Setup.Load("SystemDebugAnim")
Setup.Load("ToolDownloadThread")
' 确保动态线程限制正确初始化
If NetTaskThreadLimitDynamic = 0 Then NetTaskThreadLimitDynamic = NetTaskThreadLimit
Setup.Load("ToolDownloadCert")
Setup.Load("ToolDownloadSpeed")
'网络配置初始化
+78 -9
View File
@@ -1,5 +1,6 @@
Imports System.Net.Http.Headers
Imports System.Threading.Tasks
Imports System.Net.Sockets
Imports CacheCow.Client
Public Module ModNet
@@ -210,7 +211,8 @@ RequestFinished:
Try
Static Client As HttpClient = ClientExtensions.CreateClient(New FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With {
.AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None,
.UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies
.UseCookies = False, '不设为 False 就不能从 Header 手动传入 Cookies
.MaxConnectionsPerServer = 8 ' 限制每个服务器的最大连接数,减少资源占用
})
Url = SecretCdnSign(Url)
Request = New HttpRequestMessage(Method, Url)
@@ -282,6 +284,19 @@ RequestFinished:
''' </summary>
Public NetTaskThreadLimit As Integer
''' <summary>
''' 动态调整的线程数限制,用于处理系统资源不足的情况。
''' </summary>
Public NetTaskThreadLimitDynamic As Integer
''' <summary>
''' 最近发生的系统缓冲区错误次数。
''' </summary>
Public NetTaskSocketBufferErrors As Integer = 0
Private ReadOnly NetTaskSocketBufferErrorsLock As New Object
''' <summary>
''' 上次发生系统缓冲区错误的时间。
''' </summary>
Private NetTaskLastSocketBufferError As Long = 0
''' <summary>
''' 速度下限。
''' </summary>
Public NetTaskSpeedLimitLow As Long = 256 * 1024L '256K/s
@@ -769,7 +784,7 @@ RequestFinished:
Try
'条件检测
If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse
If NetTaskThreadCount >= NetTaskThreadLimitDynamic OrElse Not HasAvailableSource() OrElse
(IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing
SyncLock LockState
@@ -873,7 +888,8 @@ StartThread:
Dim ContentLength As Long = 0
Using CancelToken As New CancellationTokenSource(Timeout)
Static Client As New HttpClient(New HttpClientHandler With {
.AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None
.AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None,
.MaxConnectionsPerServer = 8 ' 限制每个服务器的最大连接数,减少资源占用
})
Using Response As HttpResponseMessage = Client.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
@@ -1019,8 +1035,24 @@ SourceBreak:
If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})")
End If
Catch ex As Exception
' 检查是否为系统缓冲区空间不足的错误
Dim isSocketBufferError As Boolean = False
If TypeOf ex Is SocketException Then
Dim socketEx = DirectCast(ex, SocketException)
If socketEx.SocketErrorCode = SocketError.NoBufferSpaceAvailable Then
isSocketBufferError = True
Log($"[Download] {LocalName}:检测到系统缓冲区空间不足,等待 500ms 后重试")
Thread.Sleep(500) ' 等待系统资源释放
End If
ElseIf ex.Message.Contains("系统缓冲区空间不足") OrElse ex.Message.Contains("队列已满") OrElse
ex.Message.Contains("NoBufferSpaceAvailable") OrElse ex.Message.Contains("不能执行套接字上的操作") Then
isSocketBufferError = True
Log($"[Download] {LocalName}:检测到系统缓冲区空间不足,等待 500ms 后重试")
Thread.Sleep(500) ' 等待系统资源释放
End If
Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is TaskCanceledException, $"已超时({Timeout}ms)", GetExceptionDetail(ex))}")
SourceFail(Th, ex, False)
SourceFail(Th, ex, False, isSocketBufferError)
Finally
SyncLock NetTaskThreadCountLock
NetTaskThreadCount -= 1
@@ -1030,7 +1062,21 @@ SourceBreak:
If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge(Th)
End Try
End Sub
Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean)
Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean, Optional IsSocketBufferError As Boolean = False)
' 处理系统缓冲区错误统计和动态线程限制调整
If IsSocketBufferError Then
SyncLock NetTaskSocketBufferErrorsLock
NetTaskSocketBufferErrors += 1
NetTaskLastSocketBufferError = GetTimeTick()
' 动态降低线程限制以减少系统资源压力
Dim newLimit As Integer = Math.Max(NetTaskThreadLimit \ 4, 2) ' 降低到原来的1/4,但不少于2个线程
If NetTaskThreadLimitDynamic > newLimit Then
NetTaskThreadLimitDynamic = newLimit
Log($"[Download] 检测到系统缓冲区错误,动态降低下载线程限制至 {NetTaskThreadLimitDynamic}")
End If
End SyncLock
End If
'状态变更
SyncLock LockCount
Th.Source.FailCount += 1
@@ -1041,11 +1087,14 @@ SourceBreak:
Th.State = NetState.Error
Th.Source.Ex = ex
'根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
' 对于系统缓冲区空间不足的错误,增加重试机会,避免立即禁用下载源
Dim socketBufferErrorTolerance As Integer = If(IsSocketBufferError, Math.Max(NetTaskThreadLimit * 2, 20), 0)
If IsMergeFailure OrElse
ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse
(ex.Message.Contains("(403)") AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源
(Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
(Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) + socketBufferErrorTolerance AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 + socketBufferErrorTolerance Then
'当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
Dim IsThisFail As Boolean = False
SyncLock LockSource
@@ -1202,7 +1251,7 @@ Retry:
End If
'失败,禁用当前下载源并重启下载
If File.Exists(LocalPath) Then File.Delete(LocalPath)
SourceFail(Th, ex, True)
SourceFail(Th, ex, True, False)
Finally
Cache?.Dispose()
End Try
@@ -1653,6 +1702,26 @@ Retry:
For Each Task In Tasks
Task.RefreshStat()
Next
#End Region
#Region "动态线程限制恢复"
' 如果距离上次系统缓冲区错误已超过 30 秒,逐步恢复线程限制
SyncLock NetTaskSocketBufferErrorsLock
' 安全检查:确保动态限制在合理范围内
NetTaskThreadLimitDynamic = Math.Max(Math.Min(NetTaskThreadLimitDynamic, NetTaskThreadLimit), 1)
If NetTaskLastSocketBufferError > 0 AndAlso GetTimeTick() - NetTaskLastSocketBufferError > 30000 Then
If NetTaskThreadLimitDynamic < NetTaskThreadLimit Then
NetTaskThreadLimitDynamic = Math.Min(NetTaskThreadLimitDynamic + 1, NetTaskThreadLimit)
If NetTaskThreadLimitDynamic = NetTaskThreadLimit Then
Log($"[Download] 系统缓冲区错误已恢复,下载线程限制恢复至 {NetTaskThreadLimit}")
NetTaskLastSocketBufferError = 0 ' 重置
NetTaskSocketBufferErrors = 0
Else
Log($"[Download] 下载线程限制逐步恢复至 {NetTaskThreadLimitDynamic}")
End If
End If
End If
End SyncLock
#End Region
Catch ex As Exception
Log(ex, "刷新下载公开属性失败")
@@ -1689,14 +1758,14 @@ Retry:
Next
'为等待中的文件开始线程
For Each File As NetFile In WaitingFiles
If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查
If NetTaskThreadCount >= NetTaskThreadLimitDynamic Then Continue While '最大线程数检查
Dim NewThread = File.TryBeginThread()
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
Next
'为进行中的文件追加线程
If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增
For Each File As NetFile In OngoingFiles
If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查
If NetTaskThreadCount >= NetTaskThreadLimitDynamic Then Continue While '最大线程数检查
'线程种类计数
Dim PreparingCount = 0, DownloadingCount = 0
If File.Threads IsNot Nothing Then
@@ -116,7 +116,7 @@
{"LinkHiperCertWarn", New SetupEntry(True, Source:=SetupSource.Registry)},
{"LinkIoiVersion", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"ToolHelpChinese", New SetupEntry(True, Source:=SetupSource.Registry)},
{"ToolDownloadThread", New SetupEntry(63, Source:=SetupSource.Registry)},
{"ToolDownloadThread", New SetupEntry(15, Source:=SetupSource.Registry)},
{"ToolDownloadSpeed", New SetupEntry(42, Source:=SetupSource.Registry)},
{"ToolDownloadSource", New SetupEntry(1, Source:=SetupSource.Registry)},
{"ToolDownloadVersion", New SetupEntry(1, Source:=SetupSource.Registry)},
@@ -457,6 +457,7 @@
Public Sub ToolDownloadThread(Value As Integer)
NetTaskThreadLimit = Value + 1
NetTaskThreadLimitDynamic = NetTaskThreadLimit ' 初始化动态限制
End Sub
Public Sub ToolDownloadCert(Value As Boolean)
ServicePointManager.ServerCertificateValidationCallback =
@@ -43,7 +43,7 @@ Public Class PageSpeedLeft
LabProgress.Text = "100 %"
LabSpeed.Text = "0 B/s"
LabFile.Text = "0"
LabThread.Text = "0 / " & NetTaskThreadLimit
LabThread.Text = "0 / " & If(NetTaskThreadLimitDynamic <> NetTaskThreadLimit, $"{NetTaskThreadLimitDynamic}({NetTaskThreadLimit})", NetTaskThreadLimit.ToString())
Else
'有任务,输出基本信息
Dim Tasks = LoaderTaskbar.Where(Function(l) l.Show).ToList() '筛选掉启动 MC 的任务(#6270)
@@ -52,7 +52,7 @@ Public Class PageSpeedLeft
LabProgress.Text = If(RawPercent > 0.999999, "100 %", PredictText)
LabSpeed.Text = GetString(NetManager.Speed) & "/s"
LabFile.Text = If(NetManager.FileRemain < 0, "0*", NetManager.FileRemain)
LabThread.Text = NetTaskThreadCount & " / " & NetTaskThreadLimit
LabThread.Text = NetTaskThreadCount & " / " & If(NetTaskThreadLimitDynamic <> NetTaskThreadLimit, $"{NetTaskThreadLimitDynamic}({NetTaskThreadLimit})", NetTaskThreadLimit.ToString())
End If
#End Region