Skip to content

Commit ab7f535

Browse files
Perform background refresh of credentials during preempt expiry period (#3541)
* Perform background refresh of credentials during preempt expiry time period * Remove unnecessary warning suppressions * Remove ToUniversalTime usage
1 parent b15d934 commit ab7f535

File tree

3 files changed

+329
-33
lines changed

3 files changed

+329
-33
lines changed

sdk/src/Core/Amazon.Runtime/Credentials/RefreshingAWSCredentials.cs

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ public abstract class RefreshingAWSCredentials : AWSCredentials, IDisposable
3636
/// </summary>
3737
public class CredentialsRefreshState
3838
{
39-
public ImmutableCredentials Credentials
40-
{
41-
get;
42-
set;
43-
}
39+
public ImmutableCredentials Credentials { get; set; }
4440
public DateTime Expiration { get; set; }
4541

4642
public CredentialsRefreshState()
@@ -56,8 +52,16 @@ public CredentialsRefreshState(ImmutableCredentials credentials, DateTime expira
5652
internal bool IsExpiredWithin(TimeSpan preemptExpiryTime)
5753
{
5854
var now = AWSSDKUtils.CorrectedUtcNow;
59-
var exp = Expiration.ToUniversalTime();
60-
return (now > exp - preemptExpiryTime);
55+
var exp = Expiration;
56+
return now > exp - preemptExpiryTime;
57+
}
58+
59+
internal TimeSpan GetTimeToLive(TimeSpan preemptExpiryTime)
60+
{
61+
var now = AWSSDKUtils.CorrectedUtcNow;
62+
var exp = Expiration;
63+
64+
return exp - now + preemptExpiryTime;
6165
}
6266
}
6367

@@ -110,46 +114,106 @@ public TimeSpan PreemptExpiryTime
110114
/// <returns></returns>
111115
public override ImmutableCredentials GetCredentials()
112116
{
113-
_updateGeneratedCredentialsSemaphore.Wait();
114-
try
117+
// We save the currentState as it might be modified or cleared.
118+
var tempState = currentState;
119+
120+
var ttl = tempState?.GetTimeToLive(PreemptExpiryTime);
121+
122+
if (ttl > TimeSpan.Zero)
115123
{
116-
// We save the currentState as it might be modified or cleared.
117-
var tempState = currentState;
118-
// If credentials are expired or we don't have any state yet, update
119-
if (ShouldUpdateState(tempState, PreemptExpiryTime))
124+
if (ttl < PreemptExpiryTime)
120125
{
121-
tempState = GenerateNewCredentials();
122-
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
123-
currentState = tempState;
126+
// background refresh (fire & forget)
127+
if (_updateGeneratedCredentialsSemaphore.Wait(0))
128+
{
129+
_ = System.Threading.Tasks.Task.Run(GenerateCredentialsAndUpdateState);
130+
}
124131
}
125-
return tempState.Credentials.Copy();
126132
}
127-
finally
133+
else
128134
{
129-
_updateGeneratedCredentialsSemaphore.Release();
135+
// If credentials are expired, update
136+
_updateGeneratedCredentialsSemaphore.Wait();
137+
tempState = GenerateCredentialsAndUpdateState();
138+
}
139+
140+
return tempState.Credentials.Copy();
141+
142+
CredentialsRefreshState GenerateCredentialsAndUpdateState()
143+
{
144+
System.Diagnostics.Debug.Assert(_updateGeneratedCredentialsSemaphore.CurrentCount == 0);
145+
146+
try
147+
{
148+
var tempState = currentState;
149+
// double-check that the credentials still need updating
150+
// as it's possible that multiple requests were queued acquiring the semaphore
151+
if (ShouldUpdateState(tempState, PreemptExpiryTime))
152+
{
153+
tempState = GenerateNewCredentials();
154+
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
155+
currentState = tempState;
156+
}
157+
158+
return tempState;
159+
}
160+
finally
161+
{
162+
_updateGeneratedCredentialsSemaphore.Release();
163+
}
130164
}
131165
}
132166

133167
#if AWS_ASYNC_API
134168
public override async System.Threading.Tasks.Task<ImmutableCredentials> GetCredentialsAsync()
135169
{
136-
await _updateGeneratedCredentialsSemaphore.WaitAsync().ConfigureAwait(false);
137-
try
170+
// We save the currentState as it might be modified or cleared.
171+
var tempState = currentState;
172+
173+
var ttl = tempState?.GetTimeToLive(PreemptExpiryTime);
174+
175+
if (ttl > TimeSpan.Zero)
138176
{
139-
// We save the currentState as it might be modified or cleared.
140-
var tempState = currentState;
141-
// If credentials are expired, update
142-
if (ShouldUpdateState(tempState, PreemptExpiryTime))
177+
if (ttl < PreemptExpiryTime)
143178
{
144-
tempState = await GenerateNewCredentialsAsync().ConfigureAwait(false);
145-
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
146-
currentState = tempState;
179+
// background refresh (fire & forget)
180+
if (_updateGeneratedCredentialsSemaphore.Wait(0))
181+
{
182+
_ = GenerateCredentialsAndUpdateStateAsync();
183+
}
147184
}
148-
return tempState.Credentials.Copy();
149185
}
150-
finally
186+
else
187+
{
188+
// If credentials are expired, update
189+
await _updateGeneratedCredentialsSemaphore.WaitAsync().ConfigureAwait(false);
190+
tempState = await GenerateCredentialsAndUpdateStateAsync().ConfigureAwait(false);
191+
}
192+
193+
return tempState.Credentials.Copy();
194+
195+
async System.Threading.Tasks.Task<CredentialsRefreshState> GenerateCredentialsAndUpdateStateAsync()
151196
{
152-
_updateGeneratedCredentialsSemaphore.Release();
197+
System.Diagnostics.Debug.Assert(_updateGeneratedCredentialsSemaphore.CurrentCount == 0);
198+
199+
try
200+
{
201+
var tempState = currentState;
202+
// double-check that the credentials still need updating
203+
// as it's possible that multiple requests were queued acquiring the semaphore
204+
if (ShouldUpdateState(tempState, PreemptExpiryTime))
205+
{
206+
tempState = await GenerateNewCredentialsAsync().ConfigureAwait(false);
207+
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
208+
currentState = tempState;
209+
}
210+
211+
return tempState;
212+
}
213+
finally
214+
{
215+
_updateGeneratedCredentialsSemaphore.Release();
216+
}
153217
}
154218
}
155219
#endif
@@ -246,7 +310,7 @@ protected virtual CredentialsRefreshState GenerateNewCredentials()
246310
throw new NotImplementedException();
247311
}
248312
#if AWS_ASYNC_API
249-
/// <summary>
313+
/// <summary>
250314
/// When overridden in a derived class, generates new credentials and new expiration date.
251315
///
252316
/// Called on first credentials request and when expiration date is in the past.

sdk/test/NetStandard/UnitTests/AWSSDK.UnitTests.Custom.NetStandard.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ This project file should not be used as part of a release pipeline.
5959
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
6060
<PackageReference Include="xunit" Version="2.4.2" />
6161
<PackageReference Include="Moq" Version="4.18.4" />
62-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" >
62+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
6363
<PrivateAssets>all</PrivateAssets>
6464
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6565
</PackageReference>

0 commit comments

Comments
 (0)