Skip to content

Conversation

@dscpinheiro
Copy link
Contributor

@dscpinheiro dscpinheiro commented Nov 5, 2025

Resolves #4024

Description

This PR is the second attempt to update credentials to expire during their preempt expiry period. The approach used is similar to the previous behavior (#3541) but with some important differences:

  • We added an expiration buffer to prevent credentials that are about to expire from being returned (currently 1 minute which should be enough for the current operation to complete). This is a similar approach to what other SDKs do (Java for example has a stale and prefetch time, while we were using the preempt expiry for both)
  • We also added a CredentialsLoadState enum to guarantee only one background refresh runs at a time

Motivation and Context

DOTNET-8326 (and a couple of internal support tickets too)

Testing

Dry-runs (queued):

  • .NET: DRY_RUN-c85e29cd-d738-450d-8c5b-295646a0aa7d
  • PowerShell: DRY_RUN-fbab3c8d-b969-441b-9523-fcfdc7eb1935

In addition to the unit tests I added back, I also ran a couple of simple console apps locally (calling GetCredentials on a loop with AssumeRoleAWSCredentials):

  • First app (second call to get credentials happened after they were already expired):
AssumeRoleAWSCredentials 40|2025-11-05T15:44:15.641Z|DEBUG|New credentials created for assume role that expire at 2025-11-05T15:59:15.0000000Z
RefreshingAWSCredentials 41|2025-11-05T16:04:15.648Z|INFO|Determined refreshing credentials should update. Expiration time: 2025-11-05T15:58:15.0000000Z, Current time: 2025-11-05T16:04:15.6485332Z
AssumeRoleAWSCredentials 46|2025-11-05T16:04:16.017Z|DEBUG|New credentials created for assume role that expire at 2025-11-05T16:19:15.0000000Z
  • Second app (calls to get credentials happen consistently, and they're refreshed in the preempt expiry window):
AssumeRoleAWSCredentials 40|2025-11-05T16:22:53.596Z|DEBUG|New credentials created for assume role that expire at 2025-11-05T16:37:53.0000000Z
RefreshingAWSCredentials 41|2025-11-05T16:31:53.644Z|INFO|Determined refreshing credentials are in window for preempt expiration. Preempt time: 2025-11-05T16:31:53.0000000Z, Current time: 2025-11-05T16:31:53.6446562Z
AssumeRoleAWSCredentials 46|2025-11-05T16:31:53.990Z|DEBUG|New credentials created for assume role that expire at 2025-11-05T16:46:53.0000000Z

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • My code follows the code style of this project
  • I have read the README document
  • I have added tests to cover my changes
  • All new and existing tests passed

License

  • I confirm that this pull request can be released under the Apache 2 license

@dscpinheiro dscpinheiro requested a review from normj November 5, 2025 16:33
@dscpinheiro dscpinheiro added the v4 label Nov 5, 2025
@dscpinheiro dscpinheiro requested a review from Copilot November 5, 2025 17:31
@peterrsongg peterrsongg self-requested a review November 5, 2025 17:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR re-introduces background refresh of credentials during their preempt expiry period. The key changes include:

  • Introduction of a new ITimeProvider interface for improved testability of time-dependent logic
  • Addition of an ExpirationBuffer property to provide a buffer for credential expiration
  • Refactored credential refresh logic to support background refresh during the preempt expiry window
  • Comprehensive unit tests for concurrent credential access and background refresh scenarios

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
sdk/src/Core/Amazon.Util/Internal/ITimeProvider.cs Adds new interface and default implementation for time provision to enable testability
sdk/src/Core/Amazon.Runtime/Credentials/RefreshingAWSCredentials.cs Refactors credential refresh logic with background refresh, ExpirationBuffer property, and ITimeProvider integration
sdk/src/Core/Amazon.Runtime/Credentials/InstanceProfileAWSCredentials.cs Updates to use _timeProvider instead of AWSSDKUtils.CorrectedUtcNow
sdk/test/NetStandard/UnitTests/Core/Credentials/RefreshingAWSCredentialsTests.cs Adds comprehensive unit tests for concurrent access and background refresh behavior
sdk/src/Core/GlobalSuppressions.cs Updates suppression rules for new protected field
generator/.DevConfigs/e11928c9-15fb-4b9e-91d3-91775150d378.json Adds dev config marking this as a minor version change
Comments suppressed due to low confidence (3)

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

  • This constructor call is missing the _timeProvider parameter. It should be 'new CredentialsRefreshState(state.Credentials, newExpiryTime, _timeProvider)' to ensure the time provider is properly propagated throughout the credential refresh state lifecycle.
/*

sdk/src/Core/Amazon.Runtime/Credentials/InstanceProfileAWSCredentials.cs:420

  • This CredentialsRefreshState constructor call is missing the _timeProvider parameter. It should include '_timeProvider' as the third parameter to ensure consistent time provider usage throughout the credential lifecycle.
                new CredentialsRefreshState(
                    new ImmutableCredentials(
                        credentials.AccessKeyId, 
                        credentials.SecretAccessKey, 
                        credentials.Token), 
                    credentials.Expiration);

sdk/src/Core/Amazon.Runtime/Credentials/InstanceProfileAWSCredentials.cs:443

  • This CredentialsRefreshState constructor call is missing the _timeProvider parameter. It should include '_timeProvider' as the third parameter to ensure consistent time provider usage throughout the credential lifecycle.
                new CredentialsRefreshState(
                    new ImmutableCredentials(
                        credentials.AccessKeyId,
                        credentials.SecretAccessKey,
                        credentials.Token),
                    credentials.Expiration);

@dscpinheiro dscpinheiro marked this pull request as ready for review November 5, 2025 19:06
_logger.Error(e, "Exception occurred performing background credentials refresh.");
// If any exceptions occur during background refresh, reset the state to NotLoading
// so that future GetCredentials calls can attempt to refresh again.
currentLoadState = CredentialsLoadState.NotLoading;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is setting currentLoadState while it is outside of the lock. I have not yet traced under what case this could cause an issue. However, it should only be set within the lock and should be moved to the finally on line 245 and remove the one one 243.

I also see that maybe you did this because ValidateGeneratedCredentials(newState); could throw an exception which would leave currentLoadState as "Loading" potentially if it is moved.

Unsure how to fix this yet but it is still potentially unsafe to set the variable outside of the lock.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was I need the lock to control the number of threads performing the refresh action or when I needed to change both currentLoadState and currentState atomically. In this case I only need to change the currentLoadState property and have marked that field is volatile to avoid compiler optimizations that would cause problems when multiple threads read and set variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants