@@ -36,11 +36,7 @@ public abstract class RefreshingAWSCredentials : AWSCredentials, IDisposable
36
36
/// </summary>
37
37
public class CredentialsRefreshState
38
38
{
39
- public ImmutableCredentials Credentials
40
- {
41
- get ;
42
- set ;
43
- }
39
+ public ImmutableCredentials Credentials { get ; set ; }
44
40
public DateTime Expiration { get ; set ; }
45
41
46
42
public CredentialsRefreshState ( )
@@ -56,8 +52,16 @@ public CredentialsRefreshState(ImmutableCredentials credentials, DateTime expira
56
52
internal bool IsExpiredWithin ( TimeSpan preemptExpiryTime )
57
53
{
58
54
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 ;
61
65
}
62
66
}
63
67
@@ -110,46 +114,106 @@ public TimeSpan PreemptExpiryTime
110
114
/// <returns></returns>
111
115
public override ImmutableCredentials GetCredentials ( )
112
116
{
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 )
115
123
{
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 )
120
125
{
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
+ }
124
131
}
125
- return tempState . Credentials . Copy ( ) ;
126
132
}
127
- finally
133
+ else
128
134
{
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
+ }
130
164
}
131
165
}
132
166
133
167
#if AWS_ASYNC_API
134
168
public override async System . Threading . Tasks . Task < ImmutableCredentials > GetCredentialsAsync ( )
135
169
{
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 )
138
176
{
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 )
143
178
{
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
+ }
147
184
}
148
- return tempState . Credentials . Copy ( ) ;
149
185
}
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 ( )
151
196
{
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
+ }
153
217
}
154
218
}
155
219
#endif
@@ -246,7 +310,7 @@ protected virtual CredentialsRefreshState GenerateNewCredentials()
246
310
throw new NotImplementedException ( ) ;
247
311
}
248
312
#if AWS_ASYNC_API
249
- /// <summary>
313
+ /// <summary>
250
314
/// When overridden in a derived class, generates new credentials and new expiration date.
251
315
///
252
316
/// Called on first credentials request and when expiration date is in the past.
0 commit comments