Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AsyncLoader to load and update value periodically #5590

Merged
merged 31 commits into from
Aug 2, 2024

Conversation

injae-kim
Copy link
Contributor

Fixes #5506.

Motivation:

AsyncLoader can be useful in the following situations.

  • When it is necessary to periodically read and update information from a file such as resolv.conf .
  • When data is not valid after a certain period of time, such as an OAuth 2.0 access token.

We already have an implementation for that on AbstractOAuth2AuthorizationGrant.java.
However, I hope to generalize it and add new features to use it in various cases.

Modifications:

  • Add AsyncLoader to load and update value periodically

Result:

@ikhoon ikhoon added new feature sprint Issues for OSS Sprint participants labels Apr 12, 2024
Comment on lines 109 to 113
if (token == null && fallbackTokenProvider != null) {
CompletableFuture<? extends GrantedOAuth2AccessToken> fallbackTokenFuture = null;
try {
fallbackTokenFuture = requireNonNull(
fallbackTokenProvider.get(), "fallbackTokenProvider.get() returned null");
Copy link
Contributor

Choose a reason for hiding this comment

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

Side note: It is not related to this PR but fallbackTokenProvider is a name that we should only use when a token acquisition fails since it is a fallback.

I think it would be better to remove fallback from the API method. tokenProvider seems clearer.

@injae-kim injae-kim force-pushed the async-loader branch 3 times, most recently from 526db63 to e161fcc Compare April 17, 2024 18:28
@injae-kim injae-kim requested a review from ikhoon April 18, 2024 04:22
Copy link

codecov bot commented Apr 24, 2024

Codecov Report

Attention: Patch coverage is 84.31373% with 24 lines in your changes missing coverage. Please review.

Project coverage is 74.07%. Comparing base (b8eb810) to head (cca5962).
Report is 288 commits behind head on main.

Current head cca5962 differs from pull request most recent head 17f26be

Please upload reports for the commit 17f26be to get more accurate results.

Files Patch % Lines
...t/auth/oauth2/DefaultOAuth2AuthorizationGrant.java 42.30% 13 Missing and 2 partials ⚠️
...necorp/armeria/common/util/DefaultAsyncLoader.java 94.33% 2 Missing and 4 partials ⚠️
...necorp/armeria/common/util/AsyncLoaderBuilder.java 85.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5590      +/-   ##
============================================
+ Coverage     73.95%   74.07%   +0.11%     
- Complexity    20115    21252    +1137     
============================================
  Files          1730     1848     +118     
  Lines         74161    78567    +4406     
  Branches       9465    10024     +559     
============================================
+ Hits          54847    58199    +3352     
- Misses        14837    15669     +832     
- Partials       4477     4699     +222     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

Looks nice, @injae-kim! ❤️

Please address @minwoox comments.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

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

Looks good in terms of correctness 👍 Thanks @injae-kim 🙇 👍 🙇

@ikhoon ikhoon modified the milestones: 1.29.0, 1.30.0 Jun 10, 2024
ikhoon added 3 commits June 26, 2024 19:11
- Remove RefreshingFuture and set CacheEntry as a field
- Allow null value.
- Allow no expiration.
Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Look great! Left a small suggestion. 👍

if (loadFutureUpdater.compareAndSet(this, loadFuture, future)) {
needsLoad = true;
break;
}
Copy link
Contributor

@minwoox minwoox Jul 1, 2024

Choose a reason for hiding this comment

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

What do you think of returing this.loadFuture here? If so we can remove the for loop and reduce accessing the volatile fields.

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 what I've imagined:

@Override
public CompletableFuture<T> load() {
    final CompletableFuture<T> loadFuture = this.loadFuture;
    if (!loadFuture.isDone()) {
        // A load is in progress.
        return returnCacheOrFuture(cacheEntry, loadFuture);
    }

    final CacheEntry<T> cacheEntry = this.cacheEntry;
    final boolean isValid = isValid(cacheEntry);
    if (!isValid || needsRefresh(cacheEntry)) {
        final CompletableFuture<T> newFuture = new CompletableFuture<>();
        if (loadFutureUpdater.compareAndSet(this, loadFuture, newFuture)) {
            final T cache = cacheEntry != null ? cacheEntry.value : null;
            load(cache, newFuture);
            if (isValid) {
                logger.debug("Pre-fetching a new value. cache: {}, loader: {}", cache, loader);
                return UnmodifiableFuture.completedFuture(cache);
            }
            logger.debug("Loading a new value. cache: {}, loader: {}", cache, loader);
            return newFuture;
        }
        return returnCacheOrFuture(this.cacheEntry, this.loadFuture);
    } else {
        // The cache is still valid and no need to refresh.
        return returnCacheOrFuture(cacheEntry, loadFuture);
    }
}

private CompletableFuture<T> returnCacheOrFuture(@Nullable CacheEntry<T> cacheEntry,
                                                 CompletableFuture<T> loadFuture) {
    if (isValid(cacheEntry)) {
        return UnmodifiableFuture.completedFuture(cacheEntry.value);
    }
    return loadFuture;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks nicer.

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Looks great! Thanks a lot, @injae-kim and @ikhoon! 👍 👍 👍 👍 👍

@injae-kim
Copy link
Contributor Author

injae-kim commented Jul 2, 2024

@ikhoon really thank you for polishing PR & address comments 🙇 🙇

The code looks much simpler&nicer now!

@ikhoon
Copy link
Contributor

ikhoon commented Jul 25, 2024

@jrhee17 The code has been changed after your approval. I was wondering if you want to check the code again or if you already have done.

@ikhoon
Copy link
Contributor

ikhoon commented Jul 25, 2024

Gentle ping @trustin. The usage of obtrude*() has been removed. PTAL. 🙇‍♂️

@jrhee17
Copy link
Contributor

jrhee17 commented Aug 2, 2024

I was wondering if you want to check the code again or if you already have done.

I think it should be fine to go ahead as I don't think the changes are in a critical path 👍

@ikhoon ikhoon merged commit 1145d45 into line:main Aug 2, 2024
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature sprint Issues for OSS Sprint participants
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide a way to load a value periodically or when it expires
5 participants