10
10
using Valleysoft . DockerCredsProvider ;
11
11
12
12
using Microsoft . NET . Build . Containers . Credentials ;
13
+ using System . Net . Sockets ;
13
14
14
15
namespace Microsoft . NET . Build . Containers ;
15
16
@@ -18,6 +19,8 @@ namespace Microsoft.NET.Build.Containers;
18
19
/// </summary>
19
20
public partial class AuthHandshakeMessageHandler : DelegatingHandler
20
21
{
22
+ private const int MaxRequestRetries = 5 ; // Arbitrary but seems to work ok for chunked uploads to ghcr.io
23
+
21
24
private record AuthInfo ( Uri Realm , string Service , string ? Scope ) ;
22
25
23
26
/// <summary>
@@ -161,24 +164,46 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
161
164
request . Headers . Authorization = cachedAuthentication ;
162
165
}
163
166
164
- var response = await base . SendAsync ( request , cancellationToken ) ;
165
- if ( response is { StatusCode : HttpStatusCode . OK } )
166
- {
167
- return response ;
168
- }
169
- else if ( response is { StatusCode : HttpStatusCode . Unauthorized } && TryParseAuthenticationInfo ( response , out string ? scheme , out AuthInfo ? authInfo ) )
167
+ int retryCount = 0 ;
168
+
169
+ while ( retryCount < MaxRequestRetries )
170
170
{
171
- if ( await GetAuthenticationAsync ( scheme , authInfo . Realm , authInfo . Service , authInfo . Scope , cancellationToken ) is AuthenticationHeaderValue authentication )
171
+ try
172
172
{
173
- request . Headers . Authorization = AuthHeaderCache . AddOrUpdate ( request . RequestUri , authentication ) ;
174
- return await base . SendAsync ( request , cancellationToken ) ;
173
+ var response = await base . SendAsync ( request , cancellationToken ) ;
174
+ if ( response is { StatusCode : HttpStatusCode . OK } )
175
+ {
176
+ return response ;
177
+ }
178
+ else if ( response is { StatusCode : HttpStatusCode . Unauthorized } && TryParseAuthenticationInfo ( response , out string ? scheme , out AuthInfo ? authInfo ) )
179
+ {
180
+ if ( await GetAuthenticationAsync ( scheme , authInfo . Realm , authInfo . Service , authInfo . Scope , cancellationToken ) is AuthenticationHeaderValue authentication )
181
+ {
182
+ request . Headers . Authorization = AuthHeaderCache . AddOrUpdate ( request . RequestUri , authentication ) ;
183
+ return await base . SendAsync ( request , cancellationToken ) ;
184
+ }
185
+ return response ;
186
+ }
187
+ else
188
+ {
189
+ return response ;
190
+ }
191
+ }
192
+ catch ( HttpRequestException e ) when ( e . InnerException is IOException ioe && ioe . InnerException is SocketException se )
193
+ {
194
+ retryCount += 1 ;
195
+
196
+ // TODO: log in a way that is MSBuild-friendly
197
+ Console . WriteLine ( $ "Encountered a SocketException with message \" { se . Message } \" . Pausing before retry.") ;
198
+
199
+ await Task . Delay ( TimeSpan . FromSeconds ( 1.0 * Math . Pow ( 2 , retryCount ) ) , cancellationToken ) ;
200
+
201
+ // retry
202
+ continue ;
175
203
}
176
- return response ;
177
- }
178
- else
179
- {
180
- return response ;
181
204
}
205
+
206
+ throw new ApplicationException ( "Too many retries, stopping" ) ;
182
207
}
183
208
184
209
[ GeneratedRegex ( "(?<key>\\ w+)=\" (?<value>[^\" ]*)\" (?:,|$)" ) ]
0 commit comments