diff --git a/src/main/java/com/meetup/server/event/implement/EventValidator.java b/src/main/java/com/meetup/server/event/implement/EventValidator.java index 31511ba..c5e2751 100644 --- a/src/main/java/com/meetup/server/event/implement/EventValidator.java +++ b/src/main/java/com/meetup/server/event/implement/EventValidator.java @@ -48,10 +48,4 @@ public void validateNearbySubwaysExist(List nearBySubways) { throw new EventException(EventErrorType.NO_INTERMEDIATE_SUBWAYS_FOUND); } } - - public void validateEventCacheExist(Cache.ValueWrapper eventCache) { - if (eventCache == null) { - throw new EventException(EventErrorType.CACHE_NOT_FOUND); - } - } } diff --git a/src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java b/src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java index 47db7f4..b1777aa 100644 --- a/src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java +++ b/src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java @@ -2,6 +2,8 @@ import com.meetup.server.event.dto.response.route.MeetingPointRouteGroup; import com.meetup.server.event.dto.response.route.RouteResponse; +import com.meetup.server.event.exception.EventErrorType; +import com.meetup.server.event.exception.EventException; import com.meetup.server.parkinglot.implement.ParkingLotFinder; import com.meetup.server.parkinglot.infrastructure.jpa.projection.ClosestParkingLot; import com.meetup.server.startpoint.domain.StartPoint; @@ -14,7 +16,6 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.stream.Collectors; @Slf4j @@ -22,49 +23,65 @@ @RequiredArgsConstructor public class RouteAssembler { - private static final int THREAD_POOL_SIZE = 2; + private static final int MAX_ATTEMPTS = 2; + private static final int RETRY_DELAY_MS = 100; + private final ExecutorService routeExecutor; private final ParkingLotFinder parkingLotFinder; private final RouteFetcher routeFetcher; public CompletableFuture assemble(List startPoints, Subway subway) { - try (ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE)) { - List> routeFutures = startPoints.stream() - .map(startPoint -> CompletableFuture.supplyAsync(() -> routeFetcher.fetch(startPoint, subway), executor)) - .toList(); + List> routeFutures = startPoints.stream() + .map(startPoint -> CompletableFuture.supplyAsync(() -> fetchWithRetry(startPoint, subway), routeExecutor)) + .toList(); - CompletableFuture> allRoutesFuture = CompletableFuture.allOf(routeFutures.toArray(new CompletableFuture[0])) - .thenApply(v -> routeFutures.stream() - .map(routeFuture -> { - try { - return routeFuture.get(); - } catch (Exception e) { - log.warn("[RouteAssembler] Failed fetching route", e); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList())); + CompletableFuture> allRoutesFuture = CompletableFuture.allOf(routeFutures.toArray(new CompletableFuture[0])) + .thenApply(v -> routeFutures.stream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .collect(Collectors.toList())); - CompletableFuture closestParkingLotFuture = - CompletableFuture.supplyAsync(() -> parkingLotFinder.findClosestParkingLot(subway.getPoint()), executor); + CompletableFuture closestParkingLotFuture = + CompletableFuture.supplyAsync(() -> parkingLotFinder.findClosestParkingLot(subway.getPoint()), routeExecutor); - CompletableFuture combinedFuture = CompletableFuture.allOf(allRoutesFuture, closestParkingLotFuture); - return combinedFuture.thenApply(v -> { - try { - List routes = allRoutesFuture.get(); - ClosestParkingLot closestParkingLot = closestParkingLotFuture.get(); + return allRoutesFuture.thenCombine(closestParkingLotFuture, (routes, closestParkingLot) -> { + if (routes.isEmpty()) { + log.warn("[RouteAssembler] No routes found for subway: {}", subway.getName()); + return null; + } + return MeetingPointRouteGroup.of(routes, subway, closestParkingLot); + }); + } + + private RouteResponse fetchWithRetry(StartPoint startPoint, Subway subway) { + RouteResponse route = null; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + route = routeFetcher.fetch(startPoint, subway); + + if (isValid(route)) { + return route; + } - if (routes == null || routes.isEmpty()) { - log.warn("[RouteAssembler] No routes found for subway: {}", subway); - return null; - } - return MeetingPointRouteGroup.of(routes, subway, closestParkingLot); - } catch (Exception e) { - log.warn("[RouteAssembler] Failed assembling MeetingPointRouteGroup", e); - return null; + if (attempt < MAX_ATTEMPTS - 1) { + log.warn("[RouteAssembler] Route fetch failed for {}. Retrying... (Attempt {}/{})", + startPoint.getName(), attempt + 1, MAX_ATTEMPTS); + try { + Thread.sleep(RETRY_DELAY_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new EventException(EventErrorType.ROUTE_FETCH_FAILED); } - }); + } } + + log.warn("[RouteAssembler] All retry attempts failed. StartPoint: {}, Subway: {}", startPoint.getName(), subway.getName()); + return route; + } + + private boolean isValid(RouteResponse route) { + if (route == null) return false; + return (route.getIsTransit() && route.getTransitRoute() != null) || + (!route.getIsTransit() && route.getDrivingRoute() != null); } } diff --git a/src/main/java/com/meetup/server/global/config/ThreadConfig.java b/src/main/java/com/meetup/server/global/config/ThreadConfig.java new file mode 100644 index 0000000..ba02a81 --- /dev/null +++ b/src/main/java/com/meetup/server/global/config/ThreadConfig.java @@ -0,0 +1,18 @@ +package com.meetup.server.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +@Configuration +public class ThreadConfig { + + @Bean + public ExecutorService routeExecutor() { + ThreadFactory threadFactory = Thread.ofVirtual().name("route-thread-", 0).factory(); + return Executors.newThreadPerTaskExecutor(threadFactory); + } +}