1
1
// Copyright (c) Microsoft. All rights reserved.
2
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
4
- /// This task is sourced from https://github.com/microsoft/msbuild/blob/04e508c36f9c1fe826264aef7c26ffb8f16e9bdc/src/Tasks/DownloadFile.cs
5
- /// It alleviates the problem of time outs on DownloadFile Task. We are not the version of msbuild that has this fix, hence we have to locally
6
- /// build it to get rid of the issue.
4
+ // This task is sourced from https://github.com/microsoft/msbuild/blob/04e508c36f9c1fe826264aef7c26ffb8f16e9bdc/src/Tasks/DownloadFile.cs
5
+ // Contains further modifications in followup commits.
6
+ // It alleviates the problem of time outs on DownloadFile Task. We are not the version of msbuild that has this fix, hence we have to locally
7
+ // build it to get rid of the issue.
7
8
8
9
using Microsoft . Build . Framework ;
9
10
using Microsoft . Build . Utilities ;
@@ -63,6 +64,14 @@ public sealed class DownloadFileSB : BuildTask, ICancelableTask
63
64
[ Required ]
64
65
public string SourceUrl { get ; set ; }
65
66
67
+ /// <summary>
68
+ /// Gets or sets the timeout for a successful download. If exceeded, the download continues
69
+ /// for another two timeout durations before failing. This makes it sometimes possible to
70
+ /// determine whether the timeout is just a little too short, or if the download would never
71
+ /// have finished.
72
+ /// </summary>
73
+ public string TimeoutSeconds { get ; set ; }
74
+
66
75
/// <summary>
67
76
/// Gets or sets a <see cref="HttpMessageHandler"/> to use. This is used by unit tests to mock a connection to a remote server.
68
77
/// </summary>
@@ -88,10 +97,25 @@ private async Task<bool> ExecuteAsync()
88
97
}
89
98
90
99
int retryAttemptCount = 0 ;
91
-
100
+
92
101
CancellationToken cancellationToken = _cancellationTokenSource . Token ;
93
102
94
- while ( true )
103
+ var startTime = DateTime . UtcNow ;
104
+
105
+ // Use the same API for the "success timeout" and the "would it ever succeed" timeout.
106
+ var timeout = TimeSpan . Zero ;
107
+ var successCancellationTokenSource = new CancellationTokenSource ( ) ;
108
+
109
+ if ( double . TryParse ( TimeoutSeconds , out double timeoutSeconds ) )
110
+ {
111
+ timeout = TimeSpan . FromSeconds ( timeoutSeconds ) ;
112
+ Log . LogMessage ( MessageImportance . High , $ "DownloadFileSB timeout set to { timeout } ") ;
113
+
114
+ successCancellationTokenSource . CancelAfter ( timeout ) ;
115
+ _cancellationTokenSource . CancelAfter ( ( int ) ( timeout . TotalMilliseconds * 3 ) ) ;
116
+ }
117
+
118
+ while ( true )
95
119
{
96
120
try
97
121
{
@@ -129,6 +153,35 @@ private async Task<bool> ExecuteAsync()
129
153
}
130
154
}
131
155
156
+ var finishTime = DateTime . UtcNow ;
157
+
158
+ if ( successCancellationTokenSource . IsCancellationRequested )
159
+ {
160
+ string error = $ "{ TimeoutSeconds } second timeout exceeded";
161
+
162
+ if ( ! _cancellationTokenSource . IsCancellationRequested )
163
+ {
164
+ error +=
165
+ $ ", but download completed after { finishTime - startTime } . " +
166
+ $ "Try increasing timeout from { TimeoutSeconds } if this is acceptable.";
167
+ }
168
+ else
169
+ {
170
+ error +=
171
+ $ ", and didn't complete within leeway after { finishTime - startTime } . " +
172
+ $ "The download was likely never going to terminate. Investigate logs and " +
173
+ $ "add additional logging if necessary.";
174
+ }
175
+
176
+ Log . LogError ( error ) ;
177
+ }
178
+ else
179
+ {
180
+ Log . LogMessage (
181
+ MessageImportance . High ,
182
+ $ "DownloadFileSB.Downloading Complete! Elapsed: { finishTime - startTime } ") ;
183
+ }
184
+
132
185
return ! _cancellationTokenSource . IsCancellationRequested && ! Log . HasLoggedErrors ;
133
186
}
134
187
@@ -175,19 +228,58 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken)
175
228
return ;
176
229
}
177
230
231
+ var progressMonitorCancellationTokenSource = new CancellationTokenSource ( ) ;
232
+ CancellationToken progressMonitorToken = progressMonitorCancellationTokenSource . Token ;
233
+
178
234
try
179
235
{
180
236
cancellationToken . ThrowIfCancellationRequested ( ) ;
181
237
238
+ var startTime = DateTime . UtcNow ;
239
+
240
+ var progressMonitor = Task . Run (
241
+ async ( ) =>
242
+ {
243
+ while ( ! progressMonitorToken . IsCancellationRequested )
244
+ {
245
+ destinationFile . Refresh ( ) ;
246
+ if ( destinationFile . Exists )
247
+ {
248
+ long current = destinationFile . Length ;
249
+ long total = response . Content . Headers . ContentLength ?? 1 ;
250
+ var elapsed = DateTime . UtcNow - startTime ;
251
+ double kbytesPerSecond = current / elapsed . TotalSeconds / 1000.0 ;
252
+
253
+ Log . LogMessage (
254
+ MessageImportance . High ,
255
+ $ "Progress... { elapsed } , " +
256
+ $ "current file size { current / ( double ) total : 00.0%} " +
257
+ $ "({ destinationFile . Length : #,0} / { total : #,0} ) " +
258
+ $ "~ { kbytesPerSecond : #,0.00} kB/s") ;
259
+ }
260
+ await Task . Delay ( TimeSpan . FromSeconds ( 5 ) , progressMonitorToken ) ;
261
+ }
262
+ } ,
263
+ progressMonitorToken )
264
+ . ConfigureAwait ( false ) ;
265
+
182
266
using ( var target = new FileStream ( destinationFile . FullName , FileMode . Create , FileAccess . Write , FileShare . None ) )
183
267
{
184
- Log . LogMessage ( MessageImportance . High , $ "DownloadFileSB.Downloading { SourceUrl } ", destinationFile . FullName , response . Content . Headers . ContentLength ) ;
268
+ Log . LogMessage (
269
+ MessageImportance . High ,
270
+ $ "DownloadFileSB.Downloading { SourceUrl } to " +
271
+ $ "{ destinationFile . FullName } ") ;
272
+
273
+ Log . LogMessage ( MessageImportance . Low , $ "All response headers:\n { response . Headers } ") ;
274
+ Log . LogMessage ( MessageImportance . Low , $ "All content headers:\n { response . Content . Headers } ") ;
185
275
186
276
using ( Stream responseStream = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) )
187
277
{
188
278
await responseStream . CopyToAsync ( target , 1024 , cancellationToken ) . ConfigureAwait ( false ) ;
189
279
}
190
280
281
+ Log . LogMessage ( MessageImportance . High , $ "DownloadFileSB.StreamCopyComplete { SourceUrl } ") ;
282
+
191
283
DownloadedFile = new TaskItem ( destinationFile . FullName ) ;
192
284
}
193
285
}
@@ -200,6 +292,8 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken)
200
292
// on success but we are concerned about the added I/O
201
293
destinationFile . Delete ( ) ;
202
294
}
295
+
296
+ progressMonitorCancellationTokenSource . Cancel ( ) ;
203
297
}
204
298
}
205
299
}
0 commit comments