From d279979bc40c2fd79191feaab5259208596ddb2f Mon Sep 17 00:00:00 2001 From: Olexander Kucherenko Date: Sun, 19 Apr 2026 17:31:08 +0300 Subject: [PATCH 1/3] feat: async user request processor --- .../mate/academy/AsyncRequestProcessor.java | 20 ++++++++++++++++++- src/main/java/mate/academy/Main.java | 8 +++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/mate/academy/AsyncRequestProcessor.java b/src/main/java/mate/academy/AsyncRequestProcessor.java index ee5eea1..92b3fd8 100644 --- a/src/main/java/mate/academy/AsyncRequestProcessor.java +++ b/src/main/java/mate/academy/AsyncRequestProcessor.java @@ -1,16 +1,34 @@ package mate.academy; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; public class AsyncRequestProcessor { private final Executor executor; + private final Map cache = new ConcurrentHashMap<>(); public AsyncRequestProcessor(Executor executor) { this.executor = executor; } public CompletableFuture processRequest(String userId) { - return null; + if (cache.containsKey(userId)) { + return CompletableFuture.completedFuture(cache.get(userId)); + } + return CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(400); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Task was interrupted", e); + } + + UserData userData = new UserData(userId, "Details for " + userId); + + cache.put(userId, userData); + return userData; + }, executor); } } diff --git a/src/main/java/mate/academy/Main.java b/src/main/java/mate/academy/Main.java index a3c9b9e..c3ad8b0 100644 --- a/src/main/java/mate/academy/Main.java +++ b/src/main/java/mate/academy/Main.java @@ -2,15 +2,14 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { - // Feel free to play with AsyncRequestProcessor in this main method if you want - ExecutorService executor = null; // Provide implementation that fits your needs + ExecutorService executor = Executors.newFixedThreadPool(4); AsyncRequestProcessor asyncRequestProcessor = new AsyncRequestProcessor(executor); - // Simulating multiple concurrent requests - String[] userIds = {"user1", "user2", "user3", "user1"}; // Note: "user1" is repeated + String[] userIds = {"user1", "user2", "user3", "user1"}; CompletableFuture[] futures = new CompletableFuture[userIds.length]; for (int i = 0; i < userIds.length; i++) { @@ -19,7 +18,6 @@ public static void main(String[] args) { .thenAccept(userData -> System.out.println("Processed: " + userData)); } - // Wait for all futures to complete CompletableFuture.allOf(futures).join(); executor.shutdown(); } From e5f0f39280ae16f6ac0212f067fdb542deb2b756 Mon Sep 17 00:00:00 2001 From: Olexander Kucherenko Date: Sun, 19 Apr 2026 18:32:56 +0300 Subject: [PATCH 2/3] fix: UserData --- .../mate/academy/AsyncRequestProcessor.java | 20 +++++++----------- src/main/java/mate/academy/Main.java | 6 +++++- src/main/java/mate/academy/UserData.java | 21 ++++++++++++++++++- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/java/mate/academy/AsyncRequestProcessor.java b/src/main/java/mate/academy/AsyncRequestProcessor.java index 92b3fd8..aaa1bb5 100644 --- a/src/main/java/mate/academy/AsyncRequestProcessor.java +++ b/src/main/java/mate/academy/AsyncRequestProcessor.java @@ -4,31 +4,25 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; public class AsyncRequestProcessor { private final Executor executor; - private final Map cache = new ConcurrentHashMap<>(); + private final Map> cache = new ConcurrentHashMap<>(); public AsyncRequestProcessor(Executor executor) { this.executor = executor; } public CompletableFuture processRequest(String userId) { - if (cache.containsKey(userId)) { - return CompletableFuture.completedFuture(cache.get(userId)); - } - return CompletableFuture.supplyAsync(() -> { + return cache.computeIfAbsent(userId, id -> CompletableFuture.supplyAsync(() -> { try { - Thread.sleep(400); + TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new RuntimeException("Task was interrupted", e); + throw new RuntimeException("Task was interrupted for user " + id, e); } - - UserData userData = new UserData(userId, "Details for " + userId); - - cache.put(userId, userData); - return userData; - }, executor); + return new UserData(id, "Details for " + id); + }, executor)); } } diff --git a/src/main/java/mate/academy/Main.java b/src/main/java/mate/academy/Main.java index c3ad8b0..92ece9e 100644 --- a/src/main/java/mate/academy/Main.java +++ b/src/main/java/mate/academy/Main.java @@ -15,7 +15,11 @@ public static void main(String[] args) { for (int i = 0; i < userIds.length; i++) { String userId = userIds[i]; futures[i] = asyncRequestProcessor.processRequest(userId) - .thenAccept(userData -> System.out.println("Processed: " + userData)); + .thenAccept(userData -> System.out.println("Processed: " + userData)) + .exceptionally(ex -> { + System.err.println("Error processing " + userId + ": " + ex.getMessage()); + return null; + }); } CompletableFuture.allOf(futures).join(); diff --git a/src/main/java/mate/academy/UserData.java b/src/main/java/mate/academy/UserData.java index ef80432..d5d0d5c 100644 --- a/src/main/java/mate/academy/UserData.java +++ b/src/main/java/mate/academy/UserData.java @@ -1,5 +1,24 @@ package mate.academy; -public record UserData(String userId, String details) { +public class UserData { + private final String userId; + private final String details; + public UserData(String userId, String details) { + this.userId = userId; + this.details = details; + } + + public String userId() { + return userId; + } + + public String details() { + return details; + } + + @Override + public String toString() { + return "UserData[userId=" + userId + ", details=" + details + "]"; + } } From 0302dc32f9831dd51c2c5ce83701b14cb76267a9 Mon Sep 17 00:00:00 2001 From: Olexander Kucherenko Date: Mon, 20 Apr 2026 11:21:06 +0300 Subject: [PATCH 3/3] inFlight ConcurrentHashMap --- .../mate/academy/AsyncRequestProcessor.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/mate/academy/AsyncRequestProcessor.java b/src/main/java/mate/academy/AsyncRequestProcessor.java index aaa1bb5..4dd5b8b 100644 --- a/src/main/java/mate/academy/AsyncRequestProcessor.java +++ b/src/main/java/mate/academy/AsyncRequestProcessor.java @@ -8,14 +8,19 @@ public class AsyncRequestProcessor { private final Executor executor; - private final Map> cache = new ConcurrentHashMap<>(); + private final Map cache = new ConcurrentHashMap<>(); + private final Map> inFlight = new ConcurrentHashMap<>(); public AsyncRequestProcessor(Executor executor) { this.executor = executor; } public CompletableFuture processRequest(String userId) { - return cache.computeIfAbsent(userId, id -> CompletableFuture.supplyAsync(() -> { + if (cache.containsKey(userId)) { + return CompletableFuture.completedFuture(cache.get(userId)); + } + + return inFlight.computeIfAbsent(userId, id -> CompletableFuture.supplyAsync(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { @@ -23,6 +28,13 @@ public CompletableFuture processRequest(String userId) { throw new RuntimeException("Task was interrupted for user " + id, e); } return new UserData(id, "Details for " + id); - }, executor)); + }, executor) + .whenComplete((userData, ex) -> { + if (ex == null) { + cache.put(id, userData); + } + inFlight.remove(id); + }) + ); } }