Skip to content

Commit ca2180f

Browse files
committed
add failure policy to download directory
1 parent c0693f8 commit ca2180f

File tree

15 files changed

+782
-52
lines changed

15 files changed

+782
-52
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,6 @@ sdk/test/Performance/**/BenchmarkDotNet.Artifacts/*
6969
#protocol-tests
7070
sdk/test/ProtocolTests/Generated/**/model
7171
sdk/test/ProtocolTests/Generated/**/sources
72-
sdk/test/ProtocolTests/Generated/**/build-info
72+
sdk/test/ProtocolTests/Generated/**/build-info
73+
74+
.DS_Store
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "S3",
5+
"type": "minor",
6+
"changeLogMessages": [
7+
"Add FailurePolicy property to TransferUtilityDownloadDirectoryRequest to allow configuration of failure handling behavior during directory downloads. The default behavior is set to abort on failure. Users can now choose to either abort the entire operation or continue downloading remaining files when a failure occurs.",
8+
"Add ObjectDownloadFailedEvent event to TransferUtilityDownloadDirectory to notify users when an individual file download fails during a directory download operation. This event provides details about the failed download, including the original request, the specific file request and the exception encountered."
9+
]
10+
}
11+
]
12+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*******************************************************************************
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
4+
* this file except in compliance with the License. A copy of the License is located at
5+
*
6+
* http://aws.amazon.com/apache2.0
7+
*
8+
* or in the "license" file accompanying this file.
9+
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
* *****************************************************************************
13+
* __ _ _ ___
14+
* ( )( \/\/ )/ __)
15+
* /__\ \ / \__ \
16+
* (_)(_) \/\/ (___/
17+
*
18+
* AWS SDK for .NET
19+
* API Version: 2006-03-01
20+
*
21+
*/
22+
23+
using System;
24+
using System.Threading;
25+
using System.Threading.Tasks;
26+
27+
namespace Amazon.S3.Transfer.Internal
28+
{
29+
/// <summary>
30+
/// Failure policy that cancels all related operations and rethrows the exception when
31+
/// an action fails.
32+
/// </summary>
33+
/// <remarks>
34+
/// Use this policy when any single failure should abort the entire higher-level operation.
35+
/// When an <see cref="Action"/> executed under this policy throws, the policy will cancel
36+
/// the provided <see cref="CancellationTokenSource"/>, invoke an optional failure callback,
37+
/// and then rethrow the exception so the caller can observe the original failure.
38+
/// </remarks>
39+
internal class AbortOnFailurePolicy : IFailurePolicy
40+
{
41+
/// <summary>
42+
/// Executes the provided asynchronous <paramref name="action"/> under the abort-on-failure policy.
43+
/// </summary>
44+
/// <param name="action">An asynchronous delegate that performs the work to execute under the policy.</param>
45+
/// <param name="onFailure">An optional callback that will be invoked with the exception if <paramref name="action"/> fails.</param>
46+
/// <param name="cancellationTokenSource">A <see cref="CancellationTokenSource"/> that will be canceled by this policy to signal termination
47+
/// of related work when a failure occurs.</param>
48+
/// <returns>
49+
/// A <see cref="Task{Boolean}"/> that completes with <c>true</c> when <paramref name="action"/> completes successfully.
50+
/// If <paramref name="action"/> fails, this method cancels <paramref name="cancellationTokenSource"/>, invokes
51+
/// <paramref name="onFailure"/> if provided, and rethrows the original exception; it does not return <c>false</c>.
52+
/// </returns>
53+
public async Task<bool> ExecuteAsync(Func<Task> action, Action<Exception> onFailure, CancellationTokenSource cancellationTokenSource)
54+
{
55+
try
56+
{
57+
await action().ConfigureAwait(false);
58+
59+
return true;
60+
}
61+
catch (Exception ex)
62+
{
63+
// Cancel all pending operations before propagating the exception
64+
cancellationTokenSource.Cancel();
65+
66+
onFailure?.Invoke(ex);
67+
68+
throw;
69+
}
70+
}
71+
}
72+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*******************************************************************************
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
4+
* this file except in compliance with the License. A copy of the License is located at
5+
*
6+
* http://aws.amazon.com/apache2.0
7+
*
8+
* or in the "license" file accompanying this file.
9+
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
* *****************************************************************************
13+
* __ _ _ ___
14+
* ( )( \/\/ )/ __)
15+
* /__\ \ / \__ \
16+
* (_)(_) \/\/ (___/
17+
*
18+
* AWS SDK for .NET
19+
* API Version: 2006-03-01
20+
*
21+
*/
22+
23+
using System;
24+
using System.Collections.Concurrent;
25+
using System.Threading;
26+
using System.Threading.Tasks;
27+
28+
namespace Amazon.S3.Transfer.Internal
29+
{
30+
/// <summary>
31+
/// Failure policy that records exceptions and allows other operations to continue.
32+
/// </summary>
33+
/// <remarks>
34+
/// Use this policy when individual operation failures should not abort the overall
35+
/// download directory operation. Exceptions thrown by the action are captured and
36+
/// stored in the supplied <see cref="ConcurrentBag{Exception}"/>, and an optional
37+
/// onFailure callback is invoked. For cancellation triggered by
38+
/// the provided <see cref="CancellationTokenSource"/>, cancellation is propagated
39+
/// to callers by rethrowing the <see cref="OperationCanceledException"/>.
40+
/// </remarks>
41+
internal class ContinueOnFailurePolicy : IFailurePolicy
42+
{
43+
private readonly ConcurrentBag<Exception> _errors;
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="ContinueOnFailurePolicy"/> class.
47+
/// </summary>
48+
/// <param name="errors">A <see cref="ConcurrentBag{Exception}"/> used to collect exceptions
49+
/// that occur while executing actions under this policy. Failures are added to this bag
50+
/// so the caller can examine aggregated errors after the overall operation completes.</param>
51+
internal ContinueOnFailurePolicy(ConcurrentBag<Exception> errors)
52+
{
53+
_errors = errors;
54+
}
55+
56+
/// <summary>
57+
/// Executes <paramref name="action"/> and records failures without throwing them,
58+
/// unless the failure is an operation cancellation triggered by the provided
59+
/// <paramref name="cancellationTokenSource"/>.
60+
/// </summary>
61+
/// <param name="action">The asynchronous operation to execute under the policy.</param>
62+
/// <param name="onFailure">A callback invoked with the exception when <paramref name="action"/> fails.</param>
63+
/// <param name="cancellationTokenSource">A <see cref="CancellationTokenSource"/> used to determine and signal cancellation.
64+
/// The policy will rethrow cancellations when the cancellation token was requested.</param>
65+
/// <returns>
66+
/// A <see cref="Task{Boolean}"/> that completes with <c>true</c> when the action completed successfully.
67+
/// If the action threw a non-cancellation exception, the exception is added to the internal error bag,
68+
/// <paramref name="onFailure"/> is invoked if provided, and the method completes with <c>false</c> to indicate
69+
/// the action failed but the policy handled it and allowed processing to continue.
70+
/// </returns>
71+
public async Task<bool> ExecuteAsync(Func<Task> action, Action<Exception> onFailure, CancellationTokenSource cancellationTokenSource)
72+
{
73+
try
74+
{
75+
await action().ConfigureAwait(false);
76+
77+
return true;
78+
}
79+
// If the operation was canceled via the provided token, propagate cancellation.
80+
catch (OperationCanceledException ex) when (cancellationTokenSource?.IsCancellationRequested == true)
81+
{
82+
onFailure?.Invoke(ex);
83+
84+
// Collect the exception for later reporting.
85+
_errors.Add(ex);
86+
87+
throw;
88+
}
89+
// Disabled warning CA1031 to allow catching all exceptions to continue processing.
90+
#pragma warning disable CA1031
91+
catch (Exception ex)
92+
#pragma warning restore CA1031
93+
{
94+
onFailure?.Invoke(ex);
95+
96+
// Collect the exception for later reporting but don't throw it.
97+
// This allows other downloads to continue processing.
98+
_errors.Add(ex);
99+
100+
return false;
101+
}
102+
}
103+
}
104+
}

sdk/src/Services/S3/Custom/Transfer/Internal/DownloadDirectoryCommand.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*
2121
*/
2222
using System;
23+
using System.Collections.Concurrent;
2324
using System.Collections.Generic;
2425
using System.IO;
2526
using System.Text;
@@ -30,11 +31,14 @@
3031
using Amazon.S3.Util;
3132
using Amazon.Util.Internal;
3233
using Amazon.Runtime;
34+
using Amazon.S3.Transfer.Model;
3335

3436
namespace Amazon.S3.Transfer.Internal
3537
{
3638
internal partial class DownloadDirectoryCommand : BaseCommand<TransferUtilityDownloadDirectoryResponse>
3739
{
40+
private IFailurePolicy _failurePolicy;
41+
private ConcurrentBag<Exception> _errors;
3842
private readonly IAmazonS3 _s3Client;
3943
private readonly TransferUtilityDownloadDirectoryRequest _request;
4044
private readonly bool _skipEncryptionInstructionFiles;
@@ -52,6 +56,11 @@ internal DownloadDirectoryCommand(IAmazonS3 s3Client, TransferUtilityDownloadDir
5256
this._s3Client = s3Client;
5357
this._request = request;
5458
this._skipEncryptionInstructionFiles = s3Client is Amazon.S3.Internal.IAmazonS3Encryption;
59+
_errors = request.FailurePolicy == FailurePolicy.ContinueOnFailure ? new ConcurrentBag<Exception>() : null;
60+
_failurePolicy =
61+
request.FailurePolicy == FailurePolicy.AbortOnFailure
62+
? new AbortOnFailurePolicy()
63+
: new ContinueOnFailurePolicy(_errors);
5564
}
5665

5766
private void downloadedProgressEventCallback(object sender, WriteObjectProgressArgs e)
@@ -107,12 +116,6 @@ internal TransferUtilityDownloadRequest ConstructTransferUtilityDownloadRequest(
107116
downloadRequest.IfNoneMatch = this._request.IfNoneMatch;
108117
downloadRequest.ResponseHeaderOverrides = this._request.ResponseHeaderOverrides;
109118

110-
//Ensure the target file is a rooted within LocalDirectory. Otherwise error.
111-
if(!InternalSDKUtils.IsFilePathRootedWithDirectoryPath(downloadRequest.FilePath, _request.LocalDirectory))
112-
{
113-
throw new AmazonClientException($"The file {downloadRequest.FilePath} is not allowed outside of the target directory {_request.LocalDirectory}.");
114-
}
115-
116119
downloadRequest.WriteObjectProgressEvent += downloadedProgressEventCallback;
117120

118121
return downloadRequest;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*******************************************************************************
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
4+
* this file except in compliance with the License. A copy of the License is located at
5+
*
6+
* http://aws.amazon.com/apache2.0
7+
*
8+
* or in the "license" file accompanying this file.
9+
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
* *****************************************************************************
13+
* __ _ _ ___
14+
* ( )( \/\/ )/ __)
15+
* /__\ \ / \__ \
16+
* (_)(_) \/\/ (___/
17+
*
18+
* AWS SDK for .NET
19+
* API Version: 2006-03-01
20+
*
21+
*/
22+
23+
using System;
24+
using System.Threading;
25+
using System.Threading.Tasks;
26+
27+
namespace Amazon.S3.Transfer.Internal
28+
{
29+
/// <summary>
30+
/// Defines a policy for handling failures when executing asynchronous operations.
31+
/// Implementations encapsulate cancellation behavior for
32+
/// operations that may fail and need controlled continuation or termination.
33+
/// </summary>
34+
internal interface IFailurePolicy
35+
{
36+
/// <summary>
37+
/// Executes an asynchronous <paramref name="action"/> under this failure policy.
38+
/// </summary>
39+
/// <remarks>
40+
/// Implementations of this interface control how failures that occur while running
41+
/// <paramref name="action"/> are handled (for example, whether to abort the overall
42+
/// operation, continue on failure, or aggregate errors). When <paramref name="action"/>
43+
/// throws or faults, the policy implementation is responsible for invoking
44+
/// <paramref name="onFailure"/> with the thrown <see cref="Exception"/> and for
45+
/// taking any policy-specific cancellation action (for example by calling
46+
/// <paramref name="cancellationTokenSource"/>.Cancel()).
47+
///
48+
/// The returned <see cref="Task{Boolean}"/> completes with <c>true</c> when the
49+
/// <paramref name="action"/> completed successfully according to the policy and
50+
/// the caller may proceed. It completes with <c>false</c> when the action failed and
51+
/// the policy handled the failure (the caller should treat this as a failed step).
52+
/// </remarks>
53+
/// <param name="action">A function that performs the asynchronous work to execute under the policy.</param>
54+
/// <param name="onFailure">A callback that will be invoked with the exception when <paramref name="action"/> fails.</param>
55+
/// <param name="cancellationTokenSource">A <see cref="CancellationTokenSource"/> the policy may cancel to signal termination of related work.</param>
56+
/// <returns>
57+
/// A <see cref="Task{Boolean}"/> that resolves to <c>true</c> if the action completed successfully
58+
/// (no failure), or <c>false</c> if the action failed but the policy handled the failure.
59+
/// </returns>
60+
Task<bool> ExecuteAsync(Func<Task> action, Action<Exception> onFailure, CancellationTokenSource cancellationTokenSource);
61+
}
62+
}

sdk/src/Services/S3/Custom/Transfer/Internal/_async/BaseCommand.async.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ await completedTask
8383
}
8484
}
8585

86-
protected static async Task ExecuteCommandAsync<T>(BaseCommand<T> command, CancellationTokenSource internalCts, SemaphoreSlim throttler) where T : class
86+
protected static async Task ExecuteCommandAsync<T>(BaseCommand<T> command, CancellationTokenSource internalCts) where T : class
8787
{
8888
try
8989
{
@@ -100,10 +100,6 @@ await command.ExecuteAsync(internalCts.Token)
100100
}
101101
throw;
102102
}
103-
finally
104-
{
105-
throttler.Release();
106-
}
107103
}
108104
}
109105
}

0 commit comments

Comments
 (0)