Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion src/main/java/mate/academy/AsyncRequestProcessor.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
package mate.academy;

import java.util.Map;
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<String, UserData> cache = new ConcurrentHashMap<>();
private final Map<String, CompletableFuture<UserData>> inFlight = new ConcurrentHashMap<>();

public AsyncRequestProcessor(Executor executor) {
this.executor = executor;
}

public CompletableFuture<UserData> processRequest(String userId) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Printing the result directly in the asynchronous callback can lead to nondeterministic ordering of console output compared to the sample. If tests require exact ordering, consider collecting results and printing them in the original input order after all futures complete (or otherwise coordinate printing). Note: your caching logic already returns completed futures for cached entries, but concurrent completion timing can still reorder prints.

return null;
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) {
Thread.currentThread().interrupt();
throw new RuntimeException("Task was interrupted for user " + id, e);
}
return new UserData(id, "Details for " + id);
}, executor)
.whenComplete((userData, ex) -> {
if (ex == null) {
cache.put(id, userData);
}
inFlight.remove(id);
})
);
}
}
14 changes: 8 additions & 6 deletions src/main/java/mate/academy/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The task description explicitly requires declaring the cache exactly as
Map<String, UserData> cache = new ConcurrentHashMap<>(); — here you declare a Map<String, CompletableFuture<UserData>>. Change the declaration to match the requirement, and adapt the logic (for example: keep a separate Map<String, CompletableFuture<UserData>> for in-flight processing and a Map<String, UserData> for completed cached values, or use an atomic putIfAbsent approach so only the first thread processes and others reuse the result).

// Simulating multiple concurrent requests
String[] userIds = {"user1", "user2", "user3", "user1"}; // Note: "user1" is repeated
String[] userIds = {"user1", "user2", "user3", "user1"};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The input String[] userIds includes a repeated "user1" and the task expects a specific console output ordering driven by caching behavior. With the current implementation the output order is not guaranteed because duplicate requests may be processed concurrently. Fix the caching approach (cache futures or use computeIfAbsent) so the repeated request is served from cache and the output matches the expected behavior.

CompletableFuture<?>[] futures = new CompletableFuture[userIds.length];

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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using computeIfAbsent on a Map<String, CompletableFuture<UserData>> correctly avoids the race condition (good). But the description also requires that after processing you store the resulting UserData in a Map<String, UserData> cache; currently the code only stores the future. Ensure the completed UserData is placed into the required Map<String, UserData> after processing so other code can read the cached value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Printing the result inside thenAccept is fine, but note that the exact console ordering shown in the description can be nondeterministic with purely asynchronous completion. If the exercise's tests expect the exact sample output ordering, make sure cached results are returned as already-completed futures (so their thenAccept runs immediately) or otherwise coordinate ordering.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Printing the results inside the asynchronous callback (thenAccept) can lead to nondeterministic console ordering versus the sample output in the task description. If tests expect the exact ordering, consider printing in input order after waiting on each corresponding future (e.g. keep the futures array and for each index call futures[i].join() then print), or explicitly ensure cached values are returned as already-completed futures (you already return CompletableFuture.completedFuture(...) in the processor) and handle printing so repeated requests appear immediately in the expected position.

.exceptionally(ex -> {
System.err.println("Error processing " + userId + ": " + ex.getMessage());
return null;
});
}

// Wait for all futures to complete
CompletableFuture.allOf(futures).join();
executor.shutdown();
}
Expand Down
21 changes: 20 additions & 1 deletion src/main/java/mate/academy/UserData.java
Original file line number Diff line number Diff line change
@@ -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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The task description explicitly requires the cache to be declared exactly as Map<String, UserData> cache = new ConcurrentHashMap<>();. Here the cache is declared as Map<String, CompletableFuture<UserData>>, which does not match the requirement. Change the declaration to the required type and use a separate map (e.g., an "in-flight" map of CompletableFuture<UserData>) or putIfAbsent to avoid race conditions while still keeping the required Map<String, UserData> cache.

public String userId() {
return userId;
}

public String details() {
return details;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Printing the results inside thenAccept can produce nondeterministic ordering compared to the sample output in the description. If automated tests expect the exact sequence, consider coordinating prints (e.g., collect results and print in original input order after joining, or otherwise enforce ordering).


@Override
public String toString() {
return "UserData[userId=" + userId + ", details=" + details + "]";
}
}
Loading