From 6ca2bc3ddae402c089dca9da6409bbb75389efab Mon Sep 17 00:00:00 2001 From: youngsu5582 <98307410+youngsu5582@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:56:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8A=A4=EB=A0=88=EB=93=9C=20=ED=92=80?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../corea/global/config/ThreadPoolConfig.java | 21 ++++++++++++ .../java/corea/global/util/FutureUtil.java | 5 +++ .../infrastructure/GithubReviewProvider.java | 22 ++++++++++--- .../corea/global/util/FutureUtilTest.java | 33 +++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/corea/global/config/ThreadPoolConfig.java create mode 100644 backend/src/test/java/corea/global/util/FutureUtilTest.java diff --git a/backend/src/main/java/corea/global/config/ThreadPoolConfig.java b/backend/src/main/java/corea/global/config/ThreadPoolConfig.java new file mode 100644 index 000000000..28fdc6c84 --- /dev/null +++ b/backend/src/main/java/corea/global/config/ThreadPoolConfig.java @@ -0,0 +1,21 @@ +package corea.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +public class ThreadPoolConfig { + + @Bean(name = "apiExecutor") + public Executor apiExecutor() { + final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(100); + executor.setMaxPoolSize(200); + executor.setQueueCapacity(50); + executor.initialize(); + return executor; + } +} diff --git a/backend/src/main/java/corea/global/util/FutureUtil.java b/backend/src/main/java/corea/global/util/FutureUtil.java index 4b638c20e..e90adbe39 100644 --- a/backend/src/main/java/corea/global/util/FutureUtil.java +++ b/backend/src/main/java/corea/global/util/FutureUtil.java @@ -1,6 +1,7 @@ package corea.global.util; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Supplier; public class FutureUtil { @@ -8,4 +9,8 @@ public class FutureUtil { public static CompletableFuture supplyAsync(Supplier supplier) { return CompletableFuture.supplyAsync(supplier); } + + public static CompletableFuture supplyAsync(final Supplier supplier, final Executor executor) { + return CompletableFuture.supplyAsync(supplier, executor); + } } diff --git a/backend/src/main/java/corea/review/infrastructure/GithubReviewProvider.java b/backend/src/main/java/corea/review/infrastructure/GithubReviewProvider.java index d61922349..17d1d8cfd 100644 --- a/backend/src/main/java/corea/review/infrastructure/GithubReviewProvider.java +++ b/backend/src/main/java/corea/review/infrastructure/GithubReviewProvider.java @@ -4,20 +4,22 @@ import corea.exception.ExceptionType; import corea.review.dto.GithubPullRequestReview; import corea.review.dto.GithubPullRequestReviewInfo; -import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import static corea.global.util.FutureUtil.supplyAsync; +@Slf4j @Component -@RequiredArgsConstructor public class GithubReviewProvider { private static final String HTTP_SECURE_PREFIX = "https://"; @@ -30,6 +32,13 @@ public class GithubReviewProvider { private final GithubPullRequestReviewClient reviewClient; private final GithubPullRequestCommentClient commentClient; + private final Executor apiExecutor; + + public GithubReviewProvider(GithubPullRequestReviewClient reviewClient, GithubPullRequestCommentClient commentClient, @Qualifier("apiExecutor") Executor apiExecutor) { + this.reviewClient = reviewClient; + this.commentClient = commentClient; + this.apiExecutor = apiExecutor; + } public GithubPullRequestReviewInfo provideReviewInfo(String prLink) { validatePrLink(prLink); @@ -38,12 +47,15 @@ public GithubPullRequestReviewInfo provideReviewInfo(String prLink) { } private GithubPullRequestReviewInfo getGithubPullRequestReviewInfo(String prLink) { - CompletableFuture> reviewFuture = supplyAsync(() -> reviewClient.getPullRequestReviews(prLink)); - CompletableFuture> commentFuture = supplyAsync(() -> commentClient.getPullRequestReviews(prLink)); + CompletableFuture> reviewFuture = supplyAsync(() -> reviewClient.getPullRequestReviews(prLink), apiExecutor); + CompletableFuture> commentFuture = supplyAsync(() -> commentClient.getPullRequestReviews(prLink), apiExecutor); return reviewFuture .thenCombine(commentFuture, this::collectPullRequestReviews) - .exceptionally(e -> {throw new CoreaException(ExceptionType.GITHUB_SERVER_ERROR);}) + .exceptionally(e -> { + log.warn("[리뷰 API 중 깃허브 에러 발생] 발생 링크 : {}, 에러 : {}", prLink, e.getStackTrace()); + throw new CoreaException(ExceptionType.GITHUB_SERVER_ERROR); + }) .thenApply(GithubPullRequestReviewInfo::new) .join(); } diff --git a/backend/src/test/java/corea/global/util/FutureUtilTest.java b/backend/src/test/java/corea/global/util/FutureUtilTest.java new file mode 100644 index 000000000..490ca55f9 --- /dev/null +++ b/backend/src/test/java/corea/global/util/FutureUtilTest.java @@ -0,0 +1,33 @@ +package corea.global.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FutureUtilTest { + + @Test + @DisplayName("지정하지 않으면 ForkedJoinPool 에서 동작한다.") + void default_is_work_with_forked_join_pool() throws ExecutionException, InterruptedException { + CompletableFuture future = FutureUtil.supplyAsync(() -> Thread.currentThread().getName()); + String threadName = future.get(); + assertTrue(threadName.startsWith("ForkJoinPool")); + } + + @Test + @DisplayName("지정하면 지정한 Executors 가 제공한 스레드에서 동작한다.") + void work_with_specific_executor() throws ExecutionException, InterruptedException { + final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("testExecutor"); + executor.initialize(); + + CompletableFuture future = FutureUtil.supplyAsync(() -> Thread.currentThread().getName(), executor); + String threadName = future.get(); + assertTrue(threadName.startsWith("testExecutor")); + } +}