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
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e050aaa
Add AsyncLoader to load and update value periodically
injae-kim Apr 10, 2024
39dc307
Address comments
injae-kim Apr 17, 2024
fcad899
Add exceptionHandler
injae-kim Apr 22, 2024
ea412c7
Add refreshIf for pre-fetch
injae-kim Apr 23, 2024
887d4a4
Refresh before check isValid to resolve timing issue
injae-kim Apr 24, 2024
cca5962
Add refresh failure log and enhance test
injae-kim Apr 24, 2024
4d917a1
Throws IllegalStateException when expiration is not set on builder
injae-kim Apr 25, 2024
97861bf
Fix refresh to load value before return
injae-kim Apr 25, 2024
5b88969
Introduce `RefreshingFuture` to easily handle refreshIf
injae-kim Apr 27, 2024
2b46072
Remove refreshExecutor
injae-kim May 8, 2024
4607a6b
Address comments
injae-kim May 8, 2024
6675ad5
Make tokenLoader inside of DefaultOAuth2AuthorizationGrant constructor
injae-kim May 8, 2024
fb5d2ce
Fix test
injae-kim May 8, 2024
af5fea9
Address comments
injae-kim May 19, 2024
6536909
Address @ikhoon's comment
injae-kim Jun 3, 2024
8533294
clean up
ikhoon Jun 4, 2024
e5ba07d
clean up 2
ikhoon Jun 4, 2024
290ed7f
Apply refreshIf to DefaultOAuth2AuthorizationGrant and add tests
ikhoon Jun 4, 2024
aa5fb87
Fix lint
injae-kim Jun 5, 2024
416075e
Address @minwoox's comment
injae-kim Jun 5, 2024
5282903
Fix test
injae-kim Jun 5, 2024
95592de
Revert @ikhoon's change
injae-kim Jun 5, 2024
cd45b6d
Remove unnecessary override join()
injae-kim Jun 5, 2024
cd0496d
Address @trustin's comment
injae-kim Jun 10, 2024
c9254c3
Address comments by @trustin
ikhoon Jun 26, 2024
39e494b
Rename AsyncLoader method name to 'load()'
ikhoon Jun 26, 2024
84645c5
fix a test bug
ikhoon Jun 26, 2024
c4f2df3
Address comments by @minwoox
ikhoon Jul 2, 2024
17f26be
Fix lint
injae-kim Jul 2, 2024
78e684e
Merge branch 'main' into async-loader
ikhoon Jul 25, 2024
e9853bb
nullaway
ikhoon Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.linecorp.armeria.common.util;

import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

import com.linecorp.armeria.common.annotation.Nullable;

/**
* An {@link AsyncLoader} to atomically load, cache and update value.
*/
@FunctionalInterface
public interface AsyncLoader<T> {

/**
* Returns a newly created {@link AsyncLoaderBuilder} with the specified loader.
* @param loader function to load value. {@code T} is previously cached value
* or {@code null} when nothing is cached.
*/
static <T> AsyncLoaderBuilder<T> builder(Function<@Nullable T, CompletableFuture<T>> loader) {
return new AsyncLoaderBuilder<>(loader);
}

/**
* Returns a {@link CompletableFuture} which emits loaded value.
* Loads new value by loader only if nothing is cached or loaded value is expired.
*/
CompletableFuture<T> get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2024 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.linecorp.armeria.common.util;

import static java.util.Objects.requireNonNull;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;

import com.linecorp.armeria.common.annotation.Nullable;

/**
* A builder for creating a new {@link AsyncLoader}.
*
* <p>Expiration should be set by {@link #expireAfterLoad(Duration)} or {@link #expireIf(Predicate)}.
* If expiration is not set, {@link #build()} will throw {@link IllegalStateException}.
*/
public final class AsyncLoaderBuilder<T> {

private final Function<@Nullable T, CompletableFuture<T>> loader;
@Nullable
private Duration expireAfterLoad;
@Nullable
private Predicate<@Nullable T> expireIf;
@Nullable
private Predicate<@Nullable T> refreshIf;
@Nullable
private ExecutorService refreshExecutor;
@Nullable
private BiFunction<Throwable, @Nullable T, @Nullable CompletableFuture<T>> exceptionHandler;

AsyncLoaderBuilder(Function<@Nullable T, CompletableFuture<T>> loader) {
requireNonNull(loader, "loader");
this.loader = loader;
}

/**
* Expires loaded value after duration since it was loaded.
* New value will be loaded by loader on next {@link AsyncLoader#get()}.
*/
public AsyncLoaderBuilder<T> expireAfterLoad(Duration expireAfterLoad) {
requireNonNull(expireAfterLoad, "expireAfterLoad");
this.expireAfterLoad = expireAfterLoad;
return this;
}

/**
* Expires loaded value if predicate matches.
* New value will be loaded by loader on next {@link AsyncLoader#get()}.
*/
public AsyncLoaderBuilder<T> expireIf(Predicate<@Nullable T> expireIf) {
requireNonNull(expireIf, "expireIf");
this.expireIf = expireIf;
return this;
}

/**
* Refreshes loaded value which is not expired yet asynchronously if predicate matches.
* This pre-fetch strategy can remove an additional loading time on a cache miss.
*
* <p>Note that if 1 refresh is in progress, other refreshes will be bypassed.
* Only 1 refresh is executed at the same time.
*/
public AsyncLoaderBuilder<T> refreshIf(Predicate<@Nullable T> refreshIf) {
requireNonNull(refreshIf, "refreshIf");
this.refreshIf = refreshIf;
return this;
}

/**
* Set executor service to execute refresh.
* @see AsyncLoaderBuilder#refreshIf(Predicate)
*/
public AsyncLoaderBuilder<T> refreshExecutor(ExecutorService refreshExecutor) {
requireNonNull(refreshExecutor, "refreshExecutor");
this.refreshExecutor = refreshExecutor;
return this;
}

/**
* Handles exception thrown by loader.
* If exception handler returns {@code null}, complete {@link AsyncLoader#get()} exceptionally.
*/
public AsyncLoaderBuilder<T> exceptionHandler(BiFunction<
Throwable, @Nullable T, @Nullable CompletableFuture<T>> exceptionHandler) {
requireNonNull(exceptionHandler, "exceptionHandler");
this.exceptionHandler = exceptionHandler;
return this;
}

/**
* Returns a newly created {@link AsyncLoader} with the entries in this builder.
*
* @throws IllegalStateException if expiration is not set.
*/
public AsyncLoader<T> build() {
if (expireAfterLoad == null && expireIf == null) {
throw new IllegalStateException("Must set AsyncLoader's expiration.");
}
return new DefaultAsyncLoader<>(loader, expireAfterLoad, expireIf, refreshIf,
refreshExecutor, exceptionHandler);
}
}
Loading
Loading