13
13
using NexusMods . MnemonicDB . Abstractions ;
14
14
using NexusMods . Paths ;
15
15
using Polly ;
16
+ using Polly . Retry ;
16
17
17
18
namespace NexusMods . Networking . HttpDownloader ;
18
19
@@ -26,6 +27,8 @@ public record HttpDownloadJob : IJobDefinitionWithStart<HttpDownloadJob, Absolut
26
27
private static readonly HttpClient Client = BuildClient ( ) ;
27
28
#pragma warning restore EXTEXP0001
28
29
30
+ private static readonly ResiliencePipeline < AbsolutePath > ResiliencePipeline = BuildResiliencePipeline ( ) ;
31
+
29
32
/// <summary>
30
33
/// Logger.
31
34
/// </summary>
@@ -81,10 +84,23 @@ public static IJobTask<HttpDownloadJob, AbsolutePath> Create(
81
84
return monitor . Begin < HttpDownloadJob , AbsolutePath > ( job ) ;
82
85
}
83
86
84
- /// <summary>
85
- /// Execute the job
86
- /// </summary>
87
+ /// <inheritdoc/>
87
88
public async ValueTask < AbsolutePath > StartAsync ( IJobContext < HttpDownloadJob > context )
89
+ {
90
+ var result = await ResiliencePipeline . ExecuteAsync (
91
+ callback : static ( tuple , _ ) =>
92
+ {
93
+ var ( self , context ) = tuple ;
94
+ return self . StartAsyncImpl ( context ) ;
95
+ } ,
96
+ state : ( this , context ) ,
97
+ cancellationToken : context . CancellationToken
98
+ ) ;
99
+
100
+ return result ;
101
+ }
102
+
103
+ private async ValueTask < AbsolutePath > StartAsyncImpl ( IJobContext < HttpDownloadJob > context )
88
104
{
89
105
await context . YieldAsync ( ) ;
90
106
await FetchMetadata ( context ) ;
@@ -168,9 +184,9 @@ public async ValueTask<AbsolutePath> StartAsync(IJobContext<HttpDownloadJob> con
168
184
{
169
185
await response . Content . CopyToAsync ( outputStream , context . CancellationToken ) ;
170
186
}
171
- catch ( HttpRequestException e )
187
+ catch ( HttpIOException e )
172
188
{
173
- Logger . LogError ( e , "Http error while downloading from `{PageUri}`: Server=`{Server}`,Http Version=`{Version}` " , DownloadPageUri , response . Headers . Server . ToString ( ) , response . Version ) ;
189
+ Logger . LogWarning ( e , "Exception while downloading from `{PageUri}`, downloaded `{DownloadedBytes}` from `{TotalBytes}` bytes " , DownloadPageUri , outputStream . Position , outputStream . Length ) ;
174
190
throw ;
175
191
}
176
192
finally
@@ -249,7 +265,23 @@ private async ValueTask FetchMetadata(IJobContext context)
249
265
var contentLength = response . Content . Headers . ContentLength ;
250
266
ContentLength = contentLength is not null ? Size . FromLong ( contentLength . Value ) : Optional < Size > . None ;
251
267
}
252
-
268
+
269
+ private static ResiliencePipeline < AbsolutePath > BuildResiliencePipeline ( )
270
+ {
271
+ var pipeline = new ResiliencePipelineBuilder < AbsolutePath > ( )
272
+ . AddRetry ( new RetryStrategyOptions < AbsolutePath >
273
+ {
274
+ ShouldHandle = new PredicateBuilder < AbsolutePath > ( ) . Handle < HttpIOException > ( ) ,
275
+ BackoffType = DelayBackoffType . Exponential ,
276
+ UseJitter = true ,
277
+ MaxRetryAttempts = 3 ,
278
+ Delay = TimeSpan . FromSeconds ( 3 ) ,
279
+ } )
280
+ . Build ( ) ;
281
+
282
+ return pipeline ;
283
+ }
284
+
253
285
[ Experimental ( "EXTEXP0001" ) ]
254
286
private static HttpClient BuildClient ( )
255
287
{
0 commit comments